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.mzph.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;