Linux环境下的GDB调试方法

1、GDB简介

  GDB(GNU Debugger)是GCC的调试工具。其功能强大,现描述如下:
  
  GDB主要帮忙你完成下面四个方面的功能:

  • 1.启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
  • 2.可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
  • 3.当程序被停住时,可以检查此时你的程序中所发生的事。
  • 4.动态的改变你程序的执行环境。

2、GDB基本调试命令

2.1 准备

  通过 gcc 的 -g 选项,将调试信息加到可执行文件中。

$ gcc -g hello.c -o hello

  如果使用 Makefile 构建,一般要在 CFLAGS 中指定 -g 选项。

CFLAGS := -Wall -O2 -g

  注意,给 GCC 编译器加上优化选项后,实际的执行顺序可能由于优化而与源代码顺序不同,因此利用调试器跟踪运行时,有时会执行到莫名奇妙的地方,从而造成混乱。

2.2 启动

  使用命令 gdb 程序名启动。

2.3 设置断点

  可以在函数名和行号等上设置断点。程序到达断点就会自动暂停运行。此时可以查看该时刻的变量值,显示栈帧、重新设置断点或重新运行等。断点命令 break 可以简写为 b,命令为 break <断点>
  
  断点可以通过函数名、当前文件内的行号来设置,也可以先指定文件名再指定行号,还可以指定与暂停位置的偏移量,或者用地址来设置。格式:

 

格式说明
break <函数名>对当前正在执行的文件中的指定函数设置断点
break <行号>对当前正在执行的文件中的特定行设置断点
break <文件名:行号>对指定文件的指定行设置断点,最常用的设置断点方式
break <文件名:函数名>对指定文件的指定函数设置断点
break <+/-偏移量>当前指令行+/-偏移量出设置断点
break <*地址>指定地址处设置断点


  
  设置好的断点可以通过 info break 确认。

2.4 运行

  使用 run 参数 命令开始运行,其中参数为可执行程序的参数。如果设置了断点,会执行到设置了断点的位置后暂停运行。可以简写为 r

2.5 显示栈帧

  backtrace 命令可以在遇到断点或异常而暂停执行时显示栈帧,该命令简写为 bt。此外,backtrace 的别名还有 whereinfo stack

 

格式说明

bt

显示所有栈帧

bt <N>

只显示开头 N 个栈帧

bt <-N>

只显示最后 N 个栈帧

bt full

不仅显示 backtrace,还要显示局部变量

bt full <N>


  
  显示栈帧之后,就可以看出程序在何处停止,以及程序的调用路径。

2.6 显示变量

  print 命令可以显示变量,可以简写为 p
  
  格式:print 变量

2.7 显示寄存器

  info registers 可以显示寄存器,简写为 info reg
  
  在寄存器名之前添加 $,显示寄存器的内容,例如 p $eax
  p/格式 $寄存器 可以指定寄存器的显示格式,例如 p/c $eax。可使用的格式如下:

 

格式说明
x显示为十六进制数
d显示为十进制数
u显示为无符号十进制数
o显示为八进制数
t显示为二进制数
a地址
c显示为字符(ASCII)
f浮点小数
s显示为字符串
i显示为机器语言,仅在显示内存的 x 命令中可用


  
  程序指针可以写为 $pc,也可以写为 $eip,使用 p $pc 显示程序指针内容。程序指针指向当前程序的运行点的地址。
  
  x 命令可以显示内存的内容,格式:x/<格式> <地址>。例如 x/i $ps,显示汇编指令。
  
  一般使用 x 命令时,格式为 x/<NFU> <ADDR>。此处 ADDR 为希望显示的地址,N 为重复次数,F 为前面的显示格式,U 代表的单位如下:

 

单位说明
b字节
h半字(2 字节)
w字(4 字节)(默认值)
g双字(8 字节)


  例如命令 x\10i $pc 显示从 pc 所指地址开始的 10 条指令。
  
  反汇编命令 disassemble,简写为 disas。格式:

  • disassemble。反汇编当前整个函数。
  • disassemble 程序计数器。反汇编程序计数器所在函数的整个函数。
  • disassemble 开始地址 结束地址。反汇编从开始地址到结束地址之间的部分。

2.8 单步执行

  单步执行的意思时根据源代码一行一行地执行。执行源代码中一行的命令为 next ,简写为 n。执行时如果遇到函数调用,想执行到函数内部,使用 step 命令,简写为 p
  如果要逐条执行汇编指令,可以分别使用 nexti 和 stepi 命令。

