在 Linux 上用C/C++ 编程时, 当调用标准库函数, 如 read, write, printf, malloc, realloc 时, 程序会先链接 glibc 中的 read, write, printf, malloc, realloc, 然后发起系统调用(read, write, puts, brk, mmap等). 那么如何重写(又称为 hook)这些库函数呢?

对于下面的测试程序, 要把其中的 malloc, free, new, delete 替换成自定义的 malloc, free.

1
2
3
4
5
6
7
8
9
10
11
12
// main.c
#include <stdio.h>
#include <stdlib.h>

int main() {
const int len = 128;
char * buf = (char*)malloc(len);
snprintf(buf,len - 1, "hello world");
printf("%s\n", buf);
free(buf);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// main.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

class A {
private:
int _i;
public:
A(int i) : _i(i) {}
int get_i() {
return _i;
}
};

int main() {
const int len = 128;
char * buf = (char*)malloc(len);
snprintf(buf,len - 1, "hello world");
printf("%s\n", buf);
free(buf);
A* ap = new A(123);
std::cout << ap->get_i() << std::endl;
delete ap;
return 0;
}

共有以下几种方法:

通过 LD_PRELOAD 环境变量预先加载动态库

malloc_preload.c 中自定义 malloc 和 free 函数, 其中分别调用了标准库中的 malloc 和 free.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// malloc_preload.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>

static void * (*real_malloc)(size_t size) = NULL;
static void (*real_free)(void *ptr) = NULL;

void * malloc(size_t size) {
printf("my malloc: %zu\n", size);
// use dlsym to find next malloc in dynamic libraries, ie. malloc in std library
real_malloc = dlsym(RTLD_NEXT, "malloc");
return real_malloc(size);
}

void free(void *ptr) {
printf("my free: %p\n", ptr);
real_free = dlsym(RTLD_NEXT, "free");
real_free(ptr);
}

malloc_preload.c 编译成动态库:

1
2
# 当编译动态库时, 要加上 -fPIC 参数, 以生成位置无关代码.
gcc -fPIC -shared -o libmalloc_preload.so ./malloc_preload.c -ldl

main.c 编译成可执行文件:

1
gcc main.c -o main

需要注意的是, 如果 main 可执行文件已经存在, 不需要重编. 因为在执行时, 操作系统会动态链接到libmalloc_preload.so 中的 malloc.
执行main 的同时, 设置环境变量:

1
LD_PRELOAD="./libmalloc_preload.so" ./main

可以看到, 标准库中的 malloc 和 free 被 hook 了.

使用 wrap 编译参数

  1. 给自定义的 malloc 和 free 函数加上__wrap_前缀, 如果需要调用标准库中的 malloc, free, 就加上__real_ 前缀.
  2. 编译时加上参数 -Wl,-wrap=malloc -Wl,-wrap=free 参数. 这样编译器就会自动把用户代码中的 malloc 链接到__wrap_malloc, 并把__real_malloc 链接到glibc 中的malloc. free 也是一样.

malloc_wrap.c 文件中自定义 malloc 和 free 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// malloc_wrap.c
#include <stdio.h>
#include <stdlib.h>

void * __real_malloc(size_t size);
void __real_free(void *ptr);

void * __wrap_malloc(size_t size) {
printf("my malloc: %zu\n", size);
return __real_malloc(size);
}

void __wrap_free(void *ptr) {
printf("my free: %p\n", ptr);
__real_free(ptr);
}

重新编译主程序:

1
gcc -g main.c malloc_wrap.c -o main -Wl,-wrap=malloc -Wl,-wrap=free

也可以把malloc_wrap.c 编译成静态库, 在编译 main.c 的时候链接:

1
2
3
4
gcc -c malloc_wrap.c
ar cr libmalloc_wrap.a malloc_wrap.o
gcc -g main.c malloc_wrap.c -o main -Wl,-wrap=malloc -Wl,-wrap=free
gcc main.c -L./ -lmalloc_wrap -o main -Wl,-wrap=malloc -Wl,-wrap=free

运行 main, 即可看到库函数 malloc 和 free 都被hook了.


如果主程序是C++程序, 需要在函数前加上extern "C", 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>

extern "C" void * __real_malloc(size_t size);
extern "C" void __real_free(void *ptr);

extern "C" void * __wrap_malloc(size_t size) {
printf("my malloc: %zu\n", size);
return __real_malloc(size);
}

extern "C" void __wrap_free(void *ptr) {
printf("my free: %p\n", ptr);
__real_free(ptr);
}

因为C++程序在编译之后函数名会经过 mangle 处理, 所以需要加上extern "C" 告诉编译器这是 c 风格的代码, 不要 mangle.
用命令

1
g++ main.cpp malloc_wrap.cpp -o main -Wl,-wrap=malloc -Wl,-wrap=free

编译之后同样可以看到

glibc 调试变量

这种方法不常用, 详情参考 malloc调试变量

参考资料: