Linux系列:如何用 C#调用 C方法造成内存泄漏

打印 上一主题 下一主题

主题 960|帖子 960|积分 2880

一:配景

1. 讲故事

好久没写文章了,还是来写一点吧,今年预备多写一点 Linux平台上的东西,这篇从 C# 调用 C 这个例子开始。在 windows 平台上,我们经常在 C++ 代码中用 extern "C" 导出 C风格 的函数,然后在 C# 中用 DllImport 的方式引入,那在 Linux 上怎么玩的?毕竟这对研究 Linux 上的 C# 程序非托管内存泄漏有非常大的代价,接下来我们就来看下。
二:一个简单的非托管内存泄漏

1. 构建 so 文件

在 Windows 平台上我们会通过 MSVC 编译器将 C代码编译出一个成品 .dll,在 Linux 上通常会借助 gcc 将 c 编译成 .so 文件,这个.so 全称 Shared Object,为了方便解说,先上一段简单的代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <stdint.h>
  4. #include <string.h>
  5. #define BLOCK_SIZE (10 * 1024)              // 每个块 10K
  6. #define TOTAL_SIZE (1 * 1024 * 1024 * 1024) // 总计 1GB
  7. #define BLOCKS (TOTAL_SIZE / BLOCK_SIZE)    // 计算需要的块数
  8. void heapmalloc()
  9. {
  10.     uint8_t *blocks[BLOCKS]; // 存储每个块的指针
  11.     // 分配 1GB 内存,分成多个小块
  12.     for (size_t i = 0; i < BLOCKS; i++)
  13.     {
  14.         blocks[i] = (uint8_t *)malloc(BLOCK_SIZE);
  15.         if (blocks[i] == NULL)
  16.         {
  17.             printf("内存分配失败!\n");
  18.             return;
  19.         }
  20.         // 确保每个块都被实际占用
  21.         memset(blocks[i], 20, BLOCK_SIZE);
  22.     }
  23.     printf("已经分配 1GB 内存在堆上!\n");
  24. }
复制代码
接下来使用 gcc 编译,参考如下:
  1. gcc -shared -o libmyleak.so -fPIC myleak.c
复制代码


  • -shared: 编译成共享库
  • -fPIC: 指定共享库可以在内存恣意位置被加载(地点无关性)
命令实行完之后,就可以看到一个 .so 文件了,截图如下:

最后可以用 nm 命令验证下 libmyleak.so 中是否有 Text 段下的 heapmalloc 导出函数。
  1. root@ubuntu2404:/data2/c# nm libmyleak.so
  2. 0000000000004028 b completed.0
  3.                  w __cxa_finalize@GLIBC_2.2.5
  4. 00000000000010c0 t deregister_tm_clones
  5. 0000000000001130 t __do_global_dtors_aux
  6. 0000000000003e00 d __do_global_dtors_aux_fini_array_entry
  7. 0000000000004020 d __dso_handle
  8. 0000000000003e08 d _DYNAMIC
  9. 000000000000125c t _fini
  10. 0000000000001170 t frame_dummy
  11. 0000000000003df8 d __frame_dummy_init_array_entry
  12. 00000000000020f8 r __FRAME_END__
  13. 0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
  14.                  w __gmon_start__
  15. 000000000000203c r __GNU_EH_FRAME_HDR
  16. 0000000000001179 T heapmalloc
  17. 0000000000001000 t _init
  18.                  w _ITM_deregisterTMCloneTable
  19.                  w _ITM_registerTMCloneTable
  20.                  U malloc@GLIBC_2.2.5
  21.                  U memset@GLIBC_2.2.5
  22.                  U puts@GLIBC_2.2.5
  23. 00000000000010f0 t register_tm_clones
  24.                  U __stack_chk_fail@GLIBC_2.4
  25. 0000000000004028 d __TMC_END__
复制代码
2. C# 代码调用

so构建好了之后,后面就比较好说了,使用 dotnet new console -n CSharpApplication --use-program-main true 新建一个CS项目。
  1. root@ubuntu2404:/data2/csharp# dotnet new console -n CSharpApplication --use-program-main true
  2. The template "Console App" was created successfully.
  3. Processing post-creation actions...
  4. Restoring /data2/csharp/CSharpApplication/CSharpApplication.csproj:
  5.   Determining projects to restore...
  6.   Restored /data2/csharp/CSharpApplication/CSharpApplication.csproj (in 1.7 sec).
  7. Restore succeeded.
复制代码
编译下 C# 项目,然后将 libmyleak.so 放到 C#项目的 bin目录,修改 C# 代码如下:
  1. using System.Runtime.InteropServices;
  2. namespace CSharpApplication;
  3. class Program
  4. {
  5.     [DllImport("libmyleak.so", CallingConvention = CallingConvention.Cdecl)]
  6.     public static extern void heapmalloc();
  7.     static void Main(string[] args)
  8.     {
  9.         heapmalloc();
  10.         Console.ReadLine();
  11.     }
  12. }
复制代码
最后用 dotnet CSharpApplication.dll 运行:
  1. root@ubuntu2404:/data2/csharp/CSharpApplication/bin/Debug/net8.0# dotnet CSharpApplication.dll
  2. 已经分配 1GB 内存在堆上!
复制代码
程序是跑起来了,那真的是吃了1G呢? 可以先用 htop 观察程序,从截图看没弊端。

那这 1G 真的在 heap 上吗? 可以用 maps 观察。
  1. root@ubuntu2404:~# ps -ef | grep CSharp
  2. root       10764   10730  0 13:35 pts/21   00:00:00 dotnet CSharpApplication.dll
  3. root       11049   11027  0 13:41 pts/22   00:00:00 grep --color=auto CSharp
  4. root@ubuntu2404:~# cat /proc/10764/maps
  5. 614e1f592000-614e1f598000 r--p 00000000 08:02 1479867                    /usr/lib/dotnet/dotnet
  6. 614e1f598000-614e1f5a4000 r-xp 00005000 08:02 1479867                    /usr/lib/dotnet/dotnet
  7. 614e1f5a4000-614e1f5a5000 r--p 00010000 08:02 1479867                    /usr/lib/dotnet/dotnet
  8. 614e1f5a5000-614e1f5a6000 rw-p 00010000 08:02 1479867                    /usr/lib/dotnet/dotnet
  9. 614e5b5d9000-614e9b8a8000 rw-p 00000000 00:00 0                          [heap]
  10. ...
  11. root@ubuntu2404:~# pmap 10764
  12. 10764:   dotnet CSharpApplication.dll
  13. 0000614e1f592000     24K r---- dotnet
  14. 0000614e1f598000     48K r-x-- dotnet
  15. 0000614e1f5a4000      4K r---- dotnet
  16. 0000614e1f5a5000      4K rw--- dotnet
  17. 0000614e5b5d9000 1051452K rw---   [ anon ]
  18. ...
复制代码
根据 linux 历程的内存布局,可实行image之后是 heap 堆,可以看到 [heap] 约等于1G (614e9b8a8000 - 614e5b5d9000),即 pmap 中的 1051452K。
三:总结

摆设在 Linux上的.NET程序同样存在 非托管内存泄漏 的题目,这篇文章的例子虽然很简单,希望能给大家带来一些思索和观测途径吧。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表