GDB调试手册

6/4/2022 知识

GDB调试手册

目录

0 用GDB调试程序

0.1 GDB概述

0.2 调试示例

0.2.1 源代码test1.c

0.2.2 调试过程

0.3 使用GDB

0.3.1 启动GDB

0.3.2 GDB环境中使用shell命令

0.3.3 在GDB中运行程序

1 设置断点

1.1 使用命令

1.2 示例

1.2.1 调试过程

2 程序的单步执行

2.1 使用命令

2.2 示例

2.2.1 调试过程

3 显示/修改变量的值

3.1使用命令

3.2示例

3.2.1 源代码test2.c

3.2.2 调试过程

4 监视程序变量值

4.1 使用命令

4.2 示例

4.2.1 调试过程

5 显示/修改寄存器

5.1 背景知识

5.2 使用命令

5.3 示例

5.2.1 调试过程

6 查看程序的堆栈

6.1 背景知识

6.2 使用命令

6.3 示例

6.3.1 源代码test3.c

6.3.2 调试过程

7 多线程调试

7.1 使用命令

7.2 示例

7.2.1 源代码test4.c

7.2.2 调试过程

8 死锁调试

8.1 使用命令

8.2 示例

8.2.1 源文件test5.c

8.2.2 调试过程

9 core文件调试

9.1 使用命令

9.2 示例

9.2.1 源代码test6.c

9.2.2 调试过程

# 0 用GDB调试程序

# 0.1 GDB概述

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。

一般来说,GDB主要帮忙你完成下面四个方面的功能:

1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。

2、调试的程序在你所指定的断点处停住。(断点可以是条件表达式)

3、程序被停住时,可以检查此时你的程序中所发生的事。

4、动态的改变你程序的执行环境。

从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。

# 0.2 调试示例

# 0.2.1 源代码test1.c

1.#include <stdio.h>  
2.  
3.int func(int n)  
4.{  
5.    int sum=0,i;  
6.    for(i=0; i<n; i++)  
7.    {     
8.        sum+=i;  
9.    }     
10.    return sum;  
11.}  
12.  
13.  
14.int main()  
15.{  
16.    int i = 0;  
17.    int j = 0;  
18.    int result = 0;  
19.    int m = 0;  
20.    int *p = &m;   
21.    for(i=1; i<=3; i++)  
22.    {     
23.        result += i;  
24.             
25.        for(j=1; j < 6; j++)  
26.        {     
27.            m++;  
28.        }     
29.    }     
30.  
31.    printf("result[1-100] = %d \n", result );  
32.    printf("result[1-250] = %d \n", func(250) );  
33.  
34.    return 0;  
35.}  
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

# 0.2.2 调试过程

编译生成执行文件:

