记一次 .NET某数字化协同管理系统 内存暴涨分析
一:背景
1. 讲故事
高级调试训练营里的一位朋友找到我,说他们跑在linux上的.NET程序出现了内存泄露的情况,上windbg观察发现内存都是IMAGE给吃掉了,那些image都标记了 doublemapper__deleted_ 字样,问我为啥会这样?说实话作为我们这些调试者非常喜欢和这样的人打交道,毕竟沟通起来顺畅,也特别能激发对方的探索欲,这也是训练营给予的一种魅力吧。
二:内存暴涨分析
1. 为什么会暴涨
看过我这个系列的朋友都知道观察内存用 !address -summary 命令,但这个命令是为 windows 打造的,所以在 linux 上行不通,为此sos提供了一个专门的命令 !maddress 来替代,接下来使用 !maddress -orderBySize 观察下内存分布情况。
0:000> !maddress -orderBySize
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Memory Kind | StartAddr | EndAddr-1 | Size | Type | State | Protect | Image |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Image | 7f4000000000 | 7f4007ff6000 | 127.96mb | MEM_IMAGE | MEM_COMMIT | PAGE_READWRITE | doublemapper__deleted_ |
| Image | 7f3fc4000000 | 7f3fcbff5000 | 127.96mb | MEM_IMAGE | MEM_COMMIT | PAGE_READWRITE | doublemapper__deleted_ |
| Image | 7f404c021000 | 7f4051b4c000 | 91.17mb | MEM_IMAGE | MEM_UNKNOWN | PAGE_UNKNOWN | doublemapper__deleted_ |
| Image | 7f3fae82e000 | 7f3fb4000000 | 87.82mb | MEM_IMAGE | MEM_COMMIT | PAGE_EXECUTE_READ | doublemapper__deleted_ |
| Image | 7f406c021000 | 7f40701ff000 | 65.87mb | MEM_IMAGE | MEM_UNKNOWN | PAGE_UNKNOWN | doublemapper__deleted_
...
+----------------------------------------------------------------------+
| Memory Type | Count | Size | Size (bytes) |
+----------------------------------------------------------------------+
| Image | 980 | 3.54gb | 3,801,517,056 |
| PAGE_READWRITE | 1,178 | 1.17gb | 1,255,059,968 |
| Stack | 66 | 499.35mb | 523,604,992 |
...
| NewStubPrecodeHeap | 4 | 64.00kb | 65,536 |
+----------------------------------------------------------------------+
| [TOTAL] | 8,254 | 6.01gb | 6,451,347,968 |
+----------------------------------------------------------------------+
从卦象看,总计 6.4G 的内存使用,Image 就吃了 3.8G,从 details 看确实都标记了 doublemapper__deleted_,说实话我分析了300多例的dump,Image 吃了大头是第二次遇到,这种故障案例一般是可遇不可求的,接下来我们探究下 doublemapper__deleted_ 为何方神圣。
2. doublemapper__deleted_ 是什么
要想找到这个答案,先从 coreclr 源代码中寻找蛛丝马迹,全局检索之后很快发现了关键词 doublemapper相关的代码:
bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize)
{
#ifndef TARGET_OSX
#ifdef TARGET_FREEBSD
int fd = shm_open(SHM_ANON, O_RDWR | O_CREAT, S_IRWXU);
#elif defined(TARGET_SUNOS) // has POSIX implementation
char name[24];
sprintf(name, "/shm-dotnet-%d", getpid());
name[sizeof(name) - 1] = '\0';
shm_unlink(name);
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
#else // TARGET_FREEBSD
int fd = memfd_create("doublemapper", MFD_CLOEXEC);
#endif // TARGET_FREEBSD
*pMaxExecutableCodeSize = MaxDoubleMappedSize;
*pHandle = (void*)(size_t)fd;
#else // !TARGET_OSX
*pMaxExecutableCodeSize = SIZE_MAX;
*pHandle = NULL;
#endif // !TARGET_OSX
return true;
}
从卦象看,真尼玛乱,coreclr 为了兼容各种操作系统核,加了无数的 if,else 判断,无语了,最后在非OSX,非FREEBSD,非SUNOS的情况下走了 memfd_create 函数,到这里事情有了一些进展了。
熟悉 Linux 的朋友应该知道 memfd_create 是一个 Linux 系统调用,用于创建一个匿名文件描述符,如果在 Windows 上找等价函数的话,那就是 win32api 中的 CreateFileMapping 函数,即内存映射文件,这个在源码目录中也能观之一二:

