Toc
  1. 一、gdb 常用的命令:
  2. 二、编写测试程序
  3. 三、调试程序
  4. 四、容易出现的问题
  5. 五、总结
Toc
0 results found
使用 gdb 调试程序
2013/04/17 Development Linux gdb

这篇文章来讲讲 Linux 下强大的调试工具 --gdb。

一、gdb 常用的命令:

跟在 Windows 下使用 Visual Studio 进行调试一样,gdb 的调试功能与其大同小异。

命令 功能 快捷方式
file 加载需要调试的可执行文件 file
kill 终止正在调试的程序 k
list 列出源代码的一部分 l
info 列出某个项目的信息 i
next 执行一行代码(不进入函数内部) n
step 执行下一行代码 s
continue 继续运行程序,直到下一个断点 c
run 执行已经加载的程序 r
quit 终止 gdb q
watch 监视某个变量的值 w
examine 监视某个变量的值 x
break 设置断点 b
disable / enable 使断点无效 / 有效 disable/enable
delete 删除断点 d
make 在不退出 gdb 的情况下,重新生成可执行文件 make
shell 在不退出 gdb 的情况下,使用 shell 命令 shell

这只是一部分哦。关于整个 gdb 调试可以写成一本书呢。

看到全称不要紧张,毕竟还是有些对英文不太感冒的同学,在 gdb 下按 Tab,gdb 也会帮你自动补全,避免误输入。而且按上下键也可以翻看历史命令。(这样不是个办法,还是好好学英语吧╮(╯_╰)╭)

还有一个办法,是使用简化的命令。这个方法我还是相当喜欢的。不过还是建议初学者使用全称,因为只使用首字母有时候会产生歧义,比如 disabledelete,gdb 在你输入 d 时,会默认调用delete,如果你想禁用某个断点,却输入了d 5,那这个断点就会被删除了。

当然,熟悉了之后,还是使用快捷命令吧,会提高很多的效率。

二、编写测试程序

因为要调试,偶要写一个有点 BUG 的程序,以方便调试,希望同学们不要学习我这种自残的行为。

[serious@localhost c]$ vim gdbtest.c
[serious@localhost c]$ cat gdbtest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void reverse(char* string)
{
int nLength = strlen(string);
char* temp = (char*)malloc(nLength + 1);

for(int i = 0; i < nLength; i++)
{
temp[i] = string[nLength - i];
}

strcpy(string, temp);

free(temp);
}

int main(int argc, char** argv)
{
char * str = malloc(20);
memset(str, 0, 20);
strcpy(str, "hello world!");

reverse(str);

printf("%s\n", str);

free(str);
return 0;
}

编译之:

[serious@localhost c]$gcc -g -o gdbtest gdbtest.c -std=c99  // 添加 -g 选项以产生调试信息

这个程序的用意是将一个字符串给逆序化。请无视渣算法,一切为了调试。

运行看看我们的程序:

[serious@localhost c]$ ./gdbtest
[serious@localhost c]$

咦?为什么没有输出呢?
那就来调试一下。

三、调试程序

因为刚才我们在编译程序时,已经加上了 -g 选项,就可以直接使用 gdb 进行调试了。

[serious@localhost c]$ gdb gdbtest 
GNU gdb (GDB) 7.5
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/serious/Desktop/c/gdbtest...done.
(gdb) // 这里就可以开始输入命令进行调试了

首先列出程序的内容(当然,你也可以另开一个终端界面使用 vim 的 set nu 功能显示行号)

