[轉] 微信協程庫libco研究:hook系統函數

系統爲咱們提供了 dlopen,dlsym工具,用於運行時加載動態庫。可執行文件在運行時能夠加載不一樣的動態庫,這就爲hook系統函數提供了基礎。
下面用一個小小的例子來講明如何利用dlsym工具hook系統函數。linux

假設如今咱們須要統計程序中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的目的。
編譯:it

$ 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某個頭文件中的符號時,會把該符號以前的全部符號也導入進來?

相關文章
相關標籤/搜索