GDB调试使用总结
文章目录
基本使用
启动GDB
如果要对程序进行调试,那么在编译前需要首先加上-g
选项。
使用命令gdb <program>
开始调试一个程序。
如果一个程序需要有参数才能执行,那么有两个方法指定参数。
- 在gdb启动程序后,通过
run
命令指定参数信息,比如run <parm1> <parm2> ...
。 - 在gdb启动程序后,通过
set args
命令指定参数信息,比如set args <parm1> <parm2>...
。
如果是一个已经运行的程序,那么可以通过命令gdb <program> <pid>
的方式附加上去调试。但有的时候会遇到一个运行中的程序没有调试信息的情况,这种情况下如果终止并重新编译可能不好复现问题,那么这种情况下可以先用之前的代码重新使用-g
参数再编译一个程序,之后,再指定已经运行的程序的进程id进行调试。
除了这种方式外,还可以通过gdb -p <pid>
的方式,直接附加到对应的进程上,不需要再指定可执行文件的名称。
对于core文件的分析,可以见后面的说明。
基本命令
运行
- run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
- continue (简写c ):继续执行,到下一个断点处(或运行结束)
- next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。后面可以跟数字,比如
n 2
表示连续执行该命令2次。 - step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
- stepi(简写si):每次执行一条机器指令。
- until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
- until+行号: 运行至某行,不仅仅用来跳出循环
- finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
- call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55)
- quit:简记为 q ,退出gdb。
设置断点
- break n (简写b n):在第n行处设置断点(可以带上代码路径和代码名称: b OAGUPDATE.cpp:578)
- b fn1 if a>b:条件断点设置
- break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button
- delete 断点号n:删除第n个断点
- disable 断点号n:暂停第n个断点
- enable 断点号n:开启第n个断点
- clear 行号n:清除第n行的断点
- info b (info breakpoints) :显示当前程序的断点设置情况
- delete breakpoints:清除所有断点:
- ignore 1 30:忽略断点1 30次,如果一个断点可能出错,但在前30次都不会有问题,那么可以通过igonre命令忽略前30次中断。其中1是断点号,而30次则是忽略的次数。
查看源代码
- list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。默认情况下,list会打印出main函数所在文件的源码,如果想打印指定文件的源码,那么可以通过
list 文件名:行号
的方式打印指定文件的源码,比如list test.c:1
用来打印test.c文件的第一行源码。 - list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12。
- list 函数名:将显示“函数名”所在函数的源代码,如:list main
- list :不带参数,将接着上一次 list 命令的,输出下边的内容。
- dir:用来指定源码的路径,比如
dir ./temp
。默认情况下,gdb可以通过可执行文件的调试信息找到对应的源码文件,但如果源码文件的位置被调整了,那么就可以使用该命令重新指定源码的路径。
打印表达式
- print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
- print a:将显示整数 a 的值。除了这种方式外,还有一些特殊场景。
- 如果一个变量在多个文件或函数中有定义,使用
p 'testGdb.h'::a
的方式打印testGdb.h
文件中的变量;使用p 'main'::b
的方式打印main函数中的变量。 - 使用
p *ptr
来打印指针指向的内容。如果是个数组,那么可以通过@
指定打印的长度,比如p *ptr@a
可以打印a长度的数据,或者只跟@
,那么会把所有的内容都打出来。 - 使用
$
打印上一个变量,假如有一个链表,有next
成员变量,那么使用p *linkNode
之后,可以使用p *$.next
不间断的打印链表的下一个节点的信息。 - 如果打印一个数组的成员,可以通过这种方式连续的打印。
set $index=0
,p b[$index + 1]
,使用类似环境变量的方式定义一个变量,然后累加打印,不用自己再手动修改下标打印了。 - 除了使用默认的格式打印外,还可以指定格式打印,可以指定的格式有很多,比如:
- p/x 按十六进制格式显示变量。
- p/d 按十进制格式显示变量。
- p/u 按十进制格式显示无符号整型。
- p/o 按八进制格式显示变量。
- p/t 按二进制格式显示变量。
- p/a 按十六进制格式显示变量。
- p/c 按字符格式显示变量。
- p/f 按浮点数格式显示变量。
- 如果一个变量在多个文件或函数中有定义,使用
- print ++a:将把 a 中的值加1,并显示出来
- print name:将显示字符串 name 的值
- print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
- print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
- examine(简写x):可以查看内存地址中的值,用法为
x/[n][f][u] addr
,详细介绍如下。一个例子是x/4tb &e
,打印4个字节,以二进制的形式打印。- n,表示要显示的内存单元数,默认值为1。
- f,表示要打印的格式,见print的控制格式。
- u,要打印的单元长度,常见的长度有
b 字节
、h 两个字节
、w 四个字节
、g 八字节
。 - addr,要打印的地址。
- display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
- watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a
- whatis :查询变量或函数
- info function: 查询函数扩展
- info locals: 显示当前堆栈页的所有变量
- info registers:打印寄存器信息
- info frame:打印栈帧中存储的信息
使用最多的是print
命令,这里对print的一些场景做一个说明。
查询运行信息
- where/bt :当前运行的堆栈列表;
- bt/backtrace 显示当前调用堆栈
- up/down 改变堆栈显示的深度
- set args 参数:指定运行时的参数
- show args:查看设置好的参数
- info program: 来查看程序的是否在运行,进程号,被暂停的原因。
分割窗口
- layout:用于分割窗口,可以一边查看代码,一边测试:
- layout src:显示源代码窗口
- layout asm:显示反汇编窗口,默认汇编以att格式显示,如果想改为intel的格式,那么执行命令
set disassembly-flavor intel
即可改为intel格式。查看当前的汇编格式可以通过命令show disassembly-flavor
查看。 - layout regs:显示源代码/反汇编和CPU寄存器窗口
- layout split:显示源代码和反汇编窗口
- info win: 显示所有的窗口
- layout prev: 切换到前一个窗口
- layout next: 切换到后一个窗口
- fs <窗口名>: 将焦点移动到某个窗口上
- Ctrl + L:刷新窗口
Core分析
打开Core Dump
- 打开Core dump功能
- 在终端中输入命令
ulimit -c
,输出的结果为 0,说明默认是关闭 core dump 的,即当程序异常终止时,也不会生成 core dump 文件。 - 我们可以使用命令
ulimit -c unlimited
来开启 core dump 功能,并且不限制 core dump 文件的大小; 如果需要限制文件的大小,将 unlimited 改成你想生成 core 文件最大的大小,注意单位为 blocks(KB)。 - 用上面命令只会对当前的终端环境有效,如果想需要永久生效,可以修改文件
/etc/security/limits.conf
文件,关于此文件的设置参看这里 。增加一行:
- 在终端中输入命令
|
|
- 修改core文件保存的路径
- 默认生成的 core 文件保存在可执行文件所在的目录下,文件名就为 core。
- 通过修改
/proc/sys/kernel/core_uses_pid
文件可以让生成 core 文件名是否自动加上 pid 号。例如echo 1 > /proc/sys/kernel/core_uses_pid
,生成的 core 文件名将会变成 core.pid,其中 pid 表示该进程的 PID。 - 还可以通过修改
/proc/sys/kernel/core_pattern
来控制生成 core 文件保存的位置以及文件名格式。例如可以用echo "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern
设置生成的 core 文件保存在 “/tmp/corefile” 目录下,文件名格式为 “core-命令名-pid-时间戳”。这里 有更多详细的说明!
使用gdp调试core文件
为了使用gdb调试core文件,首先需要使生成的可执行文件带上调试信息,在使用gcc编译生成可执行文件时,使用-g
参数就可以增加调试信息。
在开启core文件生成后,当程序崩溃时,就会自动生成core文件了,使用命令gdb program core
命令就可以查看core文件。其中program
是可执行文件的名称,而core
则是刚生成的core文件名。
打开core文件后,就可以按照正常的gdb命令来查看core文件的信息了。
其他的一些命令
- 如果电脑没有打开coredump,程序又挂了的时候,可以使用命令
dmesg -T
,说不定会有一些收获。 - 如果在
dmesg
的输出中看到了崩溃时的ip寄存器的信息,可以使用命令addr2line
就能够定位到出问题的代码位置了。 - 调试C++的时候,符号信息不好阅读,可以使用命令
c++filt
将符号信息转为未修饰的函数名。