一,GDB简介
GDB全称是GNU symbolic debugger,是Linux平台下最常用的一款调试器。GDB主要用于C/C++开发场景,同时也支持Go、Ada等语言的调试。GDB主要以命令行的形式在shell终端使用,它的一部分底层逻辑借助于ptrace进行实现。GDB的功能很强大,开发者可以在执行时修改函数变量的值以及程序的执行顺序,还可以在程序执行期间查看函数的调用过程、堆栈数据等,也可以利用GDB对代码进行断点调试。
二,两种常见编译模式:Debug模式 & Release模式
Debug模式:
代码在编译时会显示出完整的调试信息以定位问题,编译期间可以查看程序的运行时信息,且编译期间不考虑对代码的执行进行优化。生成的可执行文件执行速度偏慢。
Release模式:
代码在编译时不会显示调试信息,并且编译期间会优化代码的执行。生成的可执行文件执行速度较快。
GDB主要在Debug模式下进行使用。
三,GDB的使用流程
step.1: 开始编译之前,需要配置调试相关的编译命令
a.如果使用gcc/g++编译,需要加入参数“-g"。
b.如果使用cmake编译,需要在CMakeLists.txt中加入:
SET(CMAKE_BUILD_TYPE "Debug") //选择Debug编译模式
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
step.2: 编译生成可执行文件以后,利用GDB进入调试模式,常用的方式有如下三种
方式1:gdb [program]
利用gdb在当前目录直接启动可执行程序。
方式2:gdb [program] core
利用gdb同时调试可执行程序和core文件,core是程序非法执行时产生的文件,比如程序core dump后产生的文件。
方式3:gdb [program] [pid]
利用gdb调试服务的一个进程,pid指定了要调试的进程。运行该指令,gdb会以attach的方式进入进程内部开始调试。
step.3: 调试完,退出GDB界面
方式1:在gdb窗口敲下快捷键Ctrl+z
方式2:在gdb窗口输入指令"quit"或"q"
简单使用样例
Demo.cpp:
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
int findSquare(int a)
{
return a * a;
}
int main(int n, char** args)
{
for (int i = 1; i < n; i++)
{
int a = atoi(args[i]);
cout << findSquare(a) << endl;
}
return 0;
}
编译 & GDB调试指令:
g++ -g -o demo demo.cpp
gdb ./demo
GDB调试结果:
注:加一个“-tui"参数可以进入GDB的可视化界面,可以很直观地看到自己标注的断点
"gdb ./demo -tui"执行结果:
四,GDB的主要语法
1.查看运行信息的指令
命令全称(命令缩写) | 具体含义 |
---|---|
show | 显示调试器本身的信息 |
info | 显示被调试的程序信息 |
list | 显示源代码 |
打印变量值 | |
display | 用法和print类似,但支持自动打印 |
where | 显示当前行号和所在的函数 |
watch | 监控变量或表达式的值 |
whatis | 查看变量的类型 |
ptype | 查看变量的类型 |
forward-search | 从当前行向后查找和正则匹配的行 |
reverse-search | 从当前行向前查找和正则匹配的行 |
2.调试相关的指令
命令全称(命令缩写) | 具体含义 |
---|---|
break(b) | 设置断点 |
clear | 删除执行到达时的断点 |
delete | 删除所有断点 |
disable | 禁用断点 |
enable | 启用断点 |
run(r) | 一直执行,直到断点或结束 |
step(s) | 执行下一行代码,遇到函数直接进入 |
next(n) | 执行下一行代码,遇到函数不会进入 |
continue(c) | 继续执行直到下一个断点 |
jump | 直接跳到指定位置继续执行 |
until | 和next一样单步执行,常用于循环体中 |
disass | 对函数等进行反汇编 |
disassemble | 对地址进行反汇编 |
directory(dir) | 设置执行路径 |
set | 设定运行的参数 |
signal | 向被调试程序发信号 |
handle | 设置信号对应的操作 |
3.进程和线程调试相关的指令
命令全称(命令缩写) | 具体含义 |
---|---|
backtrace(bt) | 打印运行到当前位置的堆栈信息 |
frame | 在函数停止的地方,显示当前堆栈 |
up | 向前移动堆栈地址 |
down | 向后移动堆栈地址 |
atttach pid | 运行某个进程 |
info threads | 查看当前线程和id |
thread num | 切换到num指定的线程 |
info inferiors | 查看当前进程和id |
inferior num | 切换到num指定的进程 |
kill inferior num | 杀死num指定的进程 |
set follow-fork-mode parent/child | 设置fork后进入父/子进程调试 |
set detach-on-fork on/off | 设置fork时是否同时调试父子进程 |
set scheduler-locking on/off | 设置调试线程时,其他线程是否同步执行 |
五,GDB使用案例
1.多线程调试
代码样例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
void func(int pid, int ret)
{
printf("My PID is %d, fork() returned %d\n", pid, ret);
if (ret)
printf("We are in the parent process\n");
else
printf("We are in the child process\n");
}
int main()
{
int r = fork();
func(getpid(), r);
return 0;
}
调试结果:
查看当前进程:
2.分析程序core dump
a.关于core文件
当执行程序发生core dump时,一般会生成core文件。core文件主要包含了程序运行时的内存、寄存器状态、堆栈指针以及函数堆栈等信息。
b.设置core文件的生成需要使用的命令:
1.不生成core文件:
ulimit -c 0
2.生成不限制大小的core文件:
ulimit -c unlimited
3.检查生成core文件的选项是否打开 :
ulimit -a
运行结果:
代码样例:
#include <stdio.h>
void foo()
{
int *ptr = 0;
*ptr = 7;
}
int main()
{
foo();
return 0;
}
运行结果(发生了core dump):
有core文件生成:
开始用GDB分析:
调试命令:
gdb demo core
调试结果:
定位到了让程序崩溃的地方:"*ptr = 7"
查看堆栈信息
六,参考阅读
https://www.tenouk.com/Module000linuxgcc1.html
https://sourceware.org/gdb/onlinedocs/gdb/index.html#SEC_Contents
https://visualgdb.com/gdbreference/commands/set_follow-fork-mode