2.9 继续运行

  使用 continue 命令继续运行程序,简写为 c。程序会在遇到断点后再次暂停运行。使用 continue <次数> 命令指定忽略断点的次数。

2.10 监视点

  大型软件或大量使用指针的程序中,很难弄清变量在什么地方被改变,要想找到变量在何处被改变,可以使用 watch 命令(监视点,watchpoint)。格式如下:

  • watch <表达式>。表达式发生变化时暂停运行。
  • awatch <表达式>。表达式被访问、改变时暂停运行。
  • rwatch <表达式>。表达式被访问时暂停运行。

  需要注意,设置监视点可能会降低运行速度。

2.11 删除断点和监视点

  delete 命令删除断点和监视点,简写为 d。格式为 delete <断点编号>,表示删除编号指示的断点或监视点,编号可以用命令 info b 查看。
  clear 命令删除已定义的断点。可用命令包括:
    clear <函数名>
    clear <行号>
    clear <文件名:行号>
    clear <文件名:函数名>
  
  disable 命令禁用断点。命令格式如下:
    disable:禁用所有断点。
    disable <断点编号>:禁用指定断点。
    disable display <显示编号>:禁用 display 命令定义的自动显示。
    disable mem <内存显示>:禁用 mem 命令定义的内存区域。
  
  enable 命令用于启用断点。命令格式如下:
    enable
    enable <断点编号>
    enable once <断点编号>:使指定的断点只启用一次。
    enable delete <断点编号>
    enable display <显示编号>
    enable mem <内存显示>

2.12 其他断点

  硬件断点(hbreak),适用于 ROM 空间等无法修改的内存区域中的程序,在有些架构中无法使用。
  
  临时断点(tbreak)和临时硬件断点(thbreak),在运行到该处时暂停,此时断点会被删除,在只需要停止一次时用起来方便。

2.13 改变变量的值

  格式:set variable <变量=表达式>。例如命令 set variable options = 0,将变量 options 的值改成了 0。

2.14 生成内核转储文件

  使用 generate-core-file 可将调试中的进程生成内核转储文件。通过内核转储文件和调试对象,查看生成转储文件时的运行历史。
  
  gcore 命令可以从命令行直接生成内核转储文件。在命令行使用 gcore pid,其中 pid 为进程号。该命令无须停止正在运行的程序以获得内核转储文件,当需要在其他机器上单独分析问题原因,或是分析客户现场发生的问题时十分有用。


3、内核转储

  内核转储(core dump)的最大好处是,它能保存问题发生时的状态。只要有问题发生时程序的可执行文件和内核转储,就可以知道进程当前的状态。

3.1 启用内核转储

  大多数 Linux 发行版默认关闭了内核转储功能,使用 ulimit 命令可以查看当前的内核转储功能是否有效。

$ ulimit -c
0

  -c 选项表示内核转储文件的大小限制,0 表示内核转储无效。开启内核转储执行命令:

$ ulimit -c unlimited
or
$ ulimit -c 上限

  unlimited 意思是不限制内核转储文件的大小,发生问题时进程的内存就可以全部转储到内核转储文件中。或者设置内核转储文件的上限,在参数中指定上限大小,单位为 Kb。
  
  程序发生异常时会在当前目录下生成内核转储文件。例如程序 a.out 生成转储文件 core,使用以下方式启动 GDB:

$ gdb -c core ./a.out

  
  使用 GDB 的 list 命令可以查看附近的源代码。命令使用方法

 

格式说明
list <linenum>显示程序第 linenum 行周围的源代码
list <function>显示函数名为 function 的函数的源代码
list显示当前行后面的源代码
list -显示当前行前面的源代码
set listsize <count>设置一次显示源代码的行数
show listsize查看当前 listsize 的设置
list <first>,<last>显示从 first 行到 last 行之间的源代码
list ,<last>显示从当前行到 last 行之间的源代码
list +往后显示源代码

 

3.2 在专用目录中生成内核转储

  转储保存位置的完整路径可以通过 sysctl 变量 kernel.core_pattern 设置。在文件 /etc/sysctl.conf 中设置如下:

$ cat /etc/sysctl.conf
kernel.core_pattern = /var/core/%t-%e-%p-%c.core
kernel.core_uses_pid = 0
$ sysctl -p

  
  此外,还可以在 /proc/sys/kernel 下修改设置。
  /proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 pid 作为扩展 ,如果添加则文件内容为 1 ,否则为 0。
  proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名 ,可以这样修改 :

