在GDB官网有着详细的英文教程,然而完全读一遍教程费时费力。我们更希望能够快速入手,掌握基本的GDB使用。事实上,官网教程也提供了一个示范案例,借助这一案例,我们可以快速入门GDB。
下面的我们将该案例翻译成中文,随后我们对该案例使用到的GDB命令进行小结。
Quick Start
一个使用案例
-通过这个案例来了解GDB的基本使用:
GNU m4
(一个通用的宏处理器)的早先版本存在以下bug:有时候,当我们修改了引号的默认格式后,用于在一个宏定义中捕捉到其他宏定义的命令失效了。在下面的m4
程序片段中,我们定义了一个名为foo
的宏,这个宏的值是0000
;然后我们使用m4
内置的defn
函数来将bar
定义为有相同值的宏。然而,当我们将默认的左引号修改为<QUOTE>
、右引号修改为<UNQUOTE>
后,下面的步骤无法定义一个相同的宏baz
:
$ cd gnu/m4
$ ./m4
define(foo,0000)
foo
0000
define(bar,defn('foo'))
bar
0000
changequote(<QUOTE>,<UNQUOTE>)
define(baz,defn(<QUOTE>foo<UNQUOTE>))
baz
Ctrl-d
m4: End of input: 0: fatal error EOF in string
我们来用GDB看看发生了什么。
- 使用GDB打开可执行程序
m4
.
$ gdb m4
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 16.0.50.20240808-git, Copyright 1999 Free Software Foundation, Inc...
(gdb)
-这一步,我们使用GDB打开了m4
,随即出现了GDB启动时的一些信息(版权等)。之后我们进入了GDB的调试环境。
- 使用下面的命令调整显示宽度:
(gdb) set width 70
- 下面开始调试代码。我们猜测该bug可能与
m4
内置的函数changequote
有关。看过源代码后,我们知道相关的程序是m4_changequote
,因此我们使用GDB的break
命令在此处设置断点:
(gdb) break m4_changequote
Breakpoint 1 at 0x62f4: file builtin.c, line 879.
- 使用GDB的
run
命令以在GDB环境下运行m4
程序。程序会一直运行直到碰到我们在m4_changequote
这段代码处设置的断点:
(gdb) run
Starting program: /work/Editorial/gdb/gnu/m4/m4
define(foo,0000)
foo
0000
-这里,m4的运行方式是我们在终端中逐行输入代码并逐行运行,因此,只要我们不使用changequote
函数(其背后实现过程中使用了m4_changequote
函数,即我们的断点),就不会触发断点。
- 为了触发断点,我们使用
changequote
函数。此时GDB会停止执行m4
程序,并显示相关信息。
changequote(<QUOTE>,<UNQUOTE>)
Breakpoint 1, m4_changequote (argc=3, argv=0x33c70)
at builtin.c:879
879 if (bad_argc(TOKEN_DATA_TEXT(argv[0]),argc,1,3))
- 使用命令
n
(next
)来运行到当前函数的下一行代码:
(gdb) n
882 set_quotes((argc >= 2) ? TOKEN_DATA_TEXT(argv[1])\
: nil,
- 这里,我们意识到
set_quotes
函数可能是问题的所在,因此,我们改为使用命令s
(step
)而非next
来步入这个函数。
-注意步进next
和步入step
的区别,步进是直接运行到当前代码的下一行。而步入的含义则是,若当前执行的代码是一个函数(对应着另一个代码块),则会将这段代码块入栈,然后从这段代码块开始执行。
(gdb) s
set_quotes (lq=0x34c78 "<QUOTE>", rq=0x34c88 "<UNQUOTE>")
at input.c:530
530 if (lquote != def_lquote)
- 这里显示了
m4
目前暂停位置处的子程序(subroutine)及其参数,显示方式是以栈的结构显示的,但并不完整。我们可以使用backtrace
命令(简写为bt
)来看目前所处的代码栈的整体情况:
(gdb) bt
#0 set_quotes (lq=0x34c78 "<QUOTE>", rq=0x34c88 "<UNQUOTE>")
at input.c:530
#1 0x6344 in m4_changequote (argc=3, argv=0x33c70)
at builtin.c:882
#2 0x8174 in expand_macro (sym=0x33320) at macro.c:242
#3 0x7a88 in expand_token (obs=0x0, t=209696, td=0xf7fffa30)
at macro.c:71
#4 0x79dc in expand_input () at macro.c:40
#5 0x2930 in main (argc=0, argv=0xf7fffb20) at m4.c:195
-可以看到,这里有一个清晰的栈的结构:
-主程序是m4.c
文件中的main
,main
调用了macro.c
文件中的expand_input
函数,这个函数调用了同一文件下的expand_token
函数,后者进一步调用了同一文件下的expand_macro
函数,而expand_macro
调用了builtin.c
文件下的m4_changequote
函数,也就是我们上面使用的changequote(<QUOTE>,<UNQUOTE>)
那一段代码,我们怀疑这段代码有问题并设置了断点,并进一步步入了set_quotes
这个函数。
-当前正处在set_quotes
这段代码的开头。
- 我们往下运行几行来看发生了什么。在前两步,既可以使用
s
也可以使用n
(没有可步入的代码块,所以无差别);后面两步,我们使用n
以避免进入xstrdup
这段子程序。
(gdb) s
0x3b5c 532 if (rquote != def_rquote)
(gdb) s
0x3b80 535 lquote = (lq == nil || *lq == '\0') ? \
def_lquote : xstrdup(lq);
(gdb) n
536 rquote = (rq == nil || *rq == '\0') ? def_rquote\
: xstrdup(rq);
(gdb) n
538 len_lquote = strlen(rquote);
- 最后一行看着有些奇怪(l r似乎写反了)。我们可以检查变量
lquote
与rquote
的值来看它们是否就是我们定义的左引号<QUOTE>
与右引号<UNQUOTE>
。我们使用命令p
(print
)来打印变量的值:
(gdb) p lquote
$1 = 0x35d40 "<QUOTE>"
(gdb) p rquote
$2 = 0x35d50 "<UNQUOTE>"
这里lquote
与rquote
的值没有问题,是我们使用changequote(<QUOTE>,<UNQUOTE>)
定义的两个值。
- 使用
l
(list
)命令来看当前代码的上下文(显示上下总共十行源代码):
(gdb) l
533 xfree(rquote);
534
535 lquote = (lq == nil || *lq == '\0') ? def_lquote\
: xstrdup (lq);
536 rquote = (rq == nil || *rq == '\0') ? def_rquote\
: xstrdup (rq);
537
538 len_lquote = strlen(rquote);
539 len_rquote = strlen(lquote);
540 }
541
542 void
- 我们目前处在538行代码处,使用两次
next
命令了来运行538,539两行代码,来为len_lquote
与len_rquote
变量赋值,之后再检查这两个变量的值是否正确:
(gdb) n
539 len_rquote = strlen(lquote);
(gdb) n
540 }
(gdb) p len_lquote
$3 = 9
(gdb) p len_rquote
$4 = 7
- 这个结果显然不对(反了)。我们可以使用
p
命令为这两个变量重新赋值。这里的原理是,print
函数会显示后面紧跟着的表达式的值,而得到值的过程就是运行表达式并得到返回值的过程。这个表达式当然可以是一段子程序,或是一段赋值代码。因此,利用下面的命令,我们更改了程序中变量的值:
(gdb) p len_lquote=strlen(lquote)
$5 = 7
(gdb) p len_rquote=strlen(rquote)
$6 = 9
- 上面的修改是否足以纠正问题呢?我们可以使用
c
(continue
)命令让m4
程序继续运行(从而离开当前的断点),并尝试之前会报错的例子,看看是否还会继续报错。
(gdb) c
Continuing.
define(baz,defn(<QUOTE>foo<UNQUOTE>))
baz
0000
- 可以看到,这次定义宏
baz
成功了,效果和define(baz,defn('foo'))
的效果相同。因此问题应该出在了定义两个长度len_lquote
与len_rquote
时的笔误。最后,为了退出m4
程序,我们输入一个EOF。(这个就是m4
本身的设计了,与GDB无关)
Ctrl-d
Program exited normally.
- 这里的信息 ‘Program exited normally.'来自GDB,告诉我们
m4
结束运行了。之后,我们可以使用quit
命令来退出GDB环境:
(gdb) quit
小结
- 启动GDB环境,打开程序:
gdb filename
- 在GDB环境下的基本调试命令:
- 设置断点:
break 子程序名/函数名
; - 启动程序:
run
; - 步进:
next
或n
; - 步入:
step
或s
; - 打印变量的值:
print 变量名
或p 变量名
; - 更改变量的值:
print 赋值表达式
; - 离开断点,继续执行程序:
continue
或c
; - 退出GDB:
quit
;
- 设置断点:
- 其他的一些命令:
- 显示当前代码上下文:
list
或l
; - 调节GDB环境的文本宽度:
set width 宽度值
; - 显示目前所处代码的栈结构:
backtrace
或bt
;
- 显示当前代码上下文: