有時程序員需要完成這類任務:
假如你有一個二進制版的系統,例如現在流行的android,你需要爲這個系統開發一個軟件。
這個軟件牽涉到系統行爲,因此需要對系統做修改。然而你並沒有這個系統的所有源碼
( Nexus S的源碼不一定與 android 官方版本一模一樣),或者是你只有這個系統的頭文件。
當你需要修改部分系統行爲的時候,你不可能用源碼重新編譯一個共享庫來替換系統文件。
此時,就需要利用 dlfcn.h 運行時動態鏈接庫來實現我們的目標。利用 dlopen(),dlsym(),dlerror(),dlclose()這
四個函數,可以實現類似 Windows 平臺 Api Hook 機制的效果。
步驟如下:
1、首先得先將原共享庫改名。假如系統中有一個共享庫libwuxiao.so,那麼就需要
把它改成其他名字,比如,改爲 libwuxiao_orig.so。
假如這個共享庫的源碼如下:
#include <stdio.h>
void func_a(int a, int b)
{
printf("func_a called a=%d b=%d\n",a,b);
}
void func_b(char* string)
{
printf("func_b called string=%s\n",string);
}
int func_c()
{
return 123;
}
編譯一下, gcc -shared -fPIC -o libwuxiao.so wuxiao.c
可以寫一個可執行程序來測試一下這個共享庫。之後,你可以看到
我們完全不修改這個可執行文件,讓這個可執行文件執行我們增加的代碼。
#include <stdio.h>
extern void func_a(int a, int b);
extern void func_b(char* string);
extern int func_c();
int main(int argc, char* argv)
{
func_a(55,66);
func_b("hello,wuxiao");
int b = func_c();
printf("after func(), b=%d\n",b);
}
gcc -o wuxiao_test main.c -lwuxiao -L/path/to/libwuxiao.so
2、然後有兩類編程方法實現wrapper:
a) 先調用dlopen(),返回libwuxiao_orig.so的句柄,再從句柄中查找想要的函數
#include <stdio.h>#include <dlfcn.h>
static void* libwuxiao_handle = NULL;
__attribute__((constructor)) _init_wuxiao()
{
libwu_handle = dlopen("libwuxiao_orig.so",RTLD_NOW); // 注意:已經改名字了
}
__attribute__((destructor)) void _fini_wuxiao()
{
dlclose(libwu_handle);
}
int func_c()
{
int (*func_c_ptr)();
func_c_ptr = dlsym(libwuxiao_handle,"func_c");
if ( func_c_ptr != NULL )
{
printf(" add your hook code here\n");
return func_c_ptr();
}
return -1;
}
鏈接方法: gcc -shared -fPIC -o libwuxiao.so -ldl -lwuxiao_orig -L/path/to/libwuxiao_orig.so
(編譯完上面的僞裝共享庫之後,執行一下 main_test,看看增加的代碼有沒有被執行。)
上面代碼中,原共享庫的 func_c 就是我們想要修改的函數。
這裏寫了一個相同的 func_c,它查找到原函數的地址,先執行 hook 代碼,然後執行原 func_c 函數,
因而不會影響原函數的執行,只不過多執行了我們的代碼。
隨後 “僞裝”成爲libwuxiao.so,系統在執行程序的時候,就會查找僞造的 libwuxiao.so,
而僞造的libwuxiao.so又會去加載真正的 libwuxiao_orig.so。
這樣達到了我們的目標:
1、保證原libwuxiao.so中的所有函數不會丟失
2、在調用原libwuxiao.so中的函數的時候能夠執行我自己的代碼。
b) 這裏的方法更簡單,原理與上面的相同,都是通過在僞造的libwuxiao.so中鏈接原libwuxiao_orig.so,
然後再定義同型函數,查找原函數地址,然後調用原函數的方法。這裏的方法僅對GNU版本的系統有效。
#include <stdio.h>
#include <sys/types.h>
#include <dlfcn.h>
int func_c()
{
int (*func_c_ptr)();
// find func_c() in another shared object(with the same name)
dlerror();
func_c_ptr = dlsym(RTLD_DEFAULT,"func_c");
while ( func_c_ptr == func_c )
{
if ( func_c_ptr == NULL ) {
printf("Error(wrapper-2): cannot find func_c()\n");
break;
}
dlerror();
func_c_ptr = dlsym(RTLD_NEXT,"func_c");
}
if ( func_c_ptr != NULL )
{
printf("wuxiao is very powerful the second time\n");
return func_c_ptr();
}
return -1;
}
編譯: gcc -shared -fPIC -o libwuxiao.so -ldl -lwuxiao_orig -L. -D_GNU_SOURCE
這種方法雖然更簡單,但只對GNU有效,如果你的代碼要放在非GNU的系統上執行,那還是用a)中的方法吧。
總結:
1、這裏實現的wrapper的侷限性就在於,你必須知道原來的共享庫中的函數原型,但是你不必知道所有函數的原型。
你只需要知道其中你需要的函數原型即可。
2、如果你遇到一個共享庫,裏面的符號是強符號(不能再定義一個同名函數了),則這裏的方法不再有用了。