当前位置: 首页 > article >正文

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. 终端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 方法
  1. 终端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泄露

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

如何租用服务器并通过ssh连接远程服务器终端

这里我使用的是智算云扉 没有打广告 但确实很便宜 还有二十小时免费额度 链接如下 注册之后 租用新实例 选择操作系统 选择显卡型号 点击租用 选择计费方式 选择镜像 如果跑深度学习的话 就选项目对应的torch版本 没有的话 就创建纯净的cuda 自己安装 点击创建实例 创建之后 …...

Git的核心作用详解

一、版本控制与历史追溯 Git作为分布式版本控制系统&#xff0c;其核心作用是记录代码的每一次修改&#xff0c;形成完整的历史记录。通过快照机制&#xff0c;Git会保存每次提交时所有文件的完整状态&#xff08;而非仅记录差异&#xff09;&#xff0c;确保开发者可以随时回…...

华为设备链路聚合实验:网络工程实战指南

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

AUTOSAR图解==>AUTOSAR_TR_AIDesignPatternsCatalogue

AUTOSAR 人工智能设计模式目录 AUTOSAR传感器执行器与仲裁设计模式的深入解析与图解 目录 简介传感器和执行器模式 架构概述组件结构交互流程应用场景 多请求者或提供者之间的仲裁模式 架构概述组件结构仲裁流程应用场景 总结 1. 简介 AUTOSAR&#xff08;AUTomotive Open Sy…...

linux基础操作4------(权限管理)

一.前言 今天我们来讲讲linux的权限管理&#xff0c;比如文件的权限&#xff0c;如果大家看过前面说的app逆向的frida&#xff0c;我们在手机里要给frida&#xff0c;我们都要设置一下chomd 777 frida &#xff0c;这样就给了可执行权限&#xff0c;这就是这一章要讲的&#x…...

双系统电脑中如何把ubuntu装进外接移动固态硬盘

电脑&#xff1a;win11 ubuntu22.04 实体机 虚拟机&#xff1a;VMware17 镜像文件&#xff1a;ubuntu-22.04.4-desktop-amd64.iso 或者 ubuntu20.4的镜像 外接固态硬盘1个 一、首先win11中安装vmware17 具体安装方法&#xff0c;网上很多教程 二、磁盘分区 1.在笔…...

Nacos源码—Nacos集群高可用分析(三)

6.CAP原则与Raft协议 (1)CAP分别指的是什么 一.C指的是一致性Consistency 各个集群节点之间的数据&#xff0c;必须要保证一致。 二.A指的是可用性Availability 在分布式架构中&#xff0c;每个请求都能在合理的时间内获得符合预期的响应。 三.P指的是分区容错性Partition To…...

【C语言】程序的预处理,#define详解

一、预定义符号 二、#define 1.#define定义标识符 #define &#xff0b; 自定义名称 &#xff0b; 代替的内容 例&#xff1a; #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日 • 阅读时间&#xff1a;10分钟 &#x1f4a1; TL;DR: 本文详细介绍了如何完整卸载旧版Node.js&#xff0c;安装NVM&#xff0c;配置阿里云镜像源&#xff0c;以及设置node_global与node_cache目录&#xff0c;打造高效Node.js开发环境。 &#x1f4cb; 目…...

