gdb实用小技巧

本文旨在对程序debug利器gdb工具常用技巧进行汇总。从介绍、信息显示、函数、断电、观察点、打印、多进程、线程、core dump文件、汇编、图形化界面等方面展开。

信息显示

查看gdb版本信息

1
(gdb) show version

查看gdb版权信息

1
(gdb) show copying

启动时不显示提示信息

1
$ gdb -q

退出时不显示提示信息

1
2
(gdb) set confirm off
可以把这个命令加到.gdbinit文件里

函数

列出函数名字

1
2
3
(gdb) info functions
or
(gdb) info functions regex

是否进入到调试信息的函数

1
2
step (s) 进入函数;
next (n) 不进入函数

退出正在调试的函数

  • finish 函数会继续执行完,并且打印返回值,然后等待输入接下来的命令。
  • return 函数不会继续执行下面的语句,直接返回。也可以用 return expression 命令指定函数的返回值。

打印函数堆栈帧信息

使用gdb调试程序时,可以使用“i frame”命令显示函数堆栈帧信息。

1
2
3
i frame
i registers
disassemble func

选择函数堆栈帧

1
2
3
frame n (n是层数)
or
frame addr (addr是堆栈地址)

断点

命名空间设置断点

1
(gdb) b Foo::foo

在程序地址上打断点

1
b *address

当调试汇编程序时,或者没有调试信息的程序时,可用此法。

在程序入口处打断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(base) huang@mlt:~/gdb$ strip hello
(base) huang@mlt:~/gdb$ readelf -h hello
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400470
Start of program headers: 64 (bytes into file)
Start of section headers: 4472 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28

当调试没有调试信息的程序时,直接运行start命令是没有效果的:

1
2
(gdb) start
Function "main" not defined

如果不知道main在何处,那么可以在入口处打断点。先通过readelf或者进入gdb,执行info files获得入口地址,然后:

1
2
(gdb) b *0x400470
(gdb) r

在文件行号上打断点

1
2
3
(gdb) b n
or
(gdb) b file:linenum

设置临时断点

1
tb a.c:15

断点只生效一次,使用tbreak命令(tb)。

设置条件断点

1
break ... if cond

观察点

设置观察点

1
2
3
4
5
(gdb) watch n
or
(gdb) p &a
$1 = (int *) 0x6009c8 <a>
(gdb) watch *(int*)0x6009c8

只有当一个变量值发生变化时,程序会停下来。

1
info watchpoints

查看所有观察点。

打印

打印ASCII和宽字符字符串

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <wchar.h>

int main()
{
char str1[] = "abcd";
wchar_t str2[] = L"abcd";
return 0;
}

使用gdb调试时,使用“x/s”命令打印ASCII字符串。

1
2
3
4
5
6
7
8
9
(gdb) x/s str1
0x804779f: "abcd"

(gdb) n
7 wchar_t str2[] = L"abcd";
(gdb) p sizeof(wchar_t)
$1 = 4
(gdb) x/ws str2
0x8047788: U"abcd"

4个字节用“x/ws”,两个字节,则用”x/hs”命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(gdb) print a
$11 = "\000\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017"
(1)16进制格式打印数组a前16个byte的值
(gdb) x/16x a
0x7fffffffdf30: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x7fffffffdf38: 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
(2)以无符号10进制格式打印数组a前16个byte的值
(gdb) x/16u a
0x7fffffffdf30: 0 1 2 3 4 5 6 7
0x7fffffffdf38: 8 9 10 11 12 13 14 15
(3)2进制格式打印数组a前16个byte的值
(gdb) x/16t a
0x7fffffffdf30: 00000000 00000001 00000010 00000011 00000100 00000101 00000110 00000111
0x7fffffffdf38: 00001000 00001001 00001010 00001011 00001100 00001101 00001110 00001111
(4)以16进制格式打印数组第5到8 4个元素的值
(gdb) x/4x a[5]@4
0x7fffffffdf35: 0x05 0x06 0x07 0x08

打印数组中任意连续元素值

1
2
(gdb) p array[index]@num
index是数组索引,从0开始计数,num是连续多少个元素

打印数组元素下标

1
2
(gdb) set print array-indexes on
(gdb) p num

打印函数局部变量的值

1
2
3
(gdb) bt full
or
(gdb) info locals

打印进程内存信息

1
2
3
(gdb) i proc mappings
or
(gdb) i files

打印内存的值

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
int i = 0;
char a[100];
for (i=0; i<sizeof(a); i++) {
a[i] = i;
}
return 0;
}

gdb中使用“x”命令来打印内存的值,格式为“x/nfu addr”。含义为以f格式打印从addr开始的n个长度为u的内存值。具体参数含义如下:

  • n: 输出单元的个数。
  • f: 输出格式。比如f是16进制;o是8进制。
  • u:标明一个单位的长度。b是一个byte,h是两个byte(half word),w是四个byte (word),g是八个byte (giant word)。

汇编

设置汇编指令格式

1
(gdb) disassemble main

将源程序和汇编指令映射起来

源代码:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
int i = 0;
char a[100];
for (i=0; i<sizeof(a); i++) {
a[i] = i;
}
return 0;
}

