Sholck

不积跬步,无以至千里.不积小流,无以成江海

0%

程序分析利器gdb

程序分析利器gdb

简介

通过gdb可以分析进程在运行和crash的内部状态,根据man手册描述有以下作用

  1. 控制进程的开始和结束
  2. 设置进程暂停的条件
  3. 检查进程暂停时的堆栈等信息
  4. 改变程序变量等因素影响程序

使用

编译

在编译时应该增加-g选项

g++ -o out -g test.cpp

运行

  1. 调试一个程序

    gdb program

  2. 调试程序带着core

    gdb program core

  3. 指定pid调试

    gdb -p pid

选项

gdb提供了丰富的命令来,常用的选项如下:

run

开始执行程序,如果需要增加参数,需要增加在run后边

1
2
(gdb) run -m strace -f blk
Starting program: /work/chejian/test/kernel_tool_test/test-run -m strace -f blk

break

简写为b,设置断点,程序运行到此处之前停止,可以指定函数和行数

1
2
3
4
(gdb) break test_module
Breakpoint 1 at 0x401a54: file src/test/test.c, line 100.
(gdb) break 116
Breakpoint 2 at 0x401aff: file src/test/test.c, line 116.

next

简写为n,开始执行下一条指令,但是不会进入函数内部,测试代码test.c如下(以下默认测试代码均为test.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>

void test(int j){
printf("in function test\n");
}

int main() {
printf("hello world\n");
for(int i = 0; i < 10; i++){
printf("i is %d\n", i);
test(i);
}
return 0;
}

设置断点,观察next结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(gdb) break 13
Breakpoint 1 at 0x400599: file test.c, line 13.
(gdb) run
Starting program: /work/chejian/test/test
hello world

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) n
i is 0
14 test(i);
(gdb) n >>不会进入test函数
in function test
12 for(int i = 0; i < 10; i++){
(gdb) n

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);

step

简写为s,要和next进行区分,step会进行函数内部打印,比如printf和test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) b 13
Breakpoint 1 at 0x400599: file test.c, line 13.
(gdb) run
Starting program: /work/chejian/test/test
hello world

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) n
i is 0
14 test(i);
(gdb) step >>进入test函数内部
test (j=0) at test.c:7
7 printf("in function test\n");
(gdb) n
in function test
8 }
(gdb) n
main () at test.c:12
12 for(int i = 0; i < 10; i++){

continue

简写为c,继续执行程序直到遇到断点,和next命令测试共用测试代码test.c,设置断点,观察continue结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) run                                 
Starting program: /work/chejian/test/test
hello world

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) c
Continuing.
i is 0
in function test

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) c
Continuing.
i is 1
in function test

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);

backtrace

简写为bt,打印堆栈

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) b 7
Breakpoint 1 at 0x400571: file test.c, line 7.
(gdb) run
Starting program: /work/chejian/test/test
hello world
i is 0

Breakpoint 1, test (j=0) at test.c:7
7 printf("in function test\n");
(gdb) bt
#0 test (j=0) at test.c:7 >>此时stop在test函数中,因此是栈顶
#1 0x00000000004005b7 in main () at test.c:14

info

展示有用的一些信息,比如断点设置等

info break

打印设置的堆栈信息已经命中次数

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
(gdb) b 7
Breakpoint 1 at 0x400571: file test.c, line 7.
(gdb) b 13
Breakpoint 2 at 0x400599: file test.c, line 13.
(gdb) run
Starting program: /work/chejian/test/test
hello world

Breakpoint 2, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400571 in test at test.c:7
2 breakpoint keep y 0x0000000000400599 in main at test.c:13
breakpoint already hit 1 time
(gdb) c
Continuing.
i is 0

Breakpoint 1, test (j=0) at test.c:7
7 printf("in function test\n");
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400571 in test at test.c:7
breakpoint already hit 1 time
2 breakpoint keep y 0x0000000000400599 in main at test.c:13
breakpoint already hit 1 time

print

简写为p,打印一个变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(gdb) b 13
Breakpoint 1 at 0x400599: file test.c, line 13.
(gdb) run
Starting program: /work/chejian/test/test
hello world

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) p i
$1 = 0
(gdb) c
Continuing.
i is 0
in function test

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) p i
$2 = 1

x

按照选项要求来显示对应地址的内容,使用方式为x/FMT ADDRESS

FMT有如下几种设置:

o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char), s(string)
z(hex, zero padded on the left).

测试代码修改如下:

1
2
3
4
5
6
 6 void test(int j){
7 int addr = &j;
8 char buf='C';
9 char *buf1="hello world";
10 printf("in function the addr of j is: %p, test j == %d\n", addr, j);
11 }

测试如下:

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
(gdb) b 10                                                                                    
Breakpoint 1 at 0x400588: file test.c, line 10.
(gdb) run
Starting program: /work/chejian/test/test
hello world
i is 0

Breakpoint 1, test (j=0) at test.c:10
10 printf("in function the addr of j is: %p, test j == %d\n", addr, j);
(gdb) c
Continuing.
in function the addr of j is: 0xffffe51c, test j == 0
i is 1

Breakpoint 1, test (j=1) at test.c:10
10 printf("in function the addr of j is: %p, test j == %d\n", addr, j);
(gdb) c
Continuing.
in function the addr of j is: 0xffffe51c, test j == 1
i is 2

Breakpoint 1, test (j=2) at test.c:10
10 printf("in function the addr of j is: %p, test j == %d\n", addr, j);
(gdb) c
Continuing.
in function the addr of j is: 0xffffe51c, test j == 2
i is 3

Breakpoint 1, test (j=3) at test.c:10 >>第三次断点捕捉
10 printf("in function the addr of j is: %p, test j == %d\n", addr, j);
(gdb) display j
1: j = 3 >>此时j为3
(gdb) x/i &j >>i 对应指令 ,打印j的地址和对应的汇编指令
0x7fffffffe51c: add (%rax),%eax
(gdb) x/o &j >>八进制打印
0x7fffffffe51c: 03
(gdb) x/x &j >>十六进制打印
0x7fffffffe51c: 0x00000003
(gdb) x/d &j >>十进制打印
0x7fffffffe51c: 3
(gdb) x/u &j >>无符号十进制打印
0x7fffffffe51c: 3
(gdb) x/t &j >>二进制打印
0x7fffffffe51c: 00000000000000000000000000000011
(gdb) x/a &j >>地址打印
0x7fffffffe51c: 0x7800000000000003
(gdb) x/c &buf >>字符打印
0x7fffffffe523: 67 'C'
(gdb) print buf
$1 = 67 'C'
(gdb) display buf
2: buf = 67 'C'
(gdb) x/c &buf1
0x7fffffffe528: 122 'z' >>为什么z?因为 0x7fffffffe528存放的是0x40067a, 7a就是z的ASCII码
(gdb) x/s buf1
0x40067a: "hello world"
(gdb) x/c buf1
0x40067a: 104 'h'

x/i $pc也可以打印当前pc指令的地址和汇编信息,见display测试部分。

display

在==每一次==程序运行停止前打印变量,功能和print类似,但是有更多扩展,一般用法为display var

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
(gdb) b 7
Breakpoint 1 at 0x400571: file test.c, line 7.
(gdb) run
Starting program: /work/chejian/test/test
hello world
i is 0

Breakpoint 1, test (j=0) at test.c:7
7 printf("in function test j == %d\n", j);
(gdb) display j
1: j = 0 >>打印变量
(gdb) c
Continuing.
in function test j == 0
i is 1 >>停止后打印一次

Breakpoint 1, test (j=1) at test.c:7
7 printf("in function test j == %d\n", j);
1: j = 1
(gdb) c
Continuing.
in function test j == 1
i is 2

Breakpoint 1, test (j=2) at test.c:7
7 printf("in function test j == %d\n", j);
1: j = 2 >>停止后打印一次
(gdb) display &j >>打印j变量的地址
3: &j = (int *) 0x7fffffffe51c >>3是指第几次执行display命令

也可以直接display执行,这样会在每一次结束之后打印全部的变量,

display也可以打印当前的pc

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
(gdb) disassemble test
Dump of assembler code for function test:
0x0000000000400566 <+0>: push %rbp
0x0000000000400567 <+1>: mov %rsp,%rbp
0x000000000040056a <+4>: sub $0x10,%rsp
0x000000000040056e <+8>: mov %edi,-0x4(%rbp)
=> 0x0000000000400571 <+11>: mov -0x4(%rbp),%eax >>当前执行在test函数中
0x0000000000400574 <+14>: mov %eax,%esi
0x0000000000400576 <+16>: mov $0x400664,%edi
0x000000000040057b <+21>: mov $0x0,%eax
0x0000000000400580 <+26>: callq 0x400440 <printf@plt>
0x0000000000400585 <+31>: nop
0x0000000000400586 <+32>: leaveq
0x0000000000400587 <+33>: retq
End of assembler dump.
(gdb) display $pc >>当前的pc指令
10: $pc = (void (*)()) 0x400571 <test+11>
(gdb) display/i $pc >>相当于x/i $pc
11: x/i $pc >>当前的pc汇编指令
=> 0x400571 <test+11>: mov -0x4(%rbp),%eax
(gdb) display/3i $pc
12: x/3i $pc >>从当前开始的三条汇编指令
=> 0x400571 <test+11>: mov -0x4(%rbp),%eax
0x400574 <test+14>: mov %eax,%esi
0x400576 <test+16>: mov $0x400664,%edi

set

可以对 寄存器变量,调试程序中的变量进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(gdb) b 13
Breakpoint 1 at 0x400599: file test.c, line 13.
(gdb) run
Starting program: /work/chejian/test/test
hello world

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) p i
$1 = 0
(gdb) set variable i=3
(gdb) p i
$2 = 3
(gdb) c
Continuing.
i is 3
in function test

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) p i
$3 = 4

####ni/si

ni和si有点类似next和step,只不过是后面多了具体的步数,默认是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
(gdb) b 7
Breakpoint 1 at 0x400571: file test.c, line 7.
(gdb) run
Starting program: /work/chejian/test/test
hello world
i is 0

Breakpoint 1, test (j=0) at test.c:7
7 printf("in function test j == %d\n", j);
(gdb) disassemble
Dump of assembler code for function test:
0x0000000000400566 <+0>: push %rbp
0x0000000000400567 <+1>: mov %rsp,%rbp
0x000000000040056a <+4>: sub $0x10,%rsp
0x000000000040056e <+8>: mov %edi,-0x4(%rbp)
=> 0x0000000000400571 <+11>: mov -0x4(%rbp),%eax >>目前的pc地址,而且将test函数全部打印
0x0000000000400574 <+14>: mov %eax,%esi
0x0000000000400576 <+16>: mov $0x400664,%edi
0x000000000040057b <+21>: mov $0x0,%eax
0x0000000000400580 <+26>: callq 0x400440 <printf@plt> >>执行si会进入printf函数内部
0x0000000000400585 <+31>: nop
0x0000000000400586 <+32>: leaveq
0x0000000000400587 <+33>: retq
End of assembler dump.
(gdb) ni
0x0000000000400574 7 printf("in function test j == %d\n", j); >>pc移动到0x400574
(gdb) ni
0x0000000000400576 7 printf("in function test j == %d\n", j);
(gdb) ni
0x000000000040057b 7 printf("in function test j == %d\n", j);
(gdb) si
0x0000000000400580 7 printf("in function test j == %d\n", j); >>ni会跳转到函数内部
(gdb) si
0x0000000000400440 in printf@plt ()

list

简写为l,打印当前程序执行所在行周围的程序文本,如果程序还是没有开始执行,那么从第一行打印,继续回车和l命令执行会继续往下打印程序文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(gdb) b 13
Breakpoint 1 at 0x400599: file test.c, line 13.
(gdb) run
Starting program: /work/chejian/test/test
hello world

Breakpoint 1, main () at test.c:13
13 printf("i is %d\n", i);
(gdb) l
8 }
9
10 int main() {
11 printf("hello world\n");
12 for(int i = 0; i < 10; i++){
13 printf("i is %d\n", i);
14 test(i);
15 }
16 return 0;
17 }

list也可以执行函数打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(gdb) list test
1 #include <stdlib.h>
2 #include <string.h>
3 #include <stdio.h>
4 #include <pthread.h>
5
6 void test(int j){
7 printf("in function test\n");
8 }
9
10 int main() {
(gdb) list main
5
6 void test(int j){
7 printf("in function test\n");
8 }
9
10 int main() {
11 printf("hello world\n");
12 for(int i = 0; i < 10; i++){
13 printf("i is %d\n", i);
14 test(i);

disasseble

反汇编内存的指定部分,使用为disasseble ‘file’::func 或者比如disasseble, functest-run是gdb.o和test.o链接完成后的执行程序

1
2
3
4
5
6
7
8
9
10
11
(gdb) disassemble  test_module  >>test_module是test.c中的函数
Dump of assembler code for function test_module:
0x0000000000401a54 <+0>: push %r15
0x0000000000401a56 <+2>: push %**r14**
...

(gdb) disassemble test_gdb >>test_module是gdb.c中的函数
Dump of assembler code for function test_gdb:
0x0000000000400f79 <+0>: push %r15
0x0000000000400f7b <+2>: push %r14
0x0000000000400f7d <+4>: mov $0x1,%r14d
参数

disasseble有三个修饰选项可用,

/m 按照源程序顺序打印
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
(gdb) disassemble /m main                                             
Dump of assembler code for function main:
10 int main() {
0x000000000040057e <+0>: push %rbp
0x000000000040057f <+1>: mov %rsp,%rbp
0x0000000000400582 <+4>: sub $0x10,%rsp

11 printf("hello world\n");
0x0000000000400586 <+8>: mov $0x400665,%edi
0x000000000040058b <+13>: callq 0x400430 <puts@plt>

12 for(int i = 0; i < 10; i++){
0x0000000000400590 <+18>: movl $0x0,-0x4(%rbp)
0x0000000000400597 <+25>: jmp 0x4005bb <main+61>
0x00000000004005b7 <+57>: addl $0x1,-0x4(%rbp)
0x00000000004005bb <+61>: cmpl $0x9,-0x4(%rbp)
0x00000000004005bf <+65>: jle 0x400599 <main+27>

13 printf("i is %d\n", i);
0x0000000000400599 <+27>: mov -0x4(%rbp),%eax
0x000000000040059c <+30>: mov %eax,%esi
0x000000000040059e <+32>: mov $0x400671,%edi
0x00000000004005a3 <+37>: mov $0x0,%eax
0x00000000004005a8 <+42>: callq 0x400440 <printf@plt>

14 test(i);
0x00000000004005ad <+47>: mov -0x4(%rbp),%eax
0x00000000004005b0 <+50>: mov %eax,%edi
0x00000000004005b2 <+52>: callq 0x400566 <test>

15 }
16 return 0;
0x00000000004005c1 <+67>: mov $0x0,%eax

17 }
0x00000000004005c6 <+72>: leaveq
0x00000000004005c7 <+73>: retq

End of assembler dump.
/s 按照pc指令执行顺序打印

会有明显的pc在堆栈中返回的逻辑

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
(gdb) disassemble /s main
Dump of assembler code for function main:
test.c:
10 int main() {
0x000000000040057e <+0>: push %rbp
0x000000000040057f <+1>: mov %rsp,%rbp
0x0000000000400582 <+4>: sub $0x10,%rsp

11 printf("hello world\n");
0x0000000000400586 <+8>: mov $0x400665,%edi
0x000000000040058b <+13>: callq 0x400430 <puts@plt>

12 for(int i = 0; i < 10; i++){
0x0000000000400590 <+18>: movl $0x0,-0x4(%rbp)
0x0000000000400597 <+25>: jmp 0x4005bb <main+61>

13 printf("i is %d\n", i);
0x0000000000400599 <+27>: mov -0x4(%rbp),%eax
0x000000000040059c <+30>: mov %eax,%esi
0x000000000040059e <+32>: mov $0x400671,%edi
0x00000000004005a3 <+37>: mov $0x0,%eax
0x00000000004005a8 <+42>: callq 0x400440 <printf@plt>

14 test(i);
0x00000000004005ad <+47>: mov -0x4(%rbp),%eax
0x00000000004005b0 <+50>: mov %eax,%edi
0x00000000004005b2 <+52>: callq 0x400566 <test>

12 for(int i = 0; i < 10; i++){
0x00000000004005b7 <+57>: addl $0x1,-0x4(%rbp)
0x00000000004005bb <+61>: cmpl $0x9,-0x4(%rbp)
0x00000000004005bf <+65>: jle 0x400599 <main+27>

15 }
16 return 0;
0x00000000004005c1 <+67>: mov $0x0,%eax

17 }
0x00000000004005c6 <+72>: leaveq
0x00000000004005c7 <+73>: retq
End of assembler dump.
/r 额外打印16进制的原始指令
进阶

可以使用x指令或者display/i命令打印想要的汇编

help

展示gdb命令的信息,可以打印class 相关的命令

1
(gdb) help class<breakpoints|status|stack>

打印全部

1
(gdb) help

quit

退出gdb