gdb用法总结

本文主要介绍强大的gdb工具,主要有如下内容:

  • gdb使用前置条件
  • gdb一般用法
  • gdb常用命令
  • 如何用gdb找到死锁

GDB(GNU Debugger)是在Unix以及类Unix系统下的调试工具。功能极其强大,几乎涵盖了你所需要的全部功能。
GDB主要帮忙你完成下面四个方面的功能:
1.启动你的程序,可以按照你的定制要求随心所欲的运行程序。
2.可让被调试的程序在你所指定的调置的断点处停住。
3.当程序被停住时,可以检查此时你的程序中所发生的事,以及内存状态等。
4.动态的改变你程序的执行环境。

gdb使用总旨:多用help!help里面总会有你需要的信息。如果你不知道如何使用help,请在gdb里面输入:help all


gdb使用前置条件

  • 编译时加入debug信息

  • gcc/g++是在编译时加入-g,其他语言请自行百度。

    值得注意的是,-g分4个等级:
    -g0等于不加-g,即不包含任何信息;
    -g1只包含最小信息,一般来说只有你不需要debug,只需要backtrace信息,并且真的很在意程序大小,或者有其他保密/特殊需求时才会使用-g1;
    –g2为gdb默认等级,包含绝大多数你需要的信息;
    –g3包含一些额外信息,例如包含宏定义信息。当你需要调试宏定义时,请使用-g3

gdb一般用法

1. 调试程序。有几种方法可以在gdb下运行你的程序:

    1)    gdb ${你的程序} 进入gdb后,输入run(简写r) ${arg1} ${arg2} … ${argN}

    2)    gdb --args ${你的程序} ${arg1} ${arg2} … ${argN} 进入gdb后,运行run。

    3)    gdb进入gdb后,输入file ${你的程序}。然后使用set args  ${arg1} ${arg2} … ${argN} 设定好你的程序参数,再运行run。

 2. 调试正在运行的程序:

    gdb ${你的程序} ${程序pid}

 3. 查core:

    gdb ${你的程序} ${core文件}

gdb常用命令

  1. backtrace:显示栈信息,简写为bt

  2. frame x 切换到第x帧,其中x会在bt命令中显示,从0开始(0表示栈顶)简写为f

  3. up/down x 往栈顶/栈底移动x帧;当不输入x时,默认为1。

  4. print x打印x的信息,x可以是变量,也可以是对象或者数组,简写为p

  5. print */&x 打印x的内容/地址。

  6. call 调用函数。注意此命令需要一个正在运行的程序。

  7. set substitute-path from_path to_path,替换源码文件路径。当编译机与运行程序的机器代码路径不同时,需要使用该指令替换代码路径,否则你无法在gdb中看到源码。

  8. break x.cpp:n 在x.cpp的第n行设置断点,然后gdb会给出断点编号m,。命令简写为b

  9. command m 设置程序执行到断点m时要看的内容,例如:

    command n

    >printf "x is %d\n",x
    
    >c
    
    >end
    

    如果command后面没有参数n,则命令被赋给最后一个breakpoint,这其实是说break和command连在一起用,在脚本里用就非常方便了。

  10. x /nfu ${addr} 打印addr的内容。addr可以是任何合法的地址表达式,例如0x562fb3d,一个当前有效的指针变量p,或者一个当前有效的变量var的地址&var。nfu是格式,n表示查看的长度,F表示格式(例如16进制或10进制),U表示单位(例如单字节b,双字h,四字w等)。举个栗子:

    (gdb) x /3xw 0x562fb3d //这个指令的意思为:以16进制格式显示地址0x562fb3d处3个单位,每个单位四字节的内容。你将得到下列数值:
    0x562fb3d: 0x00282ff4 0x080484e0 0x00000000

  11. continue 继续运行程序。进入调试模式后,若你已经获取了你需要的信息或者需要程序继续运行时使用,简写为c

  12. until 执行到当前循环完成,可简写为u

  13. step 单步调试,步入当前函数,可简写为s

  14. next 单步调试,步过当前函数,可简写为n

  15. finish 执行到当前函数返回

  16. set var x=10 改变当前变量x的值。也可以这样用:set {int}0x83040 = 10把内存地址0x83040的值强制转换为int并赋值为10

  17. info locals 打印当前栈帧的本地变量

  18. jump使当前执行的程序跳转到某一行,或者跳转到某个地址。由于只会使程序跳转而不会改变栈值,因此若跳出函数到另外的地方 会导致return出错。另外,熟悉汇编的人都知道,程序运行时,有一个寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如: set $pc = 0x485

  19. return: 强制函数返回。可以指定返回值

如何用gdb找到死锁

定位死锁问题常用的几个命令

gdb attach pid
info threads //显示所有线程信息
thread applay all bt//可简写为t a a bt
thread 2 //跳到第2个线程
bt //查看线程2的堆栈

死锁问题实例

清单 1. 测试程序

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
#include <unistd.h> 
#include <pthread.h>
#include <string.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;

static int sequence1 = 0;
static int sequence2 = 0;

int func1()
{
pthread_mutex_lock(&mutex1);
++sequence1;
sleep(1);
pthread_mutex_lock(&mutex2);
++sequence2;
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);

return sequence1;
}

int func2()
{
pthread_mutex_lock(&mutex2);
++sequence2;
sleep(1);
pthread_mutex_lock(&mutex1);
++sequence1;
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);

return sequence2;
}

void* thread1(void* arg)
{
while (1)
{
int iRetValue = func1();

if (iRetValue == 100000)
{
pthread_exit(NULL);
}
}
}

void* thread2(void* arg)
{
while (1)
{
int iRetValue = func2();

if (iRetValue == 100000)
{
pthread_exit(NULL);
}
}
}

void* thread3(void* arg)
{
while (1)
{
sleep(1);
char szBuf[128];
memset(szBuf, 0, sizeof(szBuf));
strcpy(szBuf, "thread3");
}
}

void* thread4(void* arg)
{
while (1)
{
sleep(1);
char szBuf[128];
memset(szBuf, 0, sizeof(szBuf));
strcpy(szBuf, "thread3");
}
}

int main()
{
pthread_t tid[4];
if (pthread_create(&tid[0], NULL, &thread1, NULL) != 0)
{
_exit(1);
}
if (pthread_create(&tid[1], NULL, &thread2, NULL) != 0)
{
_exit(1);
}
if (pthread_create(&tid[2], NULL, &thread3, NULL) != 0)
{
_exit(1);
}
if (pthread_create(&tid[3], NULL, &thread4, NULL) != 0)
{
_exit(1);
}

sleep(5);
//pthread_cancel(tid[0]);

pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_join(tid[2], NULL);
pthread_join(tid[3], NULL);

pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
pthread_mutex_destroy(&mutex3);
pthread_mutex_destroy(&mutex4);

return 0;
}

清单 2. 编译测试程序

1
[dyu@xilinuxbldsrv purify]$ g++ -g lock.cpp -o lock -lpthread

清单 3. 查找测试程序的进程号
1
2
[dyu@xilinuxbldsrv purify]$ ps -ef|grep lock 
dyu 6721 5751 0 15:21 pts/3 00:00:00 ./lock

清单 4. 对死锁进程第一次执行 pstack(pstack –进程号)的输出结果(pstack用法可以查阅文末参考资料)

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
[dyu@xilinuxbldsrv purify]$ pstack 6721 
Thread 5 (Thread 0x41e37940 (LWP 6722)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a9b in func1() ()
#4 0x0000000000400ad7 in thread1(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x42838940 (LWP 6723)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a17 in func2() ()
#4 0x0000000000400a53 in thread2(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x43239940 (LWP 6724)):
#0 0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6
#1 0x0000003d19c9a364 in sleep () from /lib64/libc.so.6
#2 0x00000000004009bc in thread3(void*) ()
#3 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#4 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x43c3a940 (LWP 6725)):
#0 0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6
#1 0x0000003d19c9a364 in sleep () from /lib64/libc.so.6
#2 0x0000000000400976 in thread4(void*) ()
#3 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#4 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2b984ecabd90 (LWP 6721)):
#0 0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0
#1 0x0000000000400900 in main ()

清单 5. 对死锁进程第二次执行 pstack(pstack –进程号)的输出结果

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
 [dyu@xilinuxbldsrv purify]$ pstack 6721 
Thread 5 (Thread 0x40bd6940 (LWP 6722)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a87 in func1() ()
#4 0x0000000000400ac3 in thread1(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x415d7940 (LWP 6723)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a03 in func2() ()
#4 0x0000000000400a3f in thread2(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x41fd8940 (LWP 6724)):
#0 0x0000003d19c7aec2 in memset () from /lib64/libc.so.6
#1 0x00000000004009be in thread3(void*) ()
#2 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#3 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x429d9940 (LWP 6725)):
#0 0x0000003d19c7ae0d in memset () from /lib64/libc.so.6
#1 0x0000000000400982 in thread4(void*) ()
#2 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#3 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2af906fd9d90 (LWP 6721)):
#0 0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0
#1 0x0000000000400900 in main ()
连续多次查看这个进程的函数调用关系堆栈进行分析:当进程吊死时,多次使用 pstack 查看进程的函数调用堆栈,死锁线程将一直处于等锁的状态,对比多次的函数调用堆栈输出结果,确定哪两个线程(或者几个线程)一直没有变化且一直处于等锁的状态(可能存在两个线程 一直没有变化)。
输出分析:
根据上面的输出对比可以发现,线程 1 和线程 2 由第一次 pstack 输出的处在 sleep 函数变化为第二次 pstack 输出的处在 memset 函数。但是线程 4 和线程 5 一直处在等锁状态(pthread_mutex_lock),在连续两次的 pstack 信息输出中没有变化,所以我们可以推测线程 4 和线程 5 发生了死锁。
Gdb into thread输出:

清单 6. 然后通过 gdb attach 到死锁进程

1
2
3
4
5
6
7
8
9
10
11
  (gdb) info thread 
5 Thread 0x41e37940 (LWP 6722) 0x0000003d1a80d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
4 Thread 0x42838940 (LWP 6723) 0x0000003d1a80d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
3 Thread 0x43239940 (LWP 6724) 0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
2 Thread 0x43c3a940 (LWP 6725) 0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
* 1 Thread 0x2b984ecabd90 (LWP 6721) 0x0000003d1a807b35 in pthread_join ()
from /lib64/libpthread.so.0

清单 7. 切换到线程 5 的输出
1
2
3
4
5
6
7
8
9
10
11
(gdb) thread 5 
[Switching to thread 5 (Thread 0x41e37940 (LWP 6722))]#0 0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) where
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a9b in func1 () at lock.cpp:18
#4 0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6

清单 8. 线程 4 和线程 5 的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) f 3 
#3 0x0000000000400a9b in func1 () at lock.cpp:18
18 pthread_mutex_lock(&mutex2);
(gdb) thread 4
[Switching to thread 4 (Thread 0x42838940 (LWP 6723))]#0 0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) f 3
#3 0x0000000000400a17 in func2 () at lock.cpp:31
31 pthread_mutex_lock(&mutex1);
(gdb) p mutex1
$1 = {__data = {__lock = 2, __count = 0, __owner = 6722, __nusers = 1, __kind = 0,
__spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "\002\000\000\000\000\000\000\000B\032\000\000\001", '\000'
<repeats 26 times>, __align = 2}
(gdb) p mutex3
$2 = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = '\000' <repeats 39 times>, __align = 0}
(gdb) p mutex2
$3 = {__data = {__lock = 2, __count = 0, __owner = 6723, __nusers = 1,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "\002\000\000\000\000\000\000\000C\032\000\000\001", '\000'
<repeats 26 times>, __align = 2}
(gdb)

从上面可以发现,线程 4 正试图获得锁 mutex1,但是锁 mutex1 已经被 LWP 为 6722 的线程得到(owner = 6722),线程 5 正试图获得锁 mutex2,但是锁 mutex2 已经被 LWP 为 6723 的 得到(owner = 6723),从 pstack 的输出可以发现,LWP 6722 与线程 5 是对应的,LWP 6723 与线程 4 是对应的。所以我们可以得出, 线程 4 和线程 5 发生了交叉持锁的死锁现象。查看线程的源代码发现,线程 4 和线程 5 同时使用 mutex1 和 mutex2,且申请顺序不合理。


参考资料

GDB常用命令使用说明(一)
一个 Linux 上分析死锁的简单方法
pstack命令
pstack

如果你觉得本文对你有帮助,欢迎打赏