[译文]RMS's gdb Debugger Tutorial

RMS's gdb Debugger Tutorial原文

"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 如何一行一行地单步执行代码?

首先发送信号或者使用断点停止你的程序,然后使用 nextstep 命令。

5   while(1){
(gdb) next
7   }
(gdb)

NOTE: nextstep 命令是不同的。一行代码中包含函数调用时,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 xchar *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

6>> gdb 高级用法

推荐阅读更多精彩内容

  • 程序调试的基本思想是“分析现象->假设错误原因->产生新的现象去验证假设”这样一个循环过程,根据现象如何假设错误原...
    Manfred_Zone阅读 9,329评论 0 20
  • 作者: liigo原文链接: http://blog.csdn.net/liigo/archive/2006/01...
    wuqingyi阅读 761评论 0 4
  • iOS包含许多“秘密”调试工具,包括环境变量、偏好、GCB的常规调用,等等。本技术说明描述了这些工具。如果你开发i...
    栗子烤肉阅读 1,751评论 1 7
  • 转载 与调试器共舞 - LLDB 的华尔兹: https://objccn.io/issue-19-2/ 推荐:i...
    F麦子阅读 1,573评论 0 9
  • 我亲爱的小马 等春天再近些的时候我们就搬家吧 我们把家安在草原上 满屋子都是真切的花香 ...
    流水竹屋阅读 85评论 9 5