scanf调用malloc未触发自定义free的原因及用户态内存记录方案咨询
动态替换malloc/free时的标准库内存释放与调用过滤问题
问题描述
我尝试通过动态链接替换标准库的malloc和free函数来检测内存泄漏:编写自定义malloc.c并编译为共享库,链接到测试程序hello.c后运行。
运行后发现以下现象:
scanf调用malloc时会打印malloc called.(自定义malloc的输出,说明替换生效)- 但始终看不到自定义
free的打印输出 valgrind检测显示所有内存已被正常释放- 通过GDB回溯确认,
scanf确实为其缓冲区分配了内存
现咨询两个具体问题:
scanf分配的内存是如何释放的?- 如何仅记录用户代码中的
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
相关产品推荐
相关产品推荐

