GDB调试指北-启动调试或者附加到进程

前言

要想使用 gdb 调试程序,必须让 gdb 程序和被调试程序建立联系,这种联系可以通过程序的可执行文件、core文件或者正在运行的进程来建立,具体调试的时候使用的选项不同,涉及到参数的传递,选项的顺序,多进程启动前的设置等等,接下来可以看一些常见用法。

测试样例

首先来写一段简单的但是会自动崩溃的代码,主要是为了展示core文件的调试方法,通过调试崩溃产生的core文件是一种很直接的查找问题的方法,可以帮助我们快速定位到问题的栈帧,进而找到具体的逻辑代码。

代码内容

新建文件 examplepro.cpp,编写代码内容如下:

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

int main(int argc, char* argv[])
{
if (argc > 1)
cout << "argv[1] = " << argv[1] << endl;

int a = 3, b = 4;
int c = a + b;

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

int *p = NULL;
*p = c;

return 0;
}

代码编译

1
g++ examplepro.cpp -o examplepro -g

运行程序

1
2
3
albert@home-pc:~/WorkSpace/cpp$ ./examplepro
c = 7
Segmentation fault (core dumped)

我们发现程序在运行之后发生了段错误,这是一种比较常见的BUG,通常由访问无效内存导致,查看程序目录下内容,多了一个叫 core 的文件。

1
2
albert@home-pc:~/WorkSpace/cpp$ ls
core examplepro examplepro.cpp

通过这一步你可能看不到这个 core 文件,需要检查两点,第一是编译的时候需要加 -g 选项,第二是使用 ulimit -c unlimited 命令设置core文件占用空间的最小限制,默认大小为0,也就是不产生 core 文件,需要改为 unlimited 才可以,如果你确定产生的 core 文件不会太大,也可以设置一个具体的数值。

使用gdb调试

有了上面的程序我们就可以进行调试了,因为已经产生了 core 文件,所以先来调试一下 core 文件,看下程序崩溃的原因。

使用gdb调试core文件

启动程序的语法如下,gdb 命令之后跟程序名,然后后面跟着 core 文件的名字:

1
gdb examplepro core

具体调试的时候需要换成自己的崩溃的程序名,而core文件大多数是 core.进程id 的形式。

调试过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
albert@home-pc:~/WorkSpace/cpp$ gdb examplepro core
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from examplepro...done.
[New LWP 19786]
Core was generated by `./examplepro'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000400932 in main (argc=1, argv=0x7ffd23cc3a18) at examplepro.cpp:15
15 *p = c;
(gdb)

从调试信息来看一下就定位到了问题,在代码的第15行发生了段错误,也就是我们刚刚给野指针赋值的代码。

使用gdb直接启动程序

这种情况就是调试运行,相当于在 gdb 的监控下启动程序,一旦发生错误,gdb 会给出响应的提示,启动方式很简单,gdb 命令之后直接跟着程序名字就可以了。

1
gdb examplepro

调试过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
albert@home-pc:~/WorkSpace/cpp$ gdb examplepro
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from examplepro...done.
(gdb) run
Starting program: /home/albert/WorkSpace/cpp/examplepro
c = 7

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400932 in main (argc=1, argv=0x7fffffffdd18) at examplepro.cpp:15
15 *p = c;
(gdb)

这种情况下,启动之后需要输入 run 命令才可以运行程序,这时发现程序又崩溃了。

如果被调试的程序有参数的话,需要将启动的命令进行修改,写成 gdb --args examplepro testparam1,加上 --args 选项,然后将参数罗列在后面就好了,因为看这些声明很麻烦,我们利用之前学过的 -q 选项来屏蔽启动说明,测试如下:

1
2
3
4
5
6
7
8
9
10
11
albert@home-pc:~/WorkSpace/cpp$ gdb -q --args examplepro NB
Reading symbols from examplepro...done.
(gdb) run
Starting program: /home/albert/WorkSpace/cpp/examplepro NB
argv[1] = NB
c = 7

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400932 in main (argc=2, argv=0x7fffffffdd08) at examplepro.cpp:15
15 *p = c;
(gdb)

还有一种写法就是启动 gdb 之后再传参数,具体操作方法如下:

1
2
3
4
5
6
7
8
9
10
11
albert@home-pc:~/WorkSpace/cpp$ gdb -q examplepro
Reading symbols from examplepro...done.
(gdb) run NB
Starting program: /home/albert/WorkSpace/cpp/examplepro NB
argv[1] = NB
c = 7

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400932 in main (argc=2, argv=0x7fffffffdd08) at examplepro.cpp:15
15 *p = c;
(gdb)

这种情况是先启动 gdb,然后在执行 run 命令的时候传递参数。

使用gdb调试正在运行的文件

这时需要获得被套是程序的进程id,可以使用 pstop 或者 pidof 命令来获取进程id,然后通过 attch 的方式附加到进程。

比如查到需要调试的 examplepro 程序进程号是 3598,那么可以直接启动 gdb 附加到这个进程:

1
gdb examplepro 3598

也可以先启动 gdb,然后使用 attach 命令附加到进程:

1
2
3
albert@home-pc:~/WorkSpace/cpp$ gdb -q examplepro
Reading symbols from examplepro...done.
(gdb) attach 3598

如果此时提示进程拒绝被附加通常是权限问题,可以使用所属账号调试,或者可以尝试 sudo 命令。

语法对比

常见的调试方式就文中提到的这几种,特整理成表格方便对比和查找:

语法 解释
gdb examlepro 直接 gdb 调试启动
gdb examlepro core.3598 调试崩溃的 core 文件
gdb examlepro 3598
gdb -p 3598
附加到正在运行的程序进程上
gdb
attach 3598
先启动gdb,后附加到程序上

总结

  • gdb 不但可以调试 core 文件,还可以调试正在运行的程序,这对于难重现的 bug 来说非常有帮助
  • 在调试正在运行的程序时可以使用 pidof 命令来直接获取被调试程序的进程号
  • gdb 调试附加的进程的时候要注意权限问题,如果不成功可以尝试 sudo 命令

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

兜兜转转又换了一个住所,匆匆忙忙如蝼蚁般迁徙,路程短了,可选的路却少了。回头看看,一个窝、一段事、一群人而已~

2020-8-25 00:24:01

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