Linux系列:如何用perf跟踪.NET程序的mmap泄露
一:背景
1. 讲故事
如何跟踪.NET程序的mmap泄露,这个问题困扰了我差不多一年的时间,即使在官方的github库中也找不到切实可行的方案,更多海外大佬只是推荐valgrind这款工具,但这款工具底层原理是利用模拟器,它的地址都是虚拟出来的,你无法对valgrind 监控的程序抓dump,并且valgrind显示的调用栈无法映射出.NET函数以及地址,这几天我仔仔细细的研究这个问题,结合大模型的一些帮助,算是找到了一个相对可行的方案。
二:mmap 导致的内存泄露
1. 一个测试案例
为了方便讲述,我们通过 C 调用 mmap 方法分配256个 4M 的内存块,即总计 1G 的内存泄露,参考代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>#define BLOCK_SIZE (4096 * 1024) // 每个块 4096KB (4MB)
#define TOTAL_SIZE (1 * 1024 * 1024 * 1024) // 总计 1GB
#define BLOCKS (TOTAL_SIZE / BLOCK_SIZE) // 计算需要的块数void mmap_allocation() {uint8_t* blocks[BLOCKS]; // 存储每个块的指针// 使用 mmap 分配 1GB 内存,分成多个 4MB 块for (size_t i = 0; i < BLOCKS; i++) {blocks[i] = (uint8_t*)mmap(NULL, BLOCK_SIZE,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1, 0);if (blocks[i] == MAP_FAILED) {perror("mmap 失败");return;}// 确保每个块都被实际占用memset(blocks[i], 20, BLOCK_SIZE);}printf("已经使用 mmap 分配 1GB 内存(分成 %d 个 %dKB 块)!\n", BLOCKS, BLOCK_SIZE/1024);printf("程序将暂停 10 秒,可以使用 top/htop 查看内存使用情况...\n");sleep(10);
}int main() {mmap_allocation();return 0;
}
为了能够让 C# 调用,我们将这个 c 编译成 so 库,即 windows 中的 dll 文件,参考命令如下:
root@ubuntu2404:/data2/c# gcc -shared -o Example_18_1_5.so -fPIC -g -O0 Example_18_1_5.c
root@ubuntu2404:/data2/c# ls -lh
total 24K
-rw-r--r-- 1 root root 1.2K May 7 10:47 Example_18_1_5.c
-rwxr-xr-x 1 root root 18K May 7 10:47 Example_18_1_5.so
接下来创建一个名为 MyConsoleApp 的 Console控制台项目。
root@ubuntu2404:/data2# dotnet new console -n MyConsoleApp --framework net8.0 --use-program-main
The template "Console App" was created successfully.Processing post-creation actions...
Restoring /data2/MyConsoleApp/MyConsoleApp.csproj:Determining projects to restore...Restored /data2/MyConsoleApp/MyConsoleApp.csproj (in 1.73 sec).
Restore succeeded.root@ubuntu2404:/data2# cd MyConsoleApp
root@ubuntu2404:/data2/MyConsoleApp# dotnet run
Hello, World!
项目创建好之后,接下来就可以调用 Example_18_1_5.so 中的mmap_allocation
方法了,在真正调用之前故意用Console.ReadLine();
拦截,主要是方便用 perf 去介入监控,最后不要忘了将生成好的 Example_18_1_5.so
文件丢到 bin 目录下,参考代码如下:
using System.Runtime.InteropServices;namespace MyConsoleApp;class Program
{[DllImport("Example_18_1_5.so", CallingConvention = CallingConvention.Cdecl)]public static extern void mmap_allocation();static void Main(string[] args){MyTest();for (int i = 0; i < int.MaxValue; i++){Console.WriteLine($"{DateTime.Now} :i={i} 执行完毕,自我轮询中...");Thread.Sleep(1000);}Console.ReadLine();}static void MyTest(){Console.WriteLine("MyTest 已执行,准备执行 mmap_allocation 方法");Console.ReadLine();mmap_allocation();Console.WriteLine("MyTest 已执行,准备执行 mmap_allocation 方法");}
}
2. 使用 perf 监控mmap事件
Linux 上的 perf 你可以简单的理解成 Windows 上的 perfview,前者是基于 perf_events 子系统,后者是基于 etw事件,这里就不做具体介绍了,这里我们用它监控 mmap 的调用,因为拿到调用线程栈之后,就可以知道到底是谁导致的泄露。
为了能够让 perf 识别到 .NET 的托管栈,微软做了一些特别支持,即开启 export DOTNET_PerfMapEnabled=1
环境变量,截图如下:
更多资料参考:https://learn.microsoft.com/zh-cn/dotnet/core/runtime-config/debugging-profiling
- 在
终端1
上启动 C# 程序。
root@ubuntu2404:/data2/MyConsoleApp/bin/Debug/net8.0# export DOTNET_PerfMapEnabled=1
root@ubuntu2404:/data2/MyConsoleApp/bin/Debug/net8.0# dotnet MyConsoleApp.dll
MyTest 已执行,准备执行 mmap_allocation 方法
终端2
上开启 perf 对dontet程序的mmap进行跟踪。
root@ubuntu2404:/data2/MyConsoleApp# ps -ef | grep Console
root 3074 2197 0 11:14 pts/1 00:00:00 dotnet MyConsoleApp.dll
root 3241 3106 0 11:56 pts/3 00:00:00 grep --color=auto Console
root@ubuntu2404:/data2/MyConsoleApp# perf record -p 3074 -g -e syscalls:sys_enter_mmap
启动跟踪之后记得在 终端1
上按下Enter回车让程序继续执行,当跟踪差不多(大量的内存泄露)的时候,我们在 终端2
上按下 Ctrl+C
停止跟踪,截图如下:
root@ubuntu2404:/data2/MyConsoleApp# perf record -p 3074 -g -e syscalls:sys_enter_mmap
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.139 MB perf.data (333 samples) ]
从输出看当前的 perf.data 有 333 个样本,0.13M 的大小,由于在 linux 上分析不方便,而且又是二进制的,所以我们将 perf.data 转成 perf.txt 然后传输到 windows 上分析,参考命令如下:
root@ubuntu2404:/data2/MyConsoleApp# ls
MyConsoleApp.csproj Program.cs bin obj perf.data
root@ubuntu2404:/data2/MyConsoleApp# perf script > perf.txt
root@ubuntu2404:/data2/MyConsoleApp# sz perf.txt
经过仔细的分析 perf.txt 的 mmap 调用栈,很快就会发现有人调了 256 次 4M 的 mmap 分配吃掉了绝大部分内存,那个上层的 memfd:doublemapper
就是 JIT 代码所存放的内存临时文件,由于有 DOTNET_PerfMapEnabled=1
的加持,可以看到 [unknown]
前面的方法返回地址,截图如下:
3. 这些地址对应的 C# 方法是什么
本来我以为 JIT很给力,在 perf 生成的 /tmp/perf-3074.map
文件中弄好了符号信息,结果搜了下没有对应的方法名,比较尴尬。
root@ubuntu2404:/data2/MyConsoleApp# grep "7f42f3f11967" /tmp/perf-3074.map
root@ubuntu2404:/data2/MyConsoleApp# grep "7f42f3f11a90" /tmp/perf-3074.map
root@ubuntu2404:/data2/MyConsoleApp#
那怎么办呢?只能抓dump啦,这也是我非常擅长的,可以用 dotnet-dump
抓一个,然后使用 !ip2md
观察便知。
root@ubuntu2404:/data2/MyConsoleApp# dotnet-dump collect -p 3074Writing full to /data2/MyConsoleApp/core_20250507_113516
Complete
root@ubuntu2404:/data2/MyConsoleApp# ls -lh
total 1.2G
-rw-r--r-- 1 root root 242 May 7 10:50 MyConsoleApp.csproj
-rw-r--r-- 1 root root 769 May 7 11:05 Program.cs
drwxr-xr-x 3 root root 4.0K May 7 10:51 bin
-rw------- 1 root root 1.2G May 7 11:35 core_20250507_113516
drwxr-xr-x 3 root root 4.0K May 7 10:51 obj
-rw------- 1 root root 164K May 7 11:16 perf.data
-rw-r--r-- 1 root root 874K May 7 11:21 perf.txt
root@ubuntu2404:/data2/MyConsoleApp# dotnet-dump analyze core_20250507_113516
Loading core dump: core_20250507_113516 ...
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 7f42f3f11967
MethodDesc: 00007f42f3f9f320
Method Name: MyConsoleApp.Program.Main(System.String[])
Class: 00007f42f3fbb648
MethodTable: 00007f42f3f9f368
mdToken: 0000000006000002
Module: 00007f42f3f9cec8
IsJitted: yes
Current CodeAddr: 00007f42f3f11920
Version History:ILCodeVersion: 0000000000000000ReJIT ID: 0IL Addr: 00007f437307e250CodeAddr: 00007f42f3f11920 (MinOptJitted)NativeCodeVersion: 0000000000000000
Source file: /data2/MyConsoleApp/Program.cs @ 12
> ip2md 7f42f3f11a90
MethodDesc: 00007f42f3f9f338
Method Name: MyConsoleApp.Program.MyTest()
Class: 00007f42f3fbb648
MethodTable: 00007f42f3f9f368
mdToken: 0000000006000003
Module: 00007f42f3f9cec8
IsJitted: yes
Current CodeAddr: 00007f42f3f11a50
Version History:ILCodeVersion: 0000000000000000ReJIT ID: 0IL Addr: 00007f437307e2d2CodeAddr: 00007f42f3f11a50 (MinOptJitted)NativeCodeVersion: 0000000000000000
Source file: /data2/MyConsoleApp/Program.cs @ 28
> ip2md 7f42f3f13557
MethodDesc: 00007f42f42f42b8
Method Name: ILStubClass.IL_STUB_PInvoke()
Class: 00007f42f42f41e0
MethodTable: 00007f42f42f4248
mdToken: 0000000006000000
Module: 00007f42f3f9cec8
IsJitted: yes
Current CodeAddr: 00007f42f3f134d0
Version History:ILCodeVersion: 0000000000000000ReJIT ID: 0IL Addr: 0000000000000000CodeAddr: 00007f42f3f134d0 (MinOptJitted)NativeCodeVersion: 0000000000000000
>
从 dotnet-dump 给的输出看,可以清楚的看到调用关系为: Main -> MyTest -> ILStubClass.IL_STUB_PInvoke -> mmap_allocation -> mmap
。
至此真相大白于天下。
三:总结
这类问题的泄露真的费了我不少心思,曾经让我纠结过,迷茫过,我也捣鼓过 strace,最终都无法找出栈上的托管函数,真的,目前 .NET 在 Linux 调试生态上还是很弱,好无奈,这篇文章我相信弥补了国内,甚至国外在这一块领域的空白,也算是这一年来对自己的一个交代。
相关文章:

