前言
最近研究c++协程库libco的时候发现,它内部大量运用了dlsym技术,对如connect, sendto, recv, read, write等系统调用做了hook, 从而使得libco能在单线程的情况下调度协程。出与对dlsym技术的学习总结,出了这篇博文
dlsym概述
dlsym是动态链接库中的一个关键函数,可以通过符号名(Symbol)获取函数指针。定义如下:
void *dlsym(void *handle, const char *symbol);
其中,handle表示动态链接库的句柄,symbol表示需要查找的符号名。dlsym返回符号名对应的函数指针,如果未找到符号,则返回NULL。
dlsym使用方法
dlsym + dlopen
先准备一个libfoo.so
// foo.h
extern "C" void foo();
// foo.cpp
#include <stdio.h>
#include "foo.h"
void foo() {
printf("you are foo");
}
编译动态链接库
g++ -fPIC -shared -o libfoo.so foo.cpp -ldl
// main.cpp
#include <dlfcn.h>
#include <stdio.h>
#include "foo.h"
typedef void* (*foo_func)();
int main() {
void* handle = dlopen("./libfoo", RTLD_LAZY);
foo_func foo = (foo_func)dlsym(handle, "foo");
char* err = dlerror();
if (err) {
printf("%s\n", err);
return 0;
}
foo();
}
执行命令,可以看到我们通过dlsym这种方式调用到了libfoo.so中的foo函数
g++ -std=c++11 -o main main.cpp
LD_PRELOAD=./libfoo.so ./main
dlsym + RTLD_DEFAULT
一般而言,symbol的查找是有先后顺序的,并且可能多个动态链接库里有同一个symbol名称;
比如echo
这个symbol, 可能在liba.so里有定义,在libc.so里也有定义。对于这种情况,我们可以使用RTLD_DEFAULT。
RTLD_DEFAULT是个特殊的宏,表示在查找symbol的时候,返回第一个查找到的symbol的地址,找到了就不需往别的动态链接库里查找了。
#include <stdio.h>
#include <dlfcn.h>
typedef void (*printf_func_ptr)(const char *, ...);
int main() {
printf_func_ptr printf_ptr = (printf_func_ptr)dlsym(RTLD_DEFAULT, "printf");
printf_ptr("Hello, world!\n");
return 0;
}
执行后可以发现,我们通过dlsym调用到了系统库里的printf函数
dlsym + RTLD_NEXT + LD_PRELOAD
与RTLD_DEFAULT不同的点在于,RTLD_NEXT表示查找symbol的时候,跳过所在的动态链接库,继续去别的lib中找到symbol的位置,找到了就返回。
这有什么用呢?通过LD_PRELOAD环境变量,可以用来对某些系统调用做hook。看下面例子
// hook.cpp
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#define ENABLE_HOOK_FUNC(name) \
if (!g_sys_##name) { \
g_sys_##name = (sys_##name##_t)dlsym(RTLD_NEXT, #name); \
}
typedef void* (*sys_malloc_t)(size_t size);
static sys_malloc_t g_sys_malloc = NULL;
extern "C" {
void* malloc(size_t size) {
ENABLE_HOOK_FUNC(malloc);
printf("invoke malloc hook function\n");
void *p = g_sys_malloc(size);
return p;
}
编译动态链接库
g++ -fPIC -shared -o libhook.so hook.cpp -ldl
注意点,由于hook.cpp是c++文件,因此必须在需要做hook的函数外面写上
extern "C"
, 否则实际生成的so的方法名不一定是malloc
// main.cpp
#include <memory>
int main() {
printf("before malloc\n");
char* buf = (char*)malloc(sizeof(char));
if (!buf) {
printf("malloc error\n");
}
printf("after malloc\n");
return 0;
}
通过LD_PRELOAD即可实现对malloc函数的hook
g++ -std=c++11 -o main main.cpp
LD_PRELOAD=./libhook.so ./main
执行后可以发现,我们通过LD_PRELOAD前置了一个动态链接库,成功地实现了对系统库里的malloc函数进行hook
dlsym + RTLD_NEXT
在实际使用libco的过程中,并没有发现需要使用LD_PRELOAD,那有没有办法不需要用LD_PRELOAD也可以hook掉系统函数呢,研究发现,可以用如下方式来实现
先hook掉malloc函数
// hook.cpp
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#include <memory.h>
#define ENABLE_HOOK_FUNC(name) \
if (!g_sys_##name) { \
g_sys_##name = (sys_##name##_t)dlsym(RTLD_NEXT, #name); \
}
typedef void* (*sys_malloc_t)(size_t size);
static sys_malloc_t g_sys_malloc = NULL;
extern "C" {
void* malloc(size_t size) {
ENABLE_HOOK_FUNC(malloc);
printf("invoke malloc hook function\n");
char* p = (char*)g_sys_malloc(size);
memset(p, '0', sizeof(char)*size);
return p;
}
}
将hook.cpp编译成动态库
g++ -fPIC -shared -o libhook.so hook.cpp -ldl
// main.cpp
#include <memory>
int main() {
printf("before malloc\n");
char* buf = (char*)malloc(sizeof(char)*24);
if (!buf) {
printf("malloc error\n");
}
printf("after malloc\n");
printf("%s\n", buf);
return 0;
}
在编译main.cpp的时候,带上该动态库
g++ -std=c++11 -o main main.cpp -L /root/workspace/dlsym-demo/ -lhook
直接执行./main
,可以发现hook生效了,malloc出来的内存的内容都被设置'0'
before malloc
invoke malloc hook function
after malloc
000000000000000000000000