GDB(GNU Debugger)是GCC的调试工具。其功能强大,现描述如下: GDB主要帮忙你完成下面四个方面的功能:
1.启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 2.可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式) 3.当程序被停住时,可以检查此时你的程序中所发生的事。 4.动态的改变你程序的执行环境。通过 gcc 的 -g 选项,将调试信息加到可执行文件中。
$ gcc -g hello.c -o hello 1如果使用 Makefile 构建,一般要在 CFLAGS 中指定 -g 选项。
CFLAGS := -Wall -O2 -g 1注意,给 GCC 编译器加上优化选项后,实际的执行顺序可能由于优化而与源代码顺序不同,因此利用调试器跟踪运行时,有时会执行到莫名奇妙的地方,从而造成混乱。
使用命令 gdb 程序名启动。
可以在函数名和行号等上设置断点。程序到达断点就会自动暂停运行。此时可以查看该时刻的变量值,显示栈帧、重新设置断点或重新运行等。断点命令 break 可以简写为 b,命令为 break <断点>。 断点可以通过函数名、当前文件内的行号来设置,也可以先指定文件名再指定行号,还可以指定与暂停位置的偏移量,或者用地址来设置。格式:
格式说明break <函数名>对当前正在执行的文件中的指定函数设置断点break <行号>对当前正在执行的文件中的特定行设置断点break <文件名:行号>对指定文件的指定行设置断点,最常用的设置断点方式break <文件名:函数名>对指定文件的指定函数设置断点break <+/-偏移量>当前指令行+/-偏移量出设置断点break <*地址>指定地址处设置断点设置好的断点可以通过 info break 确认。
使用 run 参数 命令开始运行,其中参数为可执行程序的参数。如果设置了断点,会执行到设置了断点的位置后暂停运行。可以简写为 r。
backtrace 命令可以在遇到断点或异常而暂停执行时显示栈帧,该命令简写为 bt。此外,backtrace 的别名还有 where 和 info stack。
格式说明 bt 显示所有栈帧 bt <N> 只显示开头 N 个栈帧 bt <-N> 只显示最后 N 个栈帧 bt full 不仅显示 backtrace,还要显示局部变量 bt full <N>显示栈帧之后,就可以看出程序在何处停止,以及程序的调用路径。
print 命令可以显示变量,可以简写为 p。 格式:print 变量。
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 开始地址 结束地址。反汇编从开始地址到结束地址之间的部分。单步执行的意思时根据源代码一行一行地执行。执行源代码中一行的命令为 next ,简写为 n。执行时如果遇到函数调用,想执行到函数内部,使用 step 命令,简写为 p。 如果要逐条执行汇编指令,可以分别使用 nexti 和 stepi 命令。
使用 continue 命令继续运行程序,简写为 c。程序会在遇到断点后再次暂停运行。使用 continue <次数> 命令指定忽略断点的次数。
大型软件或大量使用指针的程序中,很难弄清变量在什么地方被改变,要想找到变量在何处被改变,可以使用 watch 命令(监视点,watchpoint)。格式如下:
watch <表达式>。表达式发生变化时暂停运行。awatch <表达式>。表达式被访问、改变时暂停运行。rwatch <表达式>。表达式被访问时暂停运行。需要注意,设置监视点可能会降低运行速度。
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 <内存显示>
硬件断点(hbreak),适用于 ROM 空间等无法修改的内存区域中的程序,在有些架构中无法使用。 临时断点(tbreak)和临时硬件断点(thbreak),在运行到该处时暂停,此时断点会被删除,在只需要停止一次时用起来方便。
格式:set variable <变量=表达式>。例如命令 set variable options = 0,将变量 options 的值改成了 0。
使用 generate-core-file 可将调试中的进程生成内核转储文件。通过内核转储文件和调试对象,查看生成转储文件时的运行历史。 gcore 命令可以从命令行直接生成内核转储文件。在命令行使用 gcore pid,其中 pid 为进程号。该命令无须停止正在运行的程序以获得内核转储文件,当需要在其他机器上单独分析问题原因,或是分析客户现场发生的问题时十分有用。
内核转储(core dump)的最大好处是,它能保存问题发生时的状态。只要有问题发生时程序的可执行文件和内核转储,就可以知道进程当前的状态。
大多数 Linux 发行版默认关闭了内核转储功能,使用 ulimit 命令可以查看当前的内核转储功能是否有效。
$ ulimit -c 0 12-c 选项表示内核转储文件的大小限制,0 表示内核转储无效。开启内核转储执行命令:
$ ulimit -c unlimited or $ ulimit -c 上限 123unlimited 意思是不限制内核转储文件的大小,发生问题时进程的内存就可以全部转储到内核转储文件中。或者设置内核转储文件的上限,在参数中指定上限大小,单位为 Kb。 程序发生异常时会在当前目录下生成内核转储文件。例如程序 a.out 生成转储文件 core,使用以下方式启动 GDB:
$ gdb -c core ./a.out 1使用 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 +往后显示源代码转储保存位置的完整路径可以通过 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 1234此外,还可以在 /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 1kernel.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 后可用)修改 /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 1234core_helper 的内容:
$cat usr/local/sbin/core_helper #!/bin/sh execgzip ->/var/core/$1-$2-$3-$4.core.gz 1234这样,发生内核转储时就会在 /var/core 下生成压缩的内核转储文件。
/etc/profile 文件中可以设置开启所有用户的内核转储功能,默认情况下禁止内核转储:
ulimit -S -c 0 > /dev/null 2>&1 1将其修改为
ulimit -S -c unlimited > /dev/null 2>&1 1接下来要让通过 init 脚本启动的守护进程的内核转储功能有效。在 /etc/sysconfig/init 文件中添加一行命令。
DAEMON_COREFILE_LIMIT='unlimited' 1最后在 /etc/sysctl.conf 中加入以下设置。
fs.suid_dumpable=1 1这个设置使得被 SUID 的程序也能内核转储。重新启动启动,就可以启用整个系统的内核转储。 在我使用的Ubuntu、Debian和移植的嵌入式Linux系统中,没有找到 /etc/sysconfig 文件夹。修改了文件 /etc/security/limits.conf,按照文件的内容提示修改,使 unlimited 永久生效。
多进程程序如果使用庞大的共享内存,内核转储时所有进程的共享内存全部转储,会对磁盘造成巨大的压力,转储过程也会加重系统的负载。由于共享内存的内容是相同的,只需要在某个进程中转储共享内存。 通过 /proc/<PID>/coredump_filter 进行设置。coredump_filter 使用比特掩码表示内存类型。如下所示:
比特掩码内存类型比特 0匿名专用内存比特 1匿名共享内存比特 2file-backed 专用内从比特 3file-backed 共享内存比特 4ELF 文件映射(内核版本 2.6.24 后可用)要跳过所有的共享内存区段,应将值改位 1。
要调试已经启动的进程,或是调试陷入死循环而无法返回控制台的进程时,可以使用 attach 命令。格式:attach <pid>,执行这一命令可以 attach 到进程 ID 为 pid 的进程上。 attach 之后就能使用普通的 gdb 命令。 gdb 和进程分离时使用 detach 命令,调试的进程就从 gdb 的控制下释放出来。进程被 detach 后继续运行。 进程信息可以用 info proc 命令显示。 守护者进程在启动好子进程后,会自动关闭主进程,如果没有设定监控模式的话,gdb 会提示断开与进程的链接。所以必须设定监控对象,设置命令为 set follow-fork-mode child/parent。
break <断点> if <条件>,这条命令将测试给定的条件,如果为帧则暂停运行。 如果断点已经存在,condition <断点编号> <条件> 命令给断点添加触发条件,condition <断点编号> 命令删除指定编号断点的触发条件。
ignore <断点编号> <次数>:在编号指定的断点、监视点或捕获点忽略指定的次数。 continue <次数>: 达到指定次数前,执行到断电时不暂停。 s/stepi/n/nexti <次数>:执行指定次数的相应命令。 finish:执行完当前函数后暂停。 until:执行完当前函数等代码块后暂停,如果是循环,则在执行完循环后暂停,常用于跳出循环。 until <地址>:执行到指定地址停止。
commands 命令可以定义在断点终端后自动执行的命令。格式如下:
(gdb) commands <断点编号> <命令> ... end 1234通过 print 命令显示过的值会记录在内部的值历史中。这些值通过 $ 进行引用,使用 show value 命令可以显示历史中的最后 10 个值。
变量说明$值历史的最后一个值$n值历史的第 n 个值$$值历史的倒数第 2 个值$$n值历史的倒数第 n 个值$_x 命令显示过的最后的地址$__x 命令显示过的最后的地址的值$_eexitcode调试过程中的程序的返回代码$bpnum最后设置的断点编号还可以随意定义变量,变量以 $ 开头,由英文字母和数字组成。例如:
(gdb) set $i=0 (gdb) p $i $i = 0 123show 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。
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 123456除了初始化文件,还可以把设置写在文件中,在运行 GDB 时读取这些文件。命令为 source <文件名>,例如:
(gdb) source gdbcalc (gdb) p $log10(10000.0) $1 = 4 123其中 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 12345678910111213141516171819202122232425262728GDB 常用命令及缩略形式如下表:
命令简写形式说明backtracebt、where显示 backtracebreak设置断点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执行下一行