Linux系列:如何用perf跟踪.NET程序的mmap泄露
一:背景 1. 讲故事 如何跟踪.NET程序的mmap泄露,这个问题困扰了我差不多一年的时间,即使在官方的github库中也找不到切实可行的方案,更多海外大佬只是推荐valgrind这款工具,但这款工具底层原理是利用模拟器ÿ…...

如何租用服务器并通过ssh连接远程服务器终端
这里我使用的是智算云扉 没有打广告 但确实很便宜 还有二十小时免费额度 链接如下 注册之后 租用新实例 选择操作系统 选择显卡型号 点击租用 选择计费方式 选择镜像 如果跑深度学习的话 就选项目对应的torch版本 没有的话 就创建纯净的cuda 自己安装 点击创建实例 创建之后 …...
Git的核心作用详解
一、版本控制与历史追溯 Git作为分布式版本控制系统,其核心作用是记录代码的每一次修改,形成完整的历史记录。通过快照机制,Git会保存每次提交时所有文件的完整状态(而非仅记录差异),确保开发者可以随时回…...

华为设备链路聚合实验:网络工程实战指南
链路聚合就像为网络搭建 “并行高速路”,既能扩容带宽,又能保障链路冗余,超实用! 一、实验拓扑速览 图中两台交换机 LSW1 和 LSW2,PC1、PC2 归属 VLAN 10,PC3 归属 VLAN 30。LSW1 与 LSW2 通过 GE0/0/1、…...

