记一次 .NET某数字化协同管理系统 内存暴涨分析

打印 上一主题 下一主题

主题 867|帖子 867|积分 2601

一:背景

1. 讲故事

高级调试训练营里的一位朋侪找到我,说他们跑在linux上的.NET程序出现了内存泄漏的情况,上windbg观察发现内存都是IMAGE给吃掉了,那些image都标志了 doublemapper__deleted_ 字样,问我为啥会这样?说实话作为我们这些调试者非常喜好和这样的人打交道,毕竟沟通起来顺畅,也特别能激发对方的探索欲,这也是训练营给予的一种魅力吧。
二:内存暴涨分析

1. 为什么会暴涨

看过我这个系列的朋侪都知道观察内存用 !address -summary 命令,但这个命令是为 windows 打造的,所以在 linux 上行不通,为此sos提供了一个专门的命令 !maddress 来替代,接下来使用 !maddress -orderBySize 观察下内存分布情况。
  1. 0:000> !maddress -orderBySize
  2. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  3. | Memory Kind         |        StartAddr |        EndAddr-1 |         Size | Type        | State       | Protect           | Image                                                             |
  4. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  5. | Image               |     7f4000000000 |     7f4007ff6000 |     127.96mb | MEM_IMAGE   | MEM_COMMIT  | PAGE_READWRITE    | doublemapper__deleted_                                            |
  6. | Image               |     7f3fc4000000 |     7f3fcbff5000 |     127.96mb | MEM_IMAGE   | MEM_COMMIT  | PAGE_READWRITE    | doublemapper__deleted_                                            |
  7. | Image               |     7f404c021000 |     7f4051b4c000 |      91.17mb | MEM_IMAGE   | MEM_UNKNOWN | PAGE_UNKNOWN      | doublemapper__deleted_                                            |
  8. | Image               |     7f3fae82e000 |     7f3fb4000000 |      87.82mb | MEM_IMAGE   | MEM_COMMIT  | PAGE_EXECUTE_READ | doublemapper__deleted_                                            |
  9. | Image               |     7f406c021000 |     7f40701ff000 |      65.87mb | MEM_IMAGE   | MEM_UNKNOWN | PAGE_UNKNOWN      | doublemapper__deleted_   
  10. ...
  11.   +----------------------------------------------------------------------+
  12. | Memory Type         |          Count |         Size |   Size (bytes) |
  13. +----------------------------------------------------------------------+
  14. | Image               |            980 |       3.54gb |  3,801,517,056 |
  15. | PAGE_READWRITE      |          1,178 |       1.17gb |  1,255,059,968 |
  16. | Stack               |             66 |     499.35mb |    523,604,992 |
  17. ...
  18. | NewStubPrecodeHeap  |              4 |      64.00kb |         65,536 |
  19. +----------------------------------------------------------------------+
  20. | [TOTAL]             |          8,254 |       6.01gb |  6,451,347,968 |
  21. +----------------------------------------------------------------------+
复制代码
从卦象看,总计 6.4G 的内存使用,Image 就吃了 3.8G,从 details 看确实都标志了 doublemapper__deleted_,说实话我分析了300多例的dump,Image 吃了大头是第二次遇到,这种故障案例一样平常是可遇不可求的,接下来我们探究下 doublemapper__deleted_ 为何方神圣。
2. doublemapper__deleted_ 是什么

要想找到这个答案,先从 coreclr 源代码中探求蛛丝马迹,全局检索之后很快发现了关键词 doublemapper相关的代码:
  1. bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize)
  2. {
  3. #ifndef TARGET_OSX
  4. #ifdef TARGET_FREEBSD
  5.     int fd = shm_open(SHM_ANON, O_RDWR | O_CREAT, S_IRWXU);
  6. #elif defined(TARGET_SUNOS) // has POSIX implementation
  7.     char name[24];
  8.     sprintf(name, "/shm-dotnet-%d", getpid());
  9.     name[sizeof(name) - 1] = '\0';
  10.     shm_unlink(name);
  11.     int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
  12. #else // TARGET_FREEBSD
  13.     int fd = memfd_create("doublemapper", MFD_CLOEXEC);
  14. #endif // TARGET_FREEBSD
  15.     *pMaxExecutableCodeSize = MaxDoubleMappedSize;
  16.     *pHandle = (void*)(size_t)fd;
  17. #else // !TARGET_OSX
  18.     *pMaxExecutableCodeSize = SIZE_MAX;
  19.     *pHandle = NULL;
  20. #endif // !TARGET_OSX
  21.     return true;
  22. }
复制代码
从卦象看,真尼玛乱,coreclr 为了兼容各种操作系统核,加了无数的 if,else 判断,无语了,最后在非OSX,非FREEBSD,非SUNOS的情况下走了 memfd_create 函数,到这里事情有了一些盼望了。
熟悉 Linux 的朋侪应该知道 memfd_create 是一个 Linux 系统调用,用于创建一个匿名文件描述符,假如在 Windows 上找等价函数的话,那就是 win32api 中的 CreateFileMapping 函数,即内存映射文件,这个在源码目次中也能观之一二:

大概有些朋侪对 memfd_create 的使用照旧有些含糊,我让 chatgpt 帮我生成一段简单的 demo 辅助大家理解下,简化后如下:
  1. int main() {
  2.     const char *name = "example_memfd";
  3.     int fd;
  4.     size_t size = 1024; // 1 KB
  5.     void *map;
  6.     const char *text = "Hello, memfd_create!";
  7.     // Create the memory file descriptor
  8.     fd = memfd_create(name, MFD_CLOEXEC);
  9.     // Resize the memory file to the desired size
  10.     ftruncate(fd, size)
  11.     // Map the memory file into the address space
  12.     map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  13.     // Write some data to the memory file
  14.     strncpy(map, text, strlen(text));
  15.     // Print the data from the memory file
  16.     printf("Data in memory file: %s\n", (char *)map);
  17.     // Unmap the memory
  18.     munmap(map, size)
  19.     // Close the file descriptor
  20.     close(fd);
  21.     return 0;
  22. }
复制代码
卦中的逻辑非常简单,必要注意的是这里有一个紧张步骤就是通过 mmap 将 fd 挂上物理内存,即 fd -> mmap  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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万有斥力

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表