目录
1. GDB介绍
GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。
总的来说,借助 GDB 调试器可以实现以下几个功能:
- 程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;
- 可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),即支持断点调试;
- 程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。
正如从事 Windows C/C++ 开发的一定要熟悉 Visual Studio、从事 Java 开发的要熟悉 Eclipse 或 IntelliJ IDEA、从事 Android 开发的要熟悉 Android Studio、从事 iOS 开发的要熟悉 XCode 一样,从事 Linux C/C++ 开发要熟悉 GDB。
另外,虽然 Linux 系统下读者编写 C/C++ 代码的 IDE 可以自由选择,但调试生成的 C/C++ 程序一定是直接或者间接使用 GDB。可以毫不夸张地说,我所做那些 C/C++ 项目的开发和调试包括故障排查都是利用 GDB 完成的,调试是开发流程中一个非常重要的环节,因此对于从事 Linux C/C++ 的开发人员熟练使用 GDB 调试是一项基本要求。
GDB调试的原理:系统调用 ptrace。ptrace()系统调用提供一个方法,该方法使一个程序(追踪者)可以观察和控制另外一个程序(被追踪者)的执行,并检查和改变被追踪者的内存和寄存器。它主要实现断点调试和追踪系统调用。
2. GDB使用
2.1 GDB启动方法
1、gdb program
program 也就是你的执行文件,一般在当前目录下。
2、gdb program core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。
3、gdb program 1234
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。
4、GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb -help查看。下面只列举一些比较常用的参数:
--symbols=SYMFILE
从指定文件中读取符号表。
--se=FILE
从指定文件中读取符号表信息,并把他用在可执行文件中。
--core=COREFILE
调试时core dump的core文件。
--directory=DIR
加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。
5、编译调试代码:gdb很多功能依赖debug信息,例如根据函数名设置断点,backtrace查看栈桢,info args等功能,所以为了使 gdb 正常工作,你必须使你的程序在编译时包含调试信息。调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号, gdb 利用这些信息使源代码和机器码相关联。编译源代码时防止优化:-O0 -g,其中 -O0 可以防止编译器优化,特别是对于局部变量或者只读变量, -g 要求生成的文件带有调试信息。
2.2 GDB基本命令
当以 gdb 方式启动gdb后,gdb会在PATH路径和当前目录中搜索源文件。如要确认gdb是否读到源文件,可使用l或list命令,看看gdb是否能列出源代码。
在gdb中,运行程序使用r或是run命令。程序的运行,你有可能需要设置下面四方面的事。
1 、程序运行参数。
set args 可指定运行时参数。(如:set args 10 20 30 40 50 )
show args 命令可以查看设置好的运行参数。
2 、运行环境。
path
可设定程序的运行路径。
show paths 查看程序的运行路径。
set env environmentVarname=value 设置环境变量。如:set env USER=benben
show env [varname] 查看环境变量,不带varname,打印出当前所有环境变量。
3 、工作目录。
cd 相当于shell的cd命令。
pwd 显示当前的所在目录。
4 、程序的输入输出。
info terminal 显示你程序用到的终端的模式。
使用重定向控制程序输出。如:run > outfile
tty命令可以设置输入输出使用的终端设备。如:tty /dev/tty1
5 调试已运行的程序
两种方法:
1、在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb PID process-id 格式挂接正在运行的程序。
2、先用gdb 关联上源代码,并进行gdb,在gdb中用attach process-id 命令来挂接进程的PID。并用detach来取消挂接的进程。
6 暂停 / 恢复程序运行
调试程序中,暂停程序运行是必须的,GDB可以方便地暂停程序的运行。你可以设置程序的在哪行停住,在什么条件下停住,在收到什么信号时停往等等。以便于你查看运行时的变量,以及运行时的流程。
当进程被gdb停住时,你可以使用info program 来查看程序的是否在运行,进程号,被暂停的原因。
在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops)。如果要恢复程序运行,可以使用c或是continue命令。
2.2.1 断点:break/watch/catch
1、断点(break)
用break命令来设置断点。正面有几点设置断点的方法:
break function
在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函数名。
break linenum
在指定行号停住。
break +offset
break -offset
在当前行号的前面或后面的offset行停住。offset为自然数。
break filename:linenum
在源文件filename的linenum行处停住。
break filename:function
在源文件filename的function函数的入口处停住。
break *address
在程序运行的内存地址处停住。
break
break命令没有参数时,表示在下一条指令处停住。
break ... if cond
...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序。
查看断点时,可使用info命令,如下所示:(注:n表示断点号)
info breakpoints [n]
info break [n]
info watchpoints [n]
2、数据断点(WatchPoint)
观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:
watch expr
为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。
rwatch expr
当表达式(变量)expr被读时,停住程序。
awatch expr
当表达式(变量)的值被读或被写时,停住程序。
info watchpoints
查看观察点、断点和捕捉点信息,同info break 一样.
3、捕捉点(CatchPoint)
你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常。设置捕捉点的格式为:
catch event
当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 event
只设置一次捕捉点,当程序停住以后,应点被自动删除。
4、条件断点
前面在说到设置断点时,我们提到过可以设置一个条件,当条件成立时,程序自动停止,这是一个非常强大的功能,这里,我想专门说说这个条件的相关维护命令。一般来说,为断点设置一个条件,我们使用if关键词,后面跟其断点条件。并且,条件设置好后,我们可以用condition命令来修改断点的条件。只有 break和watch命令支持if,catch目前暂不支持if,所以如果我们需要对catch使用条件断点,我们可以先添加catch断点,然后使用condition命令来添加断点的条件。
condition bnum expression
修改断点号为bnum的停止条件为expression。
condition bnum
清除断点号为bnum的停止条件。
还有一个比较特殊的维护命令ignore,你可以指定程序运行时,忽略停止条件几次。
ignore bnum count
表示忽略断点号为bnum的停止条件count次。
5、为停止点设定运行命令
我们可以使用GDB提供的command命令来设置停止点的运行命令。也就是说,当运行的程序在被停止住时,我们可以让其自动运行一些别的命令,这很有利行自动化调试。对基于GDB的自动化调试是一个强大的支持。
commands [bnum]
... command-list ...
end
为断点号bnum指写一个命令列表。当程序被该断点停住时,gdb会依次运行命令列表中的命令。
例如:
break foo if x>0
commands
printf "x is %d ",x
continue
end
断点设置在函数foo中,断点条件是x>0,如果程序被断住后,也就是,一旦x的值在foo函数中大于0,GDB会自动打印出x的值,并继续运行程序。
如果你要清除断点上的命令序列,那么只要简单的执行一下commands命令,并直接在打个end就行了。
2.2.2 运行和单步调试:run/step/next
2.2.3 查看数据:dispaly/print/x
1. print:在使用print命令时,可以对变量按指定格式进行输出,其命令格式为print /格式 + 变量名
,其中常用的变量格式:x:十六进制;d:十进制;u:无符号数;o:八进制;c:字符格式;f:浮点数
。
2. info reg :可以显示寄存器内容。
p $寄存器
:显示寄存器内容p/x $寄存器
:十六进制显示寄存器内容。
3. x : 用x命令可以显示地址内容,“x/格式 地址”。
x $pc
:显示程序指针内容x/i $pc
:显示程序指针汇编。x/10i $pc
:显示程序指针之后10条指令。x/128wx 0xfc207000
:从0xfc20700开始以16进制打印128个word。
2.2.4 堆栈相关命令:bt/frame
backtrace/bt
也可以在该命令后加上要打印的栈帧指针的个数,查看程序执行到此时,是经过哪些函数呼叫的程序,程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧)
thread apply all bt :查看所有线程堆栈命令
info frame
该命令会依次打印出当前栈帧的如下信息:
- 当前栈帧的编号,以及栈帧的地址;
- 当前栈帧对应函数的存储地址,以及该函数被调用时的代码存储的地址
- 当前函数的调用者,对应的栈帧的地址;
- 编写此栈帧所用的编程语言;
- 函数参数的存储地址以及值;
- 函数中局部变量的存储地址;
- 栈帧中存储的寄存器变量,例如指令寄存器(64位环境中用 rip 表示,32为环境中用 eip 表示)、堆栈基指针寄存器(64位环境用 rbp 表示,32位环境用 ebp 表示)等。
除此之外,还可以使用info args
命令查看当前函数各个参数的值;使用info locals
命令查看当前函数中各局部变量的值。
2.2.5 信号处理:handle
C、C++ 程序中,信号(signal)常常作为进程间通信的一种重要手段。GDB 调试器可以自动捕获 C、C++ 程序中出现的信号,并根据事先约定好的方式处理它
1. kill -l
查看Linux中定义的signal信号:
其中,每个信号代表着不同的含义,以 SIGINT 信号为例,它表示程序停止执行,该信号可以通过按 Ctrl+c
组合键发出。换句话说,对于正在执行的程序,通过按Ctrl+c
键向程序发出 SIGINT 信号,可以使程序停止执行。
2. info signals/handle
与此同时,GDB 调试器提供了info signals/handle
指令,用于查看 GDB 可以处理的信号种类,以及各个信号的具体处理方式。
其中各列的含义分别为:
- Signal:各个信号的名称;
- Stop:当信号发生时,是否终止程序执行。Yes 表示终止,No 表示当信号发生时程序认可继续执行;
- Print:当信号发生时,是否要求 GDB 打印出一条提示信息。Yes 表示打印,No 表示不打印;
- Pass:当信号发生时,该信号是否对程序可见。Yes 表示程序可以捕捉到该信息,No 表示程序不会捕捉到该信息;
- Description:对信号所表示含义的简单描述。
3. handle signal [keywords...]
其中,signal 参数表示要设定的目标信号,它通常为某个信号的全名(SIGINT)或者简称(去除‘SIG’后的部分,如 INT);如果要指定所有信号,可以用 all 表示。
keywords 参数用于明确 GDB 处理该目标信息的方式,其值可以是如下几个:
- nostop:当信号发生时,GDB 不会暂停程序,其可以继续执行,但会打印出一条提示信息,告诉我们信号已经发生;
- stop:当信号发生时,GDB 会暂停程序执行。
- noprint:当信号发生时,GDB 不会打印出任何提示信息;
- print:当信号发生时,GDB 会打印出必要的提示信息;
- nopass(或者 ignore):GDB 捕获目标信号的同时,不允许程序自行处理该信号;
- pass(或者 noignore):GDB 调试在捕获目标信号的同时,也允许程序自动处理该信号。
注意,当 GDB 捕获到信号并暂停程序执行的那一刻,程序是捕获不到信号的,只有等到程序继续执行时,信号才能被程序捕获。
举例;
可以看到,通过执行info signals SIGINT
命令,我们调取出了当前 GDB 调试器对 SIGINT 信号处理方式的默认设定,即当 SIGINT 信号发生时,GDB 调试器会暂停程序执行,同时打印出必要的提示信息,并且不让程序捕获到该信号。由此,当程序执行过程中按下Ctrl+c
组合键后,并没有执行 display() 函数,而是立即暂停了程序。
2.2.6 多线程调试
shell的命令:
(1)查看当前运行的进程:ps aux | grep book
(2)查看当前运行的轻量级进程:ps -aL | grep book
(3)查看主线程和子线程的关系:pstree -p 主线程id
gdb的命令:
(1)查看可切换调试的线程:info threads
(2)切换调试的线程:thread 线程id
(3)只运行当前线程:set scheduler-locking on
(4)运行全部的线程:set scheduler-locking off
(5)指定某线程执行某gdb命令:thread apply 线程id gdb_cmd
(6)全部的线程执行某gdb命令:thread apply all gdb_cmd
参考文档:
GDB是什么? (biancheng.net)
https://www.cnblogs.com/lvdongjie/p/8994092.html
GDB基本用法_houxiaoni01的博客-优快云博客_gdb