0%

周谈(51)- gdb调试基础

前言

GDB是程序员调试的必备工具,使用GDB可以解决大部分程序错误问题。

当然还有其他的使用方式,比如近期在项目中就使用GDB截图的方式通过了商密检测,截图作为一种方式证明了数据的确是真实的。

这几天刷头条,偶然刷到了GDB相关的文章,顺便搞个文章重新梳理一下GDB的知识结构及使用方法。

GDB

编译

为了便于调试,最好在程序编译时为gcc命令加上-g选项进行编译。测试程序随机生成给定数目的数据数据。

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
# test.c


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <signal.h>

int max_length = 128;

static uint32_t get_rand(uint8_t *a, uint32_t byteLen)
{
int result = 0;
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) {
fd = open("/dev/random", O_RDONLY);
if (fd < 0) {
fprintf(stderr, "get rand fail\n");
return 0;
}
}

result = read(fd, a, byteLen);
if (result < 0) {
fprintf(stderr, "get rand fail 2\n");
}

close(fd);
return result;
}

char * generate(int length){
int i;
char * buffer = (char*) malloc (length+1);
if (buffer == NULL)
return NULL;
get_rand(buffer, length);
buffer[length] = '\0';dd
return buffer;
}

int main(int argc, char *argv[])
{
int num;
char * buffer;

printf ("Input the string length : ");
scanf ("%d", &num);

if(num > max_length){
num = max_length;
}

buffer = generate(num);

printf ("Random string is: %s\n",buffer);

sleep(10);

free (buffer);

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

gcc -g test.c -o test

GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 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:
<https://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 test...
(gdb) b main
Breakpoint 1 at 0x12b2: file test.c, line 21.
(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

break main # 函数断点

b test.c:58 # 文件行号断点, b是break的省略写法

b test.c:28 58 num == 9 # 条件断点

tbreak test.c:58 # 临时断点,这个断点只会断一次,后续不会再断到

info b # 查看已经设置的断点

# 禁用断点

disable # 禁用所有断点
disable bnum # 禁用指定标号的断点
enable # 启用所有断点
enable bnum # 启用指点标号的断点
enable delete bnum # 启动标号为bnum的断点,并且在断到后删除该断点

# 断点清除
clear # 删除当前行所有breakpoints
clear function # 删除函数名为function处的断点
clear filename:function # 删除文件filename中函数function处的断点
clear lineNum # 删除行号为lineNum处的断点
clear f:lename:lineNum # 删除文件filename中行号为lineNum处的断点
delete # 删除所有breakpoints,watchpoints和catchpoints delete可以省略为d
delete bnum # 删除断点号为bnum的断点


变量查看

变量查看: 最常用的就是print(简写为p) 变量

查看内存: 使用examine(简写为x) 内存地址, 语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

x/[n][f][u] addr

# 格式 f
b 字节
h 半字,即双字节
w 字,即四字节
g 八字节

n 表示要显示的内存单元数,默认值为1
f 表示要打印的格式,前面已经提到了格式控制字符
u 要打印的单元长度
addr 内存地址

# 例如:
(gdb) x/32b buffer
0x555555559ac0: 0xa2 0x0a 0xc6 0x6c 0xcb 0xe8 0xb4 0x66
0x555555559ac8: 0x5a 0x96 0xb1 0x6a 0x00 0xc4 0x5b 0x5b
0x555555559ad0: 0xa2 0x05 0x12 0x73 0x04 0x97 0x13 0xc6
0x555555559ad8: 0x9e 0x2c 0x5a 0x8f 0x18 0xb3 0xd2 0x35


查看寄存器: info registers

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) info registers
rax 0x555555559ac0 93824992254656
rbx 0x0 0
rcx 0x7ffff7d15157 140737351078231
rdx 0x20 32
rsi 0x555555559ac0 93824992254656
rdi 0x3 3
rbp 0x7fffffffe350 0x7fffffffe350
rsp 0x7fffffffe320 0x7fffffffe320
r8 0x1999999999999999 1844674407370955161
r9 0x555555559ac0 93824992254656
r10 0x0 0
r11 0x246 582
r12 0x7fffffffe468 140737488348264
r13 0x555555555388 93824992236424
r14 0x555555557d78 93824992247160
r15 0x7ffff7ffd040 140737354125376
rip 0x5555555553fd 0x5555555553fd <main+117>
eflags 0x207 [ CF PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0


调试

单步调试,使用next(简写为n), next num // 继续执行num行

单步进入, step(简写为s), 一般用于进入当前行的函数,但前提是该函数有调试信息并且有源码信息。

断点继续, continue(简写为c),直接继续后面的程序

源码查看list

我们可以使用list命令, 显示当前运行的代码。

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

(gdb) list # 默认显示前后共10行代码
46
47 return buffer;
48 }
49
50 int main(int argc, char *argv[])
51 {
52 int num;
53 char * buffer;
54
55 printf ("Input the string length : ");
(gdb)

# 敲回车,继续显示后面10行
(gdb)
56 scanf ("%d", &num);
57
58 if(num > max_length){
59 num = max_length;
60 }
61
62 buffer = generate(num);
63
64 printf ("Random string is: %s\n",buffer);
65

# list + 行号, 显示行号周围的10行

(gdb) list 10
5 #include <stdint.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <sys/time.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <pthread.h>
12 #include <sys/ioctl.h>
13 #include <sys/mman.h>
14 #include <sys/sysinfo.h>
(gdb)

# list 开始行号,结束行号 , 显示开始行号 ---》 结束行号的代码

(gdb) list 10, 20
10 #include <unistd.h>
11 #include <pthread.h>
12 #include <sys/ioctl.h>
13 #include <sys/mman.h>
14 #include <sys/sysinfo.h>
15 #include <signal.h>
16
17 int max_length = 128;
18
19 static uint32_t get_rand(uint8_t *a, uint32_t byteLen)
20 {


# list - 查看前面那个list命令前的10行

(gdb) list -
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <stdint.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <sys/time.h>
9 #include <fcntl.h>

# list 函数名,查看函数附近的10行

(gdb) list get_rand
15 #include <signal.h>
16
17 int max_length = 128;
18
19 static uint32_t get_rand(uint8_t *a, uint32_t byteLen)
20 {
21 int result = 0;
22 int fd = open("/dev/urandom", O_RDONLY);
23 if (fd < 0) {
24 fd = open("/dev/random", O_RDONLY);

# list 文件名:行号 查看指定文件的行号附近10行
# list 文件名:函数名 查看指定文件的函数附近10行

layout

layout命令可以分隔窗口,让我们一边看源码一边调试。

主要有以下几种用法:
layout src:显示源代码窗口
layout asm:显示汇编窗口
layout regs:显示源代码/汇编和寄存器窗口
layout split:显示源代码和汇编窗口
layout next:显示下一个layout
layout prev:显示上一个layout
Ctrl + L:刷新窗口
Ctrl + x,再按1:单窗口模式,显示一个窗口
Ctrl + x,再按2:双窗口模式,显示两个窗口

Ctrl + x,再按a:回到传统模式,即退出layout,回到执行layout之前的调试窗口。

gdb layout

其他1

这段时间在忙一个新项目,项目组只有我一个人在吭哧吭哧的投入,其他人指望不了,工作一两年的那些同事真的没有战斗力。

硅前的工作,使用pld(paladin)环境进行验证,效率还是蛮低的,但是工作量却不少。

去年,做另一个项目的时候,投入了3个人搞了近两个月的时间,到这个项目的时候就我一个人,时间也只给三个月,呵呵,其中的滋味只有自己知道了。

而且工作量可以说比前面那个项目更多了,以前只验证基础的功能,这次还得测试各种不一样的场景及性能,加油吧。

So,忙的博客都没时间搞了,这边已经积累了很多素材了,争取每周末继续总结回顾,活到老学到老啊。

其他2

本来下半年准备报考系统架构师考试的,昨天才想起来,9月底报名的时间已经错过了,那就准备准备明年的系统分析师吧。

像我这等普通人,真的是没有毅力按计划自觉复习,进度慢而且不成体系,针对这种应试类的考试,就上网报了个班,也许只有花了真金白银了,才能驱使自己去努力了,不然每天的时间其实都是被浪费了。

加油吧,争取明年上半年通过系统分析师,明年下半年再考系统架构师。

在国企里面上班,学历已经落后他人了,虽然有想法想要提升学历,但是考研没有个一年半载的准备,自己肯定是拿不下的。先从职称这方面入手,一步步来,精力毕竟是有限的,各个击破吧。

回想高三那段拼搏的岁月,还是蛮怀念的,累是累,但是结果还是好的,毕竟考上了大学,走出了农村,不再需要如父辈那般干着体力活,为基本的生活而奔波。


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

欢迎关注

博客地址: https://fishmwei.github.io

掘金主页: https://juejin.cn/user/2084329776486919