使用GDB
一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:
1 | $gcc -g -Wall hello.c -o hello |
如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。当你用-g把调试信息加入之后,并成功编译目标代码以后,让我们来看看如何用gdb来调试他。 启动GDB的方法有以下几种: gdb <program>
program也就是你的执行文件,一般在当前目录下。 gdb <program> core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。 gdb <program> <pid>
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。 以上三种都是进入gdb环境和加载被调试程序同时进行的。也可以先进入gdb环境,在加载被调试程序,方法如下:
1 | *在终端输入:gdb |
GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb -help查看。我在下面只例举一些比较常用的参数:
1 | -symbols <file> |
gdb 的命令很多,gdb把之分成许多个种类。help命令只是例出gdb的命令种类,如果要看种类中的命令,可以使用help <class>
命令,如:help breakpoints,查看设置断点的所有命令。也可以直接help <command></command>
来查看命令的帮助。 gdb中,输入命令时,可以不用打全命令,只用打命令的前几个字符就可以了,当然,命令的前几个字符应该要标志着一个唯一的命令,在Linux下,你可以敲击两次TAB键来补齐命令的全称,如果有重复的,那么gdb会把其列出来。
示例一:在进入函数func时,设置一个断点。可以敲入break func,或是直接就是b func
1 | (gdb) b func |
示例二:敲入b按两次TAB键,你会看到所有b打头的命令:
1 | (gdb) b |
示例三:只记得函数的前缀,可以这样:
1 | (gdb) b make_ <按TAB键> |
示例四:调试C++的程序时,有可以函数名一样。如:
1 | (gdb) b 'bubble( M-? |
你可以查看到C++中的所有的重载函数及参数。(注:M-?和“按两次TAB键”是一个意思) 要退出gdb时,只用发quit或命令简称q就行了
GDB中运行UNIX的shell程序
在gdb环境中,你可以执行UNIX的shell的命令,使用gdb的shell命令来完成: shell <command string=""></command>
调用UNIX的shell来执行<command string=""></command>
,环境变量SHELL中定义的UNIX的shell将会被用来执行<command string=""></command>
,如果SHELL没有定义,那就使用UNIX的标准shell:/bin/sh
。(在Windows中使用Command.com或cmd.exe) 还有一个gdb命令是make: make <make-args>
可以在gdb中执行make命令来重新build自己的程序。这个命令等价于shell make <make-args>
。
在GDB中运行程序
当以gdb <program>
方式启动gdb后,gdb会在PATH路径和当前目录中搜索<program>
的源文件。如要确认gdb是否读到源文件,可使用l或list命令,看看gdb是否能列出源代码。 在gdb中,运行程序使用r或是run命令。程序的运行,你有可能需要设置下面四方面的事。 1、程序运行参数。 set args 可指定运行时参数。(如:set args 10 20 30 40 50) show args 命令可以查看设置好的运行参数。 2、运行环境。 `path
可设定程序的运行路径。 show paths 查看程序的运行路径。 set environment varname [=value] 设置环境变量。如:set env USER=hchen show environment [varname] 查看环境变量。 **3、工作目录。**
cd
` 相当于shell的cd命令。 pwd 显示当前的所在目录。 4、程序的输入输出。 info terminal 显示你程序用到的终端的模式。 使用重定向控制程序输出。如:run > outfile tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb
调试已运行的程序
两种方法: 1. 在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb <program> PID
格式挂接正在运行的程序。 2. 先用gdb <program>
关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID。并用detach来取消挂接的进程。
暂停/恢复程序运行
调试程序中,暂停程序运行是必须的,GDB可以方便地暂停程序的运行。你可以设置程序的在哪行停住,在什么条件下停住,在收到什么信号时停往等等。以便于你查看运行时的变量,以及运行时的流程。 当进程被gdb停住时,你可以使用info program
来查看程序的是否在运行,进程号,被暂停的原因。 在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(Watch Point)、捕捉点(Catch Point)、信号(Signals)、线程停止(Thread Stops)。如果要恢复程序运行,可以使用c或是 continue命令。
下面为重要的使用步骤, 只摘抄了部分必要的信息, 如设置断点, 查看栈信息, 其余操作, 可以在wiki.ubuntu查看
设置断点(Break Points) 我们用break命令来设置断点。下面有几点设置断点的方法: break <function>
在进入指定函数时停住。C++中可以使用class::function
或function(type,type)
格式来指定函数名。 break <linenum>
在指定行号停住。 break +offset
break -offset
在当前行号的前面或后面的offset行停住。offiset为自然数。 break filename:linenum
在源文件filename的linenum行处停住。 break filename:function
在源文件filename的function函数的入口处停住。 break *address
在程序运行的内存地址处停住。 break
break命令没有参数时,表示在下一条指令处停住。 break ... if <condition>
…可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环体中,可以设置break if i==100
,表示当i为100时停住程序。 查看断点时,可使用info命令,如下所示:(注:n表示断点号) info breakpoints [n]
info break [n]
维护停止点 上面说了如何设置程序的停止点,GDB中的停止点也就是上述的三类。在GDB中,如果你觉得已定义好的停止点没有用了,你可以使用delete
、clear
、disable
、enable
这几个命令来进行维护。 clear
清除所有的已定义的停止点。 clear <function>
clear <filename:function>
清除所有设置在函数上的停止点。 clear <linenum>
clear <filename:linenum>
清除所有设置在指定行上的停止点。 delete [breakpoints] [range...]
删除指定的断点,breakpoints为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围(如:3-7)。其简写命令为d。</filename:linenum></filename:function>
比删除更好的一种方法是disable停止点,disable了的停止点,GDB不会删除,当你还需要时,enable即可,就好像回收站一样。 disable [breakpoints] [range...]
disable所指定的停止点,breakpoints为停止点号。如果什么都不指定,表示disable所有的停止点。简写命令是dis. enable [breakpoints] [range...]
enable所指定的停止点,breakpoints为停止点号。 enable [breakpoints] once range...
enable所指定的停止点一次,当程序停止后,该停止点马上被GDB自动disable。 enable [breakpoints] delete range...
enable所指定的停止点一次,当程序停止后,该停止点马上被GDB自动删除。
恢复程序运行和单步调试 当程序被停住了,你可以用continue命令恢复程序的运行直到程序结束,或下一个断点到来。也可以使用step或next命令单步跟踪程序。 continue [ignore-count]
c [ignore-count]
fg [ignore-count]
恢复程序运行,直到程序结束,或是下一个断点到来。ignore-count表示忽略其后的断点次数。continue,c,fg三个命令都是一样的意思。 step <count>
单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是,此函数被编译有debug信息。很像VC等工具中的step in。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。 next <count>
同样单步跟踪,如果有函数调用,他不会进入该函数。很像VC等工具中的step over。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。 set step-mode
set step-mode on
打开step-mode模式,于是,在进行单步跟踪时,程序不会因为没有debug信息而不停住。这个参数很有利于查看机器码。 set step-mode off
关闭step-mode模式。 finish
运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息。 until 或 u
当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。 stepi 或 si
nexti 或 ni
单步跟踪一条机器指令!一条程序代码有可能由数条机器指令完成,stepi和nexti可以单步执行机器指令。与之一样有相同功能的命令是“display/i $pc”
,当运行完这个命令后,单步跟踪会在打出程序代码的同时打出机器指令(也就是汇编代码)
查看栈信息 当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。 下面是一些查看函数调用栈信息的GDB命令: backtrace
bt
打印当前的函数调用栈的所有信息。如:
1 | (gdb) bt |
从上可以看出函数的调用栈信息:__libc_start_main --> main() --> func()
backtrace <n>
bt <n>
n是一个正整数,表示只打印栈顶上n层的栈信息。 backtrace <-n>
bt <-n>
-n表一个负整数,表示只打印栈底下n层的栈信息。 如果你要查看某一层的信息,你需要切换当前栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。 frame <n>
f <n>
n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。 up <n>
表示向栈的上面移动n层,可以不打n,表示向上移动一层。 down <n>
表示向栈的下面移动n层,可以不打n,表示向下移动一层。
上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令: select-frame <n>
对应于 frame 命令。 up-silently <n>
对应于 up 命令。 down-silently <n>
对应于 down 命令。 查看当前栈层的信息,你可以用以下GDB命令: frame
或 f
会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。 info frame
info f
这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内存地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。如: bash (gdb) info f Stack level 0, frame at 0xbffff5d4: eip = 0x804845d in func (tst.c:6); saved eip 0x8048524 called by frame at 0xbffff60c source language c. Arglist at 0xbffff5d4, args: n=250 Locals at 0xbffff5d4, Previous frame's sp is 0x0 Saved registers: ebp at 0xbffff5d4, eip at 0xbffff5d8
info args
打印出当前函数的参数名及其值。 info locals
打印出当前函数中所有局部变量及其值。 info catch
打印出当前的函数中的异常处理信息。