root@luo:~/workspace/test/ exec\>gcc -g test1.c -o test1
使用GDB调试:
root@luo:~/workspace/test/exec\>gdb test1-------------- 启动GDB
GNU gdb (Wind River Linux Sourcery CodeBench 4.8-45) 7.6
Copyright (C) 2013 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-wrs-linux-gnu".
For bug reporting instructions, please see:
<support@windriver.com>...
Reading symbols from /home/luo/workspace/test/exec/test1...done.
(gdb) l 1-------------- 从第一行开始列出源码,l为list缩写
1	#include <stdio.h>
2	
3	int func(int n)
4	{
5		int sum=0,i;
6		for(i=0; i<n; i++)
7		{
8			sum+=i;
9		}
10		return sum;
(gdb) -------------- 直接回车表示重复上一次命令
11	}
12	
13	
14	int main()
15	{
16		int i = 0;
17		int j = 0;
18		int result = 0;
19		int m = 0;
20		int *p = &m;
(gdb) break 16 -------------- 设置断点,在源程序第16行
Breakpoint 1 at 0x400566: file test1.c, line 16.
(gdb) break func -------------- 设置断点,在func()函数入口处
Breakpoint 2 at 0x400537: file test1.c, line 5.
(gdb) info break -------------- 显示断点信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400566 in main at test1.c:16
2       breakpoint     keep y   0x0000000000400537 in func at test1.c:5
(gdb) r -------------- 运行程序,r为run简写
Starting program: /home/luo/workspace/test/exec/test1 
Breakpoint 1, main () at test1.c:16
16		int i = 0;
(gdb) n -------------- 单条语句执行,next命令简写
17		int j = 0;
(gdb) n
18		int result = 0;
(gdb) n
19		int m = 0;
(gdb) n
20		int *p = &m;
(gdb) c -------------- 继续运行程序,continue命令简写
Continuing.
result[1-100] = 6 -------------- 程序输出

Breakpoint 2, func (n=250) at test1.c:5
5		int sum=0,i;
(gdb) n
6		for(i=0; i<n; i++)
(gdb) n
8			sum+=i;
(gdb) p i -------------- 打印变量i的值,p为print命令简写
$1 = 0
(gdb) n
6		for(i=0; i<n; i++)
(gdb) n
8			sum+=i;
(gdb) p sum
$2 = 0
(gdb) n
6		for(i=0; i<n; i++)
(gdb) p i
$3 = 1
(gdb) n
8			sum+=i;
(gdb) n
6		for(i=0; i<n; i++)
(gdb) p sum
$4 = 3
(gdb) bt -------------- 查看函数堆栈
#0  func (n=250) at test1.c:6
#1  0x00000000004005dd in main () at test1.c:32
(gdb) finish -------------- 退出函数
Run till exit from #0  func (n=250) at test1.c:6
0x00000000004005dd in main () at test1.c:32
32		printf("result[1-250] = %d \n", func(250) );
Value returned is $5 = 31125
(gdb) c -------------- 继续运行
Continuing.
result[1-250] = 31125 -------------- 程序输出
[Inferior 1 (process 46602) exited normally] ------------ 程序退出,调试结束
(gdb) q -------------- 退出GDB
root@luo:~/workspace/test/exec\>
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

# 0.3 使用GDB

一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:

> cc -g hello.c -o hello

> g++ -g hello.cpp -o hello

如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。当你用-g把调试信息加入之后,并成功编译目标代码以后,让我们来看看如何用gdb来调试他。

# 0.3.1 启动GDB

启动GDB的方法有以下几种:

1、gdb <program>

program也就是你的执行文件,一般在当然目录下。

2、gdb <program> core

用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。

3、gdb <program> <PID>

如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。

GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb -help查看。下面只例举一些比较常用的参数:

-symbols <file>

-s <file>

从指定文件中读取符号表。

-se file

指定文件中读取符号表信息,并把他用在可执行文件中。

-core <file>

-c <file>

调试时core dump的core文件。

-directory <directory>

-d <directory>

加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。

# 0.3.2 GDB环境中使用shell命令

在gdb环境中,你可以执行UNIX的shell的命令,使用gdb的shell命令来完成:

shell <command string>

调用UNIX的shell来执行<command string>,环境变量SHELL中定义的UNIX的shell将会被用来执行<command string>,如果SHELL没有定义,那就使用UNIX的标准shell:/bin/sh。(在Windows中使用Command.com或cmd.exe)

还有一个gdb命令是make:

make <make-args>

可以在gdb中执行make命令来重新build自己的程序。这个命令等价于“shell make <make-args>”。

# 0.3.3 在GDB中运行程序

当以gdb <program>方式启动gdb后,gdb会在PATH路径和当前目录中搜索<program>的源文件。如要确认gdb是否读到源文件,可使用l或list命令,看看gdb是否能列出源代码。

在gdb中,运行程序使用r或是run命令。程序的运行,你有可能需要设置下面四方面的事。

1、程序运行参数。

set args 可指定运行时参数。(如:set args 10 20 30 40 50)

show args 命令可以查看设置好的运行参数。

2、运行环境。

path <dir> 可设定程序的运行路径。

show paths 查看程序的运行路径。

set environment varname [=value] 设置环境变量。如:set env USER=aaa

show environment [varname] 查看环境变量。

3、工作目录。

cd <dir> 相当于shell的cd命令。

pwd 显示当前的所在目录。

4、程序的输入输出。

info terminal 显示你程序用到的终端的模式。

使用重定向控制程序输出。如:run > outfile

tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb。

# 1 设置断点

# 1.1 使用命令

break linenum

在指定行号设置断点。

break function

在函数function的入口处设置断点。C++中可以使用class::function或function(type,type)格式来指定函数名。

break +offset/-offset

在当前行前面或后面offset行设置断点,offset为自然数。

break filename:linenum

在源文件filename的linenum行处设置断点。

break filename:function

在源文件filename的function函数的入口处设置断点。

break

break命令没有参数时,表示在下一条指令处设置断点。

break args if cond

设置一个带有条件cond的断点,只有条件为“真”时才停止。args代表上述参数之一。

delete [breakpoint num]

删除断点,breakpoint num为断点号。

tbreak args

设置仅停止一次的断点。args与上面break命令的相同。程序在该断点停止后,该断点便立即被删除。

# 1.2 示例

源代码test1.c 同上

# 1.2.1 调试过程

编译生成可执行文件,注意要带上 -g

root@luo:~/workspace/test/source\>gcc -g test.c -o ../exec/test

使用GDB调试:


root@luo:~/workspace/test/source\>gcc -g test.c -o ../exec/test
使用GDB调试:
root@luo:~/workspace/test/exec\>gdb test 
GNU gdb (Wind River Linux Sourcery CodeBench 4.8-45) 7.6
Copyright (C) 2013 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-wrs-linux-gnu".
For bug reporting instructions, please see:
<support@windriver.com>...
Reading symbols from /home/luo/workspace/test/exec/test...done.
(gdb) l 1 -------------- l为list的简写,从第一行开始列出源码
1	#include <stdio.h>
2	
3	int func(int n)
4	{
5		int sum=0,i;
6		for(i=0; i<n; i++)
7		{
8			sum+=i;
9		}
10		return sum;
(gdb) -------------- 直接回车,表示重复上一次命令
11	}
12	
13	
14	int main()
15	{
16		int i = 0;
17		int j = 0;
18		int result = 0;
19		int m = 0;
20		int *p = &m;
(gdb) 
21		for(i=1; i<=3; i++)
22		{
23			result += i;
24	    
25			for(j=1; j < 6; j++)
26			{
27				m++;
28			}
29		}
30	
(gdb) 
31		printf("result[1-100] = %d \n", result );
32		printf("result[1-250] = %d \n", func(250) );
33	
34		return 0;
35	}
(gdb) break main -------------- 设置断点,在函数main()入口处。
Breakpoint 1 at 0x400566: file test.c, line 17.
(gdb) break 21 -------------- 设置断点,在源程序第21行
Breakpoint 2 at 0x400585: file test.c, line 21.
(gdb) break test.c:func  -------------- 设置断点,在test.c文件的func()函数入口处
Breakpoint 3 at 0x400537: file test.c, line 5.
(gdb) r -------------- 运行程序,run命令简写
Starting program: /home/luo/workspace/test/exec/test 

Breakpoint 1, main () at test.c:17 ---------- 程序在main()入口停止
17		int j = 0;
(gdb) c -------------- 继续运行程序,continue命令的简写
Continuing.

Breakpoint 2, main () at test.c:22 -------------- 程序在22行停止
22		{
(gdb) tbreak 26  -------------- 设置临时断点,在26行
Temporary breakpoint 4 at 0x400594: file test.c, line 26.
(gdb) info b -------------- 查看断点信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400566 in main at test.c:17
	breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000400585 in main at test.c:21
	breakpoint already hit 1 time
3       breakpoint     keep y   0x0000000000400537 in func at test.c:5
4       breakpoint     del  y   0x0000000000400594 in main at test.c:26
(gdb) c -------------- 继续运行程序
Continuing.

Temporary breakpoint 4, main () at test.c:26  --------------程序在26行停止
26			{
(gdb) info b -------------- 再查看断点信息,临时断点已删除
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400566 in main at test.c:17
	breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000400585 in main at test.c:21
	breakpoint already hit 1 time
3       breakpoint     keep y   0x0000000000400537 in func at test.c:5
(gdb) break 27 if m = 10 ------------ 设置断点,在27行,当m=10时生效
Breakpoint 5 at 0x4005b1: file test.c, line 27.
(gdb) c -------------- 继续运行程序
Continuing.

Breakpoint 2, main () at test.c:22 ------------ 程序在22行停止
22		{
(gdb) c -------------- 继续运行程序
Continuing.

Breakpoint 2, main () at test.c:22
22		{
(gdb) c -------------- 继续运行程序
Continuing.

Breakpoint 5, main () at test.c:30 -------------- 程序在30行停止
30
(gdb) print "%d", m -------------- 打印m的值
$1 = 10 -------------- 此时m = 10
(gdb) c -------------- 继续运行程序
Continuing.
result[1-100] = 6 

Breakpoint 3, func (n=250) at test.c:5 ---------- 程序在func()函数入口处停止
5		int sum=0,i;
(gdb) tbreak +3 -------------- 在当前行+3行处设置临时断点
Temporary breakpoint 8 at 0x400547: file test.c, line 8.
(gdb) c -------------- 继续运行程序
Continuing.
Temporary breakpoint 8, func (n=250) at test.c:8
8			sum+=i;
(gdb) break -------------- 在下一条指令处设置断点
Breakpoint 9 at 0x400547: file test.c, line 8.
(gdb) c -------------- 继续运行程序
Continuing.
Breakpoint 9, func (n=250) at test.c:8 -------------- 在第8行停止
8			sum+=i;
(gdb) c -------------- 继续运行程序
Continuing.
Breakpoint 9, func (n=250) at test.c:8 -------------- 在第8行停止
8			sum+=i;
(gdb) clear --------------清楚所有断点
Deleted breakpoint 9 
(gdb) c -------------- 继续运行程序
Continuing.
result[1-250] = 31125 
(gdb)
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

# 2 程序的单步执行

# 2.1 使用命令

step <count>

单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是,此函数被编译有debug信息。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。

next <count>

同样单步跟踪,如果有函数调用,他不会进入该函数。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。

finish

运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息。

until 或 u

当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。

# 2.2 示例

源代码test1.c 同上

# 2.2.1 调试过程

root@luo:~/workspace/test/exec\>gdb -q test 
----- 启动GDB,-q使启动使版本信息不显示
Reading symbols from /home/luo/workspace/test/exec/test...done.
(gdb) break 18 -------------- 在第18行设置断点
Breakpoint 1 at 0x400574: file test.c, line 18.
(gdb) r -------------- 运行程序
Starting program: /home/luo/workspace/test/exec/test 

Breakpoint 1, main () at test.c:18 -------------- 在18行停止
18		int result = 0;
(gdb) n -------------- 下一条
19		int m = 0;
(gdb) n -------------- 下一条
20		int *p = &m;
(gdb) s -------------- 下一条
21		for(i=1; i<=3; i++)
(gdb) s -------------- 下一条
23			result += i;
(gdb) n -------------- 下一条
25			for(j=1; j < 6; j++)
(gdb) n -------------- 下一条 
27				m++;
(gdb) u -------------- 运行函数到退出当前循环
25			for(j=1; j < 6; j++)
(gdb) u -------------- 运行程序到退出当前循环
21		for(i=1; i<=3; i++)
(gdb) u -------------- 运行程序到退出当前循环
31		printf("result[1-100] = %d \n", result );
(gdb) s -------------- 下一条
result[1-100] = 6 
32		printf("result[1-250] = %d \n", func(250) );
(gdb) s -------------- step下一条
func (n=250) at test.c:5 -------------- 进入函数func()
5		int sum=0,i;
(gdb) finish -------------- 运行程序到函数完成
Run till exit from #0  func (n=250) at test.c:5
0x00000000004005dd in main () at test.c:32
32		printf("result[1-250] = %d \n", func(250) );
Value returned is $1 = 31125
(gdb) r -------------- 再次运行程序
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/luo/workspace/test/exec/test 
Breakpoint 1, main () at test.c:18 -------------- 程序在18行停止
18		int result = 0;
(gdb) break 30 -------------- 设置断点,在30行
Breakpoint 2 at 0x4005bf: file test.c, line 30.
(gdb) c -------------- 继续运行程序
Continuing.
Breakpoint 2, main () at test.c:31 -------------- 程序在31行停止
31		printf("result[1-100] = %d \n", result );
(gdb) n -------------- next下一条
result[1-100] = 6 
32		printf("result[1-250] = %d \n", func(250) );
(gdb) n -------------- next下一条
result[1-250] = 31125  -------------- 未进入函数func()
34		return 0;
(gdb)
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 3 显示/修改变量的值

# 3.1使用命令

print <expr>

print /<f> <expr>

当程序被停住时,可以使用print命令(简写为p),或同义命令inspect来查看当前程序的运行数据。<expr>是表达式,是你所调试的程序的语言的表达式(GDB可以调试多种编程语言),<f>是输出的格式,比如,如果要把表达式按16进制的格式输出,那么就是/x。

注意:GDB中,变量分为全局变量、静态全局变量、局部变量三种,当局部变量与全局变量发生重名时,用pirnt显示出的变量的值会是函数中的局部变量的值。此时如果要查看全局变量的值,可以使用“::”操作符:

file::variable

function::variable

可以通过这种形式查看某个文件或是某个函数的变量。

# 3.2示例

# 3.2.1 源代码test2.c

1.#include <stdio.h>  
2.  
3.int func(int m){   
4.    int n = 0;  
5.    int i = 0;  
6.    for(i = 0; i < m; i++){  
7.        n += m;  
8.    }     
9.  
10.    return n;  
11.
12.}  
13.  
14.int main(){  
15.    int i = 0;  
16.    int b = 0;  
17.    int n = 20;   
18.    int sum = 0;  
19.    sum = func(40);  
20.    for(i = 0; i < 10; i++){  
21.        n++;  
22.    }     
23.  
24.    return 0;  
}  
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

# 3.2.2 调试过程

编译生成可执行文件:

root@luo:~/workspace/test/source\>gcc -g test2.c -o ../exec/test2

启动GDB:


root@luo:~/workspace/test/source\>gcc -g test2.c -o ../exec/test2
启动GDB:
root@luo:~/workspace/test/exec\>gdb -q test2
Reading symbols from /home/luo/workspace/test/exec/test2...done.
(gdb) l 1
1	#include <stdio.h>
2	
3	int func(int m){
4		int n = 0;
5		int i = 0;
6		for(i = 0; i < m; i++){
7			n += m;
8		}
9	
10		return n;
(gdb) 
11	
12	}
13	
14	int main(){
15		int i = 0;
16		int b = 0;
17		int n = 20;
18		int sum = 0;
19		sum = func(40);
20		for(i = 0; i < 10; i++){
(gdb) 
21			n++;
22		}
23	
24		return 0;
25	}
(gdb) break func -------------- 设置断点,在func()函数入口处
Breakpoint 1 at 0x4004f7: file test2.c, line 4.
(gdb) break 21 -------------- 设置断点,在21行
Breakpoint 2 at 0x40055f: file test2.c, line 21.
(gdb) r -------------- 运行程序
Starting program: /home/luo/workspace/test/exec/test2 

Breakpoint 1, func (m=40) at test2.c:4
4		int n = 0;
(gdb) p n -------------- 在func()函数内,显示变量n的值
$1 = 0
(gdb) p main::n ------------ 在func()内,显示main()函数中n的值
$2 = 20 
(gdb) c -------------- 继续运行程序
Continuing.

Breakpoint 2, main () at test2.c:21
21			n++;
(gdb) p n -------------- 显示n的值
$3 = 20
(gdb) p n = 50 -------------- 修改n的值为50
$4 = 50
(gdb) p n -------------- 显示n的值
$5 = 50
(gdb) c -------------- 继续运行程序
Continuing.

Breakpoint 2, main () at test2.c:21
21			n++;
(gdb) p n ------------ 显示n的值,可以看到,n在修改基础上加1
$6 = 51
(gdb) p /x n ------------ 用16进制显示n值
$7 = 0x33 
(gdb)
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 4 监视程序变量值

# 4.1 使用命令

display <expr>

display/<fmt> <expr>

display/<fmt> <addr>

display命令可以设置一些自动显示的变量,当程序停住时,或是在单步跟踪时,这些变量会自动显示。expr是一个表达式,fmt表示显示的格式,addr表示内存地址,当用display设定好了一个或多个表达式后,只要程序被停下来,GDB会自动显示你所设置的这些表达式的值。

undisplay <dnums...>

delete display <dnums...>

删除自动显示,dnums意为所设置好了的自动显式的编号。如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)。

disable display <dnums...>

enable display <dnums...>

disable和enalbe不删除自动显示的设置,而只是让其失效和恢复。

info display

查看display设置的自动显示的信息,包括设置的编号,表达式,是否enable。

# 4.2 示例

源代码test2.c同上

# 4.2.1 调试过程

启动GDB:

root@luo:~/workspace/test/exec\>gdb -q test2

Reading symbols from /home/luo/workspace/test/exec/test2...done.

(gdb) l 1 -------------- 列出源代码

1 #include <stdio.h>

2

3 int func(int m){

4 int n = 0;

5 int i = 0;

6 for(i = 0; i < m; i++){

7 n += m;

8 }

9

10 return n;

(gdb)

11

12 }

13

14 int main(){

15 int i = 0;

16 int b = 0;

17 int n = 20;

18 int sum = 0;

19 sum = func(40);

20 for(i = 0; i < 10; i++){

(gdb)

21 n++;

22 }

23

24 return 0;

25 }

(gdb) break 21 -------------- 设置断点,在21行

Breakpoint 1 at 0x40055f: file test2.c, line 21.

(gdb) r -------------- 运行程序

Starting program: /home/luo/workspace/test/exec/test2

Breakpoint 1, main () at test2.c:21

21 n++;

(gdb) display n -------------- 跟踪变量n的值

1: n = 20

(gdb) display i -------------- 跟踪变量i的值

2: i = 0

(gdb) display n+i -------------- 跟踪n+i的值

3: n+i = 20

(gdb) display/x n -------------- 跟踪n的值,以16进制显示

4: /x n = 0x14

(gdb) display &n -------------- 跟踪n的地址

5: &n = (int *) 0x7fffffffe0a8

(gdb) c -------------- 继续运行程序

Continuing.

Breakpoint 1, main () at test2.c:21

21 n++;

5: &n = (int *) 0x7fffffffe0a8

4: /x n = 0x15

3: n+i = 22

2: i = 1

1: n = 21

(gdb) delete display 4 -------------- 删除4号跟踪

(gdb) c -------------- 继续运行程序

Continuing.

Breakpoint 1, main () at test2.c:21 ------------ 可以看到,4号没了

21 n++;

5: &n = (int *) 0x7fffffffe0a8

3: n+i = 24

2: i = 2

1: n = 22

(gdb) disable display 5 -------------- 使5号跟踪失效

(gdb) c -------------- 继续运行程序

Continuing.

Breakpoint 1, main () at test2.c:21 ------- 可以看到,5号也没显示

21 n++;

3: n+i = 26

2: i = 3

1: n = 23

(gdb) enable display 5 -------------- 使5号跟踪恢复

(gdb) c -------------- 继续运行程序

Continuing.

Breakpoint 1, main () at test2.c:21 ------ 可以看到,5号再次显示

21 n++;

5: &n = (int *) 0x7fffffffe0a8

3: n+i = 28

2: i = 4

1: n = 24

(gdb) info display -------------- 查看自动显示信息

Auto-display expressions now in effect:

Num Enb Expression

5: y &n

3: y n+i

2: y i

1: y n

(gdb) disable display 1 2 3 ----------- 使1,2,3号都失效

(gdb) c -------------- 继续运行程序

Continuing.

Breakpoint 1, main () at test2.c:21

21 n++;

5: &n = (int *) 0x7fffffffe0a8

(gdb) info display -------------- 查看自动显示信息

Auto-display expressions now in effect:

Num Enb Expression

5: y &n

3: n n+i

2: n i

1: n n

(gdb)

# 5 显示/修改寄存器

# 5.1 背景知识

先明确一点,这里指的是通用寄存器(后简称寄存器)。既然是通用的,使用并没有限制;后面介绍寄存器使用规则或者惯例,只是GCC/G++的规则。因为我们想对GCC编译的C/C++序进行分析,所以了解这些规则就很有帮助。

在体系结构教科书中,寄存器通常被说成寄存器文件,其实就是CPU上的一块存储区域,不过更喜欢使用标识符来表示,而不是地址而已。

X86-64中,所有寄存器都是64位,相对32位的x86来说,标识符发生了变化,比如:从原来的%ebp变成了%rbp。为了向后兼容性,%ebp依然可以使用,不过指向了%rbp的低32位。

X86-64寄存器的变化,不仅体现在位数上,更加体现在寄存器数量上。新增加寄存器%r8到%r15。加上x86的原有8个,一共16个寄存器。

刚刚说到,寄存器集成在CPU上,存取速度比存储器快好几个数量级,寄存器多了,GCC就可以更多的使用寄存器,替换之前的存储器堆栈使用,从而大大提升性能。

让寄存器为己所用,就得了解它们的用途,这些用途都涉及函数调用,X86-64有16个64位寄存器,分别是:

%rax,%rbx,%rcx,%rdx,%rsi,%rdi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。

其中:

%rax 作为函数返回值使用。

%rsp 栈指针寄存器,指向栈顶。

%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数…

%rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改。

%r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值。

# 5.2 使用命令

info registers

查看寄存器的情况。(不包括浮点寄存器)

info all-registers

查看所有寄存器的情况。(包括浮点寄存器)

info registers <regname …>

查看所指定的寄存器的情况。

set $<regname>=<value>

修改寄存器的值。

# 5.3 示例

源代码test2.c同上

# 5.2.1 调试过程

启动GDB:

root@luo:~/workspace/test/exec\>gdb -q test2

Reading symbols from /home/luo/workspace/test/exec/test2...done.

(gdb) b main -------------- 在main函数设置断点

Breakpoint 1 at 0x40052d: file test2.c, line 15.

(gdb) r -------------- 运行程序

Starting program: /home/luo/workspace/test/exec/test2

Breakpoint 1, main () at test2.c:15

15 int i = 0;

(gdb) s -------------- step单步执行

16 int b = 0;

(gdb)

17 int n = 20;

(gdb)

18 int sum = 0;

(gdb)

19 sum = func(40);

(gdb)

func (m=40) at test2.c:4 -------------- 进入*func()*函数了

4 int n = 0;

(gdb) i all-r -------------- 显示所有寄存器信息

rax 0x400525 4195621

rbx 0x0 0

rcx 0x0 0

rdx 0x7fffffffe1a8 140737488347560

rsi 0x7fffffffe198 140737488347544

rdi 0x28 40

rbp 0x7fffffffe090 0x7fffffffe090

rsp 0x7fffffffe090 0x7fffffffe090

r8 0x30555a3c60 207590407264

r9 0x305360ec80 207557291136

r10 0x7fffffffdf40 140737488346944

r11 0x3055221b00 207586728704

r12 0x4003b0 4195248

r13 0x7fffffffe190 140737488347536

r14 0x0 0

r15 0x0 0

rip 0x4004f7 0x4004f7 <func+7>

eflags 0x206 [ PF IF ]

cs 0x33 51

ss 0x2b 43

ds 0x0 0

es 0x0 0

fs 0x0 0

gs 0x0 0

st0 0 (raw 0x00000000000000000000)

st1 0 (raw 0x00000000000000000000)

---Type <return> to continue, or q <return> to quit---q

Quit -------------- 退出显示

(gdb) i r -------------- 显示寄存器信息,不包括浮点寄存器

rax 0x400525 4195621

rbx 0x0 0

rcx 0x0 0

rdx 0x7fffffffe1a8 140737488347560

rsi 0x7fffffffe198 140737488347544

rdi 0x28 40

rbp 0x7fffffffe090 0x7fffffffe090

rsp 0x7fffffffe090 0x7fffffffe090

r8 0x30555a3c60 207590407264

r9 0x305360ec80 207557291136

r10 0x7fffffffdf40 140737488346944

r11 0x3055221b00 207586728704

r12 0x4003b0 4195248

r13 0x7fffffffe190 140737488347536

r14 0x0 0

r15 0x0 0

rip 0x4004f7 0x4004f7 <func+7>

eflags 0x206 [ PF IF ]

cs 0x33 51

ss 0x2b 43

ds 0x0 0

es 0x0 0

fs 0x0 0

gs 0x0 0

(gdb) info registers rax -------------- 查看指定寄存器的信息

rax 0x400525 4195621

(gdb) n -------------- 单步执行

5 int i = 0;

(gdb)

6 for(i = 0; i < m; i++){

(gdb)

7 n += m;

(gdb) u -------------- 执行直至循环结束

6 for(i = 0; i < m; i++){

(gdb)

10 return n;

(gdb) i r rax -------------- 查看指定寄存器rax信息,其存放函数返回值

rax 0x28 40

(gdb) n -------------- 单步执行,此时已经return

12 }

(gdb) i r rax -------------- 查看rax信息,为1600

rax 0x640 1600

(gdb) set $rax=99 --------------rax值修改为99

(gdb) i r rax -------------- 再查看rax,为99

rax 0x63 99

(gdb) n -------------- 单步执行,此时已经退出函数

main () at test2.c:20

20 for(i = 0; i < 10; i++){

(gdb) p sum ----------- 显示sum值,其值应为刚刚*func()*函数的返回值

$1 = 99 ----------- 正确

(gdb)

# 6 查看程序的堆栈

# 6.1 背景知识

当你的程序到暂停到断点处时,你需要知道的第一件事就是知道程序停在哪儿,以及通过什么样的函数调用关系到达这里的。

每当你的程序调用一个函数,这个函数的相关信息将会生成,该信息包括这个函数在程序中的位置,函数的参数,以及被调用的局部变量的值。这些信息保存在一个数据块中,这个数据块叫做栈框。这些栈框位于内存中称为栈的区域。

当你的程序断点处停止,GDB查看堆栈的命令允许你看到所有函数相关的信息。

栈被划分为多个连续的块,每个块称为栈框;每个栈框都存有与调用函数相关的数据。栈框中包含了调用函数的参数,调用函数的局部变量。以及这个函数的执行的地址。

当程序开始执行时,栈上就只有一个栈框,也就是用来描述Main函数的。这个栈框称为初始化栈框或最外层栈框。每当调用一个函数,一个新的栈框将会生成。每当一个函数结束,该函数对应的栈框将会消除。如果一个函数是递归函数,这将会对同一个函数产生很多个栈框。正在执行函数的栈框将叫做最内层栈框。这个是所有存在栈框中最新创建的。在你的程序里,栈框通过它对应到的地址进行标识。一个栈框由多个字节组成,每个栈框都有自己的地址,当程序执行到这个栈框时。通常这个栈框地址存放在特定的寄存器中。

gdb为每个存在的栈框赋上一个标号。标号从0开始,0表示最里层的栈框。也就是正在被调用函数的栈框。这些标号在程序中并不存在,它是由gdb赋予的,gdb通过赋予这个标号来给gdb的命令指定哪个栈框。

# 6.2 使用命令

frame args

这个frame命令允许你从一个栈框移动到另一个栈框,然后可以打印出你选择的栈框,args要么是栈框的地址,要么是栈框的标号,如果没有参数,frame命令将会打印出当前的栈框。

select -frame

select -frame命令允许你从一个栈框移动到另外一个栈框而不打印栈框的信息,这个命令可以看作是frame命令的silent版本。

backtrace

bt

bt命令打印栈全部的backtrace,每个栈框显示一行。你可以在任何时间结束backtrace通过键入系统中断字符,ctrl+c。

backtrace n

bt n

bt n 命令将会显示最内层的n个栈框。

backtrace -n

bt -n

bt -n将会显示最外层的n个栈框。

backtrace full

bt full

bt full n

bt full -n

如果添加full这个参数的话,将会显示栈框中的局部变量。

frame n

f n

frame n(f n)将会选择栈框n,栈框0是最内层的栈框,也就是当前执行的函数,栈框1是调用最内层栈框的函数。最大的栈框就是main函数对应的栈框。

frame addr

f addr

通过栈框的地址选择栈框。如果栈框被bug破坏掉了,这将是主要的方法去查看栈框。栈框破坏掉了,gdb将不能通过栈框标号去定位栈框。另外,如果你的程序有多个栈,在多个栈切换时这中方法将很有用。

up n

up n将会朝外层栈框走n个。它的方向是朝最外层的栈框,也就是最大的栈框号。

down n

down n和up n是对应的,它将会朝最内层走n个栈框。

这些命令将会打印两行关于该栈框的信息,第一行将会显示栈框号,函数名,参数,以及源代码所在的文件名,和所在的行。

# 6.3 示例

# 6.3.1 源代码test3.c

  1. #include <stdio.h>
  2. int func4(int m)
  3. {
  4. int ret = m - m / 2;
  5. return ret;
  6. }
  7. int func3(int m)
  8. {
  9. int ret = m + func4(m);
  10. return ret;
  11. }
  12. int func2(int m)
  13. {
  14. int ret = m * func3(m);
  15. return ret;
  16. }
  17. int func1(int m)
  18. {
  19. int ret = func2(m) + 1;
  20. return ret;
  21. }
  22. int main()
  23. {
  24. int m = 40;
  25. int n = func1(m);
  26. return 0;
  27. }

# 6.3.2 调试过程

启动GDB:

root@luo:~/workspace/test/exec\>gdb -q test3

Reading symbols from /home/luo/workspace/test/exec/test3...done.

(gdb) b main -------------- 在main函数设置断点

Breakpoint 1 at 0x40057d: file ../source/test3.c, line 30.

(gdb) r -------------- 运行程序

Starting program: /home/luo/workspace/test/exec/test3

Breakpoint 1, main () at ../source/test3.c:30

30 int m = 40;

(gdb) s -------------- step单步执行

31 int n = func1(m);

(gdb) s

func1 (m=40) at ../source/test3.c:24

24 int ret = func2(m) + 1;

(gdb) s

func2 (m=40) at ../source/test3.c:18

18 int ret = m * func3(m);

(gdb) s

func3 (m=40) at ../source/test3.c:11

11 int ret = m + func4(m);

(gdb) s

func4 (m=40) at ../source/test3.c:5

5 int ret = m - m / 2;

(gdb) bt -------------- 显示栈信息

#0 func4 (m=40) at ../source/test3.c:5

#1 0x0000000000400527 in func3 (m=40) at ../source/test3.c:11

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

#3 0x000000000040056a in func1 (m=40) at ../source/test3.c:24

#4 0x000000000040058e in main () at ../source/test3.c:31

(gdb) frame 2 -------------- 跳到标号为2的栈框

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

18 int ret = m * func3(m);

(gdb) select-frame 2 ------------- 跳到标号为2的栈框并且不打印信息

(gdb) bt 3 -------------- 显示最内层的3个栈框

#0 func4 (m=40) at ../source/test3.c:5

#1 0x0000000000400527 in func3 (m=40) at ../source/test3.c:11

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

(More stack frames follow...)

(gdb) bt -3 -------------- 显示最外层的3个栈框

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

#3 0x000000000040056a in func1 (m=40) at ../source/test3.c:24

#4 0x000000000040058e in main () at ../source/test3.c:31

(gdb) bt full -------------- 显示栈信息,包括局部变量

#0 func4 (m=40) at ../source/test3.c:5

ret = 0

#1 0x0000000000400527 in func3 (m=40) at ../source/test3.c:11

ret = 0

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

ret = 0

#3 0x000000000040056a in func1 (m=40) at ../source/test3.c:24

ret = 0

#4 0x000000000040058e in main () at ../source/test3.c:31

m = 40

n = 0

(gdb) frame 2 -------------- 移动到标号为2的栈框

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

18 int ret = m * func3(m);

(gdb) bt

#0 func4 (m=40) at ../source/test3.c:5

#1 0x0000000000400527 in func3 (m=40) at ../source/test3.c:11

#2 0x0000000000400549 in func2 (m=40) at ../source/test3.c:18

#3 0x000000000040056a in func1 (m=40) at ../source/test3.c:24

#4 0x000000000040058e in main () at ../source/test3.c:31

(gdb) up 2 -------------- 向外层栈框移动2层

#4 0x000000000040058e in main () at ../source/test3.c:31

31 int n = func1(m);

(gdb) down 3 -------------- 向内层栈框移动3层

#1 0x0000000000400527 in func3 (m=40) at ../source/test3.c:11

11 int ret = m + func4(m);

(gdb)

# 7 多线程调试

# 7.1 使用命令

info threads

查看当前进程的线程。GDB会为每个线程分配一个ID, 后面操作线程的时候会用到这个ID。前面有*的是当前调试的线程。

thread <ID>

切换调试的线程为指定ID的线程。

break file.c:number thread all

在file.c文件第number行处为所有经过这里的线程设置断点。

set scheduler-locking off|on|step

在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的, 怎么只让被调试程序执行呢?

通过这个命令就可以实现这个需求。

off 不锁定任何线程,也就是所有线程都执行,这是默认值。

on 只有当前被调试程序会执行。

step 在单步的时候,除了next过一个函数的情况 (熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

thread apply ID1 ID2 command

让一个或多个线程执行GDB命令command。

thread apply all command

让所有被调试线程执行GDB命令command。

# 7.2 示例

# 7.2.1 源代码test4.c

  1. /*
  2. * 本文件为多线程调试测试准备
  3. */
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <pthread.h>
  7. #include <unistd.h>
  8. void func1()
  9. {
  10. int i = 0;
  11. while(1){
  12. i++;
  13. printf("**********************************\n");
  14. printf("this is function 1!\n");
  15. printf("this is %d time!\n", i);
  16. sleep(1);
  17. }
  18. return ;
  19. }
  20. void func2()
  21. {
  22. int i = 0;
  23. while(1){
  24. i++;
  25. printf("**********************************\n");
  26. printf("this is function 2!\n");
  27. printf("this is %d time!\n", i);
  28. sleep(1);
  29. }
  30. return ;
  31. }
  32. int main()
  33. {
  34. pthread_t td1, td2, td3;
  35. int ret_td1, ret_td2, ret_td3;
  36. ret_td1 = pthread_create(&td1, NULL, (void*)&func1, NULL);
  37. ret_td2 = pthread_create(&td2, NULL, (void*)&func2, NULL);
  38. ret_td3 = pthread_create(&td3, NULL, (void*)&func2, NULL);
  39. if(ret_td1 != 0){
  40. printf("线程1创建失败\n");
  41. }else{
  42. printf("线程1创建成功\n");
  43. }
  44. if(ret_td2 != 0){
  45. printf("线程2创建失败\n");
  46. }else{
  47. printf("线程2创建成功\n");
  48. }
  49. if(ret_td3 != 0){
  50. printf("线程3创建失败\n");
  51. }else{
  52. printf("线程3创建成功\n");
  53. }
  54. return 0;
  55. }

# 7.2.2 调试过程

编译生成可执行文件,注意带上 -lpthread

root@luo:~/workspace/test/exec\>gcc -g ../source/test4.c -o test4 -lpthread

启动GDB:

root@luo:~/workspace/test/exec\>gdb -q test4

Reading symbols from /home/luo/workspace/test/exec/test4...done.

(gdb) b main -------------- 在main()函数设置断点

Breakpoint 1 at 0x400776: file ../source/test4.c, line 42.

(gdb) l 1 -------------- 列出源代码

1 /*

2 * 本文件为多线程调试测试准备

3 */

4 #include <stdio.h>

5 #include <stdlib.h>

6 #include <pthread.h>

7 #include <unistd.h>

8

9 void func1()

10 {

(gdb)

11 int i = 0;

12 while(1){

13 i++;

14 printf("**********************************\n");

15 printf("this is function 1!\n");

16 printf("this is %d time!\n", i);

17 sleep(1);

18 }

19

20 return ;

(gdb)

21 }

22

23 void func2()

24 {

25 int i = 0;

26 while(1){

27 i++;

28 printf("**********************************\n");

29 printf("this is function 2!\n");

30 printf("this is %d time!\n", i);

(gdb)

31 sleep(1);

32 }

33 return ;

34 }

35

36

37 int main()

38 {

39 pthread_t td1, td2, td3;

40 int ret_td1, ret_td2, ret_td3;

(gdb)

41

42 ret_td1 = pthread_create(&td1, NULL, (void*)&func1, NULL);

43 ret_td2 = pthread_create(&td2, NULL, (void*)&func2, NULL);

44 ret_td3 = pthread_create(&td3, NULL, (void*)&func2, NULL);

45

46 if(ret_td1 != 0){

47 printf("线程1创建失败\n");

48 }else{

49 printf("线程1创建成功\n");

50 }

(gdb)

51

52 if(ret_td2 != 0){

53 printf("线程2创建失败\n");

54 }else{

55 printf("线程2创建成功\n");

56 }

57

58 if(ret_td3 != 0){

59 printf("线程3创建失败\n");

60 }else{

(gdb)

61 printf("线程3创建成功\n");

62 }

63

64 return 0;

65 }

(gdb) b 18 -------------- 在18行设置断点

Breakpoint 2 at 0x400725: file ../source/test4.c, line 18.

(gdb) b 32 -------------- 在32行设置断点

Breakpoint 3 at 0x40076c: file ../source/test4.c, line 32.

(gdb) b 46 -------------- 在46行设置断点

Breakpoint 4 at 0x4007d0: file ../source/test4.c, line 46.

(gdb) r -------------- 运行程序

Starting program: /home/luo/workspace/test/exec/test4

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, main () at ../source/test4.c:42

42 ret_td1 = pthread_create(&td1, NULL, (void*)&func1, NULL);

(gdb) info threads -------------- 查看线程信息,目前只有一个主线程

Id Target Id Frame

* 1 Thread 0x7ffff7dd2700 (LWP 40907) "test4" main ()

at ../source/test4.c:42

(gdb) c -------------- 继续运行程序

Continuing.

[New Thread 0x7ffff7dd0700 (LWP 40913)] -------------- 新线程创建

**********************************

[New Thread 0x7ffff75cf700 (LWP 40914)] -------------- 新线程创建

this is function 1!

this is 1 time!

[New Thread 0x7ffff6dce700 (LWP 40915)] -------------- 新线程创建

**********************************

Breakpoint 4, main () at ../source/test4.c:46

46 if(ret_td1 != 0){

(gdb) info threads -------------- 查看线程信息

Id Target Id Frame

4 Thread 0x7ffff6dce700 (LWP 40915) "test4" 0x00000030552d795d in write () from /lib64/libc.so.6

3 Thread 0x7ffff75cf700 (LWP 40914) "test4" __lll_lock_wait_private ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95

2 Thread 0x7ffff7dd0700 (LWP 40913) "test4" 0x00000030552b5a0d in nanosleep () from /lib64/libc.so.6

* 1 Thread 0x7ffff7dd2700 (LWP 40907) "test4" main ()

at ../source/test4.c:46

(gdb) set scheduler-locking on -------------- 设置调试模式为只有当前线程会被执行

(gdb) thread 4 -------------- 切换到4号线程

[Switching to thread 4 (Thread 0x7ffff6dce700 (LWP 40915))]

#0 0x00000030552d795d in write () from /lib64/libc.so.6

(gdb) c -------------- 当前线程执行continue

Continuing.

this is function 2!

this is 1 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

(gdb) c

Continuing.

**********************************

this is function 2!

this is 2 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

(gdb) thread 3 -------------- 切换线程

[Switching to thread 3 (Thread 0x7ffff75cf700 (LWP 40914))]

#0 __lll_lock_wait_private ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95

95 ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.

(gdb) c

Continuing.

**********************************

this is function 2!

this is 1 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

(gdb) c

Continuing.

**********************************

this is function 2!

this is 2 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

(gdb) thread 2 -------------- 切换线程

[Switching to thread 2 (Thread 0x7ffff7dd0700 (LWP 40913))]

#0 0x00000030552f081b in ?? () from /lib64/libc.so.6

(gdb) c

Continuing.

**********************************

this is function 1!

this is 2 time!

Breakpoint 2, func1 () at ../source/test4.c:18

18 }

(gdb) c

Continuing.

**********************************

this is function 1!

this is 3 time!

Breakpoint 2, func1 () at ../source/test4.c:18

18 }

(gdb) info threads -------------- 查看线程信息,当前位于2号线程

Id Target Id Frame

4 Thread 0x7ffff6dce700 (LWP 40915) "test4" func2 ()

at ../source/test4.c:32

3 Thread 0x7ffff75cf700 (LWP 40914) "test4" func2 ()

at ../source/test4.c:32

* 2 Thread 0x7ffff7dd0700 (LWP 40913) "test4" func1 ()

at ../source/test4.c:18

1 Thread 0x7ffff7dd2700 (LWP 40907) "test4" main ()

at ../source/test4.c:46

(gdb) thread apply 2 4 continue ----- 令2号和4号线程执行continue

Thread 2 (Thread 0x7ffff7dd0700 (LWP 40913)):

Continuing.

**********************************

this is function 1!

this is 4 time!

Breakpoint 2, func1 () at ../source/test4.c:18

18 }

Thread 4 (Thread 0x7ffff6dce700 (LWP 40915)):

Continuing.

**********************************

this is function 2!

this is 3 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

(gdb) thread apply all continue ------------- 所有线程执行continue

Thread 4 (Thread 0x7ffff6dce700 (LWP 40915)):

Continuing.

**********************************

this is function 2!

this is 4 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

Thread 3 (Thread 0x7ffff75cf700 (LWP 40914)):

Continuing.

**********************************

this is function 2!

this is 3 time!

Breakpoint 3, func2 () at ../source/test4.c:32

32 }

Thread 2 (Thread 0x7ffff7dd0700 (LWP 40913)):

Continuing.

**********************************

this is function 1!

this is 5 time!

Breakpoint 2, func1 () at ../source/test4.c:18

18 }

Thread 1 (Thread 0x7ffff7dd2700 (LWP 40907)):

Continuing.

线程1创建成功

线程2创建成功

线程3创建成功

[Thread 0x7ffff6dce700 (LWP 40915) exited]

[Thread 0x7ffff75cf700 (LWP 40914) exited]

[Thread 0x7ffff7dd0700 (LWP 40913) exited]

[Inferior 1 (process 40907) exited normally]

(gdb)

# 8 死锁调试

# 8.1 使用命令

ps -ef|grep <processname>

查看进程号,其中processname为进程名。

gdb attach <PID>

使用GDB调试正在运行的进程。

# 8.2 示例

# 8.2.1 源文件test5.c

  1. /*
  2. *本文件用于测试死锁调试
  3. *
  4. *代码来自网络:http://blog.chinaunix.net/uid-30343738-id-5757210.html
  5. *
  6. */
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <unistd.h>
  10. #include <string.h>
  11. #include <pthread.h>
  12. pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
  13. pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
  14. pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
  15. pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;
  16. static int sequence1 = 0;
  17. static int sequence2 = 0;
  18. int func1()
  19. {
  20. pthread_mutex_lock(&mutex1);
  21. ++sequence1;
  22. sleep(1);
  23. pthread_mutex_lock(&mutex2);
  24. ++sequence2;
  25. pthread_mutex_unlock(&mutex2);
  26. pthread_mutex_unlock(&mutex1);
  27. return sequence1;
  28. }
  29. int func2()
  30. {
  31. pthread_mutex_lock(&mutex2);
  32. ++sequence2;
  33. sleep(1);
  34. pthread_mutex_lock(&mutex1);
  35. ++sequence1;
  36. pthread_mutex_unlock(&mutex1);
  37. pthread_mutex_unlock(&mutex2);
  38. return sequence2;
  39. }
  40. void* thread1(void* arg)
  41. {
  42. while (1)
  43. {
  44. int iRetValue = func1();
  45. if (iRetValue == 100000)
  46. {
  47. pthread_exit(NULL);
  48. }
  49. }
  50. }
  51. void* thread2(void* arg)
  52. {
  53. while (1)
  54. {
  55. int iRetValue = func2();
  56. if (iRetValue == 100000)
  57. {
  58. pthread_exit(NULL);
  59. }
  60. }
  61. }
  62. void* thread3(void* arg)
  63. {
  64. while (1)
  65. {
  66. sleep(1);
  67. char szBuf[128];
  68. memset(szBuf, 0, sizeof(szBuf));
  69. strcpy(szBuf, "thread3");
  70. }
  71. }
  72. void* thread4(void* arg)
  73. {
  74. while (1)
  75. {
  76. sleep(1);
  77. char szBuf[128];
  78. memset(szBuf, 0, sizeof(szBuf));
  79. strcpy(szBuf, "thread3");
  80. }
  81. }
  82. int main()
  83. {
  84. pthread_t tid[4];
  85. if (pthread_create(&tid[0], NULL, &thread1, NULL) != 0)
  86. {
  87. _exit(1);
  88. }
  89. if (pthread_create(&tid[1], NULL, &thread2, NULL) != 0)
  90. {
  91. _exit(1);
  92. }
  93. if (pthread_create(&tid[2], NULL, &thread3, NULL) != 0)
  94. {
  95. _exit(1);
  96. }
  97. if (pthread_create(&tid[3], NULL, &thread4, NULL) != 0)
  98. {
  99. _exit(1);
  100. }
  101. sleep(5);
  102. //pthread_cancel(tid[0]);
  103. pthread_join(tid[0], NULL);
  104. pthread_join(tid[1], NULL);
  105. pthread_join(tid[2], NULL);
  106. pthread_join(tid[3], NULL);
  107. pthread_mutex_destroy(&mutex1);
  108. pthread_mutex_destroy(&mutex2);
  109. pthread_mutex_destroy(&mutex3);
  110. pthread_mutex_destroy(&mutex4);
  111. return 0;
  112. }

# 8.2.2 调试过程

执行程序,程序会卡在这里:

root@luo:~/workspace/test/exec\>./test5

另开一个终端,查看进程的信息:

root@luo:~/workspace/test/exec\>ps -ef|grep test5

root 42746 39442 0 14:25 ttyp0 00:00:00 ./test5

root 43464 42753 0 15:11 ttyp2 00:00:00 grep --color=auto test5

使用GDB工具attach该进程:

root@luo:~/workspace/test/exec\>gdb -q attach 42746

attach: No such file or directory.

Attaching to process 42746

Reading symbols from /home/luo/workspace/test/exec/test5...done.

Reading symbols from /lib64/libpthread.so.0...done.

[New LWP 42750]

[New LWP 42749]

[New LWP 42748]

[New LWP 42747]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib64/libthread_db.so.1".

Loaded symbols for /lib64/libpthread.so.0

Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.

Loaded symbols for /lib64/libc.so.6

Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.

Loaded symbols for /lib64/ld-linux-x86-64.so.2

0x00007fc3aa9ff492 in pthread_join (threadid=140478357907200, thread_return=0x0)

at pthread_join.c:92

92 pthread_join.c: No such file or directory.

(gdb) info threads ------------- 查看线程信息

Id Target Id Frame

5 Thread 0x7fc3aa9f2700 (LWP 42747) "test5" __lll_lock_wait ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

4 Thread 0x7fc3aa1f1700 (LWP 42748) "test5" __lll_lock_wait ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

3 Thread 0x7fc3a99f0700 (LWP 42749) "test5" 0x00000030552b5a0d in nanosleep

() from /lib64/libc.so.6

2 Thread 0x7fc3a91ef700 (LWP 42750) "test5" 0x00000030552b5a0d in nanosleep

() from /lib64/libc.so.6

* 1 Thread 0x7fc3aa9f4700 (LWP 42746) "test5" 0x00007fc3aa9ff492 in pthread_join (threadid=140478357907200, thread_return=0x0) at pthread_join.c:92

(gdb) set scheduler-locking on --------- 设置调试模式为只有当前线程会执行

(gdb) thread apply all bt ------------- 显示所有线程的栈信息

Thread 5 (Thread 0x7fc3aa9f2700 (LWP 42747)):

#0 __lll_lock_wait ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007fc3aaa00457 in _L_lock_913 () from /lib64/libpthread.so.0

#2 0x00007fc3aaa00280 in __GI___pthread_mutex_lock (mutex=0x6012c0 <mutex2>)

at ../nptl/pthread_mutex_lock.c:79

#3 0x00000000004008b1 in func1 () at test5.c:27

#4 0x000000000040094e in thread1 (arg=0x0) at test5.c:52

#5 0x00007fc3aa9fe092 in start_thread (arg=0x7fc3aa9f2700)

at pthread_create.c:309

#6 0x00000030552e42bd in clone () from /lib64/libc.so.6

Thread 4 (Thread 0x7fc3aa1f1700 (LWP 42748)):

#0 __lll_lock_wait ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007fc3aaa00457 in _L_lock_913 () from /lib64/libpthread.so.0

#2 0x00007fc3aaa00280 in __GI___pthread_mutex_lock (mutex=0x601280 <mutex1>)

at ../nptl/pthread_mutex_lock.c:79

#3 0x000000000040090d in func2 () at test5.c:40

#4 0x000000000040097c in thread2 (arg=0x0) at test5.c:65

#5 0x00007fc3aa9fe092 in start_thread (arg=0x7fc3aa1f1700)

at pthread_create.c:309

#6 0x00000030552e42bd in clone () from /lib64/libc.so.6

Thread 3 (Thread 0x7fc3a99f0700 (LWP 42749)):

#0 0x00000030552b5a0d in nanosleep () from /lib64/libc.so.6

#1 0x00000030552b58a4 in sleep () from /lib64/libc.so.6

#2 0x00000000004009b0 in thread3 (arg=0x0) at test5.c:78

#3 0x00007fc3aa9fe092 in start_thread (arg=0x7fc3a99f0700)

at pthread_create.c:309

#4 0x00000030552e42bd in clone () from /lib64/libc.so.6

Thread 2 (Thread 0x7fc3a91ef700 (LWP 42750)):

#0 0x00000030552b5a0d in nanosleep () from /lib64/libc.so.6

#1 0x00000030552b58a4 in sleep () from /lib64/libc.so.6

#2 0x00000000004009f5 in thread4 (arg=0x0) at test5.c:89

#3 0x00007fc3aa9fe092 in start_thread (arg=0x7fc3a91ef700)

at pthread_create.c:309

#4 0x00000030552e42bd in clone () from /lib64/libc.so.6

---Type <return> to continue, or q <return> to quit---

Thread 1 (Thread 0x7fc3aa9f4700 (LWP 42746)):

#0 0x00007fc3aa9ff492 in pthread_join (threadid=140478357907200,

thread_return=0x0) at pthread_join.c:92

#1 0x0000000000400af1 in main () at test5.c:119

从栈信息可以初步判断,有可能是4号和5号线程锁住了。

查看27行和40行源文件的代码:

(gdb) l test5.c:27

22 int func1()

23 {

24 pthread_mutex_lock(&mutex1);

25 ++sequence1;

26 sleep(1);

27 pthread_mutex_lock(&mutex2);

28 ++sequence2;

29 pthread_mutex_unlock(&mutex2);

30 pthread_mutex_unlock(&mutex1);

31

(gdb) l test5.c:40

35 int func2()

36 {

37 pthread_mutex_lock(&mutex2);

38 ++sequence2;

39 sleep(1);

40 pthread_mutex_lock(&mutex1);

41 ++sequence1;

42 pthread_mutex_unlock(&mutex1);

43 pthread_mutex_unlock(&mutex2);

44

看一下4号和5号线程是不是锁住了:

(gdb) thread 5 ------------- 切换到5号线程

[Switching to thread 5 (Thread 0x7fc3aa9f2700 (LWP 42747))]

#0 __lll_lock_wait ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135 ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.

(gdb) n ------------- 执行下一步,卡住,Ctrl+C跳出

^C

Program received signal SIGINT, Interrupt.

__lll_lock_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135 in ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S

(gdb) thread 2 ------------- 切换到2号线程

[Switching to thread 2 (Thread 0x7fc3a91ef700 (LWP 42750))]

#0 0x00000030552b5a0d in nanosleep () from /lib64/libc.so.6

(gdb) n ------------- 执行下一步,可正常执行

Single stepping until exit from function nanosleep,

which has no line number information.

0x00000030552b58a4 in sleep () from /lib64/libc.so.6

(gdb) n

Single stepping until exit from function sleep,

which has no line number information.

thread4 (arg=0x0) at test5.c:91

91 memset(szBuf, 0, sizeof(szBuf));

(gdb) thread 4 ------------- 切换到4号线程

[Switching to thread 4 (Thread 0x7fc3aa1f1700 (LWP 42748))]

#0 __lll_lock_wait ()

at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135 ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.

(gdb) n ------------- 执行下一步,卡住

^C^C

Program received signal SIGINT, Interrupt.

__lll_lock_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135 in ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S

(gdb)

以上方法可调试找出程序中出现死锁的地方。

# 9 core文件调试

# 9.1 使用命令

ulimit -c

查看core文件生成的大小限制,若为0,则表示不会生成core文件。(注:ulimit不会影响其他shell终端)

ulimit -c <size>

设置core文件生成的大小。

cat /proc/sys/kernel/core_pattern

查看core文件路径。

gdb <a.out> <corefile>

调试core文件

# 9.2 示例

# 9.2.1 源代码test6.c

一个能产生core dump的程序

  1. #include <stdio.h>
  2. static void sub(void);
  3. int main(void)
  4. {
  5. sub();
  6. return 0;
  7. }
  8. static void sub(void)
  9. {
  10. int *p = NULL;
  11. /* derefernce a null pointer, expect coredump. */
  12. printf("%d", *p);
  13. }

# 9.2.2 调试过程

查看core文件是否开启:

root@luo:~/workspace/test/exec\>ulimit -c

0

设置core文件大小:

root@luo:~/workspace/test/exec\>ulimit -c 1024

root@luo:~/workspace/test/exec\>ulimit -c

1024

查看core文件路径:

root@luo:~/workspace/test/exec\>cat /proc/sys/kernel/core_pattern

/SE/core-%e-%p

root@luo:~/workspace/test/exec\>ls /SE

ai core--22330 core--4231 log ngtp_agent web_disk

core--21300 core--23374 core--5227 mysql mp.log

执行程序,生成core文件:

root@luo:~/workspace/test/exec\>./test6

Segmentation fault (core dumped)

查看是否生成core文件:

root@luo:~/workspace/test/exec\>ls /SE

ai core--23374 core-test6-44938 ngtp_agent

core--21300 core--4231 log mp.log

core--22330 core--5227 mysql web_disk

使用GDB调试:

root@luo:~/workspace/test/exec\>gdb -q test6 /SE/core-test6-44938

Reading symbols from /home/luo/workspace/test/exec/test6...done.

[New LWP 44938]

Core was generated by `./test6'.

Program terminated with signal 11, Segmentation fault.

#0 0x0000000000400554 in sub () at test6.c:12

12 printf("%d", *p);

(gdb) bt ------------- 显示栈信息

#0 0x0000000000400554 in sub () at test6.c:12

#1 0x0000000000400539 in main () at test6.c:5

(gdb)

可以看到最后入栈的是sub函数里、位于test6.c文件第12行。所以core dump的问题可能出在这里。

# 10 gdb调试

# 10.1 命令介绍

gdb脚本基于gdb命令实现,可在虚拟机和设备上直接运行。使用此脚本可批量对多个进程执行给定命令,也可以调试-i指定的非进程。

命令 gdb
命令格式 gdb [-s|-p] [-w] [-F 'SEPERATOR'] [-i '_PIDS'] [-e '_PIDS'] -c|-f 'CMD_ARGS'
参数说明 -s 串行执行CMD(依次对每个执行CMD;默认选项) -p 并行执行CMD(同时对每个执行CMD) -w 等待所有上都执行CMD完毕,否则任意执行完CMD都将停止其他上的执行(仅对-p有效) -i 使用指定的 pids(多个pid以空格分隔),若不指定则自动扫描所有进程 -e 剔除指定的 pids(多个pid以空格分隔) -c 命令列表(多条命令以';'或-F指定的分隔符进行分隔) -f 命令文件(对于带有分支、循环等复杂控制的多条命令,推荐采用命令文件形式) -F 命令列表中的分隔符(默认为';')

# 10.2 使用示例

  1. 获取mempool状态。(PID应为master或nae的pid)

    gdb -c 'call rte_mempool_list_dump(stdout)' -i <PID>

    gdb -c 'call cfg_callback_test_coredump(0,0)' -i $pid

    gdb -c '' -i $pid

  2. 临时在指定位置添加打印函数。

    gdb -p -c 'b test.c:45; commands; echo ----will bug_on----\n; bt; c; end; c'

  3. 验证RCU是否执行session释放。

    gdb -p -c 'handle all noprint nostop; b _free_mobj if mid==session_mobj; commands; info thread; bt; end; c'

  4. 显示所有进程的calltrace信息及本地变量。

    说明:对于多、无断点场景下的私有数据、状态,可串行顺序执行。

    gdb -c 'bt full'

  5. 显示所有进程/线程的calltrace信息。(类似于tdb的info state)

说明:对于多、无断点场景下的私有数据、状态,可串行顺序执行。

gdb -c 'thread apply all bt'

  1. 查看每个进程的私有全局变量engine_id、session_local,并打印CallTrace。

    gdb -c 'p engine_id; p/x *session_local; bt'

  2. 对指定进程11662,在其neigh.c:1235行的BUG_ON(1)处设置断点,生成core文件。

    说明:这种方式生成的core文件不同于由内核生成的/SE/core--PID,它含有共享内存的绝大部分数据,便于深入分析。须注意的是这种core文件一般较大,可能达到几个GB,因此要保证足够的存储空间;存储可能需要十几秒或几十秒。若不指定文件名,则在当前目录下生成名为core.PID的core文件(此时可提前cd到/SE下)。

    gdb -c 'b neigh.c:1235; commands; silent; gcore /home/luo/workspace/core-neigh.11662; c; end; c' -i 11662

  3. 对指定进程11662,在其ut函数处设置断点,每个报文到来则打印tbuf信息。

说明:对单个进程,不存在串行/并行的区别,可按串行处理。

gdb -c 'b ut; commands; silent; p/x *tbuf; c; end; c' -i 11662

  1. 打开分片重组部分的调试开关debug_ip4_frag_recv、debug_ip4_mtu_send。

说明:这两个变量都为共享数据,只需设置单个即可,如pid为11662。

gdb -c 'set debug_ip4_frag_recv=1; set debug_ip4_mtu_send=1; printf "recv=%d,send=%d",debug_ip4_frag_recv,debug_ip4_mtu_send' -i 11662

  1. 对所有进程设置条件指令断点,触发后执行指定命令,执行命令后继续执行不退出。

说明:对于涉及多的有断点场景,应使用并行模式。

gdb -p -c 'b test.c:22 if cnt%6==4; commands; silent; echo --trace--\n; bt; printf "\nname=%s cnt=%d\n", name, cnt; c; end; c'

该命令序列也可写入命令文件:(-f /tmp/a.gdb)

handle all noprint nostop

b test.c:22 if cnt%6==4

commands

silent

echo --trace--\n

bt

printf "\nname=%s cnt=%d\n", name, cnt

c

end

c

[注]:最后的c命令,使得设完断点及commands后继续运行,倒数第二条c命令,使得断点触发/处理后仍继续运行。

  1. 对所有进程设置数据断点,当数值被改变时,打印CallTrace。

    说明:watch可监控数据是否被改变,rwatch可监控数据是否被读取,awatch包括这两类。

    gdb -p -c 'watch st.age; commands; bt; end; c; c'

  2. 对每个设置多个断点,每个断点触发时打印CallTrace及文件/函数/行信息。

    说明:每个断点对应自己的触发处理,此处采用了自定义命令hit_me的方式。

    gdb -p -f /tmp/b.gdb

    handle all noprint nostop

    define hit_me

    echo \n>>>hit_me>>>\n

    bt

    info thread

    end

    b test.c:24

    commands

    hit_me

    c

    end

    b test.c:36

    commands

    hit_me

    c

    end

    c

  3. 查看phy_dev[]中ifindex为26的结构体。

    说明:phy_dev为共享数据,因此只需查看单个即可,如pid为11662。命令序列略繁,可写入命令文件。

    gdb -f /tmp/dev.gdb -i 11662

    脚本/tmp/dev.gdb内容如下:

    handle all noprint nostop

    set print pretty

    set $idx=26

    set $i=0

    while ($i<256)

    set $d = (struct dev*)phy_dev[$i]

    if ($d == 0)

    loop_break

    end

    if ($d->ifindex==$idx)

    p/x *$d

    loop_break

    end

    set $i = $i+1

    end

  4. 查看挂接在链表mirror_filter_list上的每个filter节点。

说明:mirror_filter_list为共享数据,因此只需查看单个即可,如pid为11662。

gdb -f /tmp/mirror_filter.gdb -i 11662

脚本mirror_filter.gdb内容如下:

handle all noprint nostop

set print pretty

p/x &mirror_filter_list

set $f=mirror_filter_list.next

while (1)

if ($f==&mirror_filter_list)

echo [end of list]

loop_break

end

p/x *(struct mirror_filter_t*)$f

set $f=((struct list_head*)$f)->next

end

  1. 获取进程core大小限制。

    gdb -c 'p getrlimit(4,$rsp-32); x/2xg $rsp-32'