什么是gdb
GDB(GNU Debugger)是一个功能强大的调试器,用于调试和分析程序的运行。它是自由软件基金会(FSF)的 GNU 项目的一部分,可在多个操作系统上使用,包括 Linux、Unix、Windows 等。
GDB 可以帮助开发人员在程序运行过程中找到和修复错误,以及分析程序的行为和性能。它提供了一系列的命令和功能,用于设置断点、单步执行、查看变量和内存、跟踪函数调用等。通过与编译器和调试信息配合使用,GDB 可以提供丰富的调试信息,帮助开发人员深入了解程序的执行过程。
GDB 支持多种编程语言,包括 C、C++、Objective-C、Fortran、Java 等。它可以与不同的编译器和开发环境集成,例如 GCC、Clang、Visual Studio 等。
使用 GDB,开发人员可以通过在程序执行过程中检查和修改变量的值、跟踪函数调用和返回、查看程序的堆栈和内存状态等来诊断和解决问题。它还支持调试多线程程序和远程调试,使得在复杂的开发场景下进行调试变得更加方便和灵活。
实战
安装gdb
ubuntu安装gdb:sudo apt install gdb
centos安装gdb:sudo yum install gdb
1、准备好调试的代码
创建 main.cpp, 文件内容如下:
#include "iostream"
int getAge(){
int count=0;
count ++;
std::cout <<"getAge function" << std::endl;
return 1;
}
int main() {
std::cout << "Hello, World! yeindong" << std::endl;
std::cout << "getAge:"<<getAge() << std::endl;
return 0;
}
2、编译
输入以下命令进行编译
g++ main.cpp -g -o main_gdb
其中,-g
表示编译生成的目标文件中包含了源代码的调试信息,这些信息可以在调试器中使用;
没有报错就表示成功
如果是大项目有多个文件,就得用makefile来编译,以下是一个makefile示例
main=main.o
student=student.o
man_student=main.o student.o
main : $(man_student)
g++ -g -o main $(man_student)
$(main): main.cpp
g++ -g -c main.cpp
$(student): student.cpp student.hpp
g++ -g -c student.cpp student.hpp
clean :
rm main $(man_student)
3、启用gdb调试
输入以下命令即可开启gdb调试,main_gdb 是刚刚编译后生成的可执行文件;
gdb main_gdb
4、break 断点相关指令
那上面的代码例子,添加断点有5种方式,
比如我们想要调试getAge函数,那么就输入以下命令即可
break getAge
其他添加断点的方式
# 在程序的第5行添加断点
b 5
break 5
# 在main.cpp的第10行添加断点
b main.cpp:10
break main.cpp:10
# 条件断点,当满足条件时自动断点,当i变量等于10的时候断点
break if i == 10
b if i == 10
# 在指定地址添加断点
break *0x12345678
b *0x12345678
这样一个断点就打好了,还会告诉你断点在main.cpp的第4行
另外,如果我们打了很多断点,可以通过以下指令来查看所有的断点
infp break
i b
可以看到,目前有一个断点,最左边的1是这个断点的编号,
5、run运行程序
在第4步已经打好断点了,然后就可以运行了,输入以下指令后会自动运行到断点的位置后停住
run
运行后我们发现,自动运行到断点的位置了;
未完待续
6、continue 断点处继续执行
刚刚已经执行了run指令运行到断点位置了,通过以下指令可以让它继续执行到下一个断点,如果没有断点就会执行到结束为止;
continue
当看到[Inferior 1 (process 10693) exited normally]
字符串的时候就表示已经程序已经执行完毕了;
7、delete / clear 删除断点
删除断点有2种方式,分别为delete 和 clear
1、清除所有断点
delete # 清除前会提示确认,确认候才会删除所有断点
2、delete 删除通过编号单个断点
delete只能通过编号删除断点
delete 断点编号
先通过 info break 查询断点的编号为6,然后通过delete 6 删除断点
3、clear 删除断点
clear可以通过函数名称删除断点
比如现在要删除 getAge 函数的断点
clear getAge
执行后发现,编号为7的getAge函数断点已经没了
8、next 单步执行
在运行过程,可以通过next进行单步或者多步执行
# 单步执行
next
n
# 一次性执行2步
next 2
n 2
9、step 进入函数内部
在执行断点时可以进入函数内部
step
先在 std::cout << "getAge:"<<getAge() << std::endl;
所在行打上断点,然后输入 step 指令,会发现断点就进入到了getAge函数内部了
10、print 查看变量的值
print 变量名
11、set var xxx = x 设置变量的值
设置变量的值为10
set var count = 10
12、watch 监听变量的值
watch 用于监听变量的值,当变量的值发生改变时会自动暂停,并打印出改动前后的值,注意,必须先run了之后再设置watch指令,也就是说,如果我要监听 count,必须在栈中存在这个变量才能监听,否则是无法监听的
watch xxx
设置后,类型为 hw watchpoint
运行后监听到了count变量的变化,会打印出新值和旧值
12、backtrace查看函数调用栈
在 GDB 中,您可以使用 backtrace 或 bt 命令来查看当前的函数调用堆栈。
以下是使用
backtrace
bt
- 在 GDB 中启动程序并运行到断点处。
- 在 GDB 的命令行中输入 backtrace 或 bt 命令。
- GDB 将显示当前的函数调用堆栈,包括函数名、源文件和行号。
15、frame 查看某个栈的信息
学过数据结构都知道,栈是遵循先入后出的规则;也就是说,用bt
指令查看到最顶部的栈信息就是当前断点所在的地方;
# 查看栈顶信息
frame
f
frame 0
f 0
# 查看第二层栈的信息
frame 1
f 1
接下来我们先用bt
查看所有栈信息,然后在用f 0
和 f 1
查看栈顶和第二层栈信息
通过以下指令可以查看更详细的信息
# 查看栈顶的详细信息
info frame
info frame 0
i f
i f 0
# 查看第二层的栈详细信息
info frame 1
i f 1
14、x命令 查看内存值内容
可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:
x/<n/f/u> <addr>
其中 n、f、u都是是可选的参数。 addr 是需要查看的内存起始地址
参数 n:查看的内存长度
参数 n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
参数 f:显示格式
格式如下
格式 | 说明 |
---|---|
x | 按十六进制格式显示变量。 |
d | 按十进制格式显示变量。 |
u | 以无符号十进制格式显示数据。 |
o | 按八进制格式显示变量。 |
t | 按二进制格式显示变量。 |
a | 按地址格式显示变量。 |
c | 按字符格式显示变量。 |
s | 按字符串格式显示变量。 |
f | 按浮点数格式显示变量。 |
参数 u:显示的字节数量
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes,当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
参数可以用下面的字符来代替
格式 | 说明 |
---|---|
b | 表示单字节, |
h | 表示双字节, |
w | 表示四字节(默认值) |
g | 表示八字节。 |
举个栗子
默认什么参数都不带的情况下,展示长度为1,以10进制的方式展示
(gdb) x 0x602008 # 等价于 x/1db 0x602008
0x602008: 33
以16进制的方式查看 2个8字节长度的值,总共查看了2*8=16个字节的值
(gdb) x/2xg 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
以16进制查看地址后面10长度的值,
(gdb) x/10xb 0x602000
0x602000: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x602008: 0x21 0x00
以10进制查看地址后面10长度的值,
(gdb) x/10db 0x602000
0x602000: 0 0 0 0 0 0 0 0
0x602008: 33 0
15、quit 退出gdb
输入以下内容即可退出gdb调试
q
quit