gdb调试解决找不到源代码的问题

前言

通过 gdb 启动程序,打好断点运行,开始调试输入 list 命令,结果发现找不到源代码,是不是很糟心,让我们来看看怎么解决这种情况。

先來说明我们要处理的情况,调试程序找不到源代码首先你得有源代码,如果编译完程序你把源代码删了,或者单独把执行程序拷贝到一个没有源代码的机器上,那么拜拜吧您嘞,这种情况不是本文能解决的。

如果你确实有源代码,正常编译源代码并且加入了 -g 选项,编译完之后没有改变源代码位置,那么调试的时候基本都会找到源代码,所以这种情况也不在我们的讨论范围之内。

分析到现在就剩下一种情况,程序编译完成之后我移动了代码的位置。实际工作中可能不会这么无聊,故意改变目录位置让调试程序找不到,但是工作中常常会出现发布机编译完代码要在开发机调试的情况,两台机器上的代码时一样的,但是源代码的位置可能放置的不同,那么在个人开发机上调试这样的程序就会找不到源代码,这也就是我们要解决的问题。

找到源代码的必要性

其实在我看来找不到源代码的问题没有那么严重,编译程序里记录了文件名,行号等信息,可以在调试的时候对照着本地的源代码进行“盲调”,这种“盲调”的操作之前可没少干,因为线上环境中没有源代码,我只能一边对照着 gdb 调试输出的行号,一边对照本地的源代码进行程序分析,通过这种方法也解决了不少问题。

虽然看着源代码调试没有那么必要,但是如果可以看见那肯定是更好了,所以本文还是列举出最常见的处理方法,解决一下本来有代码,但因为目录不匹配无法正常调试的问题。

涉及到的命令

下面几个命令是 gdb 命令,注意要放到和 gdb 交互命令行输入才可以,别管会不会,先混个脸熟,以后要经常用的:

  • show dir
  • dir 目录
  • set dir 目录1:目录2:目录3
  • dir
  • pwd
  • cd 目录
  • set substitute-path from-path to-path

gdb怎样找源代码

有时候很奇怪,代码明明就在那里,gdb 你睁开眼睛行不行,为什么你就是找不到呢?其实 gdb 也很苦的好不好,一直帮你查问题还要忍受着你每天的埋怨,到底是什么原因导致 gdb 对眼前的代码视而不见呢?

其实 gdb 查找代码也要遵循一定的规则,不能每次都全盘扫描吧,那不是得给它累死。举个例子吧,我们在安装一些软件,特别是一些命令行工具的时候,总是有一步要求你把工具或软件所在目录添加到环境变量中,这个变量的名字叫做 Path

这个 Path 其实就是电脑上众多软件所在目录的集合,当你直接使用软件的程序时,会优先从 Path 这个集合中的目录下去找,成功找到就会直接调用,否则提醒你软件不存在。

源代码目录集合

而在 gdb 的调试过程中也有这样一个目录集合,我暂且称它为 SourcePathSet,后面就用这个名字了,因为还要涉及到多种查找目录,请注意区分。

gdb 在查找源码的时候首先在 SourcePathSet 中所包含的目录下找,如果找不到就会提示查找失败了,也就是这篇文章所提到的问题。

源代码文件

程序在编译的过程中会记录源文件的名字和路径,这个路径可能是绝对路径,比如 /mnt/d/main.cpp,也可能是相对路径 ../main.cpp ,究竟是哪一种取决于编译时使用的参数。

我们以绝对路径为例,比如文件名为 /mnt/d/main.cpp,我们可以把它拆分成包含路径和不包含路径两种形式:/mnt/d/main.cppmain.cpp,当 SourcePathSet 中包含一个路径叫 /mnt/e时, gdb 搜索的路径包括以下几种:

  • /mnt/d/main.cpp
  • /mnt/e/mnt/d/main.cpp
  • /mnt/e/main.cpp

当源文件是相对路径 ../main.cpp 的时候,那么搜索的路径就变成了下面两个:

  • /mnt/e/../main.cpp
  • /mnt/e/main.cpp

说到这里你可能就明白了,当 gdb 找不到源文件的时候,修改 SourcePathSet 就可以了,把想让它搜索的路径添加到 SourcePathSet,如果符合它的搜索规则,那么就可以找到了。

目录集合的默认值

SourcePathSetgdb 启动后开始生效,默认值并不是空,而是 $cdir:$cwd,这又是什么鬼?其中的 $cdir 叫做编译目录,是代码在编译时记录到程序中的,$cwd 表示当前的调试目录,可以通过 cd 命令来修改,要注意这个 cd 修改的是 gdb 会话中的当前目录,不会影响启动 gdb 前文件系统中的目录位置。

