远程调试Linux共享库(.so)时函数地址异常问题求助
远程调试Linux .so文件时的地址转换问题
环境与操作步骤
- 本地:Windows 10 64位,使用cygwin64 gdb
- 远程:Ubuntu 12.04 LTS(GNU/Linux 3.2.0-24-generic x86_64),CPU为Intel(R) Xeon(R) CPU E3-1505M v6 @ 3.00GHz
- 调试目标:Linux服务器上被进程加载的C语言.so文件
操作流程:
- 在Linux端获取加载.so的进程PID,执行命令
gdbserver --attach :9999 <pid>启动gdbserver - 在Windows端cygwin64 gdb中,先用
file命令读取本地的.so副本,再执行target remote <linuxServerIp>:9999连接远程gdbserver
遇到的问题
在Windows端远程gdb中设置断点失败:
(gdb) break my_func
Breakpoint 1 at 0x36a13: my_func. (2 locations)
(gdb) c
Continuing.
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x36a13
执行 print my_func 得到地址 0x369ff;在Linux本地gdb未attach进程时执行同样命令,得到相同地址。但本地gdb attach目标进程后:
(gdb) attach 28290
Attaching to process 28290
再执行 print my_func 显示 false,执行 break my_func 得到断点地址 0x7fffe5b46a13,此时可正常断点。将该地址用于Windows端远程gdb的 break *0x7fffe5b46a13 命令,也能正常断点。
疑问
- 为何.so文件被外部进程加载后函数地址会变化?
- 为何Linux本地gdb attach进程后能正确转换地址,而Windows端通过GDBServer连接的gdb却无法转换?
解答
1. .so加载后函数地址变化的原因
这是Linux系统**地址空间随机化(ASLR)**和动态链接库的重定位机制导致的:
- .so文件编译链接时生成的地址是相对偏移(链接时的虚拟地址),并非进程运行时的实际虚拟地址。
- 当进程加载.so时,操作系统会为其随机分配一个加载基址(ASLR用于防止恶意程序利用固定地址攻击),函数的实际运行地址 = 加载基址 + 函数在.so中的相对偏移。
- 本地gdb未attach进程时,读取的是.so文件本身存储的静态相对偏移;attach进程后,gdb会读取目标进程的内存映射信息(从
/proc/<pid>/maps文件),获取.so实际加载的基址,从而计算出函数的实际运行地址。
2. Windows端远程gdb无法自动转换地址的原因
核心原因有两个:
- 内存映射信息未同步:本地gdb attach进程时,能直接读取
/proc/<pid>/maps获取.so的加载基址;而远程gdbserver向Windows端gdb传递的信息不完整,导致Windows端gdb无法自动获取到.so实际加载的基址,只能使用本地.so文件中的静态偏移地址,而这个地址和进程中的实际地址不匹配。 - 版本兼容性问题:Ubuntu 12.04的gdbserver版本较老(对应GDB 7.4左右),而cygwin64的gdb版本通常较新,新旧版本之间的调试协议存在兼容性问题,导致远程gdb无法正确解析目标进程的动态加载信息。
另外,若本地的.so副本和远程服务器上运行的.so文件存在差异(比如编译参数不同),也可能导致地址计算偏差,但从测试结果看,手动使用实际地址能断点,所以核心还是基址同步和版本兼容问题。
内容的提问来源于stack exchange,提问作者Fitri Halim
相关产品推荐
相关产品推荐

