如何调试LD_PRELOAD库中的__attribute__((__constructor__))构造函数?
调试LD_PRELOAD共享库的__attribute__((constructor))函数
你遇到的问题是典型的:LD_PRELOAD加载的共享库构造函数会在GDB完成断点设置前就执行,哪怕用starti也拦不住——因为动态链接器在进程主程序启动前,就会加载共享库并调用其构造函数,而starti的暂停点是主程序的第一条指令,这时候构造函数早就跑完了。
下面是几种可行的调试方案:
方法一:利用GDB的共享库加载事件暂停
GDB提供了stop-on-solib-events选项,开启后会在共享库加载/卸载时自动暂停进程。我们可以利用这个特性,在构造函数执行前(也就是共享库刚加载完成时)设置断点:
具体操作:
- 启动GDB时加入相关配置:
gdb /bin/true -ex 'set environment LD_PRELOAD ./preflight.so' \ -ex 'set stop-on-solib-events 1' \ -ex 'starti'
- 当GDB输出
Stopped due to shared library event时,说明preflight.so已经加载完成,但构造函数还没执行。此时设置断点:
(gdb) break preflight (gdb) continue
这时候进程就会在preflight函数的入口处暂停,你可以正常调试了。
方法二:在构造函数中主动触发调试陷阱
直接修改共享库代码,在构造函数开头加入触发GDB暂停的代码,比如发送SIGTRAP信号:
修改后的preflight.c:
#include <stdio.h> #include <stdlib.h> #include <signal.h> __attribute__((__constructor__)) void preflight(void) { raise(SIGTRAP); // 触发GDB自动暂停 puts("Exiting from preflight"); exit(42); }
重新编译库后,用GDB启动目标程序:
gdb /bin/true -ex 'set environment LD_PRELOAD ./preflight.so' -ex 'run'
进程会在raise(SIGTRAP)处自动暂停,之后你可以继续调试后续代码。
方法三:直接指定构造函数的内存地址断点
如果上述方法不适用,你还可以先找到构造函数的入口地址,再设置硬件断点。不过这种方法需要提前知道地址,不如前两种方便:
- 先不加载库,用
gdb加载目标程序,然后手动加载共享库:
(gdb) file /bin/true (gdb) set environment LD_PRELOAD ./preflight.so (gdb) sharedlibrary ./preflight.so
- 查看
preflight函数的地址:
(gdb) info functions preflight
- 根据输出的地址设置断点:
(gdb) break *0x12345678 # 替换成实际的地址 (gdb) run
为什么原来的方法失效?
starti的作用是让进程在主程序的第一条用户指令处暂停,但动态链接器的初始化(包括加载LD_PRELOAD的库、执行构造函数)是在主程序启动前完成的。当GDB执行starti时,动态链接器已经完成了构造函数的调用,所以断点根本没机会触发。
内容的提问来源于stack exchange,提问作者Joseph Sible-Reinstate Monica
相关产品推荐
相关产品推荐