假设 $cdir 的值是 /usrcwd 的值是 /home/albert,我们又添加了 /mnt/eSourcePathSet 中,那么此时 SourcePathSet 的值为 /mnt/e:$cdir:$cwd,如果源文件的是 /mnt/d/main.cpp,查找的目录就会出现以下几种:

  • /mnt/d/main.cpp
  • /mnt/e/mnt/d/main.cpp
  • /usr/mnt/d/main.cpp
  • /home/albert/mnt/d/main.cpp
  • /mnt/e/main.cpp
  • /usr/main.cpp
  • /home/albert/main.cpp

查看各种目录

先做一下准备工作,编写一段简单代码,另存文件名为 main.cpp,保存在目录 /mnt/d/cpp 下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

int main()
{
int a = 1;
int b = 2;
int c = a + b;

cout << "c = " << c << endl;

return 0;
}

切换到目录 /mnt/d下, 查看 cpp 目录下文件并使用 g++ 编译,编译完成后将文件 mian.cpp 移动到 /mnt 目录下:

1
2
3
4
5
6
7
8
9
albert@home-pc:/mnt/d$ ls cpp/
main.cpp
albert@home-pc:/mnt/d$ g++ /mnt/d/cpp/main.cpp -g -o main
albert@home-pc:/mnt/d$ ls main
main
albert@home-pc:/mnt/d$ sudo mv cpp/main.cpp ../
[sudo] password for albert:
albert@home-pc:/mnt/d$ ls ../
c d e f main.cpp

启动 gdb 调试程序并打好断点,输入 run 运行发现,断点被触发,但是显示出 No such file or directory.,说明没有找到源代码文件。

1
2
3
4
5
6
7
8
9
albert@home-pc:/mnt/d$ gdb -q main
Reading symbols from main...done.
(gdb) b 8
Breakpoint 1 at 0x4008ac: file /mnt/d/cpp/main.cpp, line 8.
(gdb) run
Starting program: /mnt/d/main

Breakpoint 1, main () at /mnt/d/cpp/main.cpp:8
8 /mnt/d/cpp/main.cpp: No such file or directory.

查看源代码文件名和编译目录

直接在 gdb 命令行中输入 info source 回车就可以了

1
2
3
4
5
6
7
8
(gdb) info source
Current source file is /mnt/d/cpp/main.cpp
Compilation directory is /mnt/d
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb)

通过这个命令发现,源代码文件是 /mnt/d/cpp/main.cpp,编译目录是 /mnt/d

查看源代码搜索目录

gdb 环境下输入 show dir 命令就可以显示 SourcePathSet 这个集合中都有哪些目录,由于还没有设置过现在还是默认值 $cdir:$cwd

1
2
3
(gdb) show dir
Source directories searched: $cdir:$cwd
(gdb)

查看当前目录

查看当前目录就比较简单了,直接 pwd 就搞定了

1
2
3
(gdb) pwd
Working directory /mnt/d.
(gdb)

我们“如愿以偿”的让 gdb 找不到代码了,从现在的环境来看,$cdir$cwd 相同都是 /mnt/d,所以此时搜索的目录只有:

  • /mnt/d/cpp/main.cpp
  • /mnt/d/mnt/d/cpp/main.cpp
  • /mnt/d/main.cpp

而代码被我们移动到了/mnt/main.cppgdb 自然就找不到了,后面来看看具体怎么处理这种情况。

具体示例

说了这么多原理的东西,如果弄明白了这些很容易找到解决问题的办法,下面写一个完整点的例子,来感受一些具体怎么修复这个问题,新建三个文件 mainpro.cppmymath.hmymath.cpp,目录结构和内容如下:

1
2
3
4
5
6
7
albert@home-pc:/mnt/d$ tree /mnt/d/mainpro/
/mnt/d/mainpro/
|-- core
| `-- mainpro.cpp
`-- kit
|-- mymath.cpp
`-- mymath.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//mainpro.cpp
#include "../kit/mymath.h"
#include <iostream>
using namespace std;

int main()
{
int a = 1, b = 2;
mymath* m = new mymath();

int c = m->add(a, b);
cout << "c = " << c << endl;

return 0;
}
1
2
3
4
5
6
//mymath.h
class mymath
{
public:
int add(int a, int b);
};
1
2
3
4
5
6
7
8
//mymath.cpp
#include "mymath.h"

int mymath::add(int a, int b)
{
int c = a + b;
return c;
}

/mnt/d/mainpro 目录下编译代码,然后将代码文件所在目录 corekit 拷贝到 /mnt/e/newpro 目录下,将可执行文件拷贝到 /home/albert 目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
albert@home-pc:/mnt/d/mainpro$ g++ /mnt/d/mainpro/core/mainpro.cpp /mnt/d/mainpro/kit/mymath.cpp -g -o mainpro
albert@home-pc:/mnt/d/mainpro$ tree
.
|-- core
| `-- mainpro.cpp
|-- kit
| |-- mymath.cpp
| `-- mymath.h
`-- mainpro

