GDB如何改变动态链接行为?LD_PRELOAD注入段错误排查
问题解答
一、GDB如何改变动态链接行为?
你遇到的核心差异来自地址空间布局随机化(ASLR) 和程序加载阶段的重定位时机:
- 未启动程序时,
objdump或GDB里看到的0x1b65是ELF文件中的相对虚拟地址(RVA),即相对于程序默认加载基址的偏移,并非实际运行时的虚拟地址。 - GDB默认会禁用ASLR(部分系统需手动执行
set disable-randomization on),因此程序每次启动都会加载到固定基址(x86_64系统下通常为0x555555554000)。启动程序后,动态链接器完成重定位,符号实际地址就是「基址 + RVA」,也就是0x555555554000 + 0x1b65 = 0x555555555b65。 - 而GDB外默认开启ASLR,程序每次启动的加载基址随机变化,直接使用固定的
0x1b65或GDB里的固定地址,必然会访问错误内存触发段错误。
此外,GDB在启动程序时会接管动态链接器的加载流程,确保程序完成重定位后再允许查看符号地址,这也是你能在GDB中看到实际运行地址的原因。
二、如何在GDB外获取重定位后的符号地址?
以下是几种实用方法:
1. 临时禁用ASLR,固定加载基址
通过setarch命令关闭当前进程的ASLR,让程序每次启动都加载到固定基址,再用「基址 + RVA」计算实际地址:
# x86_64系统 setarch x86_64 -R ./myapp & # 获取进程PID PID=$! # 查看程序加载基址 grep myapp /proc/$PID/maps | head -1
输出第一行的起始地址即为基址(例如0x555555554000),加上objdump得到的0x1b65就是符号实际运行地址。
2. 用非交互式GDB直接获取地址
无需手动进入GDB交互界面,直接让GDB启动程序并打印目标符号地址:
gdb --batch --ex 'start' --ex 'p &your_symbol_name' --ex 'quit' ./myapp
输出会直接给出重定位后的地址,格式类似$1 = (void (*)()) 0x555555555b65。
3. 运行时通过/proc文件系统计算
程序启动后,通过/proc文件系统获取加载基址,再结合RVA计算实际地址:
# 后台启动程序 ./myapp & PID=$! # 获取可执行文件的加载基址 BASE_ADDR=$(grep -o '^[0-9a-f]*' /proc/$PID/maps | head -1) # 转换进制并计算实际地址 ACTUAL_ADDR=$(printf "0x%x" $((16#$BASE_ADDR + 16#1b65))) echo "实际符号地址: $ACTUAL_ADDR"
4. 在程序中内置打印逻辑
如果可以修改myapp代码,直接在程序启动时打印目标符号的地址:
#include <stdio.h> // 假设目标符号为foo void foo() {} int main() { printf("foo的运行地址: %p\n", &foo); // 其他业务逻辑 return 0; }
编译运行后即可直接获取实际地址。
内容的提问来源于stack exchange,提问作者stumbling.fool
相关产品推荐
相关产品推荐

