gdb调试教程
快速入门
要想快速入门gdb调试,可以直接翻阅基本案例:采内存
介绍
GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。 对于一名Linux下工作的c/c++程序员,gdb是必不可少的工具
常用指令
- shell 指令
指令 | 用法 | 介绍 |
---|---|---|
gdb | gdb +${exe} | 对生成的可执行文件进行调试 |
gdb -q | gdb -q +${exe} | 对生成的可执行文件进行调试 表示不打印gdb版本信息,界面较为干净; |
- gdb指令
指令 | 用法 | 介绍 |
---|---|---|
断点设置 | ||
break | break +Line<br>break+{Line}<br>break +Line<br>break+{func} | 可以对行号和函数进行设置断点 |
程序运行 | ||
run | run / r | 首次运行程序,运行到第一个断点 |
continue | continue / c | 继续运行程序,运行到下一个断点 |
next | next / n | 单步跳过,(执行一步程序,遇到函数,直接完成函数操作,不会进入函数) |
step | step / s | 单步进入,(执行一步程序,遇到函数会进入) |
until | until / u + ${Line} | 跳出循环体,并跳到循环体外部的某一行 |
变量查看 | ||
print +变量 | 打印变量的值 | |
whatis | whatis +变量 | 打印变量的类型 |
quit | quit / q | 退出 |
可视化 | ||
layout src | 调试的时候同时显示源代码窗口,但是必须run之后才会显示 | |
基本案例:采内存
0. 源码
本案例以下文段错误(采内存)源码进行介绍如何进行gdb调试。
通常在复杂程序调用中,指针的操作容易导致编程人员对内存空间的使用造成困扰和混乱,因此指针操作是导致内存异常问题最为常见的因素也是最难定位的因素。
源代码如下:
在本案例中,main函数里的my_user指针在addUser函数里进行资源申请。虽然成功申请到了堆中的空间。但是因为my_user是作为参数传入,因此在函数返回时,my_user又重新指向了NULL,而非堆上的空间地址导致运行发生段错误。
#include <stdio.h>
#include <stdlib.h>typedef struct User {int id;int age;
} User;void addUser(User *user, int id, int age)
{user = (User *)malloc(sizeof(User));user->id = id;user->age = age;printf("id = %d, age = %d\n", user->id, user->age);
}int main()
{printf("gdb demo\n");User *my_user = NULL;addUser(my_user, 10, 20);printf("id = %d, age = %d\n", my_user->id, my_user->age);return 0;
}
1. 编译
要想使用gdb调试功能,需要在gcc
编译器添加-g
参数,或者在cmake里编译成debug模式。
>> gcc -g -o main main.c
2. 打开gdb调试
>> gdb main
>> gdb -q main # 表示不打印gdb版本信息,界面较为干净;
3. gdb 设置断点
- break
- info breakpoints
# 使用 break 或者 b 进行设置断点。
# 可以对函数进行打点,或者对代码行进行打点。
# 我们可以使用info breakpoints查看当前所有的断点信息
(gdb) b addUser
Breakpoint 1 at 0x752: file gdb_test.c, line 11.
(gdb) b 13
Breakpoint 2 at 0x769: file gdb_test.c, line 13.
(gdb) b 20
Breakpoint 3 at 0x7aa: file gdb_test.c, line 20.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000752 in addUser at gdb_test.c:11
2 breakpoint keep y 0x0000000000000769 in addUser at gdb_test.c:13
3 breakpoint keep y 0x00000000000007aa in main at gdb_test.c:20
4. 运行
- run
- continue
- next
- step
# 当我们设置完断点进行调试的时候,首先要执行run函数开启程序进程,run会执行到第一个断点位置
# 然后我们可以执行next 或者 continue 或者 step进行推进程序
# continue: 运行到下一个断点
# next: 单步跳过,(执行一步程序,遇到函数,直接完成函数操作,不会进入函数)
# step: 单步进入,(执行一步程序,遇到函数会进入)
(gdb) run
Starting program: /home/rodney/ProjectCode/Cproject/C_coding/gcc&g++&gdb_block/bug1/main
gdb demoBreakpoint 3, main () at gdb_test.c:20
20 User *my_user = NULL;
(gdb) n
22 addUser(my_user, 10, 20);
(gdb) c
Continuing.Breakpoint 1, addUser (user=0x0, id=10, age=20) at gdb_test.c:11
11 user = (User *)malloc(sizeof(User));(gdb) c
Continuing.Breakpoint 2, addUser (user=0x555555756420, id=10, age=20) at gdb_test.c:13
13 user->age = age;(gdb) n
14 printf("id = %d, age = %d\n", user->id, user->age);
(gdb) n
id = 10, age = 20
15 }
(gdb) n
main () at gdb_test.c:23
23 printf("id = %d, age = %d\n", my_user->id, my_user->age);
(gdb) nProgram received signal SIGSEGV, Segmentation fault.
0x00005555555547cc in main () at gdb_test.c:23
23 printf("id = %d, age = %d\n", my_user->id, my_user->age);
从上文可以看出是23行打印发生了报错,那么如何确定23行的打印发生了什么具体错误,可以使用print
和whatis
进行打印显示。
5. 打印和显示变量信息
- print 打印变量的值
- whatis 打印变量的类型
# 从打印的信息可以看出,这里my_user是个空指针,因此访问出现上文的段错误。
(gdb) whatis my_user
type = User *
(gdb) whatis my_user->id
type = int
(gdb) print my_user
$1 = (User *) 0x0
(gdb) print my_user->id
Cannot access memory at address 0x0