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

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

一个简单的非托管内存泄露

1. 构建 so 文件

在 Windows 平台上我们会通过 MSVC 编译器将 C代码编译出一个成品 .dll,在 Linux 上通常会借助 gcc 将 c 编译成 .so 文件,这个.so 全称 Shared Object,为了方便讲解,先上一段简单的代码:


#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define BLOCK_SIZE (10 * 1024)              // 每个块 10K
#define TOTAL_SIZE (1 * 1024 * 1024 * 1024) // 总计 1GB
#define BLOCKS (TOTAL_SIZE / BLOCK_SIZE)    // 计算需要的块数

void heapmalloc()
{
    uint8_t *blocks[BLOCKS]; // 存储每个块的指针

    // 分配 1GB 内存,分成多个小块
    for (size_t i = 0; i < BLOCKS; i++)
    {
        blocks[i] = (uint8_t *)malloc(BLOCK_SIZE);
        if (blocks[i] == NULL)
        {
            printf("内存分配失败!\n");
            return;
        }

        // 确保每个块都被实际占用
        memset(blocks[i], 20, BLOCK_SIZE);
    }

    printf("已经分配 1GB 内存在堆上!\n");
}

接下来使用 gcc 编译,参考如下:


gcc -shared -o libmyleak.so -fPIC myleak.c

  • -shared: 编译成共享库

  • -fPIC: 指定共享库可以在内存任意位置被加载(地址无关性)

命令执行完之后,就可以看到一个 .so 文件了,截图如下:

最后可以用 nm 命令验证下 libmyleak.so 中是否有 Text 段下的 heapmalloc 导出函数。


root@ubuntu2404:/data2/c# nm libmyleak.so
0000000000004028 b completed.0
                 w __cxa_finalize@GLIBC_2.2.5
00000000000010c0 t deregister_tm_clones
0000000000001130 t __do_global_dtors_aux
0000000000003e00 d __do_global_dtors_aux_fini_array_entry
0000000000004020 d __dso_handle
0000000000003e08 d _DYNAMIC
000000000000125c t _fini
0000000000001170 t frame_dummy
0000000000003df8 d __frame_dummy_init_array_entry
00000000000020f8 r __FRAME_END__
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
000000000000203c r __GNU_EH_FRAME_HDR
0000000000001179 T heapmalloc
0000000000001000 t _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U malloc@GLIBC_2.2.5
                 U memset@GLIBC_2.2.5
                 U puts@GLIBC_2.2.5
00000000000010f0 t register_tm_clones
                 U __stack_chk_fail@GLIBC_2.4
0000000000004028 d __TMC_END__

2. C# 代码调用

so构建好了之后,后面就比较好说了,使用 dotnet new console -n CSharpApplication --use-program-main true 新建一个CS项目。


root@ubuntu2404:/data2/csharp# dotnet new console -n CSharpApplication --use-program-main true
The template "Console App" was created successfully.

Processing post-creation actions...
Restoring /data2/csharp/CSharpApplication/CSharpApplication.csproj:
  Determining projects to restore...
  Restored /data2/csharp/CSharpApplication/CSharpApplication.csproj (in 1.7 sec).
Restore succeeded.

编译下 C# 项目,然后将 libmyleak.so 放到 C#项目的 bin目录,修改 C# 代码如下:


using System.Runtime.InteropServices;

namespace CSharpApplication;

class Program
{
    [DllImport("libmyleak.so", CallingConvention = CallingConvention.Cdecl)]
    public static extern void heapmalloc();

    static void Main(string[] args)
    {
        heapmalloc();
        Console.ReadLine();
    }
}

最后用 dotnet CSharpApplication.dll 运行:


root@ubuntu2404:/data2/csharp/CSharpApplication/bin/Debug/net8.0# dotnet CSharpApplication.dll
已经分配 1GB 内存在堆上!

程序是跑起来了,那真的是吃了1G呢? 可以先用 htop 观察程序,从截图看没毛病。

那这 1G 真的在 heap 上吗? 可以用 maps 观察。


root@ubuntu2404:~# ps -ef | grep CSharp
root       10764   10730  0 13:35 pts/21   00:00:00 dotnet CSharpApplication.dll
root       11049   11027  0 13:41 pts/22   00:00:00 grep --color=auto CSharp

root@ubuntu2404:~# cat /proc/10764/maps
614e1f592000-614e1f598000 r--p 00000000 08:02 1479867                    /usr/lib/dotnet/dotnet
614e1f598000-614e1f5a4000 r-xp 00005000 08:02 1479867                    /usr/lib/dotnet/dotnet
614e1f5a4000-614e1f5a5000 r--p 00010000 08:02 1479867                    /usr/lib/dotnet/dotnet
614e1f5a5000-614e1f5a6000 rw-p 00010000 08:02 1479867                    /usr/lib/dotnet/dotnet
614e5b5d9000-614e9b8a8000 rw-p 00000000 00:00 0                          [heap]
...


root@ubuntu2404:~# pmap 10764
10764:   dotnet CSharpApplication.dll
0000614e1f592000     24K r---- dotnet
0000614e1f598000     48K r-x-- dotnet
0000614e1f5a4000      4K r---- dotnet
0000614e1f5a5000      4K rw--- dotnet
0000614e5b5d9000 1051452K rw---   [ anon ]
...

根据 linux 进程的内存布局,可执行image之后是 heap 堆,可以看到 [heap] 约等于1G (614e9b8a8000 - 614e5b5d9000),即 pmap 中的 1051452K。

总结

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

文章转载自:一线码农

原文链接:Linux系列:如何用 C#调用 C方法造成内存泄露 - 一线码农 - 博客园

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构


http://www.kler.cn/a/571944.html

相关文章:

  • 计算机毕业设计SpringBoot+Vue.js网上服装商城(源码+文档+PPT+讲解)
  • 科技快讯 | 启明星辰发布“大模型访问脱敏罩”;清华大学将扩招本科生,重点培养 AI 人才;“中国首个 AI IDE”字节跳动 Trae 国内版发布
  • 文件上传靶场(1--9关)
  • Qt 的绘图中的顺序
  • 职坐标AIGC课程实战项目深度解析
  • 【论文笔记】3DGS压缩相关工作2篇
  • 【计算机网络03】网络层协议IP(详细)
  • CRM一张表单开发的思路
  • 图论基础算法: 二分图的判定(C++)
  • Unity NGUI新手向几个问题记录
  • 基于 HTML、CSS 和 JavaScript 的智能九宫格图片分割系统
  • 使用Python或R语言重新拟合模型
  • 一文讲解GO中日志
  • FastGPT 引申:知识库辅助开发(代码符号自动提取与文件匹配工具详解)
  • K8S学习之基础十一:容器钩子
  • 【最后203篇系列】010 关于矩阵的一点思考
  • pyside6学习专栏(九):在PySide6中使用PySide6.QtCharts绘制6种不同的图表的示例代码
  • linux中使用firewall命令操作端口
  • 面试基础---Spring Cloud 微服务架构中的网关:Spring Cloud Gateway 与 Zuul 深度解析
  • 机器学习数学基础:38.统计学变量与相关系数