之前的文章里我们提到过 Libco 有一套 Hook 机制,可以通过协程的让出(yield)原语将系统的阻塞系统调用改造为非阻塞的,这篇文章我们将深入解析 Hook 机制到底是怎么运作的
Hook 机制的核心有两点
libc.so
)的实现也就是说,我们提供的实现其实是标准库实现的 wrapper
为了搞明白 Hook 机制,我们首先要了解 Linux 动态库究竟是怎么运作的
动态库是在运行时链接的,这个工作是由动态链接器来完成的(Linux 下是 /lib/ld-linux.so.2
),主要涉及到的步骤有
如果可执行文件依赖的多个动态库定义了同一个符号时,以先加载的动态库为准,那么如果想要覆盖掉动态库 A 里的符号,最简单的做法就是让我们的库在动态库 A 之前加载,通常使用环境变量 LD_PRELOAD
来实现这点
LD_PRELOAD
中列出的动态库会在所有其他动态库之前加载,包括 libc.so
LD_PRELOAD
中,这样就会覆盖掉标准库的 malloc 实现命令
LD_DEBUG=files ./a.out
可以查看动态库的加载顺序和初始化顺序
解决了第一个问题,那么剩下的问题就是如何在我们的实现里调用标准库实现了,直接用函数名调用肯定是不行的,那么我们能想到的办法就是能否给标准库的实现改一个名字呢?
为了实现这点,我们需要用到 dlsym 函数, 它的函数原型为
void *dlsym(void *restrict handle, const char *restrict symbol);
dlsym 的原意是用来获得动态加载进来的动态库中的接口(Linux 中的动态库不仅可以在程序启动时加载,还可以在程序运行过程中加载和卸载),其中 handle 是动态库的句柄,symbol 是要搜索的符号
此外,dlsym 还支持两个伪 handle
RTLD_DEFAULT
RTLD_GLOBAL
选项,那么它的符号也会出现在全局符号表里RTLD_NEXT
利用 RTLD_NEXT
就可以实现我们想要的功能,假设说我们用 LD_PRELOAD
覆盖了标准库 malloc 实现,就可以通过 dlsym 拿到标准库的 malloc 地址(前提是在 libc.so 之前没有其他库定义了 malloc),给它的函数指针起一个其他的名字,就可以在我们的实现里调用标准库 malloc 了
可以看到 Libco 里就是用这种方法拿到所有标准库实现的函数指针
typedef ssize_t (*read_pfn_t)(int fildes, void *buf, size_t nbyte); static read_pfn_t g_sys_read_func = (read_pfn_t)dlsym(RTLD_NEXT, "read");