代码改变世界

[转] 微信协程库libco研究:hook系统函数

2017-12-27 09:16  放作夥  阅读(871)  评论(0编辑  收藏  举报

系统为我们提供了 dlopen,dlsym工具,用于运行时加载动态库。可执行文件在运行时可以加载不同的动态库,这就为hook系统函数提供了基础。
下面用一个小小的例子来说明如何利用dlsym工具hook系统函数。

假设现在我们需要统计程序中malloc的调用次数,但是不能修改原有程序。最简单的思路类似于Java中动态代理Proxy的做法,先找到系统的malloc函数,然后将其替换为自定义的函数,在自定义函数中增加调用次数,并回调系统的原有malloc函数。

例如我们要统计以下main.c中调用malloc的次数:

// main.c

include <stdio.h>

include <stdlib.h>

int main() {
void *p = malloc(4);
free(p);
printf("hello world\n");
return 0;
}
为了能让自己的malloc函数回调系统的malloc函数,我们需要利用dlsym获取系统的malloc函数。

// myhook.c

include <stdlib.h>

include <dlfcn.h>

include <stdio.h>

static int count = 0;

void *malloc(size_t size) {
void (myMalloc)(size_t) = dlsym(RTLD_NEXT, "malloc");
printf("call my malloc\n");
count++;
return myMalloc(size);
}
RTLD_NEXT允许从调用方链接映射列表中的下一个关联目标文件获取符号,即找到glibc.so中的malloc函数。

下一步则是要让可执行文件main找到自定义的malloc函数。

在linux操作系统的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。loader在进行动态链接的时候,会将有相同符号名的符号覆盖成LD_PRELOAD指定的so文件中的符号。换句话说,可以用我们自己的so库中的函数替换原来库里有的函数,从而达到hook的目的。
编译:

$ gcc -o main main.c
$ gcc -o libmymalloc.so -fPIC -shared -D_GNU_SOURCE myhook.c -ld
运行:

$ LD_PRELOAD=./libmymalloc.so ./main
call my malloc
hello world
至此,malloc函数的hook已经完成。

不使用LD_PRELOAD的Hook
这样就结束了吗?我们看看libco库是如何实现hook的呢,它的makefile中并没有LD_PRELOAD相关的信息。其秘密在于co_hook_sys_call.cpp,其将 co_enable_hook_sys()的定义在该cpp文件内,这样就把该文件的所有函数都导出了(即导出符号表)。

//co_hook_sys_call.cpp
ssize_t read(int fd, void* buf, size_t bytes)
{
...
}

...

void co_enable_hook_sys() //这函数必须在这里,否则本文件会被忽略!!!
{
stCoRoutine_t *co = GetCurrThreadCo();
if( co )
{
co->cEnableSysHook = 1;
}
}
我们仍然以上面malloc的例子来说明:

// main.c

include <stdio.h>

include <stdlib.h>

include "myhook.h"

int main() {
enable_hook();
void *p = malloc(4);
free(p);
printf("hello world\n");
return 0;
}
// myhook.h
int enable_hook();
// myhook.c

include <stdlib.h>

include <dlfcn.h>

include <stdio.h>

include "myhook.h"

static int count = 0;

void *malloc(size_t size) {
void (myMalloc)(size_t) = dlsym(RTLD_NEXT, "malloc");
printf("call my malloc\n");
count++;
return myMalloc(size);
}

int enable_hook() {
return 1;
}
编译和运行:

$ gcc -o libmymalloc.so -fPIC -shared -D_GNU_SOURCE myhook.c -ldl
$ gcc -o main main.c -L./ -lmymalloc
$ ./main
call my malloc
hello world
这种方式算是对源代码进行了侵入,必须调用特定的函数(即本例中的enable_hook()),才能将hook的函数导出,并链接到现有的可执行文件的内存空间中。

总结
libco库通过非LD_PRELOAD的方法,将网络相关的read,write...等方法进行hook后,将其改造成异步操作,即相关调用阻塞后让出cpu,让其他协程继续处理,从而达到异步化的效果。libco的具体实现后续再介绍。

注:这种不使用LD_PRELOAD的方式蛮奇怪的,难道说include某个头文件中的符号时,会把该符号之前的所有符号也导入进来?