记一次 .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_FREEBSDint fd = shm_open(SHM_ANON, O_RDWR | O_CREAT, S_IRWXU);
#elif defined(TARGET_SUNOS) // has POSIX implementationchar 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_FREEBSDint 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_OSXreturn 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 KBvoid *map;const char *text = "Hello, memfd_create!";// Create the memory file descriptorfd = memfd_create(name, MFD_CLOEXEC);// Resize the memory file to the desired sizeftruncate(fd, size)// Map the memory file into the address spacemap = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);// Write some data to the memory filestrncpy(map, text, strlen(text));// Print the data from the memory fileprintf("Data in memory file: %s\n", (char *)map);// Unmap the memorymunmap(map, size)// Close the file descriptorclose(fd);return 0;
}
卦中的逻辑非常简单,需要注意的是这里有一个重要步骤就是通过 mmap 将 fd 挂上物理内存,即 fd -> mmap <- memory
,挂上之后就可以轻松的往里面写数据了。
有了这些基础之后,大家再看 doublemapper__deleted_
字样是不是有种豁然开朗的感觉?大概就是资源释放中只执行了 close(fd)
,但没有执行 mummap
,参考如下:
// Unmap the memory (某种原因未执行)//munmap(map, size) // Close the file descriptorclose(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;
#elsereturn 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某数字化协同管理系统 内存暴涨分析
一:背景 1. 讲故事 高级调试训练营里的一位朋友找到我,说他们跑在linux上的.NET程序出现了内存泄露的情况,上windbg观察发现内存都是IMAGE给吃掉了,那些image都标记了 doublemapper__deleted_ 字样,问我为啥会这样&a…...

部门管理查询部门,nginx反向代理,前端如何访问到后端Tomcat 注解@RequestParam
接口开发 增删改通常是不用返回data数据,返回null 列表查询-结果封装,时间 前后端联调测试 nginx反向代理,前端如何访问到后端Tomcat服务器 删除部门...

JS通过ASCII码值实现随机字符串的生成(可指定长度以及解决首位不出现数值)
在之前写过一篇“JS实现随机生成字符串(可指定长度)”,当时写的过于简单和传统,比较粗放。此次针对此问题,对随机生成字符串的功能进行优化处理,对随机取到的字符都通过程序自动来完成。 在写之前ÿ…...

速通Docker === 快速部署Redis主从集群
目录 镜像仓库介绍 持久化你的数据库 连接到其他容器 创建自定义网络 部署主节点 部署从节点 验证部署 总结 在现代应用架构中,Redis作为一个高性能的内存数据库,被广泛应用于缓存、会话存储、实时分析等多个领域。为了提高Redis的可用性和数据的…...

论文笔记(六十三)Understanding Diffusion Models: A Unified Perspective(一)
Understanding Diffusion Models: A Unified Perspective(一) 文章概括引言:生成模型背景:ELBO、VAE 和分层 VAE证据下界(Evidence Lower Bound)变分自编码器 (Variational Autoencoders&#x…...

stm32使用MDK5.35时遇到*** TOOLS.INI: TOOLCHAIN NOT INSTALLED
mdk5.35出现*** TOOLS.INI: TOOLCHAIN NOT INSTALLED的问题!!!! 以管理员身份重新打开MDK5.35.0.0,用keygen破解密码,但是一直提示我是没有破解成功。 解决办法: target 改成ARM...

在Ubuntu上安装RabbitMQ教程
1、安装erlang 因为rabbitmq是基于erlang开发的,所以要安装rabbitmq,首先需要安装erlang运行环境 apt-get install erlang执行命令查是否安装成功:erl,疯狂 Ctrlc 就能退出命令行 2、安装rabbitmq 1、查看erlang与rabbitmq版本…...

【算法】集合List和队列
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 零:集合,队列的用法 一:字母异位词分组 二:二叉树的锯…...

uniapps使用HTML5的io模块拷贝文件目录
最近在集成sqlite到uniapp的过程中,因为要将sqlite数据库预加载,所以需要使用HTML5的plus.io模块。使用过程中遇到了许多问题,比如文件路径总是解析不到等。尤其是应用私有文档目录’_doc’。 根据官方文档: 为了安全管理应用的…...

css‘s hover VS mobile
.animation {animation: 30s move infinite linear;/* &:hover {animation-play-state: paused;*/ }原本写的好好的,测试说:“移动端点击滚动条,跳转到其他页面后,返回当前页面,滚动条不滚动;可以优化位…...

工业制造离不开的BOM
在制造业的浩瀚星空中,物料清单(BOM)犹如“北极星”,牢牢指引着产品从设计蓝图迈向实物诞生的全过程。 BOM的分类 按照设计制造的不同阶段,将BOM划分为设计BOM、工艺BOM、制造BOM三种类型。 设计BOM Engineering BO…...

HTML中的`<!DOCTYPE html>`是什么意思?
诸神缄默不语-个人CSDN博文目录 在学习HTML时,我们经常会看到HTML文档的开头出现<!DOCTYPE html>,它是HTML文件的第一行。很多初学者可能会疑惑,为什么需要这行代码?它到底有什么作用呢?在这篇文章中࿰…...

C语言之斗地主游戏
🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 C语言之斗地主游戏 目录 程序概述程序设计 Card类CardGroup类Player类LastCards类Land…...

【玩转全栈】----Django制作部门管理页面
目录 大致效果 BootStrap BootStrap简介 BootStrap配置 BootStrap使用 基本配置 部分代码解释及注意: 用户编辑: 新添数据: 删除数据: 大致效果 我先给个大致效果,基本融合了Django、Bootstrap、css、html等等。 基于D…...

Unreal Engine 5 C++ Advanced Action RPG 十章笔记
第十章 Survival Game Mode 2-Game Mode Test Map 设置游戏规则进行游戏玩法 生成敌人玩家是否死亡敌人死亡是否需要刷出更多 肯定:难度增加否定:玩家胜利 流程 新的游戏模式类游戏状态新的数据表来指定总共有多少波敌人生成逻辑UI告诉当前玩家的敌人波数 3-Survival Game M…...

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证9)
测试数据库中只有之前记录温湿度及烟雾值的表中数据较多,在该数据库中增加AppUser表,用于登录用户身份查询,数据库表如下所示: 项目中安装SqlSugarCore包,然后修改控制器类的登录函数及分页查询数据函数ÿ…...

