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 23681msroot@ubuntu2404:/data/CSharpApplication/bin/Debug/net8.0# heaptrack stats:allocations: 122151leaked allocations: 108551temporary allocations: 4118root@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高级调试的朋友此时应该轻车熟路了,思路如下:
- 寻找 指令地址。
一般来说解析不出来都会生成对应的 指令地址 的,这个可以到 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#方法名了。
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: 0000000000000000ReJIT ID: 0IL Addr: 0000000000000000CodeAddr: 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: 0000000000000000ReJIT ID: 0IL Addr: 00007ea6de8f1250CodeAddr: 00007ea662711920 (MinOptJitted)NativeCodeVersion: 0000000000000000
Source file: /data/CSharpApplication/Program.cs @ 12
到这里恍然大悟,然来调用路径为:CSharpApplication.Program.Main -> PInvoke -> heapmalloc ,至此真相大白。
三:总结
Linux 上的调试总觉得少了一位总管太监,能分析 非托管内存的工具 不鸟dotnet, 同样的,能分析 dotnet托管内存的工具 也不鸟非托管内存,大家各自为政。。。 让习惯使用通杀一切的windbg使用者太不可思议了。
相关文章:
Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
一:背景 1. 讲故事 前面跟大家分享过一篇 C# 调用 C代码引发非托管内存泄露 的文章,这是一个故意引发的正向泄露,这一篇我们从逆向的角度去洞察引发泄露的祸根代码,这东西如果在 windows 上还是很好处理的,很多人知道…...
【HDLbits--counter】
HDLbits--counter 在IC设计中,counter是十分普遍和重要的设计内容; 题目:基础计数器 module top_module (input clk,input reset,output [9:0] q);always (posedge clk) beginif(reset) beginq < 0;end else beginif(q999) beginq < 0…...
nvm 让 Node.js 版本切换更灵活
有很多小伙伴前端开发进程中,我们常常会遇到不同项目依赖不同版本 Node.js 的情况。我们不可能去卸载重新安装适应的版本去安装依赖或者启动项目。为了避免版本冲突带来的一系列麻烦,在这里给大家推荐一款Node.js 版本管理工具——nvm(Node V…...
双向选择排序算法
一 概述 双向选择排序(又称鸡尾酒选择排序)是选择排序的优化版本,核心改进在于每轮遍历同时确定未排序部分的最小值和最大值,分别交换到序列两端,从而减少遍历轮数。 二 时间复杂度 时间复杂度为(O(n^2)),但实际比较次数约为标准选择排序的 (1/2)。 三 C++实现代…...
美畅物联丨P2P系列之STUN服务器:助力网络穿透
在当今WebRTC等实时通信应用广泛兴起的复杂网络环境下,如何在NAT(网络地址转换)环境中实现高效、稳定的点对点(P2P)连接成为关键。STUN(Session Traversal Utilities for NAT)服务器作为应对这一…...
基于SpringBoot的“积分制零食自选销售平台”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“积分制零食自选销售平台”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 局部E-R图 系统首页界面…...
DeepSeek-V3 技术报告解读
DeepSeek火了有一段时间了,春节假期因为没时间,所以关于deepseek大模型一系列的技术报告一直没看,新年开工后,抽一点时间把之前的坑补起来,关于DeepSeek-V3技术报告的解读已经有很多了,但我相信不同的人去读…...
Spring使用@Scheduled注解的参数详解
在现代Java开发中,定时任务是一个常见的需求。Spring框架提供了Scheduled注解,让我们能够以简单、直观的方式定义和管理这些定时任务。接下来,我们来深入探讨这个注解的使用,以及它的参数都有哪些含义和作用。 Scheduled注解可以…...
力扣72题编辑距离
题目 原理 三个操作对应的操作次数分别是: 插入:在原本的次数上 1删除:在原本的次数上1替换:如果两个位置的字符串一样,则等于原本的次数, 如果不等,在原本的次数上1 去三者的最小值,就是最小的编辑次数 示例 代码 答案是2 package org.example;public class _72_编辑距离 {pu…...
聊天服务器分布式改造
目前的聊天室是单节点的,无论是http接口还是socket接口都在同一个进程,无法承受太多人同时在线,容灾性也非常差。因此,一个成熟的IM产品一定是做成分布式的,根据功能分模块,每个模块也使用多个节点并行部署…...
Python编程中常见的10个案例
文章目录 1. Hello, World!2. 计算斐波那契数列3. 文件读写4. 列表推导式5. 异常处理6. 函数定义与调用7. 类和对象8. 使用模块9. 网络请求10. 数据可视化总结 1. Hello, World! 这是学习任何编程语言时的第一个程序。 代码示例 print("Hello, World!")2. 计算斐波…...
Ardupilot开源无人机之Geek SDK进展2025Q1
Ardupilot开源无人机之Geek SDK进展2025Q1 1. 源由2. 内容汇总2.1 【jetson-fpv】YOLO INT8 coco8 dataset 精度降级2.2 【OpenIPC-Configurator】OpenIPC Configurator 固件升级失败2.3 【OpenIPC-Adaptive-link】OpenIPC RF信号质量相关显示2.4 【OpenIPC-msposd】.srt/.osd…...
linux上安装redis[从0到1]
redis安装步骤 1.下载redis2.新建redis文件夹3.解压安装Redis4.编译5.修改相关配置6.错误 redis下载官网: https://download.redis.io/releases/ 找到自己需要的版本 1.下载redis 选着自己需要下载的版本后,右击选择复制链接,然后利用命令进行下载&am…...
批量删除 Excel 中的空白行、空白列以及空白表格
我们经常会碰到需要删除 Excel 文档表格中的空白行及空白列的场景,有一些空白行或空白列可能我们人工不好识别,因此删除空白行空白列对我们来讲就非常的繁琐,因为我们需要先识别哪些 Excel 文档中包含空白行或者空白列,我们才能够…...
MyBatis SQL 映射文件的作用和结构
MyBatis SQL 映射文件定义了 SQL 语句以及如何将 SQL 语句的参数和结果映射到 Java 对象。 一、 作用 (Purpose) MyBatis SQL 映射文件(通常命名为 XXXMapper.xml)的主要作用是: 定义 SQL 语句: 在 XML 映射文件中编写 SQL 语句…...
MYSQL之创建数据库和表
创建数据库db_ck (下面的创建是最好的创建方法,如果数据库存在也不会报错,并且指定使用utf8mb4) show databases命令可以查看所有的数据库名,可以找到刚刚创建的db_ck数据库 使用该数据库时,发现里面没有…...
react+ts+eslint+prettier 配置教程
1.创建项目 npx create-react-app my-app --template typescript 2.安装依赖 eslint:核心代码质量工具。 prettier:代码格式化工具。 eslint-plugin-prettier:将 Prettier 的规则集成到 ESLint 中。 eslint-config-prettier:…...
ArduPilot开源代码之AP_OSD
ArduPilot开源代码之AP_OSD 1. 源由2. 简介3. 补丁4. 框架设计4.1 启动代码 (AP_OSD::init)4.2 任务代码 (AP_OSD::osd_thread)4.3 实例初始化 (AP_OSD::init_backend) 5. 重要例程5.1 AP_OSD::update_stats5.2 AP_OSD::update_current_screen5.3 AP_OSD::update_osd 6. 总结7.…...
sysbench手动测试OceanBase v4.2.4集群
环境: 1、ocp(sysbench节点) 192.192.103.128 2、ob集群1-1-1 observer 192.192.103.125、192.192.103.126、192.192.103.127,primary_zone:random haproxy 192.192.103.125、192.192.103.126、192.192.103.127 一、安装sysben…...
腾讯元宝:AI 时代的快速论文阅读助手
1. 背景与需求 在 AI 研究领域,每天都会涌现大量学术论文。如何高效阅读并提取关键信息成为研究者的一大难题。腾讯元宝是腾讯推出的一款大模型,结合了**大语言模型(LLM)和自然语言处理(NLP)**技术&#x…...
重构谷粒商城09:人人开源框架的快速入门
谷粒商城09——人人开源框架的快速入门 前言:这个系列将使用最前沿的cursor作为辅助编程工具,来快速开发一些基础的编程项目。目的是为了在真实项目中,帮助初级程序员快速进阶,以最快的速度,效率,快速进阶…...
AAA 技术详解:认证、授权与计费的原理、应用与配置实践
AAA(Authentication, Authorization, Accounting,即认证、授权和计费)是网络安全的“身份管理员”,负责验证用户身份、分配访问权限并记录行为轨迹。它如同网络世界中的“物业管理系统”,通过三重机制确保接入安全、权…...
OneM2M:全球性的物联网标准-可应用于物联网中
OneM2M 是一个全球性的物联网(IoT)标准,旨在为物联网设备和服务提供统一的框架和接口,以实现设备之间的互操作性、数据共享和服务集成。OneM2M 由多个国际标准化组织(如 ETSI、TIA、TTC、ARIB 等)共同制定,目标是解决物联网领域的碎片化问题,提供一个通用的标准,支持跨…...
redis数据迁移教程(使用RedisShake实现不停机迁移十分便捷)
1.我的场景 需要把本地的redis数据上传到阿里云服务器上面,服务器上redis并没有开aof持久化,但是将rdb文件上传至服务器后每次重启redis,rdb文件会被覆盖导致无法同同步数据,最终决定使用RedisShake 2.RedisShake介绍 什么是 RedisShake RedisShake 是一个用于处理和迁移…...
Linux基本操作指令3
1、wget: 这是一个用于从网络上下载文件的命令行工具。它支持 HTTP、HTTPS 和 FTP 协议。 wget http://download.qt.io/archive/qt/5.12/5.12.9/qt-opensource-linux-x64-5.12.9.run 2、下载完成后,你可以通过以下命令使文件可执行并运行安装程序: ch…...
2025年2月平价旗舰手机性能对比
1、荣耀Magic7 点评:缺席潜望式长焦,3X直立长焦体验还行。兼顾性能、游戏、屏幕、影像、续航、快充等诸多方面,且外围配置比较齐全。 2、vivo x200 点评:潜望式长焦相机,拍照效果好,30W无线充电着实鸡肋&a…...
Python SQLite3 保姆级教程:从零开始学数据库操作
Python SQLite3 保姆级教程:从零开始学数据库操作 本文适合纯新手!无需任何数据库基础,跟着步骤操作即可掌握 SQLite3 的核心用法。 目标:让你像用记事本一样轻松操作数据库! 目录 什么是 SQLite3?环境准…...
第七步:简单爬虫与网页测试
Puppeteer 官方文档:https://puppeteer.bootcss.com/ 1、安装 puppeteer是一个node插件安装命令:npm i puppeteer 2、概念 无头浏览器:就是不打开浏览器的页面,直接进行浏览器后台操作 3、入门 引入:import pup…...
4.桥接模式
概况 桥接模式:将抽象部分与实现部分分离,使它们可以独立变化,通过组合而非继承的方式实现解耦。 业务场景 场景描述:开发一个跨平台的图形绘制系统,支持不同形状(如圆形、矩形)和不同渲染方式…...
Golang学习笔记_44——命令模式
Golang学习笔记_41——观察者模式 Golang学习笔记_42——迭代器模式 Golang学习笔记_43——责任链模式 文章目录 一、核心概念1. 定义2. 解决的问题3. 核心角色4. 类图 二、特点分析三、适用场景1. 事务管理系统2. 多媒体遥控器3. 操作审计系统 四、Go语言实现示例五、高级应用…...
