Linux系列:如何用heaptrack跟蹤.NET程序的非托管內存泄露

一:背景

1. 講故事

前面跟大家分享過一篇 C# 調用 C代碼引發非托管內存泄露 的文章,這是一個故意引發的正向泄露,這一篇我們從逆向的角度去洞察引發泄露的禍根代碼,這東西如果在 windows 上還是很好處理的,很多人知道開啟一個 ust 即可,讓操作系統幫忙介入,在linux上就相對復雜一點了,畢竟Linux系統是一個萬物生的場地,沒有一個人統管全局,在調試領域這塊還是蠻大的一個弊端

二:案例分析

1. 一個小案例

這里我還是用之前的例子,對應的 C 代碼 和 C#代碼 如下:

C 代碼

#include <stdlib.h>

#include <stdio.h>

#include <stdint.h>

#include <string.h>

#define BLOCK_SIZE (10 * 1024) // 每個塊 10K

#define TOTAL_SIZE (1 * 1024 * 1024 * 1024) // 總計 1GB

#define BLOCKS (TOTAL_SIZE / BLOCK_SIZE) // 計算需要的塊數

void heapmalloc()

{

uint8_t *blocks[BLOCKS]; // 存儲每個塊的指針

// 分配 1GB 內存,分成多個小塊

for (size_t i = 0; i < BLOCKS; i++)

{

blocks[i] = (uint8_t *)malloc(BLOCK_SIZE);

if (blocks[i] == NULL)

{

printf("內存分配失敗!\n");

return;

}

// 確保每個塊都被實際占用

memset(blocks[i], 20, BLOCK_SIZE);

}

printf("已經分配 1GB 內存在堆上!\n");

}

C#代碼

using System.Runtime.InteropServices;

namespace CSharpApplication;

class Program

{

[DllImport("libmyleak.so", CallingConvention = CallingConvention.Cdecl)]

public static extern void heapmalloc();

static void Main(string[] args)

{

heapmalloc();

Console.ReadLine();

}

}

2. heaptrack 跟蹤

heaptrack 是一款跟蹤 C/C++ heap分配的工具,它會攔截所有的 malloc、calloc、realloc 和 free 函數調用,并記錄分配的調用棧信息,總的來說這工具和 C# 半毛錢關系都沒有,主要是圖它的如下三點:

能夠記錄到分配的調用棧信息,雖然只有非托管部分

對程序的影響相對小

有可視化的工具觀察跟蹤文件

依次安裝 heaptrack 和 heaptrack-gui ,參考如下:

root@ubuntu2404:/data# sudo apt install heaptrack

Reading package lists... Done

Building dependency tree... Done

Reading state information... Done

heaptrack is already the newest version (1.5.0+dfsg1-2ubuntu3).

0 upgraded, 0 newly installed, 0 to remove and 217 not upgraded.

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# sudo apt install heaptrack-gui

Reading package lists... Done

Building dependency tree... Done

Reading state information... Done

heaptrack-gui is already the newest version (1.5.0+dfsg1-2ubuntu3).

0 upgraded, 0 newly installed, 0 to remove and 217 not upgraded.

安裝好以后可以用 heaptrack dotnet CSharpApplication.dll 對 dotnet 程序進行跟蹤,當泄露到一定程序之后,可以用 dotnet-dump 生成一個轉儲文件,然后 Ctrl+C 進行中斷,

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# heaptrack dotnet CSharpApplication.dll

heaptrack output will be written to "/data/CSharpApplication/bin/Debug/net8.0/heaptrack.dotnet.4368.zst"

starting application, this might take some time...

NOTE: heaptrack detected DEBUGINFOD_URLS but will disable it to prevent

unintended network delays during recording

If you really want to use DEBUGINFOD, export HEAPTRACK_ENABLE_DEBUGINFOD=1

已經分配 1GB 內存在堆上!

[createdump] Gathering state for process 4383 dotnet

[createdump] Writing full dump to file /data/CSharpApplication/bin/Debug/net8.0/core_20250307_102814

[createdump] Written 1252216832 bytes (305717 pages) to core file

[createdump] Target process is alive

[createdump] Dump successfully written in 23681ms

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# heaptrack stats:

allocations: 122151

leaked allocations: 108551

temporary allocations: 4118

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# ls -lh

total 1.2G

-rwxr-xr-x 1 root root 74K Mar 5 22:38 CSharpApplication

-rw-r--r-- 1 root root 421 Mar 5 21:52 CSharpApplication.deps.json

