在个人的经验中,绝大部分情况下gdb调试可以单独对某个动态库的行为进行调试,这样的话就可以很方便的直接在存在异常的设备上定位问题,本文介绍如何在发行设备上,针对单个动态库进行gdb定位问题
这里需要准备两个程序,一个example.c用作main调用,一个sum.c实现sum接口,代码足够精简。
sum.c的代码如下
int sum(int x, int y) { int s = x + y; return s; }
example.c的代码如下
#include <stdio.h> #include "sum.h" static int y = 2; int main() { int x = 1; printf("sum=%d \n", sum(x, y)); }
此时先编译so
gcc sum.c -shared -fPIC -o libsum.so
再编译二进制
gcc example.c -lsum -L./ -o example
上面编译都是不带g的。所以gdb可以直接看到libsum.so的符号状态如下
# export LD_LIBRARY_PATH=./ # gdb ./example (gdb) info sharedlibrary From To Syms Read Shared Object Library 0x0000fffff7fcd0c0 0x0000fffff7fe5468 Yes (*) /lib/ld-linux-aarch64.so.1 0x0000fffff7fba470 0x0000fffff7fba57c Yes (*) ./libsum.so 0x0000fffff7e26880 0x0000fffff7f15db8 Yes /lib/aarch64-linux-gnu/libc.so.6 No linux-vdso.so.1
可以看到libsum.so是无调试信息的。
这种情况下,如果调试libsum.so的sum函数内容,那么只能通过寄存器和反汇编分析。
更聪明的办法是使得其能够加载符号,最简单的办法是将libsum.so编译带上符号,如下
gcc sum.c -shared -fPIC -g -o libsum.so
如果是在debian系操作系统上原生开发,那么可以自动或者手动加载.debug_info
段即可,或者安装对应的dbg包即可。这里无需赘述。
此时再查看二进制加载的情况如下
(gdb) info sharedlibrary From To Syms Read Shared Object Library 0x0000fffff7fcd0c0 0x0000fffff7fe5468 Yes (*) /lib/ld-linux-aarch64.so.1 0x0000fffff7fba470 0x0000fffff7fba57c Yes ./libsum.so 0x0000fffff7e26880 0x0000fffff7f15db8 Yes /lib/aarch64-linux-gnu/libc.so.6 No linux-vdso.so.1
可以看到,此时libsum.so带上调试信息了,可以直接进行调试
(gdb) b sum Breakpoint 1 at 0xfffff7fba560: file sum.c, line 3. (gdb) r Starting program: /root/gdb/example Breakpoint 1, sum (x=1, y=2) at sum.c:3 3 int s = x + y; (gdb) set x=4 (gdb) set y=6 (gdb) c Continuing. sum=10 [Inferior 1 (process 994034) exited normally]
这里libsum.so动态库的映射关系也可以在gdb中直接查看,无需进proc找,如下
(gdb) info proc map process 1005668 Mapped address spaces: Start Addr End Addr Size Offset objfile 0xaaaaaaaaa000 0xaaaaaaaab000 0x1000 0x0 /root/gdb/example 0xaaaaaaaba000 0xaaaaaaabb000 0x1000 0x0 /root/gdb/example 0xaaaaaaabb000 0xaaaaaaabc000 0x1000 0x1000 /root/gdb/example 0xfffff7e06000 0xfffff7f60000 0x15a000 0x0 /usr/lib/aarch64-linux-gnu/libc-2.31.so 0xfffff7f60000 0xfffff7f70000 0x10000 0x15a000 /usr/lib/aarch64-linux-gnu/libc-2.31.so 0xfffff7f70000 0xfffff7f74000 0x4000 0x15a000 /usr/lib/aarch64-linux-gnu/libc-2.31.so 0xfffff7f74000 0xfffff7f76000 0x2000 0x15e000 /usr/lib/aarch64-linux-gnu/libc-2.31.so 0xfffff7f76000 0xfffff7f79000 0x3000 0x0 0xfffff7fba000 0xfffff7fbb000 0x1000 0x0 /root/gdb/libsum.so 0xfffff7fbb000 0xfffff7fca000 0xf000 0x1000 /root/gdb/libsum.so 0xfffff7fca000 0xfffff7fcb000 0x1000 0x0 /root/gdb/libsum.so 0xfffff7fcb000 0xfffff7fcc000 0x1000 0x1000 /root/gdb/libsum.so 0xfffff7fcc000 0xfffff7fed000 0x21000 0x0 /usr/lib/aarch64-linux-gnu/ld-2.31.so 0xfffff7ff7000 0xfffff7ffb000 0x4000 0x0 0xfffff7ffb000 0xfffff7ffc000 0x1000 0x0 [vvar] 0xfffff7ffc000 0xfffff7ffd000 0x1000 0x0 [vdso] 0xfffff7ffd000 0xfffff7ffe000 0x1000 0x21000 /usr/lib/aarch64-linux-gnu/ld-2.31.so 0xfffff7ffe000 0xfffff8000000 0x2000 0x22000 /usr/lib/aarch64-linux-gnu/ld-2.31.so 0xfffffffdf000 0x1000000000000 0x21000 0x0 [stack]
可以发现,如果知道了之前关于DWARF调试信息的事情,如何针对动态库调试这件事情就变得异常简单。
在实际工作经验中,我们不可能给发行的二进制所有的代码都加上g,这是比较笨的行为,正常的处理办法是针对问题的现象初步外围分析,从而得出大致的结论,知道问题可能出现在哪些模块上,而又因为模块对应了不同的so实现,那么只需要将怀疑的so挂上符号,这样就可以放心的调试so了。这种技巧对大型工程的调试而言,简单却非常好用。