(gdb) list 1 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void reverse(char* string)
{
int nLength = strlen(string);
char * temp = (char*)malloc(nLength);

for(int i = 0; i < nLength; i++)
```

list 命令会一次性列出 10 行源代码,list 后面加上 1 是确保可以从第一行开始显示源代码。

后面的就不列出了。我们在几个关键的位置加上断点:

``` bash
(gdb) break 7
Breakpoint 1 at 0x4006ac: file gdbtest.c, line 7.
(gdb) break 12
Breakpoint 2 at 0x4006d5: file gdbtest.c, line 12.
(gdb) b 15
Breakpoint 3 at 0x400707: file gdbtest.c, line 15.
(gdb) b 27
Breakpoint 4 at 0x400777: file gdbtest.c, line 27.
(gdb) b 29
Breakpoint 5 at 0x400783: file gdbtest.c, line 29.

然后看看我们所设置的断点:

(gdb) info break 
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004006ac in reverse at gdbtest.c:7
2 breakpoint keep y 0x00000000004006d5 in reverse at gdbtest.c:12
3 breakpoint keep y 0x0000000000400707 in reverse at gdbtest.c:15
4 breakpoint keep y 0x0000000000400777 in main at gdbtest.c:27
5 breakpoint keep y 0x0000000000400783 in main at gdbtest.c:29
  • Num 代表断点的序号,如果我们想要禁用某个断点,可以使用 disable n 命令;如果想要删除,则使用 delete n 命令。如果要全部禁用(或删除),则不需要带 n。

  • Type 代表断点的类型。有 breakpoint, watchpointcatchpoint三种类型。

    • breakpoint: 就是传说中的断点啦。

    • watchpoint: 监控变量(或表达式)

    • catchpoint: 监控事件。

  • Disp 代表当走到当前断点时,如果此断点已被禁用,是否要显示该信息。

  • Enb 是断点当前是否是启用的。启用为 y,禁用为 n。

  • Address 是断点的地址。每一个断点也是有分配相应的内存的。

  • What 是断点的详细信息,包括所在的函数名及所在源代码行数。

开始调试:

(gdb) run             // 运行程序
Starting program: /home/serious/Desktop/c/gdbtest
Breakpoint 4, main (argc=1, argv=0x7fffffffe1a8) at gdbtest.c:27
27 reverse(str);
(gdb) continue // 继续运行程序直到下一个断点
Continuing.
Breakpoint 1, reverse (string=0x601010 "hello world!") at gdbtest.c:7
7 int nLength = strlen(string);
(gdb) next // 执行一行代码
8 char * temp = (char*)malloc(nLength);
(gdb) print nLength // 查看 nLength 的值
$10 = 12 // 长度是正确的
(gdb) next
10 for(int i = 0; i < nLength; i++)
(gdb) next
Breakpoint 2, reverse (string=0x601010 "hello world!") at gdbtest.c:12
12 temp[i] = string[nLength - i];
(gdb) next
10 for(int i = 0; i < nLength; i++)
(gdb) next
Breakpoint 2, reverse (string=0x601010 "hello world!") at gdbtest.c:12
12 temp[i] = string[nLength - i];
(gdb) print temp // 查看 temp 的值
$11 = 0x601030 ""
(gdb) continue
Continuing.
Breakpoint 2, reverse (string=0x601010 "hello world!") at gdbtest.c:12
12 temp[i] = string[nLength - i];
(gdb) print temp
$12 = 0x601030 "" // 已经发现问题了,连续赋值两次,temp 却一直是""
(gdb)

程序的 BUG 在于,string[nLength - i]的值是 '\0',如果赋给 temp[0],则字符串 temp 的确就变成了 ""。

我们可以使用 examine 命令查看一下 temp 所在的内存区域:

(gdb) examine/12ub temp 
0x601030: 0 33 100 0 0 0 0 0
0x601038: 0 0 0 0

解释一下命令:

12ub 表示 temp 所在内存的显示方式,12u 代表显示 12 个计量单位,b 代表字节,所以此命令是显示以 temp 作为起始地址,向后显示 12 个字节的内容。可以看到,第一个字节是 0,第二个字节是 33,即 '!'。

更多格式,请 man gdb。

不需要退出程序,我们直接修改程序:

(gdb) shell 
[serious@localhost c]$ vim gdbtest.c
[serious@localhost c]$ gcc -g -o gdbtest gdbtest.c -std=c99
[serious@localhost c]$ exit
exit
(gdb)

将源代码第 12 行修改为:

temp[i] = string[nLength - i - 1];

再次调试到给 temp 赋值的部分,看一下 temp:

(gdb) p temp 
$13 = 0x601030 "!dlr"

有童鞋问了,老这样输出 temp 好烦啊,有什么其他办法吗?

当然有。使用 watch 命令。

在调试到第 10 行时,使用watch

(gdb) n 10		for(int i = 0; i < nLength; i++)
(gdb) watch temp[i]
Hardware watchpoint 12: temp[i]
(gdb) c
Continuing.
Hardware watchpoint 12: temp[i]
Old value = 0 '\000'
New value = 33 '!'
reverse (string=0x601010 "hello world!") at gdbtest.c:10
10 for(int i = 0; i < nLength; i++)
(gdb)

可以看到,当 temp[i]有变化时,gdb 会提示你,值有变化,从 '\000\ 变化到了'!'。

程序最后的运行结果:

[serious@localhost c]$ ./gdbtest
!dlrow olleh

四、容易出现的问题

1. 在使用 gdb 时,有时会出现这样的问题:

(gdb) n
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6.x86_64
(gdb) 

解决办法:编辑一下 debuginfo 的源,并使用 debuginfo-install 命令更新 debuginfo 库。

[serius@localhost c]$ vim /etc/yum.repos.d/CentOS-Base-debuginfo.repo: 
// 添加如下源
[base-debuginfo]
name=CentOS-$releasever -DebugInfo
baseurl=http://debuginfo.centos.org/$releasever/$basearch/
gpgcheck=0
enabled=0
protect=1
priority=1

2. 也有可能在调试你的程序时出现:

(gdb) n 
Single stepping until exit from function yourfunc, which has no line number information. 
(gdb) 

yourfunc 是你自己的函数,这表示着含有此函数的代码在编译时并没有加上 -g 选项。

还有一种可能,是你的 gcc 版本过高而 gdb 版本过低,比如我。CentOS6.4,4.8.0 版本 gcc,却是 7.2.0 版本的 gdb,无奈先升级了 gdb,才避免了这个错误。

3. 在 watch 变量时出现:

(gdb) watch var
No symbol "var" in current context. 

很明显了,gdb 在当前的代码域内找不到 var 变量,就会报这种错误。

五、总结

总的来说,gdb 是个强大的调试器,我在上面说的这一堆,也只是沧海一粟而已,gdb 的官方 pdf 文档已经堆到了 662 页。。我也不可能在一篇文章里说清楚,所以,如果你想知道更多,就去下载文档阅读吧。

凶猛的传送门

打赏
支付宝
微信
本文作者:CodingRabbit
版权声明:本文首发于CodingRabbit的博客,转载请注明出处!