gdb使用
1.编译的时候, 比如编译源码是 test.c, 则编译命令是: gcc -g -O0 test.c
-g表示生成的a.out可以gdb调试,-O0是不让gcc优化,按照程序设计顺序执行.
2.调试.
出错以后,比如提示segmentation fault(据说 segmentation fault这种错误很常见,一般就是因为访问越界造成的).那么输入 gdb a.out 就可以调试.
一般会出现一个统一的gdb调试环境.gdb的提示符是(gdb).此时就可以输入命令进行一步步的debug.(后面列出常用命令)
help 可以打印出帮助.看看gdb到底怎么用.
list 列出程序的代码(大概每次10行左右).按回车可以直接执行上一次输入的命令.因此不必每次输入list.
break 在认为可能出错的地方设置断点.比如45行,就 break 45.执行run以后,在45行以前如果没有错误就不会停止直到这个断点.然后返回(gdb)提示符,此时可以继续执行或者查看需要检查的一些变量值.
print 可以显示出想要了解的值. 比如变量a, 就 print a, 甚至可以打印出结构的具体内容.
ptype 可以显示结构原型! 比如用了一个网络结构sockaddr_in thisadd, 可以ptype thisadd 将sockaddr_in的结构打印出来.
step 单步执行.
next 但不执行.区别与step在于,step会跟踪到子程序中,而next只在本层单步.
until 如果认为最近一些行都没有问题,可以 untill 87, 表示继续执行到87行然后停下来.
quit 表示退出gdb.
上面是最基本的命令.据说gdb的功能还很强大.不过有了刚才这些命令,就可以开始上路排错了.
gdb使用指南(2)
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'命令所使用的内部断点。
设置观察点
==============
你可以使用观察点来停止一个程序,当某个表达式的值改变时,观察点会将程序
停止。而不需要先指定在某个地方设置一个断点。
由于观察点的这个特性,使观察点的使用时开销比较大,但在捕捉错误时非常有
用。特别是你不知道你的程序什么地方出了问题时。
`watch EXPR'
这个命令使用EXPR作为表达式设置一个观察点。GDB将把表达式加入到程序中
并监视程序的运行,当表达式的值被改变时GDB就使程序停止。这个也可以被用在
SPARClite DSU提供的新的自陷工具中。当程序存取某个地址或某条指令时(这个地
址在调试寄存器中指定),DSU将产生自陷。对于数据地址DSU支持'watch'命令,然而
硬件断点寄存器只能存储两个断点地址,而且断点的类型必须相同。就是两个
'rwatch'型断点,或是两个'awatch'型断点。
`rwatch EXPR'
设置一个观察点,当EXPR被程序读时,程序被暂停。
`awatch EXPR'
设置一个观察点,当EXPR被读出然后被写入时程序被暂停。这个命令和'awatch'
命令合用。
`info watchpoints'
显示所设置的观察点的列表,和'info break'命令相似。
*注意:*在多线程的程序中,观察点的作用很有限,GDB只能观察在一个线程中
的表达式的值如果你确信表达式只被当前线程所存取,那么使用观察点才有效。GDB
不能注意一个非当前线程对表达式值的改变。
断点和异常
==============
在一些语言中比如象GNU C++,实现了异常处理。你可以使用GDB来检查异常发生的
原因。而且GDB还可以列出在某个点上异常处理的所有过程。
`catch EXCEPTIONS'
你可以使用这个命令来在一个被激活的异常处理句柄中设置断点。EXCEPTIONS是
一个你要抓住的异常。
你一样可以使用'info catch'命令来列出活跃的异常处理句柄。
现在GDB中对于异常处理由以下情况不能处理。
* 如果你使用一个交互的函数,当函数运行结束时,GDB将象普通情况一样把控制返
回给你。如果在调用中发生了异常,这个函数将继续运行直到遇到一个断点,一个信号
或是退出运行。
* 你不能手工产生一个异常( 即异常只能由程序运行中产生 )
* 你不能手工设置一个异常处理句柄。
有时'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的值,你可以在你程序中设置断点,当某个特
别的异常被唤醒。当有一系列异常被唤醒时,你可以使用多重条件断点来停止你的程序。
删除断点
===================
很自然当一个断点或是一个观察点完成了它的使命后,你需要把它从程序中删去。
不然你的程序还会在相同的地方停主,给你造成干扰。使用'clear'命令来从程序中删去
一个断点。
使用'clear'命令你可以删除指定位置的断点。使用'delete'命令你可以使用断点号
来指定要删去的断点或观察点。
在删除断点时不需要先运行过它,GDB会忽略你刚才删去的断点。所以你可以继续运行
你的程序而不必管断点。
`clear'
在当前选择的栈帧上清除下一个所要执行到的断点(指令级)。当你当前选择帧是栈中
最内层时使用这个命令可以很方便的删去刚才程序停止处的断点。
`clear FUNCTION'
`clear FILENAME:FUNCTION'
删除名为FUNCITON的函数上的断点。
`clear LINENUM'
`clear FILENAME:LINENUM'
删除以LINENUM为行号上的断点。
`delete [breakpoints] [BNUMS...]'
删除参数所指定的断点,如果没有指定参数则删去程序中所有的断点。这个命令可以
缩写成为'd'
使断点暂时不起作用。
========================
如果你只是想让断点一时失去作用以方便调试的话,你可以先使断点不起作用。
当你以后又想使用时可以用'enable'命令激活它们。
你使用'enable'命令来激活断点或是观察点,使用'disable'命令来使断点或观察点
不起作用。使用'info break'或'info watch'来查看那些断点是活跃的。
断点或观察点有四种状态:
* 使能。当程序运行到断点处时,程序停止。使用'break'命令设置的断点一开始缺省
是使能的。
*不使能。断点对你程序的运行没有什么影响。
*使能一次后变为不使能。断点对你的程序运行只有一次影响,然后就自动变成不使能
状态。使用'tbreak'设置的断点一开始缺省是这个状态。
* 使能一次自动删除。断点在起了一次作用后自动被删除。
你可以使用以下的命令来使能或使不能一个断点或观察点。
`disable [breakpoints] [BNUMS...]'
使由参数指定的断点或观察点变为不使能,如果没有参数的话缺省使所有断点和观察
点变为不使能。当一个断点或观察点被不使能后在被不使能前的状态被记录下来,在断点或
观察点再次被激活时,原来的状态得到继续。比如一个条件断点或一个设置了
'ignore-counts'的断点在被使不能后记录活跃时断点被执行的次数,在不使能状态下,断
点的执行次数(ignore-counts)不增加,直到断点再次被激活时,再继续计算条件
(ignore-counts)。你可以使用'disable'命令的缩写'dis'
`enable [breakpoints] [BNUMS...]'
使能由参数指定的断点或全部断点。
`enable [breakpoints] once BNUMS...'
功能同上条命令,只是这条命令使断点只使能一次。
`enable [breakpoints] delete BNUMS...'
功能同上条命令,只是这条命令使被使能的断点起作用一次然后自动被删除。
除了使用'tbreak'命令所设置的断点以外,断点被设置时都是使能的。
断点条件
===========
最简单的断点就是当你的程序每次执行到的时候就简单将程序挂起。你也可以为断点
设置“条件”。条件只是你所使用的编程语言的一个布尔表达式,带有条件表达式的断点
在每次执行时判断计算表达式的值,当表达式值为真时才挂起程序。
这是使用“断言”的一中形式,在这种形式中你只有在断言为真时才挂起程序。如果
在C语言中你要使断言为假时挂起程序则使用:“!表达式”。
条件表达式对观察点也同样有效,但你并不需要它,因为观察点本身就计算一个表达式?
?
但它也许会简单一些。比如只在一个变量名上设置观察点然后设置一个条件来测试新的赋
值。
断点条件可能有副作用(side effects)会影响程序的运行。这一点有时也是很有用的
比如来激活一个显示程序完成情况的的函数,或使用你自己的打印函数来格式化特殊的
数据结构。当在同一位置没有另一个断点设置时,结果是可预见的。(在gdb中如果在同一
个地方使用了一个断点和一个条件断点则普通断点可能先被激活。)在条件断点的应用上
有很多技巧。
断点条件可以在设置断点的同时被设置。使用'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'命令的参数(使用一个不停减量的变量
作为条件表达式的成员)。
断点命令列表
==================
你可以为任一个断点或观察点指定一系列命令,当你程序执行到断点时,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
断点菜单
==============
一些编程语言(比如象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.
(gdb)
gdb使用指南(2)
程序环境
==========================
“环境”包括了一系列的环境变量和它们的值。环境变量一般记录了一些常用的信息,
比如你的用户名,主目录,你的终端型号和你的运行程序的搜索路径。一般你可以在shell
下设置环境变量,然后这些变量被所有你所运行的程序所共享。在调试中,可以设置恰当
的环境变量而不用退出gdb.
`path DIRECTORY'
在'PATH'环境变量前加入新的内容('PATH'提供了搜索执行文件的路径)。对于gdb和
你的程序来说你也许要设置一些专门的路径。使用':'或空格来分隔。如果DIRECTORY已经
在路径中了,这个操作将会把它移到前面。
你可以使用串'$cmd'来代表当前路径,如果你用'.'的话,它代表你使用'path'命令
时的路径,gdb将在把DIRECTORY加入搜索路径前用'.'代替当前路径
`show paths'
显示当前路径变量的设置情况。
`show environment [VARNAME]'
显示某个环境变量的值。如果你不指明变量名,则gdb会显示所有的变量名和它们的
内容。environment可以被缩写成'env'
`set environment VARNAME [=] VALUE'
设置某个环境变量的值。不过只对你所调试的程序有效。对gdb本身是不起作用的。
值可以是任何串。如果未指定值,则该变量值将被设为NULL.
看一个例子:
set env USER = foo
告诉一个linux程序,当它下一次运行是用户名将是'foo'
`unset environment VARNAME'
删除某环境变量。
注意:gdb使用'shell'环境变量所指定的shell来运行你的程序。
工作路径
================================
当你每次用'run'命令来运行你的程序时,你的程序将继承gdb的
当前工作目录。而gdb的工作目录是从它的父进程继承而来的(一般是
shell)。但你可以自己使用'cd'命令指定工作目录。
gdb的工作目录就是它去寻找某些文件或信息的途径。
`cd DIRECTORY'
把gdb的工作目录设为DIRECTORY
`pwd'
打印输出当前目录。
你程序的输入/输出
===============================
缺省时,你的程序的输入/输出和gdb的输入/输出使用同一个终端。
gdb在它自己和你的程序之间切换来和你交互,但这会引起混乱。
`info terminal'
显示你当前所使用的终端的类型信息。
你可以把你程序的输入/输出重定向。
例如:
run > outfile
运行你的程序并把你程序的标准输出写入文件outfile中。
另一个为你程序指定输入/输出的方法是使用'tty'命令,这个命令
接受一个文件名作为参量把这个文件作为以后使用'run'命令的缺省命
令文件。它还重新为子进程设置控制终端。
例如:
tty /dev/ttyb
指定以后用'run'命令启动的进程使用终端'/dev/ttyb'作为程序的输入
/输出,而且把这个终端设为你进程的控制终端。
一个清楚的使用'run'命令的重定向将重新设置'tty'所设置的内容
,但不影响控制终端。 当你使用'tty'命令或在'run'命令中对输入
/输出进行重定向时,只有你当前调试的程序的输入/输出被改变了,
并不会影响到别的程序。
调试一个已经运行的程序:
====================================
`attach PROCESS-ID'
这个命令把一个已经运行的进程(在gdb外启动)连接入gdb,以便
调试。PROCESS-ID是进程号。(UNIX中使用'ps'或'jobs -l'来查看进程)
'attach'一般不重复。(当你打了一个以上的回车时)
当然要使用'attach'命令的话,你的操作系统环境必须支持进程。
另外你还要有向此进程发信号的权力。
当使用'attach'命令时,你应该先使用'file'命令来指定进程所
联系的程序源代码和符号表。 当gdb接到'attach'命令后第一件
事就是停止进程的运行,你可以使用所有gdb的命令来调试一个“连接”
的进程,就象你用'run'命令在gdb中启动它一样。如果你要进程继续运
行,使用'continue'或'c'命令就行了。
`detach'
当你结束调试后可以使用此命令来断开进程和gdb的连接。(解除gdb
对它的控制)在这个命令执行后进程将继续执行。
如果你在用'attach'连接一个进程后退出了gdb,或使用'run'命令执
行了另一个进程,这个被'attach'的进程将被kill掉。但缺省时,gdb会
要求你确认你是否要退出或执行一个新的进程。
结束子进程
=========================
`kill'
Kill命令结束你程序在gdb下开的子进程
这个命令当你想要调试(检查)一个core dump文件时更有用。gdb在调试过程中
会忽略所有的core dump。
在一些操作系统上,一个程序当你在上面加了断点以后就不能离开gdb独立运行。
你可以用kill命令来解决这个问题。
'kill'命令当你想重新编译和连接你的程序时也很有用。因为有些系统不允许修改
正在执行的可执行程序。这样当你再一次使用'run'命令时gdb会知道你的程序已经被改
变了,那么gdb会重新load新的符号。(而且尽量保持你当前的断点设置。
附加的进程信息
==============================
一些操作系统提供了一个设备目录叫做'/proc'的,供检查进程映象。如果gdb被在这
样的操作系统下运行,你可以使用命令'info proc'来查询进程的信息。('info proc'命
令只在支持'procfs'的SVR4系统上有用。
`info proc'
显示进程的概要信息。
`info proc mappings'
报告你进程所能访问的地址范围。
`info proc times'
你进程和子进程的开始时间,用户时间(user CPU time),和系统CPU时间。
`info proc id'
报告有关进程id的信息。
`info proc status'
报告你进程的一般状态信息。如果进程停止了。这个报告还包括停止的原因和收到的
信号。
`info proc all'
显示上面这些命令返回的所有信息。
对多线程程序的调试
========================================
一些操作系统中,一个单独的程序可以有一个以上的线程在运行。线程和进程精确的定?
?
?
?
有自己的寄存器,运行时堆栈或许还会有私有内存。
gdb提供了以下供调试多线程的进程的功能:
* 自动通告新线程。
* 'thread THREADNO',一个用来在线程之间切换的命令。
* 'info threads',一个用来查询现存线程的命令。
* 'thread apply [THREADNO] [ALL] ARGS',一个用来向线程提供命令的命令。
* 线程有关的断点设置。
注意:这些特性不是在所有gdb版本都能使用,归根结底要看操作系统是否支持。
如果你的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'
显示有关你程序状态的信息:你的程序是在运行还是停止,是什么进程,为什么停
止。
断点,观察点和异常
========================================
断点的作用是当你程序运行到断点时,无论它在做什么都会被停止下来。对于每个断点
你都可以设置一些更高级的信息以决定断点在什么时候起作用。你可以使用'break’命令
来在你的程序中设置断点,在前面的例子中我们已经提到过一些这个命令的使用方法了。
你可以在行上,函数上,甚至在确切的地址上设置断点。在含有异常处理的语言(比如象
c++)中,你还可以在异常发生的地方设置断点。
在SunOS 4.x,SVR4和Alpha OSF/1的设置中,你还可以在共享库中设置断点。
观察点是一种特殊的断点。它们在你程序中某个表达式的值发生变化时起作用。你必
须使用另外一些命令来设置观察点。除了这个特性以外,你可以象对普通断点一样对观察
点进行操作--使用和普通断点操作一样的命令来对观察点使能,使不能,删除。
你可以安排当你程序被中断时显示的程序变量。
当你在程序中设置断点或观察点时gdb为每个断点或观察点赋一个数值.在许多对断点
操作的命令中都要使用这个数值。
设置断点
=============
使用'break'或简写成'b'来设置断点。gdb使用环境变量$bpnum来记录你最新设置的
断点。
你有不少方法来设置断点。
`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的值为真时程序停止。...可以为下面所说的一些参量。
`tbreak ARGS'
设置断点为只有效一次。ARGS的使用同'break'中的参量的使用。
`hbreak ARGS'
设置一个由硬件支持的断点。ARGS同'break'命令,设置方法也和
'break'相同。但这种断点需要由硬件支持,所以不是所有的系统上这个
命令都有效。这个命令的主要目的是用于对EPROM/ROM程序的调试。因为
这条命令可以在不改变代码的情况下设置断点。这可以同SPARCLite DSU
一起使用。当程序访问某些变量和代码时,DSU将设置“陷井”。注意:
你只能一次使用一个断点,在新设置断点时,先删除原断点。
gdb使用指南(1)
使用GDB:
本文描述GDB,GNU的原代码调试器。(这是4.12版1994年一月,GDB版本4。16)
* 目录:
* 摘要: GDB的摘要
* 实例: 一个使用实例
* 入门: 进入和退出GDB
* 命令: GDB 的命令
* 运行: 在GDB下运行程序
* 停止: 暂停和继续执行
* 栈: 检查堆栈
* 原文件: 检查原文件
* 数据: 检查数据
* 语言: 用不同的语言来使用GDB
* 符号: 检查符号表
* 更改: 更改执行
* GDB的文件 文件
* 对象 指定调试对象
* 控制GDB 控制
* 执行序列: 执行一序列命令
* Emacs: 使GDB和Emacs一起工作
* GDB的bug:
* 命令行编辑: 行编辑
* 使用历史记录交互:
* 格式化文档: 如何格式化和打印GDB文档
* 安装GDB :
* 索引:
GDB简介:
**************
调试器(比如象GDB)能让你观察另一个程序在执行时的内部活动,或程序出错时
发生了什么。
GDB主要能为你做四件事(包括为了完成这些事而附加的功能),帮助你找出程序
中的错误。
* 运行你的程序,设置所有的能影响程序运行的东西。
* 保证你的程序在指定的条件下停止。
* 当你程序停止时,让你检查发生了什么。
* 改变你的程序。那样你可以试着修正某个bug引起的问题,然后继续查找另一
个bug.
你可以用GDB来调试C和C++写的程序。(参考 *C 和C++)
部分支持Modula-2和chill,但现在还没有这方面的文档。
调试Pascal程序时,有一些功能还不能使用。
GDB还可以用来调试FORTRAN程序,尽管现在还不支持表达式的输入,输出变量,
或类FORTRAN的词法。
* GDB是"free software",大家都可以免费拷贝。也可以为GDB增加新的功能,不
过可要遵守GNU的许可协议幺。反正我认为GNU还是比较不错的:-)
就这句话:
Fundamentally, the General Public License is a license which says
that you have these freedoms and that you cannot take these freedoms
away from anyone else.
GDB的作者:
Richard Stallman是GDB的始作俑者,另外还有许多别的GNU的成员。许多人
为此作出了贡献。(都是老外不提也罢,但愿他们不要来找我麻烦:-))
这里是GDB的一个例子:
原文中是使用一个叫m4的程序。但很遗憾我找不到这个程序的原代码,
所以没有办法来按照原文来说明。不过反正是个例子,我就拿一个操作系统的
进程调度原码来说明把,原代码我会附在后面。
首先这个程序叫os.c是一个模拟进程调度的原程序(也许是个老古董了:-))。
先说明一下如何取得包括原代码符号的可执行代码。大家有心的话可以去看一下gcc的
man文件(在shell下打man gcc)。gcc -g <原文件.c> -o <要生成的文件名>
-g 的意思是生成带原代码调试符号的可执行文件。
-o 的意思是指定可执行文件名。
(gcc 的命令行参数有一大堆,有兴趣可以自己去看看。)
(忍不住要加个注,现在应该用gcc -ggdb指定吧!因为有很多人都在问,因为除了gdb还有别的工具:-)
反正在linux下把os.c用以上方法编译连接以后就产生了可供gdb使用的可执行文件。
我用gcc -g os.c -o os,产生的可执行文档叫os.
然后打gdb os,就可进入gdb,屏幕提示:
GDB is free software and you are welcome to distribute copies
of it under certain conditions; type "show copying" to see
the conditions.
There is absolutely no warranty for GDB; type "show warranty"
for details.
GDB 4.16, Copyright 1995 Free Software Foundation, Inc...
(gdb)
(gdb)是提示符,在这提示符下可以输入命令,直到退出。(退出命令是q/Q)
为了尽量和原文档说明的命令相符,即使在本例子中没用的命令我也将演示。
首先我们可以设置gdb的屏幕大小。键入:
(gdb)set width 70
就是把标准屏幕设为70列。
然后让我们来设置断点。设置方法很简单:break或简单打b后面加行号或函数名
比如我们可以在main 函数上设断点:
(gdb)break main
或(gdb)b main
系统提示:Breakpoint 1 at 0x8049552: file os.c, line 455.
然后我们可以运行这个程序,当程序运行到main函数时程序就会停止返回到gdb的
提示符下。运行的命令是run或r(gdb中有不少alias,可以看一下help,在gdb下打help)
run 后面可以跟参数,就是为程序指定命令行参数。
比如r abcd,则程序就会abcd以作为参数。(这里要说明的是可以用set args来指定参
数)。打入r或run后,程序就开始运行直到进入main的入口停止,显示:
Starting program: <路径>/os
Breakpoint 1, main () at os.c:455
455 Initial();
这里455 Initial();是将要执行的命令或函数。
gdb提供两种方式:1.单步进入,step into就是跟踪到函数内啦。命令是step或s
2.单步,next,就是简单的单步,不会进入函数。命令是next或n
这两个命令还有别的用法以后再说。
我们用n命令,键入:
(gdb)n
Success forking process# 1 ,pid is 31474
Success forking process# 2 ,pid is 31475
Success forking process# 3 ,pid is 31476
Success forking process# 4 ,pid is 31477
Success forking process# 5 ,pid is 31478
Success forking process# 6 ,pid is 31479
Dispatching Algorithm : FIFO
********************************************************************************
PCB# PID Priority PC State
1 31474 24 0 WAITING
2 31475 19 0 WAITING
3 31476 16 0 WAITING
4 31477 23 0 WAITING
5 31478 22 0 WAITING
6 31479 20 0 WAITING
******************************************************************************
CPU : NO process running
IO : No process
Waiting CPU!!! 31474 31475 31476 31477 31478 31479
Waiting IO NONE
456 State=WAITING;
最后的一行就是下一句要执行的命令。我们现在在另一个函数上加断点。注意我们
可以用l/list命令来显示原代码。这里我们键入
(gdb)l
451 main()
452 {
453 int message;
454
455 Initial();
456 State=WAITING;
457 printf("Use Control-C to halt \n");
458 signal(SIGALRM,AlarmMessage);
459 signal(SIGINT,InteruptMessage);
460 signal(SIGUSR2,IoMessage);
(gdb) l
461 alarm(TimeSlot);
462 for(;;)
463 {
464 message=GetMessage();
465 switch(message)
466 {
467 case INTERRUPT : printf("Use Control-C t;
468 break;
469 case CHILD_IO: WaitingIo();
470 break;
显示了原代码,现在在AlarmMessage上加断点。
(gdb) b AlarmMessage
Breakpoint 2 at 0x8048ee3: file os.c, line 259.
(gdb)
然后我们继续运行程序。
(gdb)c
c或continue命令让我们继续被中断的程序。 显示:
Continuing.
Use Control-C to halt
Breakpoint 2, AlarmMessage () at os.c:259
259 ClearSignal();
注意我们下一句语句就是ClearSignal();
我们用s/step跟踪进入这个函数看看它是干什么的。
(gdb) s
ClearSignal () at os.c:227
227 signal(SIGINT,SIG_IGN);
用l命令列出原代码:
(gdb) l
222 }
223
224
225 void ClearSignal() /* Clear other signals */
226 {
227 signal(SIGINT,SIG_IGN);
228 signal(SIGALRM,SIG_IGN);
229 signal(SIGUSR2,SIG_IGN);
230 }
231
(gdb)
我们可以用s命令继续跟踪。现在让我们来试试bt或backtrace命令。这个命令可以
显示栈中的内容。
(gdb) bt
#0 ClearSignal () at os.c:227
#1 0x8048ee8 in AlarmMessage () at os.c:259
#2 0xbffffaec in ?? ()
#3 0x80486ae in ___crt_dummy__ ()
(gdb)
大家一定能看懂显示的意思。栈顶是AlarmMessage,接下来的函数没有名字--就是
没有原代码符号。这显示了函数调用的嵌套。
好了,我们跟踪了半天还没有检查过变量的值呢。检查表达式的值的命令是p或print
格式是p <表达式>
444444让我们来找一个变量来看看。:-)
(gdb)l 1
还记得l的作用吗?l或list显示原代码符号,l或list加<行号>就显示从<行号>开始的
原代码。好了找到一个让我们来看看WaitingQueue的内容
(gdb) p WaitingQueue
$1 = {1, 2, 3, 4, 5, 6, 0}
(gdb)
WaitingQueue是一个数组,gdb还支持结构的显示,
(gdb) p Pcb
$2 = {{Pid = 0, State = 0, Prior = 0, pc = 0}, {Pid = 31474, State = 2,
Prior = 24, pc = 0}, {Pid = 31475, State = 2, Prior = 19, pc = 0}, {
Pid = 31476, State = 2, Prior = 16, pc = 0}, {Pid = 31477, State = 2,
Prior = 23, pc = 0}, {Pid = 31478, State = 2, Prior = 22, pc = 0}, {
Pid = 31479, State = 2, Prior = 20, pc = 0}}
(gdb)
这里可以对照原程序看看。
原文档里是一个调试过程,不过我想这里我已经把gdb的常用功能介绍了一遍,基本上
可以用来调试程序了。:-)
运行GDB(一些详细的说明):
前面已经提到过如何运行GDB了,现在让我们来看一些更有趣的东西。你可以在运行
GDB时通过许多命令行参数指定大量的参数和选项,通过这个你可以在一开始就设置好
程序运行的环境。
这里将要描述的命令行参数覆盖了大多数的情况,事实上在一定环境下有的并没有
什么大用处。最通常的命令就是使用一个参数:
$gdb <可执行文档名>
你还可以同时为你的执行文件指定一个core文件:
$gdb <可执行文件名> core
你也可以为你要执行的文件指定一个进程号:
$gdb <可执行文件名> <进程号> 如:&gdb os 1234将使gdb与进程1234相联系(attach)
除非你还有一个文件叫1234的。gdb首先检查一个core文件。
如果你是使用一个远程终端进行远程调试的话,那如果你的终端不支持的话,你将无法
使用第二个参数甚至没有core dump。如果你觉得开头的提示信息比较碍眼的话,你可以
用gdb -silent。你还可以用命令行参数更加详细的控制GDB的行为。
打入gdb -help或-h 可以得到这方面的提示。所有的参数都被按照排列的顺序传给gdb
除非你用了-x参数。
当gdb开始运行时,它把任何一个不带选项前缀的参数都当作为一个可执行文件或core
文件(或进程号)。就象在前面加了-se或-c选项。gdb把第一个前面没有选项说明的参数
看作前面加了-se 选项,而第二个(如果有的话)看作是跟着-c选项后面的。
许多选项有缩写,用gdb -h可以看到。在gdb中你也可以任意的把选项名掐头去尾,只
要保证gdb能判断唯一的一个参数就行。
在这里我们说明一些最常用的参数选项
-symbols <文件名>(-s <文件名>)------从<文件名>中读去符号。
-exec <文件名>(-e <文件名>)----在合适的时候执行<文件名>来做用正确的数据与core
dump的作比较。
-se <文件名>------从<文件名>中读取符号并把它作为可执行文件。
-core <文件名>(-c <文件名>)--指定<文件名>为一个core dump 文件。
-c <数字>----连接到进程号为<数字>,与attach命令相似。
-command <文件名>
-x <文件名>-----执行gdb命令,在<文件名>指定的文件中存放着一序列的gdb命令,就
象一个批处理。
-directory(-d) <路径>---指定路径。把<路径>加入到搜索原文件的路径中。
-m
-mapped----
注意这个命令不是在所有的系统上都能用。如果你可以通过mmap系统调用来获得内存
映象文件,你可以用这个命令来使gdb把你当前文件里的符号写入一个文件中,这个文件
将存放在你的当前路径中。如果你调试的程序叫/temp/fred那么map文件就叫
./fred.syms这样当你以后再调试这个程序时,gdb会认识到这个文件的存在,从而从这
个文件中读取符号,而不是从可执行文件中读取。.syms与主机有关不能共享。
-r
-readnow---马上从符号文件中读取整个符号表,而不是使用缺省的。缺省的符号表是
调入一部分符号,当需要时再读入一部分。这会使开始进入gdb慢一些,但可以加快以后
的调试速度。
-m和-r一般在一起使用来建立.syms文件
接下来再谈谈模式的设置(请听下回分解 :-))
附:在gdb文档里使用的调试例子我找到了在minix下有这个程序,叫m4有兴趣的
可以自己去看看
模式的选择
--------------
现在我们来聊聊gdb运行模式的选择。我们可以用许多模式来运行gdb,例如在“批模式”
或“安静模式”。这些模式都是在gdb运行时在命令行作为选项指定的。
`-nx'
`-n'
不执行任何初始化文件中的命令。(一般初始化文件叫做`.gdbinit').一般情况下在
这些文件中的命令会在所有的命令行参数都被传给gdb后执行。
`-quiet'
`-q'
“安静模式”。不输出介绍和版权信息。这些信息在“批模式”中也被跳过。
`-batch'
“批模式”。在“批模式”下运行。当在命令文件中的所有命令都被成功的执行后
gdb返回状态“0”,如果在执行过程中出错,gdb返回一个非零值。
“批模式”在把gdb作为一个过滤器运行时很有用。比如在一台远程计算机上下载且
执行一个程序。信息“ Program exited normally”(一般是当运行的程序正常结束
时出现)不会在这种模式中出现。
`-cd DIRECTORY'
把DIRECTORY作为gdb的工作目录,而非当前目录(一般gdb缺省把当前目录作为工作目
录)。
`-fullname'
`-f'
GNU Emacs 设置这个选项,当我们在Emacs下,把gdb作为它的一个子进程来运行时,
Emacs告诉gdb按标准输出完整的文件名和行号,一个可视的栈内容。这个格式跟在
文件名的后面。行号和字符重新按列排,Emacs-to-GDB界面使用\032字符作为一个
显示一页原文件的信号。
`-b BPS'
为远程调试设置波特率。
`-tty DEVICE'
使用DEVICE来作为你程序的标准输入输出
`quit'
使用'quit'命令来退出gdb,或打一个文件结束符(通常是' CTROL-D')。如果
你没有使用表达式,gdb会正常退出,否则它会把表达式的至作为error code
返回。
一个中断(通常是'CTROL-c)不会导致从gdb中退出,而是结束任何一个gdb的命
令,返回gdb的命令输入模式。一般在任何时候使用'CTROL-C'是安全的,因为
gdb会截获它,只有当安全时,中断才会起作用。
如果你正在用gdb控制一个被连接的进程或设备,你可以用'detach'命令来释放
它。
Shell命令
==============
当你偶尔要运行一些shell命令时,你不必退出调试过程,也不需要挂起它;你
可以使用'shell'命令。
`shell COMMAND STRING'
调用标准shell来执行'COMMAND STRING'.环境变量'SHELL'决定了那个shell被
运行。否则gdb使用'/bin/sh'.
'make'工具经常在开发环境中使用,所以你可以不用'shell'命令而直接打'make'
`make MAKE-ARGS'
用指定的命令行变量来运行'make'程序,这等于使用'shell make MAKE-ARGS'
GDB 命令
************
我们可以把一个gdb命令缩写成开头几个字母,如果这没有二意性你可以直接回车来
运行。你还可以使用TAB键让gdb给你完成接下来的键入,或向你显示可选择的命令,
如果有不止一个选择的话。
Command语法
==============
一个gdb命令是一个单行的输入。长度没有限制。它一个命令开头,后面可以跟参量。
比如命令'step'接受一个参量表示单步执行多少步。你也可以不用参量。有的命令
不接受任何参量。
gdb命令只要没有二意性的话就可以被缩写。另外一些缩写作为一个命令列出。在某些
情况下二意也是允许的。比如's'是指定'step'的缩写,但还有命令'start'。你可以把
这些缩写作为'help'命令的参量来测试它们。
空行(直接回车)表示重复上一个命令。但有些命令不能重复比如象'run',就不会以这
种方式重复,另外一些当不小心重复会产生严重后果的命令也不能用这种方法重复。
'list'和'x'命令当你简单的打回车时,会建立新的变量,而不是简单的重复上一个命
令。这样你可以方便的浏览原代码和内存。
gdb还有一种解释RET的方法:分割长输出。这种方法就和'more'命令相似。由于这时经
常会不小心多打回车,gdb将禁止重复当一个命令产生很长的输出时。
任何用'#'开头一直到行尾的命令行被看作是注释。主要在命令文件中使用。
输入命令的技巧
==================
前面已经提到过TAB键的使用。使用TAB键能让你方便的得到所要的命令。比如
在gdb中:
(gdb)info bre <TAB>(键入info bre,后按TAB键)
gdb能为你完成剩下的输入。它还能萎蔫提供选择的可能性。如果有两个以上可
能的话,第一次按<TAB>键,gdb会响铃提示,第二次则显示可能的选择。同样gdb
也可以为一些子命令提供快速的访问。用法与上相同。
上例中显示
(gdb)info breakepoints
你也可以直接打回车,gdb就将你输入的作为命令的可能的缩写。来判断执行。
如果你打入的缩写不足以判断,那么gdb会显示一个列表,列出可能的命令。同样的
情况对于命令的参数。在显示完后gdb把你的输入拷贝到当前行以便让你继续输入。
如果你只想看看命令的列表或选项,你可以在命令行下打M-?(就是按着ESC键
同时按SHIFT和?键)。你可以直接在命令行下打试试。
(gdb)<M-?>
gdb会响铃并显示所有的命令。不过这种方式好象在远程调试是不行。当有的命令
使用一个字符串时,你可以用" ' "将其括起来。这种方法在调试C++程序时特别有用。
因为C++支持函数的重载。当你要在某个有重载函数上设断点时,不得不给出函数参数
以区分不同的重载函数。这时你就应该把整个函数用" ' "括起来。比如,你要在一个
叫name的函数上设断点,而这个函数被重载了(name(int)和name(float))。你将不得
不给出参变量以区分不同的函数。使用'name(int)'和'name(float)'。这里有个技巧,
你可以在函数名前加一个" ' "符号。然后打M-?.
你可以使用help命令来得到gdb的在线帮助。
`help'
`h'
你可以使用help或h后面不加任何参数来得到一个gdb命令类的列表。
(gdb) help
List of classes of commands:
running -- Running the program
stack -- Examining the stack
data -- Examining data
breakpoints -- Making program stop at certain points
files -- Specifying and examining files
status -- Status inquiries
support -- Support facilities
user-defined -- User-defined commands
aliases -- Aliases of other commands
obscure -- Obscure features
Type "help" followed by a class name for a list of
commands in that class.
Type "help" followed by command name for full
documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
`help CLASS'
使用上面列出的help class作为help或h的参量,你可以得到单一的命令列表。
例如显示一个'status'类的列表。
(gdb) help status
Status inquiries.
List of commands:
show -- Generic command for showing things set
with "set"
info -- Generic command for printing status
Type "help" followed by command name for full
documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
`help COMMAND'
详细列出单个命令的资料。
`complete ARGS'
列出所有以ARGS开头的命令。例如:
complete i
results in:
info
inspect
ignore
This is intended for use by GNU Emacs.
除了使用'help'你还可以使用gdb的命令'info'和'show'来查询你程序的
状态,每个命令可以查询一系列的状态。这些命令以恰当的方式显示所有的
子命令。
`info'
此命令(可以缩写为'i')用来显示你程序的状态。比如,你可以使用info
args 列出你程序所接受的命令行参数。使用info registers列出寄存器的状态。
或用info breakpoint列出在程序中设的断点。要获得详细的关于info的信息打
help info.
`set'
这个命令用来为你的程序设置一个运行环境(使用一个表达式)。比如你
可以用set prompt $来把gdb的提示符设为$.
`show'
与'info'相反,'show'命令用来显示gdb自身的状态。你使用'set'命令来
可以改变绝大多数由'show'显示的信息。比如使用show radix命令来显示基数。
用不带任何参变量的'set'命令你可以显示所有你可以设置的变量的值。
有三个变量是不可以用'set'命令来设置的。
`show version'
显示gdb的版本号。如果你发现gdb有bug的话你应该在bug-reports里加
入gdb的版本号。
`show copying'
显示版权信息。
`show warranty'
显示担保信息。
在gdb下运行你的程序
**************************
当你在gdb下运行程序时,你必须先为gdb准备好带有调试信息的可执行文档。
还可以在gdb中为你的程序设置参变量,重定向你程序的输入/输出,设置环境变
量,调试一个已经执行的程序或kill掉一个子进程。
这里许多内容在早先的例子中都已经用到过,可以参见gdb(二)。
目录:
* 编译:: 为调试编译带调试信息的代码
* 运行:: 运行你的程序
* 参变量:: 为你的程序设置参变量
* 运行环境:: 为你的程序设置运行时环境
* 设置工作目录:: 在gdb中设置程序的工作目录。
* 输入/输出:: 设定你程序的输入和输出
* 连接:: 调试一个已经运行的程序
* 结束子进程:: Kill子进程
* 进程信息:: 附加的进程信息
* 线程:: 调试带多线程的程序
* 多进程:: 调试带多进程的程序
为调试准备带调试信息的代码
===========================
为了高效的调试一个程序,你需要使用编译器来产生附带调试信息的可执行代码
这些调试信息存储在目标文件中;描述了变量数据类型和函数声明,在原文件代码行
和执行代码之间建立联系。
为产生调试信息,当你使用编译器时指定'-g'选项,就可以为你的程序产生带有
调试信息的可执行代码。
有些c编译器不支持'-g'选项和'-O'选项,那你就有麻烦了,或者有别的方法产生
带调试信息的可执行代码,要不就没办法了。
gcc,GNU的c语言编译器支持'-g'和'-O'选项。这样你就可以产生带调试信息的且
优化过的可执行代码.
当你使用gdb来调试一个使用'-g','-O'选项产生的程序时,千万记住编译器为了优
化你的程序重新安排了你的程序。不要为运行次序与你原来设想的不同,最简单的例子
就是当你定义了一个变量但从未使用过它时,gdb中是看不到这个变量的--因为它已经
被优化掉了。
所以有时你不要使用'-O'选项,如果当你不用优化时产生的程序是正确的,而优化
过后变的不正确了,那么这是编译器的bug你可以向开发者提供bug-reports(包括出错
的例子)。
早期的GUN C语言编译器允许'-gg'选项,也用来产生调试信息,gdb不再支持这种格
式的调试信息,如果你的编译器支持'-gg'选项,请不要使用它。
`run'
`r'
使用'run'命令在gdb下启动你的程序。你必须先指定你程序的名字(用gdb的命令行
参数)或使用'file'命令,来指定文件名。如果你在一个支持多进程的环境下运行你的程
序'run'命令创建一个子进程然后加载你的程序。如果环境不支持进程,则gdb直接调到
程序的第一条命令。
一些父进程设置的参量可以决定程序的运行。gdb提供了指定参量的途径,但你必须
在程序执行前设置好他们。你也可以在运行过程中改变它们,但每次改变只有在下一次
运行中才会体现出来。这些参量可以分为四类:
---参数
你可以在使用'run'命令时设置,如果shell支持的话,你还可以使用通配符,或
变量代换。在UNIX系统中你可以使用'shell环境变量'来控制shell。
---环境:
你的程序一般直接从gdb那里继承环境变量。但是你可以使用'set environment'
命令来设置专门的环境变量。
---工作目录
你的程序还同时从gdb那里继承了工作目录,你可以使用'cd'命令在gdb中改变工作
目录。
---标准输入/输出
你的程序一般使用与gdb所用的相似的设备来输入/输出。不过你可以为你的程序的
输入/输出进行重定向。使用'run'或'tty'命令来设置于gdb所用不同的设备。
*注意:当你使用输入/输出重定向时,你将不能使用无名管道来把你所调试的程序的输出
传给另一个程序。这样gdb会认为调试程序出错。
当你发出'run'命令后,你的程序就开始运行。
如果你的符号文件的时间与gdb上一次读入的不同,gdb会废弃原来的符号表并重新读
入。当前的断点不变。
GDB使用简介
1、GDB 是什么?
GDB(GNU symbolic debugger)简单地说就是一个调试工具。它是一个受通用公共许可证即GPL保护的自由软件。
2、GDB特性
象所有的调试器一样,GDB可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量,寄存器,内存及堆栈。更进一步你可以修改变量 及内存值。GDB是一个功能很强大的调试器,它可以调试多种语言。在此我们仅涉及C和C++的调试,而不包括其它语言。还有一点要说明的是,GDB是一个 调试器,而不象VC一样是一个集成环境。你可以使用一些前端工具如XXGDB,DDD等。他们都有图形化界面,因此使用更方便,但它们仅是GDB的一层外 壳。因此,你仍应熟悉GDB命令。事实上,当你使用这些图形化界面时间较长时,你才会发现熟悉GDB命令的重要性。下面我们将结合简单的例子,来介绍 GDB的一些重要的常用命令。在你调试你的程序之前,当你编译你的源程序时,不要忘了-g选项或其它相应的选项,才能将调试信息加到你要调试的程序中。例 如:gcc -g -o hello hello.c 。
3、GDB常用命令简介
GDB的命令很多,本文不会全部介绍,仅会介绍一些最常用的。在介绍之前,先介绍GDB中的一个非常有用的功能:补齐功能。它就如同Linux下 SHELL中的命令补齐一样。当你输入一个命令的前几个字符,然后输入TAB键,如果没有其它命令的前几个字符与此相同,SHELL将补齐此命令。如果有 其它命令的前几个字符与此相同,你会听到一声警告声,再输入TAB键,SHELL将所有前几个字符与此相同的命令全部列出。而GDB中的补齐功能不仅能补 齐GDB命令,而且能补齐参数。
本文将先介绍常用的命令,然后结合一个具体的例子来演示如何实际使用这些命令。下面的所有命令除了第一条启动GDB命令是在SHELL下输入的,其余 都是GDB内的命令。大部分GDB内的命令都可以仅输入前几个字符,只要不与其它指令冲突。如quit可以简写为q,因为以q打头的命令只有quit。 List可以简写为l,等等
3.1 启动GDB
你可以输入GDB来启动GDB程序。GDB程序有许多参数,在此没有必要详细介绍,但一个最为常用的还是要介绍的:如果你已经编译好一个程序,我们假设文件名为hello,你想用GDB调试它,可以输入gdb hello来启动GDB并载入你的程序。如果你仅仅启动了GDB,你必须在启动后,在GDB中再载入你的程序。
3.2 载入程序 === file
在GDB内,载入程序很简单,使用file命令。如file hello。当然,程序的路径名要正确。
退出GDB === quit
在GDB的命令方式下,输入quit,你就可以退出GDB。你也可以输入'C-d'来退出GDB。
3.3 运行程序 === run
当你在GDB中已将要调试的程序载入后,你可以用run命令来执行。如果你的程序需要参数,你可以在run指令后接着输入参数,就象你在SHELL下执行一个需要参数的命令一样。
3.4 查看程序信息 === info
info指令用来查看程序的信息,当你用help info查看帮助的话,info指令的参数足足占了两个屏幕,它的参数非常多,但大部分不常用。我用info指令最多的是用它来查看断点信息。
3.4.1 查看断点信息
info br
br是断点break的缩写,记得GDB的补齐功能吧。用这条指令,你可以得到你所设置的所有断点的详细信息。包括断点号,类型,状态,内存地址,断点在源程序中的位置等。
3.4.2 查看当前源程序
info source
3.4.3 查看堆栈信息
info stack
用这条指令你可以看清楚程序的调用层次关系。
3.4.4 查看当前的参数
info args
3.5 列出源一段源程序 === list
3.5.1 列出某个函数
list FUNCTION
3.5.2 以当前源文件的某行为中间显示一段源程序
list LINENUM
3.5.3 接着前一次继续显示
list
3.5.4 显示前一次之前的源程序
list -
3.5.5 显示另一个文件的一段程序
list FILENAME:FUNCTION 或 list FILENAME:LINENUM
3.6 设置断点 === break
现在我们将要介绍的也许是最常用和最重要的命令:设置断点。无论何时,只要你的程序已被载入,并且当前没有正在运行,你就能设置,修改,删除断点。设置断点的命令是break。有许多种设置断点的方法。如下:
3.6.1 在函数入口设置断点
break FUNCTION
3.6.2 在当前源文件的某一行上设置断点
break LINENUM
3.6.3 在另一个源文件的某一行上设置断点
break FILENAME:LINENUM
3.6.4 在某个地址上设置断点,当你调试的程序没有源程序是,这很有用
break *ADDRESS
除此之外,设置一个断点,让它只有在某些特定的条件成立时程序才会停下,我们可以称其为条件断点。这个功能很有用,尤其是当你要在一个程序会很多次执 行到的地方设置断点时。如果没有这个功能,你必须有极大的耐心,加上大量的时间,一次一次让程序断下,检查一些值,接着再让程序继续执行。事实上,大部分 的断下并不是我们所希望的,我们只希望在某些条件下让程序断下。这时,条件断点就可以大大提高你的效率,节省你的时间。条件断点的命令如下,在后面的例子 中会有示例。
3.6.5 条件断点
break ...if COND
COND是一个布尔条件表达式,语法与C语言中的一样。条件断点与一般的断点不同之处是每当程序执行到断点处,都要计算条件表达式,如果为真,程序才会断下,否则程序会一直执行下去。
3.7 其它断点操作
GDB给每个断点赋上一个整数数字,这个数字在操作断点时起到重要作用,它实际上就代表相应的断点。GDB中的断点有四种状态:
有效(Enabled)
禁止(Disabled)
一次有效(Enabled once)
有效后删除(Enabled for deletion)
在上面的四个状态有效和禁止都很好理解,禁止就是让断点暂时失效。一次有效就是当程序在此断点断下后,断点状态自动变为禁止状态。有效后删除就是当程序在此断点断下后,断点被删除。实际上,后两种状态一般不会碰到。
当你设置一个断点后,它的确省状态是有效。你可以用enable和disable指令来设置断点的状态为有效或禁止。例如,如果你想禁止2号断点,可以用下面的指令:
disable 2
相应的,如果想删除2号断点,可以有下面的指令:
delete 2
3.8 设置监视点 === watch
当你调试一个很大的程序,并且在跟踪一个关键的变量时,发现这个变量不知在哪儿被改动过,如何才能找到改动它的地方。这时你可以使用watch命令。简单地说,监视点可以让你监视某个表达式或变量,当它被读或被写时让程序断下。watch命令的用法如下:
watch EXPRESSION
watch指令是监视被写的,当你想监视某个表达式或变量被读的话,需要使用rwatch指令,具体用法是一样的。要注意的是,监视点有硬件和软件两 种方式,如果可能Linux尽可能用硬件方式,因为硬件方式在速度上要大大快于软件方式。软件方式由于要在每次执行一条指令后都要检查所要监视的值是否被 改变,因此它的执行速度会大大降低。同时它也无法设置成被读时让程序断下,因为读操作不会改变值,所以GDB无法检测到读操作。幸运的是,目前的PC机基 本都支持硬件方式。如果你想确认一下你的机器是否支持硬件,你可以在调试程序时用watch设置一个监视点,如果GDB向你显示:
Hardware watchpoint NUM: EXPR
那么你可以放心了,你的机器支持硬件方式。
3.9 检查数据
最常用的检查数据的方法是使用print命令。
print exp
print指令打印exp表达式的值。却省情况下,表达式的值的打印格式依赖于它的数据类型。但你可以用一个参数/F来选择输出的打印格式。F是一个 代表某种格式的字母,详细可参考输出格式一节。表达式可以是常量,变量,函数调用,条件表达式等。但不能打印宏定义的值。表达式exp中的变量必须是全局 变量或当前堆栈区可见的变量。否则GDB会显示象下面的一条信息:
No symbol "varible" in current context
3.10 修改变量值
在调试程序时,你可能想改变一个变量的值,看看在这种情况下会发生什么。用set指令可以修改变量的值:
set varible=value
例如你想将一个变量tmp的值赋为10,
set tmp=10
3.11 检查内存值
检查内存值的指令是x,x是examine的意思。用法如下:
x /NFU ADDR
其中N代表重复数,F代表输出格式(见2.13),U代表每个数据单位的大小。U可以去如下值:
b :字节(byte)
h :双字节数值
w :四字节数值
g :八字节数值
因此,上面的指令可以这样解释:从ADDR地址开始,以F格式显示N个U数值。例如:
x/4ub 0x4000
会以无符号十进制整数格式(u)显示四个字节(b),0x4000,0x4001,0x4002,0x4003。
3.12 输出格式
缺省情况下,输出格式依赖于它的数据类型。但你可以改变输出格式。当你使用print命令时,可以用一个参数/F来选择输出的打印格式。F可以是以下的一些值:
'x' 16进制整数格式
'd' 有符号十进制整数格式
'u' 无符号十进制整数格式
'f' 浮点数格式
3.13 单步执行指令
单步执行指令有两个step和next。Step可以让你跟踪进入一个函数,而next指令则不会进入函数。
3.14 继续执行指令
当程序被断下后,你查看了所需的信息后,你会希望程序执行下去,输入 continue, 程序会继续执行下去。
3.15 帮助指令help
在GDB中,如果想知道一条指令的用法,最方便的方法是使用help。使用方法很简单,在help后跟上指令名。例如,想知道list指令用法,输入
help list
4.一个简单的例子
上面仅是GDB常用指令的简单介绍。本节将结合一个简单的例子,向大家演示这些常用指令的具体应用。这是一个冒泡排序算法的程序,这个例子的目的仅仅是演示,并不是实际调试。将下面的源程序存为bubble.c文件,并编译好。
#include <stdio.h>
#define MAX_RECORD_NUMBER 10
int record[MAX_RECORD_NUMBER] =
{12,76,48,62,94,17,37,52,69,32};
swap(int * x , int * y )
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int i,j;
for( i = 0 ; i < MAX_RECORD_NUMBER - 1; i++ )
{
for( j = MAX_RECORD_NUMBER - 1; j > i; j--)
if( record[j] < record[j-1] )
swap(&record[j],&record[j-1]);
}
for( i = 0; i < MAX_RECORD_NUMBER -1; i++)
printf("%d ",record[i]);
printf("\n");
return 1;
}
记得在编译时用-g开关。如:gcc -g -o bubble bubble.c。你能在当前子目录下得到一个编译好的文件bubble。我们下面将以这个程序为例子向大家演示上面的指令在实际中的应用。
首先启动GDB,可以在启动的同时载入文件bubble,如:gdb bubble。也可以分两步进行,先启动GDB,执行gdb,进入GDB后,再执行file bubble。
这时可以用list指令列出源程序,list的使用比较简单,但其实在GDB中最不方便的就是看源程序,主要原因是因为GDB仅是一个文本方式的调试器,无法让你用鼠标和光标键来翻阅源程序,在这方面ddd等窗口程序有巨大的优势。
我们先来查看一下当前源程序的信息,如下:
(gdb) info source
Current source file is bubble.c
Compilation directory is /root/sample/
Located in /root/sample/bubble.c
Contains 32 lines.
Source language is c.
Compiled with stabs debugging format.
我们可以知道程序名,目录,文件大小,语言等信息。
下面我们来设置断点,我们想在函数swap出设置一个断点:
(gdb) br swap
Breakpoint 1 at 0x80483d6: file bubble.c, line 11.
br是break的简写。上面的一行是GDB告诉我们这个断点的信息,我们可以知道这个断点的断点号是1,地址是0x80483d6,它在文件bubble.c的11行。
我们再在一个行号上设一个断点,
(gdb) br 23
Breakpoint 2 at 0x804844a: file bubble.c, line 23.
我们已经设了两个断点,许多时候你会想查看一下断点的信息和状态,因此你会用到你最常使用的info指令,info br。
(gdb) info br
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483d6 in swap at bubble.c:11
2 breakpoint keep y 0x0804844a in main at bubble.c:23
我用这条指令的大多数原因是想查看一下某个断点的断点号,就是第一列的数值。有时也会看一下断点的状态是enable还是disable。以上的两个断点都是y,也就是都处于enable状态。type列显示breakpoint,是因为info br指令同时也会显示watch的信息,因此用type来识别是断点breakpoint还是检查点watch。
如果你知道断点号,想删除断点很简单,例如想删除断点2,执行del 2就行了。
在程序中,断点2本来设在循环中,那样程序会频烦断下,这也许不是我们希望的。也许我们仅想在某个条件下让它断下,如想当j=5时。
(gdb) br 23 if j==5
Breakpoint 3 at 0x804844a: file bubble.c, line 23.
(gdb) info br
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483d6 in swap at bubble.c:11
3 breakpoint keep y 0x0804844a in main at bubble.c:23
stop only if j == 5
注意现在的断点信息,虽然断点2被删除了,但新设的断点号没有使用2号,而是使用了3号。新设的断点是个条件断点,这从"stop only if j == 5"可以清楚的看出。
现在执行程序,输入run指令。
(gdb) run
Starting program: /root/sample/bubble
Breakpoint 1, swap (x=0x80495a4, y=0x80495a0) at bubble.c:11
11 temp = *x;
程序已经在断点1停了下来。当断点停下时,我们经常需要查看变量值。如查看x值。
(gdb) p x
$1 = (int *) 0x80495a4
GDB告诉我们x是一个指向整数的指针,指针值是0x80495a4。如果想查看指针指向的值。执行:
(gdb) p *x
$2 = 32
(gdb) p *y
$3 = 69
单步执行
(gdb) n
12 *x = *y;
查看变量temp值
(gdb) p temp
$4 = 32
(gdb) n
13 *y = temp;
(gdb) p *x
$5 = 69
现在删除断点1
(gdb) del 1
继续执行
(gdb) cont
Continuing.
Breakpoint 3, main () at bubble.c:23
23 swap(&record[j],&record[j-1]);
程序在断点3停下,记得断点3是个条件断点。要验证很简单,查看一下变量j的值是不是5。
(gdb) p j
$6 = 5
我们可以查看一下全局变量record的值,
(gdb) p record
$7 = {12, 76, 48, 62, 94, 17, 32, 37, 52, 69}
也可以查看一下变量record的地址,
(gdb) p &record
$8 = (int (*)[10]) 0x8049580
知道地址时,也可以x指令查看内存值。
(gdb) x/4uw 0x8049580
0x8049580 <record>: 12 76 48 62
上面的指令查看4个4字节数,以整数方式显示。可以看到这与reocrd值是相附的。
(gdb) x/4bb record
0x8049580 <record>: 12 0 0 0
显示4个单字节数,以字节当时显示。上面的4个字节值正好是record数组第一个整数值,因为整数是4字节,而且intel机器的数值是低字节在前。
改变变量值也很简单,如果想将reocrd数组第一个值该为1,
(gdb) set record[0]=1
看一下值是否改变了。
(gdb) p record
$10 = {1, 76, 48, 62, 94, 17, 32, 37, 52, 69}
第一个值以改成了1。
以上简单地介绍了一些常用的GDB指令,由于篇幅所限,我们无法涉及GDB所有指令及GDB其它许多功能,读者应当自己在实践中不断地学习。Linux系统中会有详细的GDB的资料,你可以用info gdb来查阅这些资料。