AUTOSAR图解==>AUTOSAR_TR_AIDesignPatternsCatalogue
AUTOSAR 人工智能设计模式目录 AUTOSAR传感器执行器与仲裁设计模式的深入解析与图解 目录 简介传感器和执行器模式 架构概述组件结构交互流程应用场景 多请求者或提供者之间的仲裁模式 架构概述组件结构仲裁流程应用场景 总结 1. 简介 AUTOSAR(AUTomotive Open Sy…...
linux基础操作4------(权限管理)
一.前言 今天我们来讲讲linux的权限管理,比如文件的权限,如果大家看过前面说的app逆向的frida,我们在手机里要给frida,我们都要设置一下chomd 777 frida ,这样就给了可执行权限,这就是这一章要讲的&#x…...

双系统电脑中如何把ubuntu装进外接移动固态硬盘
电脑:win11 ubuntu22.04 实体机 虚拟机:VMware17 镜像文件:ubuntu-22.04.4-desktop-amd64.iso 或者 ubuntu20.4的镜像 外接固态硬盘1个 一、首先win11中安装vmware17 具体安装方法,网上很多教程 二、磁盘分区 1.在笔…...
Nacos源码—Nacos集群高可用分析(三)
6.CAP原则与Raft协议 (1)CAP分别指的是什么 一.C指的是一致性Consistency 各个集群节点之间的数据,必须要保证一致。 二.A指的是可用性Availability 在分布式架构中,每个请求都能在合理的时间内获得符合预期的响应。 三.P指的是分区容错性Partition To…...

【C语言】程序的预处理,#define详解
一、预定义符号 二、#define 1.#define定义标识符 #define + 自定义名称 + 代替的内容 例: #define MAX 100 #define CASE break;case #define CASE break;caseint main() {int n 0;switch (n){case 1:CASE 2:CASE 3:CASE 4:}return …...

NVM完全指南:安装、配置与最佳实践
发布于 2025年5月7日 • 阅读时间:10分钟 💡 TL;DR: 本文详细介绍了如何完整卸载旧版Node.js,安装NVM,配置阿里云镜像源,以及设置node_global与node_cache目录,打造高效Node.js开发环境。 📋 目…...

(二)毛子整洁架构(CQRS/Dapper/领域事件处理器/垂直切片)
文章目录 项目地址一、Application 层1.1 定义CQRS的接口以及其他服务1. Command2. IQuery查询3. 当前时间服务接口4. 邮件发送服务接口 1.2 ReserveBooking Command1. 处理传入的参数2. ReserveBookingCommandHandler3. BookingReservedDomainEvent 1.3 Query使用Sql查询1. 创…...
基于大核感知与非膨胀卷积的SPPF改进—融合UniRepLK的YOLOv8目标检测创新架构
在当前目标检测领域中,YOLO系列模型因其优异的速度-精度平衡能力而被广泛部署于工业界与科研场景。YOLOv8作为该系列的最新版本,在主干网络与特征金字塔结构上进行了多项优化,进一步提升了其实时性与鲁棒性。然而,其核心组件—SPPF(Spatial Pyramid Pooling Fast)模块仍采用…...
基于SpringBoot网上书店的设计与实现
pom.xml配置文件 1. 项目基本信息(没什么作用) <groupId>com.spring</groupId> <!--项目组织标识,通常对应包结构--> <artifactId>boot</artifactId> <!--项目唯一标识--> <version>0.0.1-SNAPSHOT</ve…...
小程序多线程实战
在小程序开发中,由于微信小程序的运行环境限制,原生并不支持传统意义上的多线程编程,但可以通过以下两种核心方案实现类似多线程的并发处理效果,尤其在处理复杂计算、避免主线程阻塞时非常关键: 一、官方方案ÿ…...

如何修改MySQL数据库密码
文章目录 一、忘记数据库密码该如何修改1. 关闭数据库的服务2.跳过安全检查3. 重置密码4.查询用户是否存在5.退出验证密码是否正确 二、未忘记密码该如何修改密码1.直接修改密码2.登录mysql 时间久了,忘记数据库密码了。。。。。 一、忘记数据库密码该如何修改 1. …...

【Python】mat npy npz 文件格式
1、简介 MAT 文件和 NP(.npy 或 .npz)文件是两种不同的格式,用于存储数组数据。它们分别由 MATLAB 和 NumPy 开发,主要用于各自环境中的数据存储和交换。以下是这两种格式的主要区别: 1.1 格式和用途 MAT 文件&…...

SpringBoot快速入门WebSocket(JSR-356附Demo源码)
现在我想写一篇Java快速入门WebSocket,就使用 JSR-356的websocket,我想分以下几点, 1. websocket介绍, 1.1 介绍 什么是WebSocket? WebSocket 是一种基于 TCP 的全双工通信协议,允许客户端和服务器在单个长连接上实…...
JDBC执行sql过程
1. 加载数据库驱动 JDBC 通过 驱动(Driver) 实现与不同数据库的通信。驱动需提前加载到 JVM: 手动加载(JDBC 4.0 前): Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 驱…...
VNC windows连接ubuntu桌面
✅ 步骤 1:安装 VNC 服务器 首先,我们需要在 Winux 系统上安装一个 VNC 服务器。这里我们使用 tigervnc 作为例子,它是一个常用的 VNC 服务器软件。 打开终端并更新你的软件包: sudo apt update安装 tigervnc 服务器:…...
CSS中的@import指令
一、什么是import指令? import 是CSS提供的一种引入外部样式表的方式,允许开发者在CSS文件中引入其他CSS文件,或者在HTML的<style>标签中引入外部样式。与常见的<link>标签相比,import 提供了一种更“CSS原生”的样式…...

【安装配置教程】ubuntu安装配置Kodbox
目录 一、引言 二、环境配置 1. 服务器配置 2. 必备组件 三、安装基础环境 1. 安装 PHP 8.1 及扩展 2. 安装 MySQL 数据库 3.安装 Redis(可选,提升缓存性能) 4. 配置nginx文件 4.1. 创建 Kodbox 站点目录 4.2. 编写 Ng…...
【软件设计师:数据库】13.数据库控制与安全
一、数据库语言SQL SQL是结构化查询语言(Structured Query Language)的缩写,其功能包括数据查询、数据操纵、数据定义和数据控制四个部分。 SQL 语言简洁、方便实用、功能齐全,已成为目前应用最广的关系数据库语言。SQL既是自含式语言(联机交互),又是嵌入式语言(宿主语…...

LabVIEW车牌自动识别系统
在智能交通快速发展的时代,车牌自动识别系统成为提升交通管理效率的关键技术。本案例详细介绍了基于 LabVIEW 平台,搭配大恒品牌相机构建的车牌自动识别系统,该系统在多个场景中发挥着重要作用,为交通管理提供了高效、精准的解决方…...
el-menu 折叠后小箭头不会消失
官方示例 <template><el-radio-group v-model"isCollapse" style"margin-bottom: 20px"><el-radio-button :value"false">expand</el-radio-button><el-radio-button :value"true">collapse</el-ra…...

c语言第一个小游戏:贪吃蛇小游戏01
hello啊大家好 今天我们用一个小游戏来增强我们的c语言! 那就是贪吃蛇 为什么要做一个贪吃蛇小游戏呢? 因为这个小游戏所涉及到的知识有c语言的指针、数组、链表、函数等等可以让我们通过这个游戏来巩固c语言,进一步认识c语言。 一.我们先…...
6. HTML 锚点链接与页面导航
在开发长页面或文档类网站时,锚点链接(Anchor Links)是一个非常实用的功能。通过学习 HTML 锚点技术,将会掌握如何在同一页面内实现快速跳转,以及如何优化长页面的导航体验。以下是基于给定素材的学习总结和实践心得 一、什么是锚点链接? 锚点链接(也称为页面内链接)允…...

[项目总结] 抽奖系统项目技术应用总结
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...
Axios替代品Alova
介绍alova | Alova.JS Multipart 实体请求 | Axios中文文档 | Axios中文网 1. 极致的轻量与性能 Tree-shaking优化:仅打包使用到的功能模块 零依赖:基础包仅 4KB(Axios 12KB) 2. 智能请求管理(开箱即用࿰…...
Python OpenCV性能优化与部署实战指南
在计算机视觉领域,OpenCV作为开源视觉库的标杆,其性能表现直接影响着从工业检测到AI模型推理的各类应用场景。本文结合最新技术趋势与生产实践,系统性梳理Python环境下OpenCV的性能优化策略与部署方案。 一、性能优化核心技术矩阵 1.1 内存…...
k8s的flannel生产实战与常见问题排查
关于 Kubernetes Flannel 插件的详细教程及生产环境实战指南,涵盖核心概念、安装配置、常见问题排查与优化策略 Flannel通信流程 一、Flannel 概述 Flannel 是 Kubernetes 最常用的 CNI(Container Network Interface)插件之一,…...