这篇文章为了让你深入了解gdb的工作原理,以及如何在linux环境下使用强大的gdb调试程序功能。
我们先了解下程序是个什么东西?
root@100.115.21.77:/data/mariolu/tls13/openssl-OpenSSL_1_1_1/apps#file openssl
openssl: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=264f60f58cadd21c0df8c40dabe0b4babe9f9014, not stripped
输出信息显示openssl是个可执行程序,并且是ELF格式的文件。
使用readelf命令解析这个程序,-S指定打印section信息,-W不选择换行显示。这里会显示很多section,可能会包括名字带有stab或者debug的东西。
readelf -WS ./a.out | egrep '\.(stab |debug)'
这里的stabs或者debug又是什么东西呢。顾名思义,这些是编译进程序的debug信息。Linux当前主流的debug信息格式有STABS或者DWARF格式。如果二进制有STABS,会看到.stab。如果有DWARF,你将看到.debug_info,.debug_line等。如果你根本没有调试信息,那么你什么也看不到。
这里的openssl编译使用的是DWARF的debug信息,所以可以使用dwarfdump -a openssl解析出dwarf基本单元DIE(DIE, Debugging Information Entry)。程序有很多像如下图所示的基本单元信息。
每个DIE有:
AT_low_pc
, AT_high_pc
分别代表函数的 起始/结束 PC地址
AT_frame_base
表达函数的栈帧基址(frame base) 为寄存器 rbp
的值
AT_name
描述函数的名字为 s_client_main
AT_decl_file
说这个函数在 apps/s_client.c 文件中声明
AT_decl_line
说这个函数在 foo.c 第879(十六进制36F)行声明
AT_prototyped
为一个 Bool 值, 为 True 时代表这是一个子程序/函数(subroutine)
AT_type
属性描述这个函数返回值的类型是什么
AT_external
Bool值, 这个函数是否为全局可访问
AT_sibling
连接了兄弟DIE,通过它连接了所有的基本实体(DIE)
这些信息依赖于gdb编译时候加上-g生成(除了-g,还可以显示指定格式-gdwarf、-gstabs、-gxconff,但是-g可移植性更强,-ggdb可以生成针对gdb特定的格式。-ggdb3或者-ggdb2可以生成额外的调试信息,比如宏定义等)
-g 选项可以产生符合操作系统本地格式的调试信息(stabs、COFF、XCOFF ,或者 DWARF 2)。gdb可以基于这里调试信息进行工作。
GCC 允许你同时使用 -g 和 -O 选项。代码在-Ox(x是数字,代表优化等级)编译优化后可能会产生令人惊奇的结果:一些你声明的变量可能已经不存在了;控制流可能走到了你未曾想象到的位置;一些语句可能不会被执行,因为其计算结果是常量,或者其结果早已经被获得;一些语句可能在不同的地方被执行,因为其被移出了当前循环。 所以编译调试信息时务必加上-O0选项。
除了用readelf,dwarfdump,如果你更熟悉objdump,也可以用以下命令查看调试信息。
objdump -d openssl
如果知道是dwarf格式,可以显示指定
objdump -dwarf openssl | less
当然如果格式不对,objdump会输出无效的文件格式目标
objdump -stabs openssl
objdump: openssl: Invalid bfd target
一个elf程序中有symbol table,symbol是一段程序或者变量的符号链接,使用#nm -g openssl
可以查看。当程序执行到某个symbol,根据dwarf描述的对应的debug信息就可以还原出当时的源代码(文件,行号)信息。
那么gdb是如果跟踪到进程当前执行的symbol所包含的信息。原来linux有个ptrace系统调用。ptrace该系统调用运行父进程追踪子进程的运行数据。gdb的跟踪调试功能设置程序的断点break point,父进程通过ptrace接管子进程除了SIGKILL之外所有的信号。当子进程(就是我们调试的程序)在发送break point或者单步调试,会产生一个信号SIGTRAP,被父进程(这里的gdb)捕获到,这时用户就可以通过gdb实时观察到当前的子进程状态。
gdb有三种使用方法:
常用的有:
这里我比较少用到有,读者有兴趣可以继续做这个方面的调研
举个比较实用的例子:
下面是非常有用的shell脚本用来查找指定函数,并在这些函数上设置断点,然后运行程序,在每次这些函数被调用的时候,打印出5层堆栈。程序结束,自动退出。如果设置足够多的函数断点,可以打印出所有的函数调用关系,然后后处理该脚本的输出,可以得到一个函数调用图。这是一个比较快捷的方法。
最后的args 文件中需要保存运行workbinary命令的参数。
funcs=`gdb –batch ../bin/Debug/workbinary -ex “info functions Foo” | sed -n “/(/ p” | cut -f 1 -d ‘(‘ | cut -f 2 -d ‘ ‘`
rm -rf gdbcommands
touch gdbcommands
cnt=0
for f in $funcs
do
echo “b $f” >> gdbcommands
cnt=`expr $cnt + 1`
done
times=`seq 1 $cnt`
for t in $times
do
echo “commands $t” >>gdbcommands
echo “bt 5” >>gdbcommands
echo “c” >> gdbcommands
echo “end” >> gdbcommands
echo “” >> gdbcommands
done
gdb –batch ../bin/Debug/workbinary -x gdbcommands -x args
break [break-args] if (condition)
condition <break_list> (condition)
还有几个比较实用的命令:
当然gdb的命令还有很多,可以在gdb的help菜单中查看到“aliases,data,breakpoints,flies,internals,obscure,running,stack,support,tracepoints,user-defined主题”,感兴趣的读者可以进一步深入研究。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。