基于CSAPP实验:malloc运行时存根库调用printf触发段错误
问题描述
我正在按照《CSAPP》指导开展动态库相关实验,通过LD_PRELOAD加载自定义malloc存根库时,只要启用代码中的printf语句就会触发段错误,注释该语句后程序运行正常。已知printf内部会调用malloc,想明确以下问题:
- 是否形成了循环调用?
- 段错误的具体原因是什么?
- 如何在该存根库中安全使用printf?
存根库代码
#include <dlfcn.h> #include <stdlib.h> #include <stdio.h> // int data; void *malloc(size_t size) { void *(*mallocp)(size_t size); char *error; mallocp = (void*(*)(size_t))dlsym(RTLD_NEXT, "malloc"); if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } char *ptr = NULL; ptr = (char*)mallocp(size); // printf("malloc(%d) @ %p\n", (int)size, 0); return ptr; }
编译与运行步骤
- 编译存根库:
gcc -fpic -shared dynamic.cpp -ldl -o dynamic.so -g - 编译测试主程序(仅调用malloc):
gcc -g test.cpp - 加载库运行:
export LD_PRELOAD=./dynamic.so && ./a.out,启动阶段触发段错误。
问题分析与解决
1. 确实形成了循环调用
当你在自定义malloc中调用printf时,printf内部会调用malloc申请内存(比如用于格式化字符串、管理输出缓冲区)。由于LD_PRELOAD的优先级高于标准库,printf调用的malloc会再次跳转到你的自定义malloc函数,形成无限递归调用。
2. 段错误的原因
无限递归会持续向栈中压入函数调用帧,快速耗尽进程的栈空间,最终触发栈溢出,也就是你看到的Segmentation fault。此外,程序启动早期标准库的malloc可能还未完成初始化,反复递归调用会打乱初始化流程,进一步加剧崩溃。
3. 安全使用printf的可行方案
要避免递归调用,核心是让printf使用标准库的原始malloc,而非你的自定义版本,或者改用不依赖malloc的输出方式,以下是两种可靠方案:
方案一:提前缓存原始malloc地址
利用__attribute__((constructor))标记初始化函数,在程序启动时就获取标准库malloc的地址并存入全局变量,后续自定义malloc直接使用该缓存指针,printf调用时也会走原始malloc:
#include <dlfcn.h> #include <stdlib.h> #include <stdio.h> // 全局缓存标准库原始malloc的地址 static void *(*original_malloc)(size_t) = NULL; // 程序启动时优先执行的初始化函数 __attribute__((constructor)) void init_stub() { original_malloc = (void*(*)(size_t))dlsym(RTLD_NEXT, "malloc"); if (!original_malloc) { fputs(dlerror(), stderr); exit(1); } } void *malloc(size_t size) { void *ptr = original_malloc(size); // 此时printf调用的是原始malloc,不会触发递归 printf("malloc(%zu) @ %p\n", size, ptr); return ptr; }
方案二:改用不依赖malloc的输出函数
直接使用write系统调用,它无需申请用户态内存,可直接向文件描述符写入数据:
#include <dlfcn.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> void *malloc(size_t size) { void *(*mallocp)(size_t size); char *error; mallocp = (void*(*)(size_t))dlsym(RTLD_NEXT, "malloc"); if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } char *ptr = (char*)mallocp(size); // 用write替代printf,手动格式化字符串到栈缓冲区 char buf[64]; int len = snprintf(buf, sizeof(buf), "malloc(%zu) @ %p\n", size, ptr); write(STDOUT_FILENO, buf, len); return ptr; }
注:短字符串场景下,标准库的snprintf会使用栈上的临时缓冲区,不会调用malloc;若要彻底规避,可手动拼接字符串。
内容的提问来源于stack exchange,提问作者zkh
相关产品推荐
相关产品推荐

