GDB中应该知道的几个调试方法 2

七、八年前写过一篇《用GDB调试程序》,于是,从那以后,很多朋友在MSN上以及给我发邮件询问我关于GDB的问题,一直到今天,还有人在问GDB的相关问题。这么多年来,有一些问题是大家反复在问的,一方面,我觉得我以前的文章可能没有说清楚,另一方面,我觉得大家常问的问题正是最有用的,所以,在这里罗列出来。希望大家补充。

普通模式与 tui 模式的切换的快捷键:

C-x C-a

C-x a

C-x A

Enter or leave the TUI mode. When leaving the TUI mode, the curses window management stops and GDB operates using its standard mode, writing on the terminal directly. When reentering the TUI mode, control is given back to the curses windows. The screen is then refreshed.

防止程序输出到调试命令窗口对调试进行干扰:

使用 gdb 的 tty 命令来将当前tty的输出重定向到另一个tty。

打开一个新的窗口,使用tty命令来查看当前窗口的tty编号,假设输出为 /dev/pts/4。

然后在gdb里输入以下命令即可: tty /dev/pts/4

一、多线程调试
多线程调试可能是问得最多的。其实,重要就是下面几项:

1、 info threads 查看当前进程的所有线程。
2、 thread <ID> 切换调试的线程为指定ID的线程。
3、 break file.c:100 thread all 在file.c文件第100行处为所有经过这里的线程设置断点。
4、 set scheduler-locking off|on|step,这个是问得最多的。在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
off 不锁定任何线程,也就是所有线程都执行,这是默认值。
on 只有当前被调试程序会执行。
step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。 该模式是对single-stepping模式的优化。此模式会阻止其他线程在当前线程单步调试时,抢占当前线。因此调试的焦点不会被以外的改变。其他线程不可能抢占当前的调试线程。其他线程只有下列情况下会重新获得运行的机会:

当你‘next’一个函数调用的时候。
当你使用诸如‘continue’、‘until‘、’finish‘命令的时候。
其他线程遇到设置好的断点的时候。
  show scheduler-locking 可以查看其当前值。

5、 ‘set print thread-events’,用于设定是否提示线程启动或停止时的信息。

6、 ‘thread applay [threadno] [all] args’ ,在指定的线程上执行特定的命令args。 该工具用于在一个或多个线程执行指定的命令command。threadid可以是一个或多个线程id,或是一个范围值,例如,1-3。 如 thread apply ID1 ID2 command 或 thread apply all command。 示例:

  thread apply all bt   查看所用线程上的堆栈信息

7、 设置 follow-fork-mode detach-on-fork, 默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(fork追踪模式, 默认值:parent)和detach-on-fork(指示GDB在fork之后是否断开某个进程的调试, 默认值:on)即可。 两者结合起来构成了GDB的调试模式:

  follow-fork-mode       detach-on-fork     说明

parent on   只调试主进程(GDB默认)
child on     只调试子进程
parent off     同时调试两个进程,gdb跟主进程,子进程block在fork位置
child off     同时调试两个进程,gdb跟子进程,主进程block在fork位置
设置方法:set follow-fork-mode [parent|child|ask] set detach-on-fork [on|off]

设置gdb在fork时询问跟踪哪一个进程: set follow-fork-mode ask

查看gdb默认的参数设置:

show follow-fork-mode

show detach-on-fork

7.1、 进程的操作

查询正在调试的进程:info inferiors

切换调试的进程: inferior <infer number>

添加新的调试进程: add-inferior [-copies n] [-exec executable] ,可以用file executable来分配给inferior可执行文件。

其他:remove-inferiors infno, detach inferior

** 8、 info threads 看不到多线程的信息?**

造成这个问题的原因有:

  1. 使用的库是glibc而libpthread库被strip过。

  2. libthread_db库和libpthread库不匹配。