$ echo "/corefile/core-%e-%p-%t" > core_pattern

  
  kernel.core_pattern 中可以设置的格式符如下:

 

格式符说明
%%% 字符本身
%p被转储进程的进程 ID(PID)
%u被转储进程的真实用户 ID(real UID)
%g被转储进程的真实组 ID(real GID)
%s引发转储的信号编号
%t转储时刻(从 1970/1/1 0:00 开始的秒数)
%h主机名(同 uname(2) 返回的 nodename)
%e可执行文件名
%c转储文件的大小上限(内核版本 2.6.24 后可用)

 

3.3 使用用户模式辅助程序自动压缩内核转储文件

  修改 /etc/sysctl.conf中 的 kernel.core_pattern 变量来设置。

 $ cat /etc/sysctl.conf
kernel.core_pattern= | usr/local/sbin/core_helper %t %e %p %c
kernel.core_uses_pid= 0
 $ sysctl -p

  core_helper 的内容:

$cat usr/local/sbin/core_helper
#!/bin/sh

execgzip ->/var/core/$1-$2-$3-$4.core.gz

  这样,发生内核转储时就会在 /var/core 下生成压缩的内核转储文件。

3.4 启用整个系统的内核转储功能

  /etc/profile 文件中可以设置开启所有用户的内核转储功能,默认情况下禁止内核转储:

ulimit -S -c 0 > /dev/null 2>&1

  将其修改为

ulimit -S -c unlimited > /dev/null 2>&1

  
  接下来要让通过 init 脚本启动的守护进程的内核转储功能有效。在 /etc/sysconfig/init 文件中添加一行命令。

DAEMON_COREFILE_LIMIT='unlimited'

  
  最后在 /etc/sysctl.conf 中加入以下设置。

fs.suid_dumpable=1

  这个设置使得被 SUID 的程序也能内核转储。重新启动启动,就可以启用整个系统的内核转储。
  
  在我使用的Ubuntu、Debian和移植的嵌入式Linux系统中,没有找到 /etc/sysconfig 文件夹。修改了文件 /etc/security/limits.conf,按照文件的内容提示修改,使 unlimited 永久生效。

3.4 利用内核转储掩码排除共享内存

  多进程程序如果使用庞大的共享内存,内核转储时所有进程的共享内存全部转储,会对磁盘造成巨大的压力,转储过程也会加重系统的负载。由于共享内存的内容是相同的,只需要在某个进程中转储共享内存。
  
  通过 /proc/<PID>/coredump_filter 进行设置。coredump_filter 使用比特掩码表示内存类型。如下所示:

 

比特掩码内存类型
比特 0匿名专用内存
比特 1匿名共享内存
比特 2file-backed 专用内从
比特 3file-backed 共享内存
比特 4ELF 文件映射(内核版本 2.6.24 后可用)


  要跳过所有的共享内存区段,应将值改位 1。


4、GDB调试技巧

4.1 attach 到进程

  要调试已经启动的进程,或是调试陷入死循环而无法返回控制台的进程时,可以使用 attach 命令。格式:attach <pid>,执行这一命令可以 attach 到进程 ID 为 pid 的进程上。
  attach 之后就能使用普通的 gdb 命令。
  
  gdb 和进程分离时使用 detach 命令,调试的进程就从 gdb 的控制下释放出来。进程被 detach 后继续运行。
  
  进程信息可以用 info proc 命令显示。
  守护者进程在启动好子进程后,会自动关闭主进程,如果没有设定监控模式的话,gdb 会提示断开与进程的链接。所以必须设定监控对象,设置命令为 set follow-fork-mode child/parent

4.2 条件断点

  break <断点> if <条件>,这条命令将测试给定的条件,如果为帧则暂停运行。
  
  如果断点已经存在,condition <断点编号> <条件> 命令给断点添加触发条件,condition <断点编号> 命令删除指定编号断点的触发条件。

4.3 反复执行

  ignore <断点编号> <次数>:在编号指定的断点、监视点或捕获点忽略指定的次数。
  continue <次数>: 达到指定次数前,执行到断电时不暂停。
  s/stepi/n/nexti <次数>:执行指定次数的相应命令。
  finish:执行完当前函数后暂停。
  until:执行完当前函数等代码块后暂停,如果是循环,则在执行完循环后暂停,常用于跳出循环。
  until <地址>:执行到指定地址停止。