(二)毛子整洁架构(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> <!--项目组织标识&#xff0c;通常对应包结构--> <artifactId>boot</artifactId> <!--项目唯一标识--> <version>0.0.1-SNAPSHOT</ve…...

小程序多线程实战

在小程序开发中&#xff0c;由于微信小程序的运行环境限制&#xff0c;原生并不支持传统意义上的多线程编程&#xff0c;但可以通过以下两种核心方案实现类似多线程的并发处理效果&#xff0c;尤其在处理复杂计算、避免主线程阻塞时非常关键&#xff1a; 一、官方方案&#xff…...

如何修改MySQL数据库密码

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

【Python】mat npy npz 文件格式

1、简介 MAT 文件和 NP&#xff08;.npy 或 .npz&#xff09;文件是两种不同的格式&#xff0c;用于存储数组数据。它们分别由 MATLAB 和 NumPy 开发&#xff0c;主要用于各自环境中的数据存储和交换。以下是这两种格式的主要区别&#xff1a; 1.1 格式和用途 MAT 文件&…...

SpringBoot快速入门WebSocket(​​JSR-356附Demo源码)

现在我想写一篇Java快速入门WebSocket,就使用 JSR-356的websocket,我想分以下几点, 1. websocket介绍, 1.1 介绍 什么是WebSocket&#xff1f;​​ WebSocket 是一种基于 ​​TCP​​ 的​​全双工通信协议​​&#xff0c;允许客户端和服务器在​​单个长连接​​上实…...

JDBC执行sql过程

1. 加载数据库驱动​ JDBC 通过 ​​驱动&#xff08;Driver&#xff09;​​ 实现与不同数据库的通信。驱动需提前加载到 JVM&#xff1a; 手动加载&#xff08;JDBC 4.0 前&#xff09;​​&#xff1a; Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 驱…...

VNC windows连接ubuntu桌面

✅ 步骤 1&#xff1a;安装 VNC 服务器 首先&#xff0c;我们需要在 Winux 系统上安装一个 VNC 服务器。这里我们使用 tigervnc 作为例子&#xff0c;它是一个常用的 VNC 服务器软件。 打开终端并更新你的软件包&#xff1a; sudo apt update安装 tigervnc 服务器&#xff1a;…...

CSS中的@import指令

一、什么是import指令&#xff1f; import 是CSS提供的一种引入外部样式表的方式&#xff0c;允许开发者在CSS文件中引入其他CSS文件&#xff0c;或者在HTML的<style>标签中引入外部样式。与常见的<link>标签相比&#xff0c;import 提供了一种更“CSS原生”的样式…...

【安装配置教程】ubuntu安装配置Kodbox

目录 一、引言 二、环境配置 1. 服务器配置​ 2. 必备组件​ 三、安装基础环境​ 1. 安装 PHP 8.1 及扩展​ 2. 安装 MySQL 数据库 3.安装 Redis&#xff08;可选&#xff0c;提升缓存性能&#xff09; 4. 配置nginx文件 4.1. 创建 Kodbox 站点目录​ 4.2. 编写 Ng…...

【软件设计师:数据库】13.数据库控制与安全

一、数据库语言SQL SQL是结构化查询语言(Structured Query Language)的缩写,其功能包括数据查询、数据操纵、数据定义和数据控制四个部分。 SQL 语言简洁、方便实用、功能齐全,已成为目前应用最广的关系数据库语言。SQL既是自含式语言(联机交互),又是嵌入式语言(宿主语…...

LabVIEW车牌自动识别系统

在智能交通快速发展的时代&#xff0c;车牌自动识别系统成为提升交通管理效率的关键技术。本案例详细介绍了基于 LabVIEW 平台&#xff0c;搭配大恒品牌相机构建的车牌自动识别系统&#xff0c;该系统在多个场景中发挥着重要作用&#xff0c;为交通管理提供了高效、精准的解决方…...

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语言&#xff01; 那就是贪吃蛇 为什么要做一个贪吃蛇小游戏呢&#xff1f; 因为这个小游戏所涉及到的知识有c语言的指针、数组、链表、函数等等可以让我们通过这个游戏来巩固c语言&#xff0c;进一步认识c语言。 一.我们先…...

6. HTML 锚点链接与页面导航

在开发长页面或文档类网站时,锚点链接(Anchor Links)是一个非常实用的功能。通过学习 HTML 锚点技术,将会掌握如何在同一页面内实现快速跳转,以及如何优化长页面的导航体验。以下是基于给定素材的学习总结和实践心得 一、什么是锚点链接? 锚点链接(也称为页面内链接)允…...

[项目总结] 抽奖系统项目技术应用总结

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

Axios替代品Alova

介绍alova | Alova.JS Multipart 实体请求 | Axios中文文档 | Axios中文网 1. 极致的轻量与性能 Tree-shaking优化&#xff1a;仅打包使用到的功能模块 零依赖&#xff1a;基础包仅 4KB&#xff08;Axios 12KB&#xff09; 2. 智能请求管理&#xff08;开箱即用&#xff0…...

Python OpenCV性能优化与部署实战指南

在计算机视觉领域&#xff0c;OpenCV作为开源视觉库的标杆&#xff0c;其性能表现直接影响着从工业检测到AI模型推理的各类应用场景。本文结合最新技术趋势与生产实践&#xff0c;系统性梳理Python环境下OpenCV的性能优化策略与部署方案。 一、性能优化核心技术矩阵 1.1 内存…...

k8s的flannel生产实战与常见问题排查

关于 Kubernetes Flannel 插件的详细教程及生产环境实战指南&#xff0c;涵盖核心概念、安装配置、常见问题排查与优化策略 Flannel通信流程 一、Flannel 概述 Flannel 是 Kubernetes 最常用的 CNI&#xff08;Container Network Interface&#xff09;插件之一&#xff0c;…...