用 gdb 调试 C 程序
linux包含了一个 GNU 调试程序 gdb。 gdb 是一个用来调试 c 程序的调试器。gdb的格式为:gdb -d
按两次Esc键 或 长按Esc键 可以显示所有gdb命令。按回车键重复上一次的命令。下面介绍一些常用的命令:
help(h)
查看topic 的帮助信息。 如 help print , help break 。
run(r) args
直接设置参数。
set args argv1 argv2 ...
在run前设置运行使参数。
show paths
显示当前路径变量的设置情况。
show environment [VARNAME]
显示某个环境变量的值。如果你不指明变量名,则gdb会显示所有的变量名和它们的
内容。environment可以被缩写成 env
shell
在gdb环境中执行shell命令。
info bre
键入info bre后按TAB键, gdb能为你完成剩下的输入察看相关命令的信息。如 break watch display 。
与断点相关的命令:
awatch、 break、 clear 、commands、 condition 、delete 、disable 、enable、 ignore、 rwatch 、tbreak、 watch
watch EXPR
这个命令使用EXPR作为表达式设置一个观察点。GDB将把表达式加入到程序中并监视程序的运行,当表达式的值被改变时GDB就使程序停止。
rwatch EXPR
设置一个观察点,当EXPR被程序读时,程序被暂停。
awatch EXPR
设置一个观察点,当EXPR被读出然后被写入时程序被暂停。这个命令和'awatch'命令合用。
info watchpoints
显示所设置的观察点的列表,和'info break'命令相似。
*注意*
在多线程的程序中,观察点的作用很有限,GDB只能观察在一个线程中的表达式的值如果你确信表达式只被当前线程所存取,那么使用观察点才有效。GDB 不能注意一个非当前线程对表达式值的改变。
break FUNCTION
此命令用来在某个函数上设置断点。当你使用允许函数重载的语言比如C++时,有可能同时在几个重载的函数上设置了断点。
break +OFFSET
break -OFFSET
在当前程序运行到的前几行或后几行设置断点。OFFSET为行号。
break LINENUM
在行号为LINENUM的行上设置断点。程序在运行到此行之前停止。
break FILENAME:LINENUM
在文件名为FILENAME的原文件的第LINENUM行设置断点。
break FILENAME:FUNCTION
在文件名为FILENAME的原文件的名为FUNCTION的函数上设置断点。
当你的多个文件中可能含有相同的函数名时必须给出文件名。
break *ADDRESS
在地址ADDRESS上设置断点,这个命令允许你在没有调试信息的程序中设置断点。
break
当break命令不包含任何参数时,break命令在当前执行到的程序运行栈中的下一条指令上设置一个断点。除了栈底以外,这个命令使程序在一旦从当前函数返回时停止。相似的命令是finish,但finish 并不设置断点。这一点在循环语句中很有用。 gdb在恢复执行时,至少执行一条指令。
break ... if COND
这个命令设置一个条件断点,条件由COND指定;在gdb每次执行到此 断点时COND都被计算当COND的值为非零时,程序在断点处停止。这意味着 COND的值为真时程序停止。break 可以简写为 b。
tbreak ARGS
临时设置断点,设置断点为只有效一次。ARGS的使用同break中的参量的使用。
使用clear命令你可以删除指定位置的断点。使用delete命令你可以使用断点号来指定要删去的断点或观察点。 在删除断点时不需要先运行过它,GDB会忽略你刚才删去的断点。
所以你可以继续运行你的程序而不必管断点。
clear
在当前选择的栈帧上清除下一个所要执行到的断点(指令级)。当你当前选择帧是栈中最内层时使用这个命令可以很方便的删去刚才程序停止处的断点。
clear FUNCTION
clear FILENAME:FUNCTION
删除名为FUNCITON的函数上的断点。
clear LINENUM
clear FILENAME:LINENUM
删除以LINENUM为行号上的断点。
delete [breakpoints] [BNUMS...]
删除参数所指定的断点,如果没有指定参数则删去程序中所有的断点。这个命令可以缩写成为d 。
如果你想让断点一时失去作用以方便调试的话,你可以先使断点不起作用(disable)。 你使用enable命令来激活断点或是观察点,使用disable命令来使断点或观察点 不起作用。使用 info break 或 info watch 来查看那些断点是活跃的。 断点或观察点有四种状态:
l 使能。
当程序运行到断点处时,程序停止。使用'break'命令设置的断点一开始缺省使能的。
l 不使能。
断点对你程序的运行没有什么影响。
l 使能一次后变为不使能。
断点对你的程序运行只有一次影响,然后就自动变成不使能状态。使用'tbreak'设置的断点一开始缺省是这个状态。
l 使能一次自动删除。
断点在起了一次作用后自动被删除。
disable [breakpoints] [BNUMS...]
使由参数指定的断点或观察点变为不使能,如果没有参数的话缺省使所有断点和观察点变为不使能。当一个断点或观察点被不使能后在被不使能前的状态被记录下来,在断点或观察点再次被激活时,原来的状态得到继续。比如一个条件断点或一个设置了'ignore-counts'的断点在被使不能后记录活跃时断点被执行的次数,在不使能状态下,断点的执行次数(ignore-counts)不增加,直到断点再次被激活时,再继续计算条件(ignore-counts)。你可以使用'disable'命令的缩写'dis' 。
enable [breakpoints] [BNUMS...]
使能由参数指定的断点或全部断点。
enable [breakpoints] once BNUMS...
功能同上条命令,只是这条命令使断点只使能一次。
enable [breakpoints] delete BNUMS...
功能同上条命令,只是这条命令使被使能的断点起作用一次然后自动被删除。
注意:
除了使用'tbreak'命令所设置的断点以外,断点被设置时都是使能的。
断点条件 condition
最简单的断点就是当你的程序每次执行到的时候就简单将程序挂起。你也可以为断点设置“条件”。条件只是你所使用的编程语言的一个布尔表达式,带有条件表达式的断点 在每次执行时判断计算表达式的值,当表达式值为真时才挂起程序。这是使用“断言”的一中形式,在这种形式中你只有在断言为真时才挂起程序。如果在C语言中你要使断言为假时挂起程序则使用:“!表达式”。条件表达式对观察点也同样有效,但你并不需要它,因为观察点本身就计算一个表达式但它也许会简单一些。比如只在一个变量名上设置观察点然后设置一个条件来测试新的赋值。 断点条件可能有副作用(side effects)会影响程序的运行。这一点有时也是很有用的比如来激活一个显示程序完成情况的的函数,或使用你自己的打印函数来格式化特殊的数据结构。当在同一位置没有另一个断点设置时,结果是可预见的。断点条件可以在设置断点的同时被设置。使用'if'命令作为'break'命令的参数。断点条件也可以在任何时候使用'condition'命令来设置。'watch'命令不能以'if'作为参数。所以使用'condition'命令是在观察点上设置条件的唯一方法。
condition BNUM EXPRESSION
把'EXPRESSIN'作为断点条件。断点用'BNUM'来指定。在你为BNUM号断点设置了条件后,只有在条件为真时程序才被暂停。当你使用'condition'命令GDB马上同步的检查 'EXPRESSION'的值判断表达式中的符号在断点处是否有效,但GDB并不真正计算表达式 的值。
condition BNUM
删除在'BNUM'号断点处的条件。使之成为一个普通断点。
一个条件断点的特殊例子是使一个程序在执行了某句语句若干次后停止。由于这个功能非常常用,你可以使用一个命令来直接设置它那就是'ignore count'。每个断点都有'ignore count',缺省是零。如果'ignore count'是正的那么你的程序在运行过断点处'count'次后被暂停。
ignore BNUM COUNT
设置第BNUM号断点的'ignore count'为'COUNT'。 如果要让断点在下次执行到时就暂停程序,那么把'COUNT'设为0. 当你使用'continue'命令来继续你程序的执行时,你可以直接把'ignore count' 作为'continue'的参数使用。你只要直接在'continue'命令后直接跟要"ignore"的 次数就行。如果一个断点同时有一个ignore count和一个条件时,条件不被检查。只有当 'ignore count'为零时GDB才开始检查条件的真假。 另外你可以用'condition'命令来获得与用‘ignore count'同样效果的断点。是用类似于'$foo--<=0'的参量作为'condition'命令的参数(使用一个不停减量的变量作为条件表达式的成员)。
commands :断点命令列表
你可以为任一个断点或观察点指定一系列命令,当你程序执行到断点时,GDB自动执行
这些命令。例如:你可以打印一些表达式的值,或使能其他的断点。
commands [BNUM]
... COMMAND-LIST ...
end
为断点号为BNUM的断点设置一个命令列表。这些命令在'...COMMAND-LIST...'中列出,使用'end'命令来表示列表的结束。要删除断点上设置的命令序列,你只需在command命令后直接跟end命令就可以。当不指定BNUM时,GDB缺省为最近遇到的断点或是观察点设置命令列表。使用回车来表示重复使用命令的特性在'...command list...'中不能使用。你可以使用命令列表中的命令来再次使你的程序进入运行状态。简单的在命令列表中使用'continue'命令,或'step'命令。在使程序恢复执行的命令后的命令都被忽略。这是因为一旦你的程序重新运行就可能遇到新的命令列表,那么就应该执行新的命令。防止了二义。如果你在命令列表中使用了'silent'命令,那么你程序在断点处停止的信息将不被显示。这对于用一个断点然后显示一些信息,接着再继续执行很有用。但'silent'命令只有在命令列表的开头有效。命令'echo','output'和'printf'允许你精确的控制显示信息,这些命令在"silent" 断点中很有用。
例如:这个例子演示了使用断点命令列表来打印'x'的值.
break foo if x>0
commands
silent
printf "x is %d\n",x
cont
end
断点命令列表的一个应用是在遇到一个buf之后改正数据然后继续调试的过程。使用命令来修改含有错误值的变量,然后使用'continue'命令继续程序的运行。使用'silent'命令屏蔽输出:
break 403
commands
silent
set x = y + 4
cont
end
关于程序运行命令 : continue 、handle、 jump、 kill 、next 、step
continue(cont c) num
使程序在断点后继续运行。参数num表示在以后的执行过程中忽略断点num-1次。缺省num为1。
handle
对信号设置处理函数。
jump
指定程序开始调试的指令或地址。
kill
结束当前程序的调试。
next (n) [num]
继续程序运行,越过子程序调用。 num给出这种操作的次数。
step (s)
用来执行一条语句,不越过子程序调用。
关于堆栈的命令 : backtrace、frame、 select-frame
backtrace (bt) num(要打印的栈帧指针的个数)
用来打印栈帧(stack frame)指针的
frame num
打印某一stack frame,同时选定该frame
info frame 可以显示该frame 的详细信息。
select-frame
选定某一frame
与显示数据相关的命令: display 、info display 、delete display 、disable display 、enable display 、undisplay、whatis 、print 、ptype 、set
display x+y
用来显示一些表达式的值。每当程序运行到断点处都会显示该表达式的值。
info display
用来显示当前所有的要显示值的表达式的相关情况。
delete display [num]
用来删除一个已经设置为display的表达式。num 为表达式编号。如果num不填,则删除所有表达式。
disable display [num]
使某一个要显示值的表达式暂时无效。如果num不填,则是所有表达式暂时无效。
enable display [num]
使某个表达式恢复显示。如果num不填,则使所有表达式恢复显示。
undisplay [num]
同 delete display [num]
print (p) expr
打印某个表达式的值。 p x+y
还可以给变量赋值。 p x=8
还可以打印连续内存空间。 p x@3
print 'find.c' :: x 打印文件find.c中的变量x
print &blat :: idx 打印函数blat中的变量idx
print /x m 16进制
ptype
ptype出类型定义。
whatis expr
显示expr的数据类型。
set
set 可以给变量赋值。 set variable var=表达式。
set prompt $$$ 将 gdb 提示符变为 $$$
关于文件的命令: cd、 directory 、file、 forward 、list、 path 、pwd、 reverse-search、 search
cd
改变当前工作目录。退出gdb后,linux会返回到进入gdb前的目录中。
pwd
显示当前工作目录。
directory 目录
用来向源文件搜索路径添加一个目录。
path 目录
用来向目标文件的搜索路径中增加目录。
file 文件名
file用来装入待调试程序。
forward 字符串
从当前行向下查找第一个匹配某个字符串的程序行。 同 search 。
reverse-research 字符串
从当前行向上查找第一个匹配某个字符串的程序行。
list [args]
显示文件内容。
args 可以是如下:
linenum -- 当前文件的 linenum 行。
file:linenum -- file的linenum 行。
function -- 当前文件的 function 函数。
file:function -- file 的 function 函数。
下面讲讲有关多线程的调试。
gdb提供了以下供调试多线程的进程的功能:
l 自动通告新线程。
l thread THREADNO,一个用来在线程之间切换的命令。
l info threads,一个用来查询现存线程的命令。
l thread apply [THREADNO] [ALL] ARGS,一个用来向线程提供命令的命令。
l 线程有关的断点设置。
注意:
这些特性不是在所有gdb版本都能使用,归根结底要看操作系统是否支持。
(gdb) info threads
(gdb) thread 1
Thread ID 1 not known. Use the "info threads" command to
see the IDs of currently known threads.
gdb的线程级调试功能允许你观察你程序运行中所有的线程,但无论什么时候,gdb控制总有一个“当前”线程。调试命令对“当前”进程起作用。一旦gdb发现了你程序中的一个新的线程,它会自动显示有关此线程的系统信息。比如:[New process 35 thread 27] 不过格式和操作系统有关。为了调试的目的,gdb自己设置线程号。
info threads
显示进程中所有的线程的概要信息。gdb按顺序显示:
1.线程号(gdb设置)
2.目标系统的线程标识。
3.此线程的当前堆栈。
例如: (前面打'*'的线程表示是当前线程)
(gdb) info threads
3 process 35 thread 27 0x34e5 in sigpause ()
2 process 35 thread 23 0x34e5 in sigpause ()
* 1 process 35 thread 13 main (argc=1, argv=0x7ffffff8)
at threadtest.c:68
thread THREADNO
把线程号为THREADNO的线程设为当前线程。命令行参数THREADNO是gdb内定的线程号。你可以用info threads命令来查看gdb内设置的线程号。gdb显示该线程的系统定义的标识号和线程对应的堆栈。
比如: (前面打'*'的线程表示是当前线程)
(gdb) thread 2
[Switching to process 35 thread 23]
0x34e5 in sigpause ()
"Switching后的内容取决于你的操作系统对线程标识的定义。
thread apply [THREADNO] [ALL] ARGS
此命令让你对一个以上的线程发出相同的命令"ARGS",[THREADNO]的含义同上。如果你要向你进程中的所有的线程发出命令使用[ALL]选项。无论gdb何时中断了你的程序(因为一个断点或是一个信号),它自动选择信号或断点发生的线程为当前线程。gdb将用一个格式为[Switching to SYSTAG]的消息来向你报告。
调试多进程的程序
=================
gdb对调试使用'fork'系统调用产生新进程的程序没有很多支持。当一个程序开始一个新进程时,gdb将继续对父进程进行调试,子进程将不受影响的运行。如果你在子 进程可能会执行到的地方设了断点,那么子进程将收到'SIGTRAP'信号,如果子进程没 有对这个信号进行处理的话那么缺省的处理就是使子进程终止。然而,如果你要一定要调试子进程的话,这儿有一个不是很麻烦的折衷的办法。在子进程被运行起来的开头几句语句前加上一个'sleep'命令。这在调试过程中并不会引起程序中很大的麻烦(不过你要自己注意例外的情况)。然后再使用'ps'命令列出新开的子进程号,最后使用'attach'命令。这样就没有问题了。关于这一段,本人觉得实际使用上并不全是这样。我在调试程中就试过,好象不一定能起作用,要看gdb的版本和你所使用的操作系统了。
停止和继续
==============
调试器的基本功能就是让你能够在程序运行时在终止之前在某些条件下停止下来,然后再继续运行,这样的话你就可以检查当你的程序出错时你的程序究竟做了些什么。在gdb内部,你的程序会由于各种原因而暂时停止,比如一个信号,一个断点,或是由于你用了'step'命令。在程序停止的时候你就可以检查和改变变量的值,设置或去掉断点,然后继续你程序的运行。一般当程序停下来时gdb都会显示一些有关程序状态的信息。比如象程序停止的原因,堆栈等等。如果你要了解更详细的信息,你可以使用'info program'命令。另外,在任何时候你输入这条命令,gdb都会显示当前程序运行的状态信息。
info program
显示有关你程序状态的信息:你的程序是在运行还是停止,是什么进程,为什么停止。
设置断点补充
hbreak ARGS
设置一个由硬件支持的断点。ARGS同'break'命令,设置方法也和 'break'相同。但这种断点需要由硬件支持,所以不是所有的系统上这个命令都有效。这个命令的主要目的是用于对EPROM/ROM程序的调试。因为这条命令可以在不改变代码的情况下设置断点。这可以同SPARCLite DSU 一起使用。当程序访问某些变量和代码时,DSU将设置“陷井”。注意:
你只能一次使用一个断点,在新设置断点时,先删除原断点。
thbreak ARGS
设置只有一次作用的硬件支持断点。ARGS用法同'hbreak'命令。这个命令和'tbreak'命令相似,它所设置的断点只起一次作用,然后就被自动的删除。这个命令所设置的断点需要有硬件支持。
rbreak REGEX
在所有满足表达式REGEX的函数上设置断点。这个命令在所有相匹配的函数上设置无条件断点,当这个命令完成时显示所有被设置的断点信息。这个命令设置的断点和'break'命令设置的没有什么不同。这样你可以象操作一般的断点一样对这个命令设置的断点进行删除,使能,使不能等操作。当调试C++程序时这个命令在重载函数上设置断点时非常有用。
info breakpoints [N]
info break [N]
info watchpoints [N]
显示所有的断点和观察点的设置表,有下面一些列:
*Breakpoint Numbers*----断点号
*Type*----断点类型(断点或是观察点)
*Disposition*---显示断点的状态。
*Enabled or Disabled*---使能或不使能。'y'表示使能,'n'表示不使能。
*Address*----地址,断点在你程序中的地址(内存地址)
*What*---地址,断点在你程序中的行号。
如果断点是条件断点,此命令还显示断点所需要的条件。带参数N的 info break 命令只显示由N指定的断点的信息。此命令还显示断点的运行信息(被执行过几次),这个功能在使用 ignore 命令时很有用。你可以 ignore 一个断点许多次。使用这个命令可以查看断点被执行了多少次。这样可以更快的找到错误。gdb允许你在一个地方设置多个断点。但设置相同的断点无疑是弱智的。不过你可以使用条件断点,这样就非常有用。gdb有时会自动在你的程序中加入断点。这主要是gdb自己的需要。比如为了正确的处理C语言中的 longjmp。这些内部断点都是负值,以'-1'开始。'info breakpoints'不会显示它们。不过你可以使用命令maint info breakpoints来查看这些断点。
maint info breakpoints
使用格式和'info breakpoints'相同,显示所有的断点,无论是你设置的还是 gdb自动设置的。
breakpoint : 断点,普通断点。
watchpoint : 普通观察点。
longjmp : 内部断点,用于处理 longjmp 调用。
longjmp resume : 内部断点,设置在'longjmp'调用的目标上。
until : until命令所使用的内部断点。
finish : finish命令所使用的内部断点。
断点和异常
==============
在一些语言中比如象GNU C++,实现了异常处理。你可以使用GDB来检查异常发生的原因。而且GDB还可以列出在某个点上异常处理的所有过程。
catch EXCEPTIONS
你可以使用这个命令来在一个被激活的异常处理句柄中设置断点。EXCEPTIONS是一个你要抓住的异常。你一样可以使用'info catch'命令来列出活跃的异常处理句柄。现在GDB中对于异常处理有以下情况不能处理。
l 如果你使用一个交互的函数,当函数运行结束时,GDB将象普通情况一样把控制返回给你。如果在调用中发生了异常,这个函数将继续运行直到遇到一个断点,一个信号或是退出运行。
l 你不能手工产生一个异常( 即异常只能由程序运行中产生 )
l 你不能手工设置一个异常处理句柄。
有时'catch'命令不一定是调试异常处理的最好的方法。如果你需要知道异常产生的确切位置,最好在异常处理句柄被调用以前设置一个断点,这样你可以检查栈的内容。如果你在一个异常处理句柄上设置断点,那么你就不容易知道异常发生的位置和原因。要仅仅只在异常处理句柄被唤醒之前设置断点,你必须了解一些语言的实现细节。比如在GNU C++中异常被一个叫'__raise_exception'的库函数所调用。这个函数的原型是:
/* ADDR is where the exception identifier is stored.
ID is the exception identifier. */
void __raise_exception (void **ADDR, void *ID);
要使GDB在栈展开之前抓住所有的句柄,你可以在函数'__raise_exception'上设置断点。 对于一个条件断点,由于它取决于ID的值,你可以在你程序中设置断点,当某个特别的异常被唤醒。当有一系列异常被唤醒时,你可以使用多重条件断点来停止你的程序。
断点菜单
==============
一些编程语言(比如象C++)允许一个函数名被多次使用(重载),以方便应用的使用。当一个函数名被重载时,'break FUNCITON'命令向GDB提供的信息不够GDB了解你要设置 断点的确切位置。如果你了解到这个问题,你可以使用'break FUNCITONS(TYPES)'命令来指定断点的确切位置。否则GDB会提供一个函数的选择的菜单供你选择。使用提示符'>'来等待你的输入。开始的两个选择一般是'[0] cancel'和'[1] all'输入1则在所有同名函数上加入断点。输入0则退出选择。下例为企图在重载的函数符号'String::after'上设置断点。
(gdb) b String::after
[0] cancel
[1] all
[2] file:String.cc; line number:867
[3] file:String.cc; line number:860
[4] file:String.cc; line number:875
[5] file:String.cc; line number:853
[6] file:String.cc; line number:846
[7] file:String.cc; line number:735
> 2 4 6
Breakpoint 1 at 0xb26c: file String.cc, line 867.
Breakpoint 2 at 0xb344: file String.cc, line 875.
Breakpoint 3 at 0xafcc: file String.cc, line 846.
Multiple breakpoints were set.
Use the "delete" command to delete unwanted
breakpoints.