可能有些朋友对 memfd_create 的使用还是有些模糊,我让 chatgpt 帮我生成一段简单的 demo 辅助大家理解下,简化后如下:
int main() {
const char *name = "example_memfd";
int fd;
size_t size = 1024; // 1 KB
void *map;
const char *text = "Hello, memfd_create!";
// Create the memory file descriptor
fd = memfd_create(name, MFD_CLOEXEC);
// Resize the memory file to the desired size
ftruncate(fd, size)
// Map the memory file into the address space
map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// Write some data to the memory file
strncpy(map, text, strlen(text));
// Print the data from the memory file
printf("Data in memory file: %s\n", (char *)map);
// Unmap the memory
munmap(map, size)
// Close the file descriptor
close(fd);
return 0;
}
卦中的逻辑非常简单,需要注意的是这里有一个重要步骤就是通过 mmap 将 fd 挂上物理内存,即 fd -> mmap <- memory,挂上之后就可以轻松的往里面写数据了。
有了这些基础之后,大家再看 doublemapper__deleted_ 字样是不是有种豁然开朗的感觉?大概就是资源释放中只执行了 close(fd),但没有执行 mummap,参考如下:
// Unmap the memory (某种原因未执行)
//munmap(map, size)
// Close the file descriptor
close(fd);
哈哈,当然我的推测不一样对,熟悉 linux 的朋友可以指点指点。 接下来研究方向在哪里呢?既然我已经推测出貌似存在某种逻辑bug,但 coreclr 代码不是我们写的,所以我能不能绕过去呢?
3. 可以绕过 memfd_create 吗?
要想知道能不能绕过去,还得从源代码中寻找答案,天不负有心人,还真给找到了,简化后的代码如下:
bool ExecutableAllocator::Initialize()
{
if (IsDoubleMappingEnabled())
{
if (!VMToOSInterface::CreateDoubleMemoryMapper(&m_doubleMemoryMapperHandle, &m_maxExecutableCodeSize))
{
g_isWXorXEnabled = false;
return true;
}
m_CriticalSection = ClrCreateCriticalSection(CrstExecutableAllocatorLock,CrstFlags(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD));
}
return true;
}
bool ExecutableAllocator::IsDoubleMappingEnabled()
{
#if defined(HOST_OSX) && defined(HOST_ARM64)
return false;
#else
return g_isWXorXEnabled;
#endif
}
bool ExecutableAllocator::g_isWXorXEnabled = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableWriteXorExecute) != 0;
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableWriteXorExecute, W("EnableWriteXorExecute"), 1, "Enable W^X for executable memory.");
从卦中代码看,最终是由 EnableWriteXorExecute 外部变量控制的,那这个变量是什么意思呢?其实它是操作系统和CPU联合提供的功能,在 https://en.wikipedia.org/wiki/W%5EX 上对 W^X 特性做了介绍,大概意思就是:
它是一种内存保护策略,根据该策略,进程或内核地址空间中的每个页面要么是可写的,要么是可执行的,但不能同时具备这两种属性,如果没有这种保护,程序就可以在原本用于存储数据的内存区域中写入(作为数据 “W”)CPU 指令,然后运行(作为可执行代码 “X”;或读 - 执行 “RX”)这些指令。如果写入内存的一方怀有恶意,这就会带来危险。
而且 EnableWriteXorExecute 这东西导致的问题在 github 上有很多的讨论:
- https://github.com/dotnet/runtime/issues/97765
- https://stackoverflow.com/questions/77164379/how-do-i-debug-a-net-core-console-app-with-windbg-by-launch-executable
- https://github.com/dotnet/runtime/issues/79469
大家给出的建议都是将其关闭,操作方式如下:
export DOTNET_EnableWriteXorExecute=0
让朋友关闭了这个选项之后,朋友反馈程序运行正常。
4. 到底是什么代码导致的
虽然可以通过 export DOTNET_EnableWriteXorExecute=0 搞定这个问题,那到底是什么业务导致产生了很多的 doublemapper 呢?这就需要从这些内存段上寻找答案了,仔细想想,既然是内存文件嘛,大概率承载了 .NET 的 dll 文件,而 dll 文件都是魔术 MZ 开头的。所以使用 s-a 抽查其中一个内存段。
0:000> s-a 7f3fc4000000 7f3fcbff5000-0x1 "MZ"
00007f3f`c4059ce4 4d 5a 00 00 00 00 00 00-00 00 00 00 7c 00 00 00 MZ..........|...
00007f3f`c44f2989 4d 5a 3c 40 7f 00 00 b1-05 00 00 94 99 00 00 80 MZ<@............
00007f3f`c44f2b69 4d 5a 3c 40 7f 00 00 b1-05 00 00 98 99 00 00 40 MZ<@...........@
00007f3f`c44f3d99 4d 5a 3c 40 7f 00 00 b2-05 00 00 ac 99 00 00 80 MZ<@............
00007f3f`c44f4d49 4d 5a 3c 40 7f 00 00 b2-05 00 00 b6 99 00 00 80 MZ<@............
00007f3f`c45a3c61 4d 5a c4 3f 7f 00 00 00-00 00 00 00 00 00 00 cd MZ.?............
00007f3f`c45a3ca1 4d 5a c4 3f 7f 00 00 00-00 00 00 00 00 00 00 cd MZ.?............
00007f3f`c45a3ce1 4d 5a c4 3f 7f 00 00 00-00 00 00 00 00 00 00 cd MZ.?............
00007f3f`c45a3d21 4d 5a c4 3f 7f 00 00 00-00 00 00 00 00 00 00 cd MZ.?............
...
然后用了一段私藏的脚本导出来后,发现是大量的项目dll,这个就不截图了,朋友也有说他们程序有动态生成代码的逻辑。
四:总结
EnableWriteXorExecute 特性是在 .NET7 之后默认将0设为1的,在某些开源linux上会因为各种兼容性问题导致各种奇葩的问题发生,这东西我感觉目前还是能禁掉就禁掉吧。

记一次 .NET某数字化协同管理系统 内存暴涨分析的更多相关文章
- 记一次 .NET 某RFID标签管理系统 CPU 暴涨分析
一:背景 1. 讲故事 前段时间有位朋友说他的程序 CPU 出现了暴涨现象,由于程序是买来的,所以问题就比较棘手了,那既然找到我,就想办法帮朋友找出来吧,分析下来,问题比较经典,有必要和大家做一下分享 ...
- 记一次 .NET WPF布草管理系统 挂死分析
一:背景 1. 讲故事 这几天看的 dump 有点多,有点伤神伤脑,晚上做梦都是dump,今天早上头晕晕的到公司就听到背后同事抱怨他负责的WPF程序挂死了,然后测试的小姑娘也跟着抱怨...嗨,也不知道 ...
- 记一次 .NET 某风控管理系统 内存泄漏分析
一:背景 1. 讲故事 上个月中旬,星球里的一位朋友在微信找我,说他的程序跑着跑着内存会不断的缓慢增长并无法释放,寻求如何解决 ? 得,看样子星球还得好好弄!!! 不管怎么说,先上 windbg 说话 ...
- 记一次 .NET医疗布草API程序 内存暴涨分析
一:背景 1. 讲故事 我在年前写过一篇关于CPU爆高的分析文章 再记一次 应用服务器 CPU 暴高事故分析 ,当时是给同济做项目升级,看过那篇文章的朋友应该知道,最后的结论是运维人员错误的将 IIS ...
- 记一次 .NET 某外贸Web站 内存泄漏分析
一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬. 沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了 ...
- 记一次 .NET 某三甲医院HIS系统 内存暴涨分析
一:背景 1. 讲故事 前几天有位朋友加wx说他的程序遭遇了内存暴涨,求助如何分析? 和这位朋友聊下来,这个dump也是取自一个HIS系统,如朋友所说我这真的是和医院杠上了,这样也好,给自己攒点资源, ...
- 记一次 .NET 某WMS仓储打单系统 内存暴涨分析
一:背景 1. 讲故事 七月中旬有一位朋友加wx求助,他的程序在生产上跑着跑着内存就飙起来了,貌似没有回头的趋势,询问如何解决,截图如下: 和这位朋友聊下来,感觉像是自己在小县城当了个小老板,规律的生 ...
- 记一次 .NET 某招聘网后端服务 内存暴涨分析
一:背景 1. 讲故事 前段时间有位朋友wx找到我,说他的程序存在内存阶段性暴涨,寻求如何解决,和朋友沟通下来,他的内存平时大概是5G 左右,在某些时点附近会暴涨到 10G+, 画个图大概就是这样. ...
- 记一次 .NET 某电厂Web系统 内存泄漏分析
一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...
- 记一次 .NET 某工控软件 内存泄露分析
一:背景 1.讲故事 上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也 ...
随机推荐
- ExtJS & Asp.NET
企业应用中,要快速开发Web应用,前端使用ExtJS还行,包含许多常用的控件,图标,配色方案... 帖上部分载图: 完全可订制的登录界面: 可调整的布局: 可综合使用的模态对话框: 树结构及动态加载: ...
- bootstrapTable初始化常用参数
bootstrapTable初始化常用参数,前端分页排序,后端获取表格数据 $('#table').bootstrapTable({ toolbar: '#mybar', //工具按钮用哪个容器 st ...
- java内存区域——daicy
Java虚拟机 运行时数据区 主要分为五部分:方法区,堆(这两块是所有线程共享的区域),程序计数器,本地方法栈,虚拟机栈(vm stack)(这三块为线程隔离区域) 程序计数器(Program Cou ...
- Nuxt.js 应用中的 dev:ssr-logs 事件钩子
title: Nuxt.js 应用中的 dev:ssr-logs 事件钩子 date: 2024/11/28 updated: 2024/11/28 author: cmdragon excerpt: ...
- 一个大幅提高开发效率的工具库 WYBasisKit
WYBasisKit (持续更新) WYBasisKit 是做什么的? WYBasisKit 不仅可以帮助开发者快速构建一个工程,还有基于常用网络框架和系统API而封装的各种实用方法.扩展,开发者只需 ...
- (三)Springboot + vue + 达梦数据库构建RBAC权限模型前后端分离脚手架保姆级教程(前端项目)
XX后台管理系统 1.技术选型与环境要求 1.1 项目技术选型 1.1.1 前端技术 HTML 5 CSS 3 lavaScript Vue Element UI 1.1.2 后端技术 SpringB ...
- 虚拟机 ubuntu18 树莓派4 QT5.14.2 交叉编译
编译过程主要参考了 <为树莓派4交叉编译QT5.14.2(带EGLFS支持)>,可以按照教程一步一步进行,在整个过程中,有2个地方需要注意. 1. sudo rpi-update 因为网络 ...
- 痞子衡嵌入式:MCUXpresso IDE下C++源文件中嵌套定义的复合数据类型命名空间认定
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MCUXpresso IDE下C++源文件中嵌套定义的复合数据类型命名空间认定. 痞子衡之前写过一篇文章 <MCUXpresso ...
- 重磅宣布|强强联合,腾讯云携手Veeam提供云上数据存储服务
近日获悉,腾讯云对象存储COS正式通过Veeam备份软件标准化测试,携手为用户提供云上数据存储服务. Veeam对COS的支持是通过SOBR( Scale out backup repository) ...
- 介绍 MSTest Runner - CLI, Visual Studio 等更多
介绍 MSTest Runner - CLI, Visual Studio 等更多 https://devblogs.microsoft.com/dotnet/introducing-ms-test- ...