You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

scanf调用malloc未触发自定义free的原因及用户态内存记录方案咨询

动态替换malloc/free时的标准库内存释放与调用过滤问题

问题描述

我尝试通过动态链接替换标准库的malloc和free函数来检测内存泄漏:编写自定义malloc.c并编译为共享库,链接到测试程序hello.c后运行。

运行后发现以下现象:

  • scanf调用malloc时会打印malloc called.(自定义malloc的输出,说明替换生效)
  • 但始终看不到自定义free的打印输出
  • valgrind检测显示所有内存已被正常释放
  • 通过GDB回溯确认,scanf确实为其缓冲区分配了内存

现咨询两个具体问题:

  1. scanf分配的内存是如何释放的?
  2. 如何仅记录用户代码中的malloc和free调用,忽略标准库(如scanf)的调用?

问题1:scanf分配的内存是如何释放的?

这是因为标准库内部的内存管理可能绕过了你替换的全局free符号:

  • 直接调用内部私有实现:以glibc为例,scanf使用的缓冲区虽然通过全局malloc分配(所以触发了你的自定义函数),但释放时调用的是__libc_free——这是标准库内部的私有函数,不经过动态链接的符号表,自然不会触发你替换的free。
  • 缓冲区复用机制:部分场景下,scanf会把分配的缓冲区缓存起来,供后续重复使用,直到程序退出时由系统统一回收,或者在特定内部条件(如缓冲区大小不足、库内部清理逻辑触发)下才释放。但valgrind能检测到释放记录,说明内存确实被正常释放了,只是没走你替换的free路径。

问题2:如何仅记录用户代码中的malloc和free调用?

核心思路是通过调用栈溯源过滤来源,具体实现方式如下:

方法1:在自定义函数中解析调用栈

利用backtrace()和backtrace_symbols()获取调用栈信息,判断每个栈帧的来源是否为标准库,只处理用户代码的调用:

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>

void *malloc(size_t size) {
    static void *(*real_malloc)(size_t) = NULL;
    if (!real_malloc) {
        real_malloc = dlsym(RTLD_NEXT, "malloc");
    }

    // 获取调用栈(跳过当前malloc自身的栈帧)
    void *stack[10];
    int frame_cnt = backtrace(stack, 10);
    char **symbols = backtrace_symbols(stack, frame_cnt);
    int is_user_call = 0;

    for (int i = 1; i < frame_cnt; i++) {
        // 过滤标准库的调用:匹配libc.so或系统库路径
        if (!strstr(symbols[i], "libc.so") && !strstr(symbols[i], "/lib/") && !strstr(symbols[i], "/usr/lib/")) {
            is_user_call = 1;
            break;
        }
    }

    if (is_user_call) {
        printf("[USER MALLOC] Size: %zu\n", size);
    }

    free(symbols);
    return real_malloc(size);
}

void free(void *ptr) {
    static void (*real_free)(void *) = NULL;
    if (!real_free) {
        real_free = dlsym(RTLD_NEXT, "free");
    }

    void *stack[10];
    int frame_cnt = backtrace(stack, 10);
    char **symbols = backtrace_symbols(stack, frame_cnt);
    int is_user_call = 0;

    for (int i = 1; i < frame_cnt; i++) {
        if (!strstr(symbols[i], "libc.so") && !strstr(symbols[i], "/lib/") && !strstr(symbols[i], "/usr/lib/")) {
            is_user_call = 1;
            break;
        }
    }

    if (is_user_call) {
        printf("[USER FREE] Pointer: %p\n", ptr);
    }

    free(symbols);
    real_free(ptr);
}

编译时需要链接动态库和启用符号导出:

gcc -shared -fPIC malloc.c -o libmymalloc.so -ldl -rdynamic

运行测试程序时通过LD_PRELOAD加载:

LD_PRELOAD=./libmymalloc.so ./hello

方法2:编译时标记用户代码(需修改源码)

如果允许修改用户代码,可以给用户代码的内存分配单独封装一层,比如定义user_malloc和user_free,只在这些函数中记录日志,标准库保持调用默认的malloc/free。这种方式性能更高,但需要侵入用户代码。


内容的提问来源于stack exchange,提问作者GKxx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 23:20:38