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

基于CSAPP实验:malloc运行时存根库调用printf触发段错误

问题描述

我正在按照《CSAPP》指导开展动态库相关实验,通过LD_PRELOAD加载自定义malloc存根库时,只要启用代码中的printf语句就会触发段错误,注释该语句后程序运行正常。已知printf内部会调用malloc,想明确以下问题:

  1. 是否形成了循环调用?
  2. 段错误的具体原因是什么?
  3. 如何在该存根库中安全使用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;
}

编译与运行步骤

  1. 编译存根库:gcc -fpic -shared dynamic.cpp -ldl -o dynamic.so -g
  2. 编译测试主程序(仅调用malloc):gcc -g test.cpp
  3. 加载库运行: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 00:09:22