第1种情况一般是嵌入式开发在板子上运行GDB所遇到的情况,为了 节省板子上的空间,有时候会把所有的库都strip一下,就遇见了这种现象,解决这种问题的办法是:拷贝一个没有strip过的libpthread库到 板子上,或者使用“strip --strip-debug libpthread.so.0”代替直接strip。 请检查运行环境中的libpthread是否被strip过,如果是,请换一个没有strip过的libpthread,问题应该会得到解决。

第2种情况一般是在板子使用gdbserver,远程使用GDB进 行调试,这时候往往板子和运行GDB的机器的体系结构不同,造成了库的不匹配。这时候可以在用remote命令连接gdbserver以前,用“set solib-absolute-prefix”或者“set sysroot”命令设置正确的库搜索路径,就可以解决这个问题。

9、 dump:

一般都涉及内存非法读写。可以使用下面的命令打开系统开关,让其可以在死掉的时候生成core文件。
ulimit -c unlimited
这样的话死掉的时候就可以在当前目录看到core.pid(pid为进程号)的文件。接着使用gdb:
gdb ./bin ./core.pid
进去后,使用bt查看死掉时栈的情况(命令如: thread apply all bt),再使用frame命令。

10、 gdb处理signal信号:

GDB有能力在你调试程序的时候处理任何一种信号,你可以告诉GDB需要处理哪一种信号。你可以要求GDB收到你所指定的信号时,马上停住正在运行的程序,以供你进行调试。你可以用GDB的handle命令来完成这一功能。

handle <signal> <keywords…>
在GDB中定义一个信号处理。信号<signal>可以以SIG开头或不以SIG开头,可以用定义一个要处理信号的范围(如:SIGIO- SIGKILL,表示处理从SIGIO信号到SIGKILL的信号,其中包括SIGIO, SIGIOT,SIGKILL三个信号),也可以使用关键字 all来标明要处理所有的信号。一旦被调试的程序接收到信号,运行程序马上会被GDB停住,以供调试。其<keywords>可以是以下几种关键字的一个或多个。

nostop
当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。
stop
当被调试的程序收到信号时,GDB会停住你的程序。
print
当被调试的程序收到信号时,GDB会显示出一条信息。
noprint
当被调试的程序收到信号时,GDB不会告诉你收到信号的信息。
pass
noignore
当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序处理。
nopass
ignore
当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。

info signals
info handle
查看有哪些信号在被GDB检测中。

11、 再谈线程

如果你程序是多线程的话,你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作。

break <linespec> thread <threadno>
break <linespec> thread <threadno> if …
linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过“info threads”命令来查看正在运行程序中的线程信息。如果你不指定
thread <threadno>则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如:

(gdb) break frik.c:13 thread 28 if bartab > lim

当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。

12、 学会使用watch, catch及tcatch

观察点(WatchPoint)
观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:
watch
为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。
rwatch
当表达式(变量)expr被读时,停住程序。
awatch
当表达式(变量)的值被读或被写时,停住程序。
info watchpoints
列出当前所设置了的所有观察点。
三、设置捕捉点(CatchPoint)
你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常。设置捕捉点的格式为:
catch
当event发生时,停住程序。event可以是下面的内容:
1、throw 一个C++抛出的异常。(throw为关键字)
2、catch 一个C++捕捉到的异常。(catch为关键字)
3、exec 调用系统调用exec时。(exec为关键字,目前此功能只在HP-UX下有用)
4、fork 调用系统调用fork时。(fork为关键字,目前此功能只在HP-UX下有用)
5、vfork 调用系统调用vfork时。(vfork为关键字,目前此功能只在HP-UX下有用)
6、load 或 load载入共享库(动态链接库)时。(load为关键字,目前此功能只在HP-UX下有用)
7、unload 或 unload卸载共享库(动态链接库)时。(unload为关键字,目前此功能只在HP-UX下有用)
tcatch
只设置一次捕捉点,当程序停住以后,应点被自动删除。

gdb 有 watchpoint, catchpoint, checkpoint, tracepoint, breakpoint 等point 工具。

二、调试宏

gdb打印预定义的宏macro的方法:

必须同时使用以下几个编译选项才能打印:

-g:

Produce debugging information in the operating system's native format (stabs, COFF, XCOFF, or DWARF 2). GDB can work with this debugging information. On most systems that use stabs format, -g enables use of extra debugging information that only GDB can use; this extra information makes debugging work better in GDB but probably makes other debuggers crash or refuse to read the program. If you want to control for certain whether to generate the extra information, use -gstabs+, -gstabs, -gxcoff+, -gxcoff, or -gvms (see below).

-ggdb:

Produce debugging information for use by GDB. This means to use the most expressive format available (DWARF 2, stabs, or the native format if neither of those are supported), including GDB extensions if at all possible.

-gvmslevel: (示例: -g3, 要打印macro,请使用 -g3)
Request debugging information and also use level to specify how much information. The default level is 2. Level 0 produces no debug information at all. Thus, -g0 negates -g.

Level 3 includes extra information, such as all the macro definitions present in the program. Some debuggers support macro expansion when you use -g3.
https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html

-DDEBUG
最好加上,特别是调试内核及内核驱动模块。

然后在gdb中使用:

info macro YOUR_MACRO 或者
macro expand
YOUR_MACRO
查看相应的macro。

info macro – 你可以查看这个宏在哪些文件里被引用了,以及宏定义是什么样的。
macro expand – 你可以查看宏展开的样子。

三、源文件
这个问题问的也是很多的,太多的朋友都说找不到源文件。在这里我想提醒大家做下面的检查:

编译程序员是否加上了-g参数以包含debug信息。
路径是否设置正确了。使用GDB的directory命令来设置源文件的目录。
下面给一个调试/bin/ls的示例(ubuntu下)

apt−getsourcecoreutils sudo apt-get install coreutils-dbgsym
gdb/bin/lsGNUgdb(GDB)7.1−ubuntu(gdb)listmain1192ls.c:Nosuchfileordirectory.inls.c(gdb)directory /src/coreutils−7.4/src/Sourcedirectoriessearched:/home/hchen/src/coreutils−7.4:cdir:$cwd
(gdb) list main
1192 }
1193 }
1194
1195 int
1196 main (int argc, char **argv)
1197 {
1198 int i;
1199 struct pending *thispend;
1200 int n_files;
1201

附:对于directory命令及相对路径或绝对路径的说明

因为gcc 根据你编译的时候指定的是绝对路径还是 ../../XXX.cpp之类的相对路径,在生成debug_info的时候,也把这个路径保存为debug_info 里面的文件名字,就是最后 gdb list 里面找到的文件名字。
这个可以list 查看是不是绝对路径,然后可以用命令

readelf -p .debug_str exe_or_so_file
看到里面保存是是完整的绝对路径。

gdb 的directory 命令添加的源码搜索路径只对相对路径的情况有效。
一个解决办法就是在gcc的时候,使用相对路径那么gdb里面你就可以使用 dir来设置了。像些CMake之类的,它喜欢使用绝对路径,有人可以写个脚本让它使用相对路径,参考 http://stackoverflow.com/questions/9607155/make-gcc-put-relative-filenames-in-debug-information
如果gcc里面-g 生成的debug_info 使用的绝对路径了,最简单的办法就是你把源码也放到相应的位置上去了。
但如果你不想使用这个绝对路径,那也还是有办法的。
GDB还提供另外一个选择,可以让你修改源码搜索路径,把源码绝对路径里面的一个path映射到另一个path上去,这样即使你debug info了里面的是绝对路径,源码也可以放到另外的目录。
这就是命令

set substitute-path from_path to_path
比如 list显示的源码是 /home/aaa/1.cpp

那么设置了 set substitute-path /home/aaa/ /home/bbb/
之后,即使你的源文件1.cpp 放在 /home/bbb下面也是可以找到的了。因为gdb帮你做了字符串替换。

四、条件断点
条件断点是语法是:break [where] if [condition],这种断点真是非常管用。尤其是在一个循环或递归中,或是要监视某个变量。注意,这个设置是在GDB中的,只不过每经过那个断点时GDB会帮你检查一下条件是否满足。

五、命令行参数
有时候,我们需要调试的程序需要有命令行参数或环境变量,很多朋友都不知道怎么设置调试的程序的命令行参数。其实,有两种方法:
1)、gdb环境中 set args命令;

