GDB调试指南-源码查看

前言

我们在调试过程中难免要对照源码进行查看,如果已经开始了调试,而查看源码或者编辑源码却要另外打开一个窗口,那未免显得太麻烦。文本将会介绍如何在GDB调试模式下查看源码或对源码进行编辑

准备工作

为了说明后面的内容,我们先准备一些源码,分别是main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main.c
#include<stdio.h>
#include"test.h"
int main(void)
{
printf("it will print from 5 to 1\n");
printNum(5);
printf("print end\n");
printf("it will print 1 to 5\n");
printNum1(5);
printf("print end\n");
return 0;
}

头文件test.h:

1
2
3
4
5
6
#ifndef _TEST_H
#define _TEST_H
#include<stdio.h>
void printNum(int n);
void printNum1(int n);
#endif

以及test.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include"test.h"
void printNum(int n)
{
if( n < 0)
return;
while(n > 0)
{
printf("%d\n",n);
n--;
}
}
void printNum1(int n)
{
if( n < 0)
return;
int i = 1;
while(i <= n)
{
printf("%d\n",i);
i++;
}
}

编译运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ gcc -g -o main main.c test.c
$ chmod +x main
$ ./main
it will print from 5 to 1
5
4
3
2
1
print end
it will print 1 to 5
1
2
3
4
5
print end

程序功能比较简单,用来打印5到1的数以及1到5的数,这里也就不多做解释。

列出源码

首先要介绍的就是list命令(可简写为l),它用来打印源码。

