gdb调试教程

发布时间:2023-07-23 00:58:41 作者:yexindonglai@163.com 阅读(991)

什么是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, 文件内容如下:

  1. #include "iostream"
  2. int getAge(){
  3. int count=0;
  4. count ++;
  5. std::cout <<"getAge function" << std::endl;
  6. return 1;
  7. }
  8. int main() {
  9. std::cout << "Hello, World! yeindong" << std::endl;
  10. std::cout << "getAge:"<<getAge() << std::endl;
  11. return 0;
  12. }
2、编译

输入以下命令进行编译

  1. g++ main.cpp -g -o main_gdb

其中,-g表示编译生成的目标文件中包含了源代码的调试信息,这些信息可以在调试器中使用;
没有报错就表示成功

如果是大项目有多个文件,就得用makefile来编译,以下是一个makefile示例

  1. main=main.o
  2. student=student.o
  3. man_student=main.o student.o
  4. main : $(man_student)
  5. g++ -g -o main $(man_student)
  6. $(main): main.cpp
  7. g++ -g -c main.cpp
  8. $(student): student.cpp student.hpp
  9. g++ -g -c student.cpp student.hpp
  10. clean :
  11. rm main $(man_student)
3、启用gdb调试

输入以下命令即可开启gdb调试,main_gdb 是刚刚编译后生成的可执行文件;

  1. gdb main_gdb
4、break 断点相关指令

那上面的代码例子,添加断点有5种方式,
比如我们想要调试getAge函数,那么就输入以下命令即可

  1. break getAge

其他添加断点的方式

  1. # 在程序的第5行添加断点
  2. b 5
  3. break 5
  4. # 在main.cpp的第10行添加断点
  5. b main.cpp:10
  6. break main.cpp:10
  7. # 条件断点,当满足条件时自动断点,当i变量等于10的时候断点
  8. break if i == 10
  9. b if i == 10
  10. # 在指定地址添加断点
  11. break *0x12345678
  12. b *0x12345678

这样一个断点就打好了,还会告诉你断点在main.cpp的第4行

另外,如果我们打了很多断点,可以通过以下指令来查看所有的断点

  1. infp break
  2. i b

可以看到,目前有一个断点,最左边的1是这个断点的编号,

5、run运行程序

在第4步已经打好断点了,然后就可以运行了,输入以下指令后会自动运行到断点的位置后停住

  1. run

运行后我们发现,自动运行到断点的位置了;

未完待续

6、continue 断点处继续执行

刚刚已经执行了run指令运行到断点位置了,通过以下指令可以让它继续执行到下一个断点,如果没有断点就会执行到结束为止;

  1. continue

当看到[Inferior 1 (process 10693) exited normally]字符串的时候就表示已经程序已经执行完毕了;

7、delete / clear 删除断点

删除断点有2种方式,分别为delete 和 clear

1、清除所有断点

  1. delete # 清除前会提示确认,确认候才会删除所有断点

2、delete 删除通过编号单个断点
delete只能通过编号删除断点

  1. delete 断点编号

先通过 info break 查询断点的编号为6,然后通过delete 6 删除断点

3、clear 删除断点
clear可以通过函数名称删除断点

比如现在要删除 getAge 函数的断点

  1. clear getAge

执行后发现,编号为7的getAge函数断点已经没了

8、next 单步执行

在运行过程,可以通过next进行单步或者多步执行

  1. # 单步执行
  2. next
  3. n
  4. # 一次性执行2步
  5. next 2
  6. n 2

9、step 进入函数内部

在执行断点时可以进入函数内部

  1. step

先在 std::cout << "getAge:"<<getAge() << std::endl; 所在行打上断点,然后输入 step 指令,会发现断点就进入到了getAge函数内部了

10、print 查看变量的值
  1. print 变量名

11、set var xxx = x 设置变量的值

设置变量的值为10

  1. set var count = 10

12、watch 监听变量的值

watch 用于监听变量的值,当变量的值发生改变时会自动暂停,并打印出改动前后的值,注意,必须先run了之后再设置watch指令,也就是说,如果我要监听 count,必须在栈中存在这个变量才能监听,否则是无法监听的

  1. watch xxx

设置后,类型为 hw watchpoint

运行后监听到了count变量的变化,会打印出新值和旧值

12、backtrace查看函数调用栈

在 GDB 中,您可以使用 backtrace 或 bt 命令来查看当前的函数调用堆栈。

以下是使用

  1. backtrace
  2. bt
  1. 在 GDB 中启动程序并运行到断点处。
  2. 在 GDB 的命令行中输入 backtrace 或 bt 命令。
  3. GDB 将显示当前的函数调用堆栈,包括函数名、源文件和行号。

15、frame 查看某个栈的信息

学过数据结构都知道,栈是遵循先入后出的规则;也就是说,用bt指令查看到最顶部的栈信息就是当前断点所在的地方;

  1. # 查看栈顶信息
  2. frame
  3. f
  4. frame 0
  5. f 0
  6. # 查看第二层栈的信息
  7. frame 1
  8. f 1

接下来我们先用bt查看所有栈信息,然后在用f 0f 1 查看栈顶和第二层栈信息

通过以下指令可以查看更详细的信息

  1. # 查看栈顶的详细信息
  2. info frame
  3. info frame 0
  4. i f
  5. i f 0
  6. # 查看第二层的栈详细信息
  7. info frame 1
  8. i f 1

14、x命令 查看内存值内容

可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:

  1. 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进制的方式展示

  1. (gdb) x 0x602008 # 等价于 x/1db 0x602008
  2. 0x602008: 33

以16进制的方式查看 2个8字节长度的值,总共查看了2*8=16个字节的值

  1. (gdb) x/2xg 0x602000
  2. 0x602000: 0x0000000000000000 0x0000000000000021

以16进制查看地址后面10长度的值,

  1. (gdb) x/10xb 0x602000
  2. 0x602000: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
  3. 0x602008: 0x21 0x00

以10进制查看地址后面10长度的值,

  1. (gdb) x/10db 0x602000
  2. 0x602000: 0 0 0 0 0 0 0 0
  3. 0x602008: 33 0
15、quit 退出gdb

输入以下内容即可退出gdb调试

  1. q
  2. quit

关键字c++