使用“disas /m fun”命令将函数代码和汇编指令映射起来。

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) disas /m main
Dump of assembler code for function main:
3 int main(){
0x0000000000400546 <+0>: push %rbp
0x0000000000400547 <+1>: mov %rsp,%rbp
0x000000000040054a <+4>: sub $0x30,%rsp
0x000000000040054e <+8>: mov %fs:0x28,%rax
0x0000000000400557 <+17>: mov %rax,-0x8(%rbp)
0x000000000040055b <+21>: xor %eax,%eax

4 int i = 0;
0x000000000040055d <+23>: movl $0x0,-0x24(%rbp)

5 char a[16];
6 for(i=0; i<sizeof(a); ++i)
0x0000000000400564 <+30>: movl $0x0,-0x24(%rbp)
0x000000000040056b <+37>: jmp 0x40057f <main+57>
0x000000000040057b <+53>: addl $0x1,-0x24(%rbp)
0x000000000040057f <+57>: mov -0x24(%rbp),%eax
0x0000000000400582 <+60>: cmp $0xf,%eax
0x0000000000400585 <+63>: jbe 0x40056d <main+39>

7 a[i] = i;
0x000000000040056d <+39>: mov -0x24(%rbp),%eax
0x0000000000400570 <+42>: mov %eax,%edx
0x0000000000400572 <+44>: mov -0x24(%rbp),%eax
0x0000000000400575 <+47>: cltq
0x0000000000400577 <+49>: mov %dl,-0x20(%rbp,%rax,1)

8 return 0;
=> 0x0000000000400587 <+65>: mov $0x0,%eax

9 }
0x000000000040058c <+70>: mov -0x8(%rbp),%rcx
0x0000000000400590 <+74>: xor %fs:0x28,%rcx
0x0000000000400599 <+83>: je 0x4005a0 <main+90>
0x000000000040059b <+85>: callq 0x400420 <__stack_chk_fail@plt>
0x00000000004005a0 <+90>: leaveq
0x00000000004005a1 <+91>: retq

End of assembler dump.

查看某行所对应的地址范围,使用“disassemble [start], [end]”命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Breakpoint 4, main () at mem.c:4
4 int i = 0;
(gdb) l
1 #include <stdio.h>
2
3 int main(){
4 int i = 0;
5 char a[16];
6 for(i=0; i<sizeof(a); ++i)
7 a[i] = i;
8 return 0;
9 }
(gdb) i line 4
Line 4 of "mem.c" starts at address 0x40055d <main+23> and ends at 0x400564 <main+30>.
(gdb) disassemble 0x40055d, 0x400564
Dump of assembler code from 0x40055d to 0x400564:
=> 0x000000000040055d <main+23>: movl $0x0,-0x24(%rbp)
End of assembler dump.

打印寄存器的值

1
2
3
(gdb) i registers
(gdb) i all-registers
(gdb) i registers eax

改变字符串的值

1
2
3
4
5
6
7
8
9
#incldue <stdio.h>
int main() {
char p1[] = "sam";
char *p2 = "Bob";

printf("p1 is %s\n", p1);
printf("p2 is %s\n", p2);
return 0;
}

可以使用set命令改变字符串的值:

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
(gdb) r
Starting program: /home/huang/gdb/set
p1 is sam

Breakpoint 1, main () at set.c:7
7 printf("p2 is %s\n", p2);
(gdb) l
2 int main() {
3 char p1[] = "sam";
4 char *p2 = "Bob";
5
6 printf("p1 is %s\n", p1);
7 printf("p2 is %s\n", p2);
8 return 0;
9 }
(gdb) print p1
$1 = "sam"
(gdb) print p2
$2 = 0x400694 "Bob"
(gdb) set p1="abc"
(gdb) set p2="zzz"
(gdb) n
p2 is zzz

Breakpoint 2, main () at set.c:8
8 return 0;

设置变量的值

1
(gdb) set var variable=expr

跳转到指定位置

1
(gdb) jump line

显示共享链接库信息

1
(gdb) info sharedlibrary regex

脚本

配置gdb init文件

配置文件:.gdbinit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 保存历史命令
set history filename ~/.gdb_history
set history save on

# 退出时不显示提示信息
set confirm off

# 按照派生类型打印对象
set print object on

# 打印数组的索引下标
set print array-indexes on

#每行打印一个结构体成员
set print pretty on

图形化界面

进入和退出图形化调试界面

1
2
3
4
5
6
7
$ gdb -tui program
# 显示汇编窗口
(gdb) layout asm
or
(gdb)layout split
# 显示寄存器窗口
(gdb) layout regs

缩写技巧

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
b -> break
c -> continue
d -> delete
f -> frame
i -> info
j -> jump
l -> list
n -> nxet
p -> print
r -> run
s -> step
u -> until
----
aw -> awatch
bt -> backtrace
dir -> directory
disas -> disassemble
fin -> finish
ig -> ignore
ni -> nexti
rw -> rwatch
si -> stepi
tb -> tbreak
wa -> watch
win -> winheight

来源:https://github.com/hellogcc/100-gdb-tips