East - Hook Module

基于C语言全局符号唯一,我们可以提前link自己的动态库,之后链接libc的时候由于符号已经存在,我们就会调用到我们实现的函数中去了,但并不是所有的场景都需要调用我们hook的函数,所以我们利用dlsym将libc中的符号的函数地址保存到我们提前声明的函数指针中,借助这个函数指针,我们可以调用原先的库函数。

1
2
3
void *dlsym(void *handle, const char *symbol);

eg: dlsym(RLDT_NEXT, "sleep")

RLDT_NEXT的含义:下一个含有这个符号的动态库运行时的地址,而不是下一个加载的库,ref:理解RLDT_NEXT

Hook原理, 以sleep函数举例:
1.定义一个类型sleep_func,方便使用
2.声明外部变量sleep_f
3.将实际调用通过dlsym赋值给这个外部变量,然后我们自己来实现实际调用sleep,sleep_f就变成了库版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
extern "C" {
...
//sleep
typedef unsigned int (*sleep_func)(unsigned int seconds);
extern sleep_func sleep_f; //声明一个外部变量sleep_f, 他的类型是一个指针
...


unsigned int sleep(unsigned int seconds) {
if (!East::is_hook_enable()) {
return sleep_f(seconds);
}

auto fiber = East::Fiber::GetThis();
auto io_mgr = East::IOManager::GetThis();

// auto func = std::bind(
// (void (East::IOManager::*)(East::Fiber::sptr, int thread_id))(&East::IOManager::schedule),
// io_mgr, fiber, -1);

if (nullptr != fiber && nullptr != io_mgr) {
io_mgr->addTimer(seconds * 1000, [io_mgr, fiber]() {
if (nullptr != io_mgr) {
io_mgr->schedule(fiber);
}
});
}
//East::Fiber::YieldToHold();
fiber->yield();
return 0u;
}
};

//将要hook的函数从动态库中找出来赋值, HOOK_FUNC(hook)会展开成hook(sleep) 等所有要hook的函数,然后展开: hook(sleep) sleep_f = (sleep_func)dlsym(RTLD_NEXT, "sleep")
//从下一个动态库(通常是 libc)中查找 "sleep" 函数的地址(避开当前这个hook)把结果赋给 sleep_f
#define hook(name) name##_f = (name##_func)dlsym(RTLD_NEXT, #name);
HOOK_FUNC(hook)
#undef hook

Tips:

在test_tcp_server程序中,我发现accept调用一直没有进入我们hook的版本中,而调用sleep就成功调用了hook的版本。
1.使用LD_PRELOAD 强制指定libEast.so发现work
2.使用LD_DEBUG=bindings发现accept符号使用licpthread.so中的符号

基于上述两点,发现我的cmake中g++的编译参数加上了-lpthread, 因为在后面添加依赖的时候已经有pthread库了,所以这里是多余的。
不光多余,这里添加之后,phtread库就会先链接,所以我们hook函数也就失效了。所以要想hook生效这里必须要去掉。

1
2
3
4
5
6
7
8
9
...
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -fPIC -rdynamic -g -std=c++17 -Wall -Wno-deprecated -Werror -Wno-unused-function -ldl")
...
set(LIBS
East
pthread
dl # hook需要依赖dl库
yaml-cpp)
...

目前已经hook的函数:

1
2
3
4
5
6
#define HOOK_FUNC(func)                                                  \
func(sleep) func(usleep) func(nanosleep) func(socket) func(connect) \
func(accept) func(read) func(readv) func(recv) func(recvfrom) \
func(recvmsg) func(write) func(writev) func(send) func(sendto) \
func(sendmsg) func(close) func(fcntl) func(ioctl) \
func(getsockopt) func(setsockopt)

和socket相关的函数普遍都与IO操作相关,我们封装了一个模板函数,他的主要流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
调用 do_io()

执行真实系统调用 → 如果成功 → 返回

errno = EAGAIN ?

addEvent(fd, event)

设置 timeout timer(可选)

当前 fiber yield 掉

等待 epoll 唤醒

epoll 事件来了?
↙ ↘
是,取消 timer 否,timer 超时触发
↓ ↓
fiber resume fiber resume
↓ ↓
重试 errno = ETIMEDOUT, return -1

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2015-2025 Xudong0722
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信