-rw-r--r-- 1 root root 4.5K Mar 5 22:38 CSharpApplication.dll

-rw-r--r-- 1 root root 11K Mar 5 22:38 CSharpApplication.pdb

-rw-r--r-- 1 root root 257 Mar 5 21:52 CSharpApplication.runtimeconfig.json

-rw------- 1 root root 1.2G Mar 7 10:28 core_20250307_102814

-rw-r--r-- 1 root root 277K Mar 7 10:32 heaptrack.dotnet.4368.zst

-rwxr-xr-x 1 root root 16K Mar 5 21:52?libmyleak.so

從卦中看已產生了一個 heaptrack.dotnet.4368.zst 文件,這是一種專有的壓縮格式,可以借助 heaptrack_print 轉成 txt 文件,方便從生產上拿下來分析

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# heaptrack_print heaptrack.dotnet.4368.zst > heaptrack.txt

真實的場景下肉眼觀察 heaptrack.txt 是不大現實的,所以還得借助可視化工具,觀察 Bottom-Up 選擇項,信息如下:

左邊面板

可以觀察到 Leaked 最多的是?libmyleak.so?中的 heapmalloc 函數

右邊面板

可以觀察到執行 heapmalloc 方法的上層函數,給大家截圖二張

稍微仔細看的話,會發現Backtrace上有很多的 unresolved 符號,這個沒辦法,畢竟人家是 C/C++ 的跟蹤器,和你C#沒關系,那這些未解析的符號到底是什么函數呢?

3. 未解析符號的地址在哪里

既然是 C# 程序,大概率就是 C#方法了,那如何把方法名給找出來呢?熟悉.NET高級調試的朋友此時應該輕車熟路了,思路如下:

尋找 指令地址[-](查詢持倉 · 開發文檔 · jvQuant)

一般來說解析不出來都會生成對應的 指令地址 的,這個可以到 heaptrack.txt 中尋找蛛絲馬跡,截圖如下:

抓 core 文件

要想抓 .NET 的 core 文件,dotnet-dump 即可,這個就不介紹了哈,參考如下:

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# ps -ef | grep CSharp

root 4368 2914 0 10:25 pts/0 00:00:00 /bin/sh /usr/bin/heaptrack dotnet CSharpApplication.dll

root 4383 4368 2 10:25 pts/0 00:00:03 dotnet CSharpApplication.dll

root 4421 4336 0 10:28 pts/3 00:00:00 grep --color=auto CSharp

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# dotnet-dump collect -p 4383

Writing full to /data/CSharpApplication/bin/Debug/net8.0/core_20250307_102814

Complete

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# ls -lh

total 1.2G

-rwxr-xr-x 1 root root 74K Mar 5 22:38 CSharpApplication

-rw-r--r-- 1 root root 421 Mar 5 21:52 CSharpApplication.deps.json

-rw-r--r-- 1 root root 4.5K Mar 5 22:38 CSharpApplication.dll

-rw-r--r-- 1 root root 11K Mar 5 22:38 CSharpApplication.pdb

-rw-r--r-- 1 root root 257 Mar 5 21:52 CSharpApplication.runtimeconfig.json

-rw------- 1 root root 1.2G Mar 7 10:28 core_20250307_102814

-rw-r--r-- 1 root root 0 Mar 7 10:25 heaptrack.dotnet.4368.zst

-rwxr-xr-x 1 root root 16K Mar 5 21:52?libmyleak.so

core_20250307_102814 生成好之后,就可以借助 sos 的 ip2md 尋找這個指令地址對應的C#方法名了[-](注冊Token · 開發文檔 · jvQuant)

root@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# dotnet-dump analyze core_20250307_102814

Loading core dump: core_20250307_102814 ...

Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.

Type 'quit' or 'exit' to exit the session.

> ip2md 0x7ea6627119f6

MethodDesc: 00007ea6627cd3d8

Method Name: ILStubClass.IL_STUB_PInvoke()

Class: 00007ea6627cd300

MethodTable: 00007ea6627cd368

mdToken: 0000000006000000

Module: 00007ea66279cec8

IsJitted: yes

Current CodeAddr: 00007ea662711970

Version History:

ILCodeVersion: 0000000000000000

ReJIT ID: 0

IL Addr: 0000000000000000

CodeAddr: 00007ea662711970 (MinOptJitted)

NativeCodeVersion: 0000000000000000

> ip2md 0x7ea662711947

MethodDesc: 00007ea66279f328

Method Name: CSharpApplication.Program.Main(System.String[])

