C语言函数指针安全交换问题:malloc替换引发栈溢出求助
解决LD_PRELOAD替换malloc时的递归栈溢出问题
嘿,我看你在尝试实现自定义malloc和原生libc malloc的切换时遇到了递归栈溢出的问题——这是LD_PRELOAD钩子场景里非常典型的坑,咱们先理清楚问题出在哪,再给你修复方案。
问题根源分析
你的代码里有两个致命问题:
- 无限递归调用:在自定义的
malloc包装函数里直接调用malloc(sz),但此时动态链接器已经把所有对malloc的引用绑定到了你自己的包装函数上,这就导致了无限递归,最终栈溢出。 - 错误的函数指针替换逻辑:你试图通过
swap_malloc修改局部函数指针来切换malloc实现,但这种方式根本不会改变全局的符号绑定——LD_PRELOAD的符号替换是静态完成的,运行时修改局部指针对其他代码的malloc调用没有任何影响,反而会让自己的逻辑陷入混乱。
修复思路
咱们换个思路:用全局标志变量来控制当前应该使用自定义malloc还是原生malloc,所有对原生malloc的调用必须直接通过保存的malloc_mem_real指针来触发,绝对不能直接调用malloc(否则必然递归)。
修复后的完整代码
#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> // 保存原生内存函数的指针,确保只初始化一次 static void *(*malloc_mem_real)(size_t) = NULL; static void (*free_mem_real)(void*) = NULL; // 全局模式标志:0=使用自定义malloc,1=使用原生libc malloc static int use_real_malloc = 0; // 懒加载初始化原生函数指针,避免启动时的初始化顺序问题 static void memory__init(void) { if (!malloc_mem_real) { malloc_mem_real = dlsym(RTLD_NEXT, "malloc"); if (!malloc_mem_real) { fprintf(stderr, "Failed to get real malloc: %s\n", dlerror()); exit(EXIT_FAILURE); } } if (!free_mem_real) { free_mem_real = dlsym(RTLD_NEXT, "free"); if (!free_mem_real) { fprintf(stderr, "Failed to get real free: %s\n", dlerror()); exit(EXIT_FAILURE); } } } // 切换malloc模式的函数,直接翻转标志位 void swap_malloc() { use_real_malloc = !use_real_malloc; write(1, use_real_malloc ? "1\n" : "0\n", 2); } // 自定义malloc的核心实现 static void* _custom_malloc(size_t sz) { // 先切换到原生模式:因为自定义malloc里要调用printf,而printf会触发malloc swap_malloc(); void *ptr = malloc_mem_real(sz * 2); // 直接调用原生malloc,不会递归 printf("Custom malloc: allocated %zu bytes (requested %zu)\n", sz*2, sz); // 操作完成后切回自定义模式 swap_malloc(); return ptr; } // 对外暴露的malloc包装函数 void* malloc(size_t sz) { // 第一次调用时初始化原生函数指针 static int initialized = 0; if (!initialized) { memory__init(); initialized = 1; } if (use_real_malloc) { // 当前是原生模式,直接调用原生malloc return malloc_mem_real(sz); } else { // 当前是自定义模式,调用我们的自定义实现 return _custom_malloc(sz); } } // 包装free函数,确保始终调用原生free void free(void *ptr) { if (!free_mem_real) { memory__init(); } free_mem_real(ptr); } int main() { char *h; h = malloc(50); // 这里会触发自定义malloc,分配100字节 printf("Allocated pointer: %p\n", h); // 示例:切换到原生malloc,执行需要原生malloc的操作 swap_malloc(); char *temp = malloc(20); // 这里调用原生malloc,分配20字节 printf("Temp pointer (real malloc): %p\n", temp); swap_malloc(); free(temp); free(h); return 0; }
关键改进点
- 彻底避免递归:所有需要调用原生malloc的地方,都直接用
malloc_mem_real指针,再也不直接调用malloc,从根源上切断递归链。 - 用标志位控制模式:
use_real_malloc全局变量清晰控制当前的malloc实现,逻辑简单且不会破坏动态链接的符号绑定。 - 安全的模式切换:在自定义malloc内部,先切换到原生模式再执行printf等可能调用malloc的操作,执行完成后再切回自定义模式,确保这些辅助操作不会触发自定义malloc的递归。
- 懒加载初始化:只在第一次调用malloc时初始化原生函数指针,避免了动态链接时的初始化顺序问题。
编译和运行方法
编译的时候需要链接dl库:
gcc -shared -fPIC -o libmymalloc.so mymalloc.c -ldl
然后通过LD_PRELOAD加载我们的库运行程序:
LD_PRELOAD=./libmymalloc.so ./a.out
预期输出
运行后你会看到类似这样的输出,完全符合你的预期:
0 Custom malloc: allocated 100 bytes (requested 50) 0 Allocated pointer: 0x55f8d7c6f2a0 1 Temp pointer (real malloc): 0x55f8d7c6f310 0
内容的提问来源于stack exchange,提问作者Clark Kent
相关产品推荐
相关产品推荐