2 directories, 4 files
albert@home-pc:/mnt/d/mainpro$ mkdir /mnt/e/newpro
albert@home-pc:/mnt/d/mainpro$ sudo mv core/ /mnt/e/newpro/
albert@home-pc:/mnt/d/mainpro$ sudo mv kit/ /mnt/e/newpro/
albert@home-pc:/mnt/d/mainpro$ mv mainpro /home/albert/

/home/albert 目录下启动 gdb 开始调试,先在 main 函数打断点,查询源文件路径和编译目录等信息;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
albert@home-pc:~$ gdb -q mainpro
Reading symbols from mainpro...done.
(gdb) b main
Breakpoint 1 at 0x4008de: file /mnt/d/mainpro/core/mainpro.cpp, line 7.
(gdb) run
Starting program: /home/albert/mainpro

Breakpoint 1, main () at /mnt/d/mainpro/core/mainpro.cpp:7
7 /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) info source
Current source file is /mnt/d/mainpro/core/mainpro.cpp
Compilation directory is /mnt/d/mainpro
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb) list
2 in /mnt/d/mainpro/core/mainpro.cpp
(gdb) pwd
Working directory /home/albert.
(gdb) show dir
Source directories searched: $cdir:$cwd
(gdb)

果然找不到源代码了,从上面的调试信息来看,可以得到以下信息:

  • 源代码文件为 /mnt/d/mainpro/core/mainpro.cpp
  • 程序编译目录为 /mnt/d/mainpro
  • 当前目录为 /home/albert

而源代码查找列表中只有 $cdir:$cwd,说明只包含 /mnt/d/mainpro/home/albert,那么查找的目录有:

  • /mnt/d/mainpro/core/mainpro.cpp
  • /mnt/d/mainpro/mnt/d/mainpro/core/mainpro.cpp
  • /home/albert/mnt/d/mainpro/core/mainpro.cpp
  • /mnt/d/mainpro/mainpro.cpp
  • /home/albert/mainpro.cpp

这些目录显然找不到源代码文件了,因为文件已经被我移动到 /mnt/e/newpro/ 目录下了,也就是 /mnt/e/newpro/core/mainpro.cpp,下面来尝试一些解决方法。

使用 dir 命令解决

刚才说了源代码查找集合 SourcePathSet 中只有 $cdir:$cwd,我们可以自己加一个嘛,比如像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) dir /mnt/e/newpro/core/
Source directories searched: /mnt/e/newpro/core:$cdir:$cwd
(gdb) list
2 #include <iostream>
3 using namespace std;
4
5 int main()
6 {
7 int a = 1, b = 2;
8 mymath* m = new mymath();
9
10 int c = m->add(a, b);
11 cout << "c = " << c << endl;
(gdb)

这样就可以找到了,我们接着在 add 函数上下个断点,继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) b mymath::add
Breakpoint 2 at 0x4009a6: file /mnt/d/mainpro/kit/mymath.cpp, line 6.
(gdb) c
Continuing.

Breakpoint 2, mymath::add (this=0x613c20, a=1, b=2) at /mnt/d/mainpro/kit/mymath.cpp:6
6 /mnt/d/mainpro/kit/mymath.cpp: No such file or directory.
(gdb) list
1 in /mnt/d/mainpro/kit/mymath.cpp
(gdb) info source
Current source file is /mnt/d/mainpro/kit/mymath.cpp
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb)