Class: 00007ea6627bb640

MethodTable: 00007ea66279f358

mdToken: 0000000006000002

Module: 00007ea66279cec8

IsJitted: yes

Current CodeAddr: 00007ea662711920

Version History:

ILCodeVersion: 0000000000000000

ReJIT ID: 0

IL Addr: 00007ea6de8f1250

CodeAddr: 00007ea662711920 (MinOptJitted)

NativeCodeVersion: 0000000000000000

Source file: /data/CSharpApplication/Program.cs @ 12

到這里恍然大悟,然來調用路徑為:CSharpApplication.Program.Main -> PInvoke -> heapmalloc ,至此真相大白

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71682.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71682.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71682.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vite.config.js 是Vite 項目的配置文件,分析具體用法

vite.config.js 是 Vite 項目的配置文件&#xff0c;用于定義項目的構建、開發服務器、插件等配置選項。以下是示例代碼中各部分的作用分析&#xff1a; 1. 導入模塊 import { fileURLToPath, URL } from node:url import { defineConfig } from vite import vue from vitejs…

行為模式---責任鏈模式

概念 責任鏈模式是一種行為設置模式&#xff0c;它的核心思想就是將請求的發送者和接收者進行解耦&#xff0c;每個接收者都可以處理請求。 在責任鏈模式中將每個接收者連成一個鏈條&#xff0c;當有請求發送上來的時候會經過每一個接收者。直到消息被處理。 適用場景 1、當…

pytest結合allure

Allure 一、文檔二、指令三、裝飾器3.1 allure.step裝飾器3.2 allure.description裝飾器3.3 allure.title裝飾器3.4 allure.link、allure.issue 和 allure.testcase裝飾器3.5 allure.epic、allure.feature 和 allure.story裝飾器3.6 allure.severity裝飾器 一、文檔 allure文檔…

前端知識點---http.createHttp()的理解(arkts)

通俗易懂的例子&#xff1a;點外賣 &#x1f354;&#x1f964; 想象一下&#xff0c;你在家里點外賣&#xff0c;HTTP 請求就像是你和餐廳之間的溝通方式。 1?? 沒有 http.createHttp()&#xff1a;每次點餐都重新撥電話 &#x1f4de; 如果你每次點餐都重新撥打餐廳的電話…

大模型開發(五):P-Tuning項目——新零售決策評價系統(下)

P-Tuning項目——新零售決策評價系統&#xff08;下&#xff09; 0 前言1 P-Tuning原理2 數據處理 0 前言 上篇文章我們介紹了使用PET方式微調BERT模型&#xff0c;PET屬于提示詞微調的一種&#xff0c;另一種比較常見的提示詞微調是P-Tuning&#xff0c;我們今天在相同的項目…

分布式中間件:Redis介紹

目錄 Redis 概述 Redis 的特點 高性能 豐富的數據結構 持久化 分布式特性 簡單易用 Redis 的數據結構 字符串&#xff08;String&#xff09; 哈希&#xff08;Hash&#xff09; 列表&#xff08;List&#xff09; 集合&#xff08;Set&#xff09; 有序集合&…

在昇騰GPU上部署DeepSeek大模型與OpenWebUI:從零到生產的完整指南

引言 隨著國產AI芯片的快速發展&#xff0c;昇騰&#xff08;Ascend&#xff09;系列GPU憑借其高性能和兼容性&#xff0c;逐漸成為大模型部署的重要選擇。本文將以昇騰300i為例&#xff0c;手把手教你如何部署DeepSeek大模型&#xff0c;并搭配OpenWebUI構建交互式界面。無論…

系統思考—組織診斷

“未經過診斷的行動是盲目的。” — 托馬斯愛迪生 最近和一家教育培訓機構溝通時&#xff0c;發現他們面臨一個有意思的問題&#xff1a;每年招生都挺不錯&#xff0c;但教師的整體績效一直提升緩慢&#xff0c;導致師生之間存在長期的不匹配。管理層試了很多辦法&#xff0c;…

AI大模型學習(五): LangChain(四)

Langchian讀取數據庫 案例&#xff1a;在數據庫中表格數據上的問題系統的基本方法,將涵蓋使用鏈和代理的視線,通過查詢數據庫中的數據并得到自然語言的答案,兩者之間的主要區別在于,我們代理可以根據多次循環查詢數據庫以回答問題 實現思路: 1.將問題轉換成DSL查詢,模型將用…

人工智能與深度學習的應用案例:從技術原理到實踐創新

