重写库函数或系统调用
在 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
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 | // main.cpp |
共有以下几种方法:
通过 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
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 编译参数
- 给自定义的 malloc 和 free 函数加上
__wrap_
前缀, 如果需要调用标准库中的 malloc, free, 就加上__real_
前缀. - 编译时加上参数
-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
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
4gcc -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
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调试变量