基本使用

启动GDB

如果要对程序进行调试,那么在编译前需要首先加上-g选项。

使用命令gdb <program>开始调试一个程序。

如果一个程序需要有参数才能执行,那么有两个方法指定参数。

  1. 在gdb启动程序后,通过run命令指定参数信息,比如run <parm1> <parm2> ...
  2. 在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 *[email protected]可以打印a长度的数据,或者只跟@,那么会把所有的内容都打出来。
    • 使用$打印上一个变量,假如有一个链表,有next成员变量,那么使用p *linkNode之后,可以使用p *$.next不间断的打印链表的下一个节点的信息。
    • 如果打印一个数组的成员,可以通过这种方式连续的打印。set $index=0p 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文件,关于此文件的设置参看这里 。增加一行:
1
2
# /etc/security/limits.conf##Each line describes a limit for a user in the form:##<domain>   <type>   <item>   <value>
    *          soft     core   unlimited
  • 修改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文件的信息了。

其他的一些命令

  1. 如果电脑没有打开coredump,程序又挂了的时候,可以使用命令dmesg -T,说不定会有一些收获。
  2. 如果在dmesg的输出中看到了崩溃时的ip寄存器的信息,可以使用命令addr2line就能够定位到出问题的代码位置了。
  3. 调试C++的时候,符号信息不好阅读,可以使用命令c++filt将符号信息转为未修饰的函数名。

参考链接

  1. gdb调试利器
  2. GDB调试入门指南
  3. Linux Core Dump
  4. C语言中文网—GDB
  5. set disassembly-flavor command
  6. Beej的GDB快速指南