"Don't worry if it doesn't work right. If everything did, you'd be out of a job."
--Unknown
Table of Contents
1>> 如何使用 gdb
调试工具?
当你编译你的程序时,你必须告诉编译器生成程序的同时兼容调试器。调试器需要指定的信息才能正确的运行。为了达到这个目的,你必须使用编译选项
-g
。这一步骤非常重要。如果没有这个选项,调试器无法获得符号信息。这就意味着它不知道函数和变量是如何调用的。当你需要信息时,它也无法理解。
1.1 如何在编译时获得调试符号?
给编译器指定选项 -g
:
prompt > gcc -g program.c -o programname
NOTE: 如果你的程序包括多个文件,每一个文件都要指定
-g
选项,在链接时,同样也需要设置该选项。
1.2 如何在调试器时运行程序?
在开启调试器时,将你的程序名作为第一个参数。
prompt> gdb programname
接下来使用 run 命令开始执行程序,通过以下方式来设置程序运行需要的参数。
(gdb) run arg1 "arg2" ...
1.3 如何在调试器中重新运行程序?
先使用 kill 命令停止调试程序。接下来使用 run
命令再次进入调试。
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) run ...
1.4 如何退出调试器?
使用 quit 命令。
(gdb) quit
NOTE: 当你执行该命令时,可能会提示你是否需要结束当前程序,输入
y
确认。
(gdb) quit
The program is running. Exit anyway? (y or n) y
prompt >
1.5 如何在调试时查看帮助?
使用 help 命令,gdb 对每一个命令都有相应的描述,比这篇文章提到的命令要多得多。提供帮助的参数是您想要获得的信息。如果你只是输入 "help" 不带任何参数。你将获得一个类似于以下的帮助主题列表:
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands
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.
2>> 如何观察程序的执行?
gdb
的功能有点像程序的解释器,你可以在任何时候给程序发送信号停止来你的程序。通常情况下,中断信号SIGINT
是由Ctrl - C
组合键来完成。在gdb之外,这将终止你的程序。gdb
捕捉这个信号,并停止执行你的程序。使用断点你也可以让程序在任意一行代码或函数调用处停止。一旦你的程序暂停,你可以检查你在代码的那个位置,你可以查看当前在作用域中的变量,以及内存空间和cpu寄存器。你也可以改变变量的值和内存,去看看对你的代码有什么影响。
2.1 如何停止执行?
你可以通过发送UNIX信号(如SIGINT
)来停止程序的执行。这是使用 CTRL + C
组合键完成的。在接下来的例子中,我会在 'Starting Program...'出现后按下 Ctrl-C。
(gdb) run
Starting Program: /home/ug/ryansc/a.out
Program received signal SIGINT, Interrupt.
0x80483b4 in main(argc=1, argv=0xbffffda4) at loop.c:5
5 while(1){
...
(gdb)
2.2 如何继续执行程序?
使用 continue
命令,在程序停止的时候重新启动程序。
2.3 怎么知道程序停止的位置?
使用 list
命令使 gdb 打印出断点所在位置附近的代码。下面这个例子,断点在第8行。
(gdb) list
3 int main(int argc, char **argv)
4 {
5 int x = 30;
6 int y = 10;
7
8 x = y;
9
10 return 0;
11 }
2.4 如何一行一行地单步执行代码?
首先发送信号或者使用断点停止你的程序,然后使用 next和step 命令。
5 while(1){
(gdb) next
7 }
(gdb)
NOTE: next和step 命令是不同的。一行代码中包含函数调用时,next 会跳过这个函数的内部执行细节,运行下一行代码,而 step 会跳转到函数的内部中。
next 命令:
(gdb)
11 fun1();
(gdb) next
12 }
step 命令:
(gdb)
11 fun1();
(gdb) step;
fun1 () at loop.c:5
5 return 0;
(gdb)
2.5 如何检查变量的值?
使用变量名作为 print 的参数。比如,如果你程序中有 int x
和 char *s
:
(gdb) print x
$1 = 900
(gdb) print s
$3 = 0x8048470 "Hello World!\n"
(gdb)
NOTE: print 命令的输出总是
$## = (value)
格式。$##
是只是一个记数器,可以跟踪你检查过的变量。
2.6 如何更改变量的值?
使用 set 命令,C语言中的赋值语句作为其参数。比如,改变 x 的值为3:
(gdb) set x = 3
(gdb) print x
$4 = 3
NOTE: 在 gdb 的新版本中,使用
set var
是必要的,这里就应该是set var x = 3
。
2.7 如何调用链接到我的程序中的函数?
在调试器命令行中,你可以使用 call 命令来调用任意函数链接到你的程序中去。这包括你自己的代码和标准库函数。比如,如果你希望你的程序 dump core
。
(gdb) call abort()
2.8 如何从一个函数中返回?
使用 finish 命令,结束当前函数的执行并返回到该函数的调用者处。这条命令也会显示函数的返回值。
(gdb) finish
Run till exit from #0 fun1 () at test.c:5
main (argc=1, argv=0xbffffaf4) at test.c:17
17 return 0;
Value returned is $1 = 1
3>> 如何使用调用栈?
调用堆栈是我们找到控制程序流的堆栈帧。当一个函数被调用时,它会创建一个栈帧,告诉计算机在函数结束执行之后如何将控制权返回给调用者。栈帧也是局部变量和函数参数存储的地方。我们可以观察栈帧来推断程序是如何运行的。找到当前帧下面的栈帧列表称为回溯(backtrace)。
3.1 如何获得 backtrace?
使用 backtrace命令,在下面的backtrace中,我们可以看到当前在 func2(),是被
func1()调用的,
func1()又是被
main()`调用的。
(gdb) backtrace
#0 func2 (x=30) at test.c:5
#1 0x80483e6 in func1 (a=30) at test.c:10
#2 0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
#3 0x40037f5c in __libc_start_main () from /lib/libc.so.6
(gdb)
3.2 如何改变栈帧?
使用frame命令,注意上面的每一个栈帧都有编号。这个编号作为使用 frame 命令的参数。
(gdb) frame 2
#2 0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
19 x = func1(x);
(gdb)
3.3 如何检查栈帧?
有3个有用的命令可以用来获得当前帧的内容。info frame 显示当前栈帧的信息。info locals 显示当前栈帧的局部变量的列表和它们的值。info args显示参数列表。
(gdb) info frame
Stack level 2, frame at 0xbffffa8c:
eip = 0x8048414 in main (test.c:19); saved eip 0x40037f5c
called by frame at 0xbffffac8, caller of frame at 0xbffffa5c
source language c.
Arglist at 0xbffffa8c, args: argc=1, argv=0xbffffaf4
Locals at 0xbffffa8c, Previous frame's sp is 0x0
Saved registers:
ebp at 0xbffffa8c, eip at 0xbffffa90
(gdb) info locals
x = 30
s = 0x8048484 "Hello World!\n"
(gdb) info args
argc = 1
argv = (char **) 0xbffffaf4
4>> 如何使用断点?
断点是告诉调试器你想运行到程序代码的固定行的方法。你也可以在你的程序调用指定的函数处停止运行。一旦你的程序停止,你可以在内存中查看所有变量的值,检查栈,并单步执行程序。
4.1 如何在某一行上设置断点?
设置断点的命令是 break
。如果你只有一个源文件,你可以像这样设置一个断点:
(gdb) break 19
Breakpoint 1 at 0x80483f8: file test.c, line 19
如果不只一个文件,你必须给break
命令提供一个文件名:
(gdb) break test.c:19
Breakpoint 2 at 0x80483f8: file test.c, line 19
4.2 如何在一个C函数上设置一个断点?
在一个C函数上设置断点,要传递一个函数名给break
(gdb) break func1
Breakpoint 3 at 0x80483ca: file test.c, line 10
4.3 如何在一个C++函数上设置一个断点?
方法类似于在C函数设置断点。然而C++是多态的,因此你必须在设置断点时指定函数的版本(甚至只有一个函数也需要)。要做到这一点,你可以告诉它参数类型的列表。
(gdb) break TestClass::testFunc(int)
Breakpoint 1 at 0x80485b2: file cpptest.cpp, line 16.
4.4 如何设置一个临时断点?
使用tbreak
取代break
命令。一个临时断点只在断点处停止一次,然后会被移除。
4.5 如何获取断点列表?
使用info breakpoints
命令:
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep y 0x080483c3 in func2 at test.c:5
3 breakpoint keep y 0x080483da in func1 at test.c:10
4.6 如何禁用断点?
使用disable
命令,并指定一个参数,这个参数为你希望禁用的断点的序号。你可以在断点列表查找断点序号,具体方法在上面提过。在下面的例子中,我们可以看到断点2
被关闭(Enb
列有一个n
标识)
(gdb) disable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep n 0x080483c3 in func2 at test.c:5
3 breakpoint keep y 0x080483da in func1 at test.c:10
4.7 如何跳过一个断点?
想跳过一个断点固定的次数,我们可以使用ignore
命令,这个命令带有两个参数:待跳过的断点序号,跳过该断点的次数。
(gdb) ignore 2 5
Will ignore next 5 crossings of breakpoint 2.
5>> 如何使用监测点(watchpoints)?
监测点与断点类似。然而监测点不是为函数或是某一行代码准备的。监测点是为了变量准备的。当那些变量被读写时,监测点会被触发,程序会停止执行。
通过以上的描述理解watchpoints
会有点困难,因此接下来的示例程序会被作为命令使用例子。
#include <stdio.h>
int main(int argc, char **argv)
{
int x = 30;
int y = 10;
x = y;
return 0;
}
5.1 如何为一个变量设置一个write watchpoint
?
使用
watch
命令。watch
命令的参数是一个被评估的表达式。这意味着你想要设置一个watchpoint
的变量必须在当前范围内。因此,要在非全局变量上设置一个watchpoint
,你必须设置一个断点,当变量处于作用域时,它将停止你的程序。在程序中断之后,设置监视点。
NOTE: 在下面的示例中,你可能会注意到,打印的代码行与更改变量x的行不匹配,这是因为设置
watchpoint
的存储指令是执行“x=y”任务所需的最后一个序列。所以调试器已经进入下一行代码了。在示例中,在'main'函数中设置了一个断点,并被触发以停止该程序。
(gdb) watch x
Hardware watchpoint 4: x
(gdb) c
Continuing.
Hardware watchpoint 4: x
Old value = -1073743192
New value = 11
main (argc=1, argv=0xbffffaf4) at test.c:10
10 return 0;
5.2 如何为一个变量设置一个read watchpoint
?
使用rwatch
命令,用法与watch
命令相同
(gdb) rwatch y
Hardware read watchpoint 4: y
(gdb) continue
Continuing.
Hardware read watchpoint 4: y
Value = 1073792976
main (argc=1, argv=0xbffffaf4) at test.c:8
8 x = y;
5.3 如何为变量设置一个read/write
监测点?
使用awatch
命令,用法与watch
命令相同
5.4 如何关闭监测点?
激活的监测点显示在断点列表。使用 info breakpoints
命令来获得这个列表。然后使用disable
命令关闭一个watchpoint,就像禁用断点一样。
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c6 in main at test.c:5
breakpoint already hit 1 time
4 hw watchpoint keep y x
breakpoint already hit 1 time
(gdb) disable 4