4.4 断点命令

  commands 命令可以定义在断点终端后自动执行的命令。格式如下:

(gdb) commands <断点编号>
<命令>
...
end

4.5 值的历史

  通过 print 命令显示过的值会记录在内部的值历史中。这些值通过 $ 进行引用,使用 show value 命令可以显示历史中的最后 10 个值。

 

变量说明
$值历史的最后一个值
$n值历史的第 n 个值
$$值历史的倒数第 2 个值
$$n值历史的倒数第 n 个值
$_x 命令显示过的最后的地址
$__x 命令显示过的最后的地址的值
$_eexitcode调试过程中的程序的返回代码
$bpnum最后设置的断点编号


  
  还可以随意定义变量,变量以 $ 开头,由英文字母和数字组成。例如:

(gdb) set $i=0
(gdb) p $i
$i = 0

4.6 命令历史

  show history 将命令历史保存到文件中,默认命令历史文件位于 ./.gdb_history。
  

  set history expansion
  show history expansion

  可以使用 csh 风格的 ! 字符。
  

  set history filename <文件名>
  show history filename

  将命令历史保存到文件中。可以通过环境变量 GDBHISTFILE 改变默认文件名。
  

  set history save
  show history save

  启用命令历史保存到文件和恢复的功能。
  

  set history size <数字>
  show history size

  可设置保存到命令历史中的命令数量。默认值为 256。

4.7 初始化文件(.gdbinit)

  Linux 环境下的初始化文件为 .gdbinit。如果存在 .gdbinit 文件,GDB 会在启动之前将其作为命令文件运行。初始化文件和命令文件的运行顺序如下。

  • 1、$HOME/.gdbinit。
  • 2、运行命令行选项。
  • 3、./.gdbinit。
  • 4、通过 -x 选项给出的命令文件。

  初始化文件和命令文件的语法相同。利用 define 命令可以自定义命令,document 命令给自定义命令添加说明,GDB 运行时使用 help <命令名> 可以查看定义的命令。示例如下:

define li
    x/10i $pc
end
document li
    list machine instruction
end

  
  除了初始化文件,还可以把设置写在文件中,在运行 GDB 时读取这些文件。命令为 source <文件名>,例如:

(gdb) source gdbcalc
(gdb) p $log10(10000.0)
$1 = 4

  其中 gdbcalc 文件内容如下:

#!/usr/bin/gdb -x
file /usr/bin/gdb
start
set $e = 2.7182818284590452354
set $pi = 3.14159265358979323846
set $fabs = (double (*)(double)) fabs
set $sqrt = (double (*)(double)) sqrt
set $cbrt = (double (*)(double)) cbrt
set $exp = (double (*)(double)) exp
set $exp2 = (double (*)(double)) exp2
set $exp10 = (double (*)(double)) exp10
set $log = (double (*)(double)) log
set $log2 = (double (*)(double)) log2
set $log10 = (double (*)(double)) log10
set $pow = (double (*)(double, double)) pow
set $sin = (double (*)(double)) sin
set $cos = (double (*)(double)) cos
set $tan = (double (*)(double)) tan
set $asin = (double (*)(double)) asin
set $acos = (double (*)(double)) acos
set $atan = (double (*)(double)) atan
set $atan2 = (double (*)(double, double)) atan
set $sinh = (double (*)(double)) sinh
set $cosh = (double (*)(double)) cosh
set $tanh = (double (*)(double)) tanh
set $asinh = (double (*)(double)) asinh
set $acosh = (double (*)(double)) acosh
set $atanh = (double (*)(double)) atanh

5、总结

  GDB 常用命令及缩略形式如下表:

 

命令简写形式说明
backtracebt、where显示 backtrace
break 设置断点
continuec、cont继续运行
deleted删除断点
finish 运行到函数结束
info breakpoints 显示断点信息
nextn执行下一行
printp显示表达式
runr运行程序
steps一次执行一行,包括函数内部
x 显示内存内容
untilu执行到指定行
directorydir插入目录
disabledis禁用断点
downdo在当前调用的栈帧中选择要显示的栈帧
edite编辑文件或函数
framef选择要显示的栈帧
forward-searchfo向前搜索
generate-core-filegcore生成内核转储
helph显示帮助一览
infoi显示信息
listl显示函数或行
nextini执行下一行(以汇编代码为单位)
print-objectpo显示目标信息
sharedlibraryshare加载共享库的符号
setpisi执行下一行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值