缓存之美:万文详解 Caffeine 实现原理(上)
由于社区最大字数限制,本文章将分为两篇,第二篇文章为缓存之美:万文详解 Caffeine 实现原理(下) 大家好,我是 方圆。文章将采用“总-分-总”的结构对配置固定大小元素驱逐策略的 Caffeine 缓存进行介绍&…...

Spark/Kafka
文章目录 项目地址一、Spark1. RDD1.1 五大核心属性1.2 执行原理1.3 四种创建方式二、Kafka2.1 生产者(1)分区器(2)生产者提高吞吐量(3) 生产者数据可靠性数据传递语义幂等性和事务数据有序2.2 Broker(1)Broker工作流程(2)节点服役和退役2.3 副本(1)Follower故障细…...

深入浅出:Go语言中的Unicode与字符编码详解
深入浅出:Go语言中的Unicode与字符编码详解 引言 在当今的编程世界中,字符编码和Unicode是不可或缺的技术基础。Go语言作为一种强大的编程语言,其对Unicode的支持和字符编码的处理方式,对于开发者来说至关重要。本文将从Unicode的基础知识入手,逐步深入探讨Go语言中字符编…...

什么是SSL及SSL的工作流程
什么是 SSL SSL(Secure Sockets Layer,安全套接层)是一种保护互联网通信安全的加密协议,用于确保数据在客户端和服务器之间传输时的保密性、完整性和身份验证。它已被TLS(Transport Layer Security,传输层安全协议)取代,但很多场景仍习惯称其为SSL。 SSL/TLS 的主要目…...

.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)
系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…...

AD7606, 逐次逼近型ADC以及一次被GPT坑了的过程.
首先, 我的项目中, 已有的一个ADC芯片, 8通道, 并行, Analog家的ad7606, 在采集高速的正弦信号的时候, 我发现采集到的值怎么都不太对. 但是宏观来看, 并没有太大问题, 首先我怀疑的是量程问题, 接入一个5伏直流, 得到的读数确实是接近16bit的正半量程的读数, 32xxx. 接着我用信…...

抬手、放手识别算法
在一款智能手表中, 平时手表处于息屏的状态, 用于节省功耗,延长使用时间。 在用户进行抬手的时候,其实是希望能够及时看一下时间、消息通知等信息的。这时手表应该能够检测到用户的抬手动作,自动进行屏幕的点亮。当用户…...

深度学习篇---AnacondaLabelImg
文章目录 前言第一部分:Anaconda是什么?1.简介2.特点(1)包管理器Conda(2)环境管理(3)预装包(4)跨平台(5)社区支持 3.安装WindowsLinux…...

探索云原生可观测性:技术与团队协作的深度结合
TheNewStack 出品的电子书《Cloud Native Observability for DevOps Teams》读后感,老书新读,还是另有一番领悟。 阅读原文请转到:https://jimmysong.io/blog/cloud-native-observability-devops/ 最近读了 TheNewStack 发布的电子书《Cloud …...

解决 Django 5.1 中的 TemplateSyntaxError 错误
解决 Django 5.1 中的 TemplateSyntaxError 错误 在 Django 开发过程中,我们经常会遇到 TemplateSyntaxError 错误,尤其是在模板文件中使用不被支持或错误的模板标签时。最近,我们遇到的一个常见错误是: Invalid block tag on l…...

基于SSM的自助购药小程序设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

04JavaWeb——Maven-SpringBootWeb入门
Maven 课程内容 初识Maven Maven概述 Maven模型介绍 Maven仓库介绍 Maven安装与配置 IDEA集成Maven 依赖管理 01. Maven课程介绍 1.1 课程安排 学习完前端Web开发技术后,我们即将开始学习后端Web开发技术。做为一名Java开发工程师,后端Web开发…...

场馆预定平台高并发时间段预定实现V2
🎯 本文档介绍了场馆预订系统接口V2的设计与实现,旨在解决V1版本中库存数据不一致及性能瓶颈的问题。通过引入令牌机制确保缓存和数据库库存的最终一致性,避免因服务器故障导致的库存错误占用问题。同时,采用消息队列异步处理库存…...

如何利用边缘节点服务打造极致用户体验?
随着互联网和数字化技术的飞速发展,用户对网络访问速度和服务体验的要求也在不断提高。在一个信息快速传播的时代,延迟过高或访问卡顿的问题会直接影响用户体验,甚至导致用户流失。因此,企业如何优化网络性能、提升用户访问速度&a…...