使用GDB进行调试 -- 1 应用场景
标题的翻译我拿不准,不过说的是一个意思。GDB的功能很强大,使用很繁杂,但掌握其中一部分常用的命令就可以实现一些调试过程了。一般情况下,这些也就够了。
GNU m4的早期版本中有一个漏洞:有时候,将它的引用字符串(quote strings)从默认值改变时,用来在宏中抓取宏定义的命令工作不正常。下面基于m4,先将宏foo定义为0000,然后使用m4的内建defn定义bar成与foo相同。然而,当将开放引用字符串(open quote string)改为<QUOTE>,并将封闭引用字符串(close quote string)改为<UNQUOTE>,同样的步骤却会失败:
a. 成功的情况
$ cd gnu/m4
$ ./m4
define(foo,0000)
foo
0000
define(bar,defn('foo'))
bar
0000
从输出来看,两次定义都成功了。
b. 失败的情况
changequote(<QUOTE>,<UNQUOTE>)
define(baz,defn(<QUOTE>foo<UNQUOTE>))
baz
Ctrl-d
m4: End of input: 0: fatal error: EOF in string
c. 用GDB查看发生了什么
$ gdb m4
...
(gdb)
GDB只需要足够的符号数据来确定怎么找到其他数据就可以了,因此第一个提示符出现的非常快。现在告诉GDB使用更窄的显示宽度
(gdb)set width 70
因为想要直到内建的changequote是如何工作的,而从源程序中可以直到对应的子例程为m4_changequote,那么我们要使用break命令设置一个断点
(gdb) break m4_changequote
Breakpoint 1 at 0x62f4: file buitin.c, line 879.
使用run命令,可以让m4在GDB的控制下运行。只要程序没有运行到m4_changequote就会按正常执行
(gdb) run
Starting program: /work/Editorial/gdb/gnu/m4/m4
define(foo,0000)
foo
0000
调用changequote就会触发断点。触发断点以后GDB会挂起m4的执行,并小时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)进入子例程
(gdb) s
set_quotes (lq=0x34c78 "<QUOTE>", rq=0x34c88 "<UNQUOTE>")
at input.c:530
530 if (lquote != def_lquote)
显示子例程被挂起,子例程在调用一个栈帧显示。要想了解全部的栈帧关系,要用bt(backtrace)命令
//栈帧的概念我还不是很清楚,现在了解的就是一个子例程可能被多次调用,每次调用都会存储通信参数(栈帧)
(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
继续执行几次看看会发生什么。前面两步使用s,后面两步使用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);
最后一行看起来有点奇怪。可以通过查看变量lquote和rquote来看它们是不是我们制定的量。使用命令p(print)来看这些值
(gdb) p lquote
$ 1 = 0x35d40 "<QUOTE>"
(gdb) p rquote
$ 2 = 0x35d50 "<UNQUOTE>"
lquote和rquote确实是新的引用。要继续看一些上下文,可以用命令l(list)来查看当前代码附近的十行代码
(gdb) l
(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
再执行两行,然后看看两个设定的变量的值
(gdb) n
539 len_rquote = strlen(lquote);
(gdb) n
540 }
(gdb) p len_lquote
$ 3 = 9
(gdb) p len_rquote
$ 4 = 7
明显错了,假设len_lquote和len_rquote分别是lquote和rquote的长度,我们可以用p命令给它们赋上正确的数值(通过任意的表达式,包括调用子进程)
(gdb) p len_lquote=strlen(lquote) $5 = 7 (gdb) p len_rquote=strlen(rquote) $6 = 9
这样是不是就可以修复m4内建的defn引起的问题呢?现在使用命令c(continue)让m4连续执行,然后试着试试前面引起问题的例子
(gdb) c Continuing. define(baz,defn(<QUOTE>foo<UNQUOTE>)) baz 0000
成功了!新的引用可以和默认引用一样工作正常。问题看起来就是给两个类型定义了错误的长度。现在给m4输入一个EOF使它退出
Ctrl-d Program exited normally.
信息‘program exited normally.’ 是GDB给出的,说明m4已结束。现在可以使用名q(quit)退出GDB
(gdb) quit
浙公网安备 33010602011771号