结果发现又找不到文件 /mnt/d/mainpro/kit/mymath.cpp 了,因为和之前不是一个文件,这个文件在其他的目录下,所以还要使用 dir 命令,把新的目录加到源代码查找集合 SourcePathSet 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) dir /mnt/e/newpro/kit/
Source directories searched: /mnt/e/newpro/kit:/mnt/e/newpro/core:$cdir:$cwd
(gdb) list
1 #include "../kit/mymath.h"
2 #include <iostream>
3 using namespace std;
4
5 int main()
6 {
7 int a = 1, b = 2;
8 mymath* m = new mymath();
9
10 int c = m->add(a, b);
(gdb)

这次又能成功找到了,可是如果有好多个文件要调试,难道要把所有的目录都加进去吗?其实可以有简便方法的,在启动 gdb的时候可以指定搜索的源代码路径,这些路径都会被加到到源代码查找集合 SourcePathSet 中,具体操作如下,先退出gdb,然后重新加参数启动如下:

1
2
3
4
5
albert@home-pc:~$ gdb -q mainpro `find /mnt/e/newpro/ -type d -printf '-d %p '`
Reading symbols from mainpro...done.
(gdb) show dir
Source directories searched: /mnt/e/newpro/kit:/mnt/e/newpro/core:/mnt/e/newpro:$cdir:$cwd
(gdb)

其实这条命令的本来面目是 gdb -q mainpro -d xxxxx,只不过这组合了 find 命令以后使用起来更加方便了,可以把指定目录下的子目录全都添加到参数中

使用 cd 命令解决

如果是临时调试倒是用不到上面设置启动参数那么麻烦,因为变量 $cwd 也在搜索集合中,既然在编译时记录的源文件被改变了位置,那么我们调整我们的当前位置,让代码出现搜索路径中,还是上面的这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
albert@home-pc:~$ pwd
/home/albert
albert@home-pc:~$ gdb -q mainpro
Reading symbols from mainpro...done.
(gdb) b main
Breakpoint 1 at 0x4008de: file /mnt/d/mainpro/core/mainpro.cpp, line 7.
(gdb) r
Starting program: /home/albert/mainpro

Breakpoint 1, main () at /mnt/d/mainpro/core/mainpro.cpp:7
7 /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) list
2 in /mnt/d/mainpro/core/mainpro.cpp
(gdb) cd /mnt/e/newpro/core/
Working directory /mnt/e/newpro/core.
(gdb) list
2 #include <iostream>
3 using namespace std;
4
5 int main()
6 {
7 int a = 1, b = 2;
8 mymath* m = new mymath();
9
10 int c = m->add(a, b);
11 cout << "c = " << c << endl;
(gdb)

上面的操作通过 cd /mnt/e/newpro/core/ 命令直接进入了源代码目录,当然就找到了,但是这还是会有点问题,当碰到需要调试好几个文件的时候就需要使用 cd 命令跳来跳去,要想一劳永逸,请看下面这个方法。

使用 set substitute-path 命令解决

我们移动源代码的时候往往会整个目录移动,或者说开发机和发布机上面的代码文件组织结构是一样,只是所在的磁盘位置是不一样的,所以如果可以设置用一个路径替换原代码文件的路径就好了, set substitute-path from-path to-path 这个命令就可以达到想要的目的,这个命令还可以简写成 set substitute from-path to-path,比如还是前面的例子,源代码从 /mnt/d/mainrpo 目录整体移动到了 /mnt/e/newpro 目录,调试时找不到源代码可以使用 set substitute /mnt/d/mainrpo /mnt/e/newpro 命令来指定替换目录,这样就可以找到源代码啦,下面来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
albert@home-pc:~$ gdb -q mainpro
Reading symbols from mainpro...done.
(gdb) set substitute-path /mnt/d/mainrpo /mnt/e/newpro
(gdb) b main
Breakpoint 1 at 0x4008de: file /mnt/d/mainpro/core/mainpro.cpp, line 7.
(gdb) run
Starting program: /home/albert/mainpro

Breakpoint 1, main () at /mnt/d/mainpro/core/mainpro.cpp:7
7 /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) cd /mnt/e/newpro/
Working directory /mnt/e/newpro.
(gdb) list
2 /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) set substitute-path /mnt/d/mainpro /mnt/e/newpro
(gdb) list 0
1 #include "../kit/mymath.h"
2 #include <iostream>
3 using namespace std;
4
5 int main()
6 {
7 int a = 1, b = 2;
8 mymath* m = new mymath();
9
10 int c = m->add(a, b);
(gdb) info source
Current source file is /mnt/d/mainpro/core/mainpro.cpp
Compilation directory is /mnt/d/mainpro
Located in /mnt/e/newpro/core/mainpro.cpp
Contains 14 lines.
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb) pwd
Working directory /home/albert.
(gdb)

通过调试信息 Located in /mnt/e/newpro/core/mainpro.cpp 可以看到,果然在新的位置找到了源代码。

总结

  • 调试的时候找不到源码有多种解决方法,需要根据实际情况选择最合适的解决方案。
  • 编译时使用绝对路径时,推荐使用 set substitute-path from-path to-path 的方式。
  • 编译时使用相对路径时,使用 set substitute from-path to-path 或者 dir new-path 都可以。
  • 对于临时查找一个问题,单独调试某一个文件时使用 cd 命令就可以搞定了。
  • 直接在 gdb 环境输入 dir 命令回车确认,可以重置 dir 目录 或者 set dir 目录 命令修改过的源代码搜索目录集合。

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

当人的才华不足以撑起个人的欲望时就会感到焦虑,当面对不利的情况和事件却又无力改变时就会感到愤怒,而弱肉强食一直都是生活的本质,惟有强大才是解决这一切负面情绪的良药~

2020-7-18 15:36:53

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客