[QuickStart] GDB调试快速入门

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看看发生了什么。

  1. 使用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的调试环境。

  1. 使用下面的命令调整显示宽度:
(gdb) set width 70
  1. 下面开始调试代码。我们猜测该bug可能与m4内置的函数changequote有关。看过源代码后,我们知道相关的程序是m4_changequote,因此我们使用GDB的break命令在此处设置断点:
(gdb) break m4_changequote
Breakpoint 1 at 0x62f4: file builtin.c, line 879.
  1. 使用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函数,即我们的断点),就不会触发断点。

  1. 为了触发断点,我们使用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))
  1. 使用命令n(next)来运行到当前函数的下一行代码:
(gdb) n
882         set_quotes((argc >= 2) ? TOKEN_DATA_TEXT(argv[1])\
 : nil,
  1. 这里,我们意识到set_quotes函数可能是问题的所在,因此,我们改为使用命令sstep)而非next来步入这个函数。
    -注意步进next和步入step的区别,步进是直接运行到当前代码的下一行。而步入的含义则是,若当前执行的代码是一个函数(对应着另一个代码块),则会将这段代码块入栈,然后从这段代码块开始执行。
(gdb) s
set_quotes (lq=0x34c78 "<QUOTE>", rq=0x34c88 "<UNQUOTE>")
    at input.c:530
530         if (lquote != def_lquote)
  1. 这里显示了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文件中的mainmain调用了macro.c文件中的expand_input函数,这个函数调用了同一文件下的expand_token函数,后者进一步调用了同一文件下的expand_macro函数,而expand_macro调用了builtin.c文件下的m4_changequote函数,也就是我们上面使用的changequote(<QUOTE>,<UNQUOTE>)那一段代码,我们怀疑这段代码有问题并设置了断点,并进一步步入了set_quotes这个函数。

-当前正处在set_quotes这段代码的开头。

  1. 我们往下运行几行来看发生了什么。在前两步,既可以使用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);
  1. 最后一行看着有些奇怪(l r似乎写反了)。我们可以检查变量lquoterquote的值来看它们是否就是我们定义的左引号<QUOTE>与右引号<UNQUOTE>。我们使用命令p (print)来打印变量的值:
(gdb) p lquote
$1 = 0x35d40 "<QUOTE>"
(gdb) p rquote
$2 = 0x35d50 "<UNQUOTE>"

这里lquoterquote的值没有问题,是我们使用changequote(<QUOTE>,<UNQUOTE>)定义的两个值。

  1. 使用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
  1. 我们目前处在538行代码处,使用两次next命令了来运行538,539两行代码,来为len_lquotelen_rquote变量赋值,之后再检查这两个变量的值是否正确:
(gdb) n
539         len_rquote = strlen(lquote);
(gdb) n
540     }
(gdb) p len_lquote
$3 = 9
(gdb) p len_rquote
$4 = 7
  1. 这个结果显然不对(反了)。我们可以使用p命令为这两个变量重新赋值。这里的原理是,print函数会显示后面紧跟着的表达式的值,而得到值的过程就是运行表达式并得到返回值的过程。这个表达式当然可以是一段子程序,或是一段赋值代码。因此,利用下面的命令,我们更改了程序中变量的值:
(gdb) p len_lquote=strlen(lquote)
$5 = 7
(gdb) p len_rquote=strlen(rquote)
$6 = 9
  1. 上面的修改是否足以纠正问题呢?我们可以使用c(continue)命令让m4程序继续运行(从而离开当前的断点),并尝试之前会报错的例子,看看是否还会继续报错。
(gdb) c
Continuing.

define(baz,defn(<QUOTE>foo<UNQUOTE>))

baz
0000
  1. 可以看到,这次定义宏baz成功了,效果和define(baz,defn('foo'))的效果相同。因此问题应该出在了定义两个长度len_lquotelen_rquote时的笔误。最后,为了退出m4程序,我们输入一个EOF。(这个就是m4本身的设计了,与GDB无关)
Ctrl-d
Program exited normally.
  1. 这里的信息 ‘Program exited normally.'来自GDB,告诉我们m4结束运行了。之后,我们可以使用quit命令来退出GDB环境:
(gdb) quit

小结

  1. 启动GDB环境,打开程序:gdb filename
  2. 在GDB环境下的基本调试命令:
    1. 设置断点: break 子程序名/函数名
    2. 启动程序: run
    3. 步进:nextn
    4. 步入:steps
    5. 打印变量的值: print 变量名p 变量名
    6. 更改变量的值:print 赋值表达式
    7. 离开断点,继续执行程序:continuec
    8. 退出GDB:quit
  3. 其他的一些命令:
    1. 显示当前代码上下文: listl
    2. 调节GDB环境的文本宽度:set width 宽度值
    3. 显示目前所处代码的栈结构:backtracebt
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值