直接打印源码

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ gdb main
(gdb) l
1 //main.c
2 #include<stdio.h>
3 #include"test.h"
4 int main(void)
5 {
6 printf("it will print from 5 to 1\n");
7 printNum(5);
8 printf("print end\n");
9
10 printf("it will print 1 to 5\n");
(gdb)

直接输入l可从第一行开始显示源码,继续输入l,可列出后面的源码。后面也可以跟上+或者-,分别表示要列出上一次列出源码的后面部分或者前面部分。

列出指定行附近源码

l后面可以跟行号,表明要列出附近的源码:

1
2
3
4
5
6
7
8
9
10
11
(gdb) l 9
4 int main(void)
5 {
6 printf("it will print from 5 to 1\n");
7 printNum(5);
8 printf("print end\n");
9
10 printf("it will print 1 to 5\n");
11 printNum1(5);
12 printf("print end\n");
13 return 0;

在这里,l后面跟上9,表明要列出第9行附近的源码。

列出指定函数附近的源码

这个很容易理解,而使用也很简单,l后面跟函数名即可,例如:

1
2
3
4
5
6
7
8
9
10
11
(gdb) l printNum
1 #include"test.h"
2 void printNum(int n)
3 {
4 if( n < 0)
5 return;
6 while(n > 0)
7 {
8 printf("%d\n",n);
9 n--;
10 }

在这里,l后面跟上函数名printNum,它便列出了printNum函数附近的源码。

设置源码一次列出行数

不知道你有没有发现,在列出函数源码的时候,它并没有列全,因为l每次只显示10行,那么有没有方法每次列出更多呢?
我们可以通过listsize属性来设置,例如设置每次列出20行:

1
2
3
(gdb) set listsize 20
(gdb) show listsize
Number of source lines gdb will list by default is 20.

这样每次就会列出20行,当然也可以设置为0或者unlimited,这样设置之后,列出就没有限制了,但源码如果较长,查看将会不便。

列出指定行之间的源码

list first,last
例如,要列出3到15行之间的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) l 3,15
3 {
4 if( n < 0)
5 return;
6 while(n > 0)
7 {
8 printf("%d\n",n);
9 n--;
10 }
11 }
12
13 void printNum1(int n)
14 {
15 if( n < 0)

启始行和结束行号之间用逗号隔开。两者之一也可以省略,例如:

1
2
3
4
5
6
7
8
9
10
11
(gdb) list 3,
3 {
4 if( n < 0)
5 return;
6 while(n > 0)
7 {
8 printf("%d\n",n);
9 n--;
10 }
11 }
12

省略结束行的时候,它列出从开始行开始,到指定大小行结束,而省略开始行的时候,到结束行结束,列出设置的大小行,例如默认设置为10行,则到结束行为止,总共列出10行。前面我们也介绍了修改和查看默认列出源码行数的方法。

列出指定文件的源码

前面执行l命令时,默认列出main.c的源码,如果想要看指定文件的源码呢?可以

1
l location

其中location可以是文件名加行号或函数名,因此可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) l test.c:1
1 #include"test.h"
2 void printNum(int n)
3 {
4 if( n < 0)
5 return;
6 while(n > 0)
7 {
8 printf("%d\n",n);
9 n--;
10 }
(gdb)

来查看指定文件指定行,或者指定文件指定函数:

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) l test.c:printNum1
9 n--;
10 }
11 }
12
13 void printNum1(int n)
14 {
15 if( n < 0)
16 return;
17 int i = 1;
18 while(i <= n)
(gdb)

或者指定文件指定行之间:

1
2
3
4
5
(gdb) l test.c:1,test.c:3
1 #include"test.h"
2 void printNum(int n)
3 {
(gdb)

指定源码路径

在查看源码之前,首先要确保我们的程序能够关联到源码,一般来说,我们在自己的机器上加上-g参数编译完之后,使用gdb都能查看到源码,但是如果出现下面的情况呢?

源码被移走

例如,我现在将main.c移动到当前的temp目录下,再执行l命令:

1
2
3
(gdb) l
1 main.c: No such file or directory.
(gdb)

它就会提示找不到源码文件了,那么怎么办呢?
我们可以使用dir命名指定源码路径,例如:

1
2
(gdb) dir ./temp
Source directories searched: /home/hyb/workspaces/gdb/sourceCode/./temp:$cdir:$cwd

这个时候它就能找到源码路径了。我这里使用的是相对路径,保险起见,你也可以使用绝对路径。

更换源码目录

例如,你编译好的程序文件,放到了另外一台机器上进行调试,或者你的源码文件全都移动到了另外一个目录,怎么办呢?当然你还可以使用前面的方法添加源码搜索路径,也可以使用set substitute-path from to将原来的路径替换为新的路径,那么我们如何知道原来的源码路径是什么呢?借助readelf命令可以知道:

1
2
3
4
5
6
$ readelf main -p .debug_str
[ 0] long unsigned int
[ 12] short int
[ 1c] /home/hyb/workspaces/gdb/sourceCode
[ 40] main.c
(显示部分内容)

main为你将要调试的程序名,这里我们可以看到原来的路径,那么我们现在替换掉它:

1
2
3
4
5
(gdb) set substitute-path /home/hyb/workspaces/gdb/sourceCode /home/hyb/workspaces/gdb/sourceCode/temp
(gdb) show substitute-path
List of all source path substitution rules:
`/home/hyb/workspaces/gdb/sourceCode' -> `/home/hyb/workspaces/gdb/sourceCode/temp'.
(gdb)

设置完成后,可以通过show substitute-path来查看设置结果。这样它也能在正确的路径查找源码啦。

需要注意的是,这里对路径做了字符串替换,那么如果你有多个路径,可以做多个替换。甚至可以对指定文件路径进行替换。

最后你也可以通过unset substitute-path [path]取消替换。

编辑源码

为了避免已经启动了调试之后,需要编辑源码,又不想退出,可以直接在gdb模式下编辑源码,它默认使用的编辑器是/bin/ex,但是你的机器上可能没有这个编辑器,或者你想使用自己熟悉的编辑器,那么可以通过下面的方式进行设置:

1
2
$ EDITOR=/usr/bin/vim
$ export EDITOR

/usr/bin/vim可以替换为你熟悉的编辑器的路径,如果你不知道你的编辑器在什么位置,可借助whereis命令或者witch命令查看:

1
2
3
4
$ whereis vim
vim: /usr/bin/vim /usr/bin/vim.tiny /usr/bin/vim.basic /usr/bin/vim.gnome /etc/vim /usr/share/vim /usr/share/man/man1/vim.1.gz
$ which vim
/usr/bin/vim

设置之后,就可以在gdb调试模式下进行编辑源码了,使用命令edit location,例如:

1
2
3
(gdb)edit 3 #编辑第三行
(gdb)edit printNum #编辑printNum函数
(gdb)edit test.c:5 #编辑test.c第五行

可自行尝试,这里的location和前面介绍的一样,可以跟指定文件的特定行或指定文件的指定函数。
编辑完保存后,别忘了重新编译程序:

1
(gdb)shell gcc -g -o main main.c test.c

这里要注意,为了在gdb调试模式下执行shell命令,需要在命令之前加上shell,表明这是一条shell命令。这样就能在不用退出GDB调试模式的情况下编译程序了。

另外一种模式

启动时,带上tui(Text User Interface)参数,会有意想不到的效果,它会将调试在多个文本窗口呈现:

1
gdb main -tui

GDB-TUI

但是本文不作介绍,有兴趣的可以探索一下。

总结

本文介绍了GDB调试中的源码查看,源码编辑以及如何在GDB调试模式下执行shell命令。

守望 wechat
关注[编程珠玑]获取更多原创技术文章
出入相友,守望相助!