第一章 引言 人工智能(AI)作為21世紀最具變革性的技術之一,正通過深度學習(Deep Learning)等核心技術推動各行業的智能化進程。從計算機視覺到自然語言處理,從醫療診斷到工業制造,深度學習通過模擬人腦神經網絡的層次化學習機制,實現了對復雜數據的高效分析與決策。本…

支持向量機的深度解析:從理論到C++實現

支持向量機(SVM)是一種強大的監督學習算法,廣泛應用于分類和回歸任務。本文詳細探討了SVM的理論基礎,包括最大間隔分離超平面、軟間隔和核技巧(Kernel Trick)的數學原理,并通過LaTeX公式推導其優化目標。接著,我們用C++實現了一個簡單的線性SVM,包括梯度下降優化求解支…

企業如何選擇研發項目進度管理軟件?盤點15款實用工具

這篇文章介紹了以下工具: 1. PingCode&#xff1b; 2. Worktile&#xff1b; 3. 騰訊 TAPD&#xff1b; 4. 華為 DevCloud&#xff1b; 5. 億方云&#xff1b; 6. 阿里云效&#xff1b; 7. CODING 碼云&#xff1b; 8. 明道云&#xff1b; 9. 進度貓&#xff1b; 10. 輕流等。 …

c++: 容器vector

文章目錄 介紹initializer_list與string的不同底層總代碼 介紹 C 中的 vector 是一種序列容器&#xff0c;它允許你在運行時動態地插入和刪除元素。 vector 是基于數組的數據結構&#xff0c;但它可以自動管理內存&#xff0c;這意味著你不需要手動分配和釋放內存。 與 C 數組相…

Qt常用控件之表格QTableWidget

表格QTableWidget QTableWidget 是一個表格控件&#xff0c;行和列交匯形成的每個單元格&#xff0c;是一個 QTableWidgetItem 對象。 1. QTableWidget屬性 QTableWidget 的屬性只有兩個&#xff1a; 屬性說明rowCount當前行的個數。columnCount當前列的個數。 2. QTableW…

Golang學習筆記_47——訪問者模式

Golang學習筆記_44——命令模式 Golang學習筆記_45——備忘錄模式 Golang學習筆記_46——狀態模式 文章目錄 一、核心概念1. 定義2. 解決的問題3. 核心角色4. 類圖 二、特點分析三、適用場景1. 編譯器實現2. 財務系統3. UI組件系統 四、Go語言實現示例完整實現代碼執行結果 五、…

棧概念和結構

文章目錄 1. 棧的概念2. 棧的分類3. 棧的實現&#xff08;數組棧&#xff09;3.1 接口設計&#xff08;Stack.h&#xff09;3.2 接口實現&#xff08;Stack.c&#xff09;1&#xff09;初始化銷毀2&#xff09;棧頂插入刪除3&#xff09;棧頂元素、空棧、大小 3.3 完整代碼Stac…

GitCode 助力 vue3-element-admin:開啟中后臺管理前端開發新征程

源碼倉庫&#xff1a; https://gitcode.com/youlai/vue3-element-admin 后端倉庫&#xff1a; https://gitcode.com/youlai/youlai-boot 開源助力&#xff0c;開啟中后臺快速開發之旅 vue3-element-admin 是一款精心打造的免費開源中后臺管理前端模板&#xff0c;它緊密貼合…

算法.習題篇

算法 — 地大復試 模擬 while循環和MOD循環計數 1.約瑟夫問題 http://bailian.openjudge.cn/practice/3254 using namespace std;bool isNoPeople(vector<bool> c)//判斷當前數組是否一個小孩都沒有了 {bool nopeople true;for (bool ival : c){if ( ival true)nop…

大白話JavaScript實現一個函數,將字符串中的每個單詞首字母大寫。

大白話JavaScript實現一個函數&#xff0c;將字符串中的每個單詞首字母大寫。 答題思路 理解需求&#xff1a;要寫一個函數&#xff0c;它能接收一個字符串&#xff0c;然后把這個字符串里每個單詞的第一個字母變成大寫。分解步驟 拆分單詞&#xff1a;一般單詞之間是用空格隔…

react中如何使用使用react-redux進行數據管理

以上就是react-redux的使用過程&#xff0c;下面我們開始優化部分&#xff1a;當一個組件只有一個render生命周期&#xff0c;那么我們可以改寫成一個無狀態組件&#xff08;UI組件到無狀態組件&#xff0c;性能提升更好&#xff09;