查看原文
其他

GDB调试指南-源码查看

守望先生 编程珠玑 2021-02-01

相关内容

前言

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

准备工作

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

//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:

#ifndef _TEST_H
#define _TEST_H
#include<stdio.h>
void printNum(int n);
void printNum1(int n);
#endif

以及test.c:

#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++;
    }
}

编译运行:

$ 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),它用来打印源码。

直接打印源码

例如:

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后面可以跟行号,表明要列出附近的源码:

(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后面跟函数名即可,例如:

(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行:

(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行之间的源码:

(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)

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

(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的源码,如果想要看指定文件的源码呢?可以

l location  

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

(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)

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

(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) 

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

(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命令:

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

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

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

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

更换源码目录

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

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

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

(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,但是你的机器上可能没有这个编辑器,或者你想使用自己熟悉的编辑器,那么可以通过下面的方式进行设置:

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

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

$ 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,例如:

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

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

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

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

另外一种模式

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

gdb main -tui
GDB-TUI

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

总结

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

思考

test.h中#ifndef _TEST_H相关行的是什么?欢迎留言讨论。

最后,更多或最新内容可点击阅读原文。

关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存