前言
通过 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.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
下:
1 |
|
切换到目录 /mnt/d
下, 查看 cpp
目录下文件并使用 g++
编译,编译完成后将文件 mian.cpp
移动到 /mnt
目录下:
1 | albert@home-pc:/mnt/d$ ls cpp/ |
启动 gdb
调试程序并打好断点,输入 run
运行发现,断点被触发,但是显示出 No such file or directory.
,说明没有找到源代码文件。
1 | albert@home-pc:/mnt/d$ gdb -q main |
查看源代码文件名和编译目录
直接在 gdb
命令行中输入 info source
回车就可以了
1 | (gdb) info source |
通过这个命令发现,源代码文件是 /mnt/d/cpp/main.cpp
,编译目录是 /mnt/d
查看源代码搜索目录
在 gdb
环境下输入 show dir
命令就可以显示 SourcePathSet
这个集合中都有哪些目录,由于还没有设置过现在还是默认值 $cdir:$cwd
1 | (gdb) show dir |
查看当前目录
查看当前目录就比较简单了,直接 pwd
就搞定了
1 | (gdb) pwd |
我们“如愿以偿”的让 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
,目录结构和内容如下:
1 | albert@home-pc:/mnt/d$ tree /mnt/d/mainpro/ |
1 | //mainpro.cpp |
1 | //mymath.h |
1 | //mymath.cpp |
在 /mnt/d/mainpro
目录下编译代码,然后将代码文件所在目录 core
和 kit
拷贝到 /mnt/e/newpro
目录下,将可执行文件拷贝到 /home/albert
目录下。
1 | albert@home-pc:/mnt/d/mainpro$ g++ /mnt/d/mainpro/core/mainpro.cpp /mnt/d/mainpro/kit/mymath.cpp -g -o mainpro |
在 /home/albert
目录下启动 gdb
开始调试,先在 main
函数打断点,查询源文件路径和编译目录等信息;
1 | albert@home-pc:~$ gdb -q mainpro |
果然找不到源代码了,从上面的调试信息来看,可以得到以下信息:
- 源代码文件为
/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 | (gdb) dir /mnt/e/newpro/core/ |
这样就可以找到了,我们接着在 add
函数上下个断点,继续执行
1 | (gdb) b mymath::add |
结果发现又找不到文件 /mnt/d/mainpro/kit/mymath.cpp
了,因为和之前不是一个文件,这个文件在其他的目录下,所以还要使用 dir
命令,把新的目录加到源代码查找集合 SourcePathSet
中:
1 | (gdb) dir /mnt/e/newpro/kit/ |
这次又能成功找到了,可是如果有好多个文件要调试,难道要把所有的目录都加进去吗?其实可以有简便方法的,在启动 gdb
的时候可以指定搜索的源代码路径,这些路径都会被加到到源代码查找集合 SourcePathSet
中,具体操作如下,先退出gdb
,然后重新加参数启动如下:
1 | albert@home-pc:~$ gdb -q mainpro `find /mnt/e/newpro/ -type d -printf '-d %p '` |
其实这条命令的本来面目是 gdb -q mainpro -d xxxxx
,只不过这组合了 find
命令以后使用起来更加方便了,可以把指定目录下的子目录全都添加到参数中
使用 cd 命令解决
如果是临时调试倒是用不到上面设置启动参数那么麻烦,因为变量 $cwd
也在搜索集合中,既然在编译时记录的源文件被改变了位置,那么我们调整我们的当前位置,让代码出现搜索路径中,还是上面的这个例子:
1 | albert@home-pc:~$ pwd |
上面的操作通过 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 | albert@home-pc:~$ gdb -q mainpro |
通过调试信息 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