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

C语言函数指针安全交换问题:malloc替换引发栈溢出求助

解决LD_PRELOAD替换malloc时的递归栈溢出问题

嘿,我看你在尝试实现自定义malloc和原生libc malloc的切换时遇到了递归栈溢出的问题——这是LD_PRELOAD钩子场景里非常典型的坑,咱们先理清楚问题出在哪,再给你修复方案。

问题根源分析

你的代码里有两个致命问题:

  1. 无限递归调用:在自定义的malloc包装函数里直接调用malloc(sz),但此时动态链接器已经把所有对malloc的引用绑定到了你自己的包装函数上,这就导致了无限递归,最终栈溢出。
  2. 错误的函数指针替换逻辑:你试图通过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;
}

关键改进点

  1. 彻底避免递归:所有需要调用原生malloc的地方,都直接用malloc_mem_real指针,再也不直接调用malloc,从根源上切断递归链。
  2. 用标志位控制模式:use_real_malloc全局变量清晰控制当前的malloc实现,逻辑简单且不会破坏动态链接的符号绑定。
  3. 安全的模式切换:在自定义malloc内部,先切换到原生模式再执行printf等可能调用malloc的操作,执行完成后再切回自定义模式,确保这些辅助操作不会触发自定义malloc的递归。
  4. 懒加载初始化:只在第一次调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:06:21