通过 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
查找代码也要遵循一定的规则,不能每次都全盘扫描吧,那不是得给它累死。举个例子吧,我们在安装一些软件,特别是一些命令行工具的时候,总是有一步要求你把工具或软件所在目录添加到环境变量中,这个变量的名字叫做 Path
。
这个 Path
其实就是电脑上众多软件所在目录的集合,当你直接使用软件的程序时,会优先从 Path
这个集合中的目录下去找,成功找到就会直接调用,否则提醒你软件不存在。
而在 gdb
的调试过程中也有这样一个目录集合,我暂且称它为 SourcePathSet
,后面就用这个名字了,因为还要涉及到多种查找目录,请注意区分。
gdb
在查找源码的时候首先在 SourcePathSet
中所包含的目录下找,如果找不到就会提示查找失败了,也就是这篇文章所提到的问题。
程序在编译的过程中会记录源文件的名字和路径,这个路径可能是绝对路径,比如 /mnt/d/main.cpp
,也可能是相对路径 ../main.cpp
,究竟是哪一种取决于编译时使用的参数。
我们以绝对路径为例,比如文件名为 /mnt/d/main.cpp
,我们可以把它拆分成包含路径和不包含路径两种形式:/mnt/d/main.cpp
和 main.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
,如果符合它的搜索规则,那么就可以找到了。
SourcePathSet
在 gdb
启动后开始生效,默认值并不是空,而是 $cdir:$cwd
,这又是什么鬼?其中的 $cdir
叫做编译目录,是代码在编译时记录到程序中的,$cwd
表示当前的调试目录,可以通过 cd
命令来修改,要注意这个 cd
修改的是 gdb
会话中的当前目录,不会影响启动 gdb
前文件系统中的目录位置。
假设 $cdir
的值是 /usr
,cwd
的值是 /home/albert
,我们又添加了 /mnt/e
到 SourcePathSet
中,那么此时 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
下:
#include
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
目录下:
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.
,说明没有找到源代码文件。
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
回车就可以了
(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
(gdb) show dir
Source directories searched: $cdir:$cwd
(gdb)
查看当前目录就比较简单了,直接 pwd
就搞定了
(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.cpp
,gdb
自然就找不到了,后面来看看具体怎么处理这种情况。
说了这么多原理的东西,如果弄明白了这些很容易找到解决问题的办法,下面写一个完整点的例子,来感受一些具体怎么修复这个问题,新建三个文件 mainpro.cpp
、mymath.h
、mymath.cpp
,目录结构和内容如下:
albert@home-pc:/mnt/d$ tree /mnt/d/mainpro/
/mnt/d/mainpro/
|-- core
| `-- mainpro.cpp
`-- kit
|-- mymath.cpp
`-- mymath.h
//mainpro.cpp
#include "../kit/mymath.h"
#include
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;
}
//mymath.h
class mymath
{
public:
int add(int a, int b);
};
//mymath.cpp
#include "mymath.h"
int mymath::add(int a, int b)
{
int c = a + b;
return c;
}
在 /mnt/d/mainpro
目录下编译代码,然后将代码文件所在目录 core
和 kit
拷贝到 /mnt/e/newpro
目录下,将可执行文件拷贝到 /home/albert
目录下。
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
函数打断点,查询源文件路径和编译目录等信息;
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
,下面来尝试一些解决方法。
刚才说了源代码查找集合 SourcePathSet
中只有 $cdir:$cwd
,我们可以自己加一个嘛,比如像下面这样:
(gdb) dir /mnt/e/newpro/core/
Source directories searched: /mnt/e/newpro/core:$cdir:$cwd
(gdb) list
2 #include
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
函数上下个断点,继续执行
(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
中:
(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
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
,然后重新加参数启动如下:
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
命令以后使用起来更加方便了,可以把指定目录下的子目录全都添加到参数中
如果是临时调试倒是用不到上面设置启动参数那么麻烦,因为变量 $cwd
也在搜索集合中,既然在编译时记录的源文件被改变了位置,那么我们调整我们的当前位置,让代码出现搜索路径中,还是上面的这个例子:
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
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 from-path to-path
这个命令就可以达到想要的目的,这个命令还可以简写成 set substitute from-path to-path
,比如还是前面的例子,源代码从 /mnt/d/mainrpo
目录整体移动到了 /mnt/e/newpro
目录,调试时找不到源代码可以使用 set substitute /mnt/d/mainrpo /mnt/e/newpro
命令来指定替换目录,这样就可以找到源代码啦,下面来测试一下:
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
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 目录
命令修改过的源代码搜索目录集合。当人的才华不足以撑起个人的欲望时就会感到焦虑,当面对不利的情况和事件却又无力改变时就会感到愤怒,而弱肉强食一直都是生活的本质,惟有强大才是解决这一切负面情绪的良药~