如: set env environmentVarname=value 设置环境变量。 示例: set env USER=benben

2)、gdb命令行的 --args 参数,使用示例如下:

[~]$ gdb --args pizzamaker --deep-dish --toppings=pepperoni
...
(gdb) show args
Argument list to give program being debugged when it is started is
" --deep-dish --toppings=pepperoni".
(gdb) b main
Breakpoint 1 at 0x45467c: file oven.c, line 123.
(gdb) run
...

六、gdb的变量
有时候,在调试程序时,我们不单单只是查看运行时的变量,我们还可以直接设置程序中的变量,以模拟一些很难在测试中出现的情况,比较一些出错,或是switch的分支语句。使用set命令可以修改程序中的变量。

另外,你知道gdb中也可以有变量吗?就像shell一样,gdb中的变量以开头,比如你想打印一个数组中的个个元素,你可以这样:(gdb)seti = 0

(gdb) p a[i++] ... #然后就一路回车下去了 当然,这里只是给一个示例,表示程序的变量和gdb的变量是可以交互的。 七、x命令 也许,你很喜欢用p命令。所以,当你不知道变量名的时候,你可能会手足无措,因为p命令总是需要一个变量名的。x命令是用来查看内存的,在gdb中 “help x” 你可以查看其帮助。 x/x 以十六进制输出 x/d 以十进制输出 x/c 以单字符输出 x/i 反汇编 – 通常,我们会使用 x/10iip-20 来查看当前的汇编($ip是指令寄存器)
x/s 以字符串输出
八、command命令
有一些朋友问我如何自动化调试。这里向大家介绍command命令,简单的理解一下,其就是把一组gdb的命令打包,有点像字处理软件的“宏”。下面是一个示例:

(gdb) break func
Breakpoint 1 at 0x3475678: file test.c, line 12.
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".

print arg1
print arg2
print arg3
end
(gdb)
当我们的断点到达时,自动执行command中的三个命令,把func的三个参数值打出来。

(全文完)

1、gdb 调式子进程:
set follow-fork-mode child
设置了这句之后,每次遇到fork就会自动进入子进程的代码。

2、gdb 保存和恢复breakpoints 断点:

将断点

save breakpoints <filename>
恢复断点:

source [-s] [-v] filename
其中的 -s 参数迫使gdb在它的搜索路径里搜索源代码文件, -v 是 verbose,让gdb打印它做了哪些事。

3、经常用到的恢复程序运行和单步调试的命令有:

continue 继续运行程序直到下一个断点(类似于VS里的F5)

next   逐过程步进,不会进入子函数(类似VS里的F10); 另外还有: next count 一次执行count,不进入函数

setp  逐语句步进,会进入子函数(类似VS里的F11); 另外还有: step count 一次性执行count步,如果有函数会进入函数

until   运行至当前语句块结束

finish  运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)




使用断点:

建立断点

(gdb)break //可以简写为(gdb)b

(gdb)b <function> || <file>:<function> //这种是为还没有打开的源代码设置断点

(gdb)b <line> || <file>:<line> || <address> //在一个物理地址设置断点

查看断点

(gdb)info b

删除断点

(gdb)delete <breakpoint number>

(gdb)clear <line> || <file>:<line>

条件断点

(gdb)break <operator(通常是函数名)> if <op> = <value> //用于在满足某个条件时中断执行,在该operator处暂停

更多的打断点方式:

b linenum 在某行打断点
b +offset/-offset 在当前行号的前面或后面offset停住
b filename:linenum 在某文件的某行打断点
b filename:function 在某文件某个函数入口停住
b *address 在程序的运行地址处停住
b 没有参数在下一句停住
b where if condition 当某个条件满足时,在某一行停住(这个很有用,比如b 10 if ret == 5)

posted @ 2021-11-24 16:09  微信公众号--共鸣圈  阅读(368)  评论(0编辑  收藏  举报