GDB简介
GNU调试器(GNU Debugger,简称GDB)是一款GNU软件系统中的标准调试器,现有GDB所能支持调试的编程语言有C、C++、Pascal以及FORTRAN。GDB具有极好的移携性,经过多个版本的调修与重新编译,如今许多的类UNIX操作系统上都可以使用GDB。
GDB官网:
GDB: The GNU Project Debuggerwww.gnu.orgGDB和LLDB
GNU调试器(GNU Debugger,简称GDB)被反复利用且广泛使用了LLVM中现有库(如Clang表达式解析器和LLVM反汇编程序)的组件。此外,LLDB是一款开源的具有REPL(Read-Evaluation-Print-Loop)特征的调试工具,相比GDB在部分场景下更加先进和高效。Xcode自4.3版本后默认内置了LLDB,因此macOS用户可以直接使用LLDB对程序进行调试。(当然macOS同样也可以安装GDB调试工具) 。
GDB与LLDB命令对照表:
GDB to LLDB command maplldb.llvm.orgWINDOWS下安装GCC和GDB
MinGW(Minimalist GNU For Windows)是Windows下的一个小型GNU工具集。新版MinGW中集成了GCC和GDB的相关工具,因此只需安装MinGW即可使用GCC和GDB。
下载MinGW及GCC包
首先前往MinGW官方网站:MinGW 并在Download界面下载MinGW管理工具。
![41d47ddf1d860d29c8d636320865dfe4.png](https://i-blog.csdnimg.cn/blog_migrate/90ef3a133e4c19fa9dd3d7809cccebcc.jpeg)
![6e0efd815ecc6f4119291445f31637f7.png](https://i-blog.csdnimg.cn/blog_migrate/8775a825006d4fd7ec9b42a36b7786af.jpeg)
安装MinGW管理工具并打开。
![e213069a9ef3b5c7b44f0a814f3d2815.png](https://i-blog.csdnimg.cn/blog_migrate/ebae4544d54f416ff5edca5695dea75d.jpeg)
选择所需的工具集,点击左上角的Installation->Apply Changes即可安装。(由于受国内网络环境影响,可能会出现下载失败的情况,可以多尝试几次或使用其他网络连接方式下载)。若使用C/C++进行开发,通常仅需安装mingw32-base-bin及mingw32-gcc-g++-bin即可。
配置系统环境变量
安装完成后,我们需要配置系统环境变量使得其可以在CMD中执行。
右键此电脑->属性->高级系统设置->环境变量,在系统变量框中找到Path变量,选择编辑,在最后新增一行填入你的MinGW安装路径bin,确定退出。
![68cf9aaac32a75a2a2023aedd3dfe817.png](https://i-blog.csdnimg.cn/blog_migrate/9bbb2f03cf6f3d2488c7e85198a126a7.jpeg)
打开CMD,输入gcc -v及gdb -v,若显示相关版本信息,表明环境变量配置成功。
![736f4801ef7ea2260927d54697513cda.png](https://i-blog.csdnimg.cn/blog_migrate/f0e9723a237f0c3f4ca69efb19c33dd7.jpeg)
在其他IDE中调用GDB
CLion
CLion本身已经集成了十分完善的可视化调试工具,但如果想在CLion中使用GDB或LLDB调试程序同样十分方便。
使用通常的方法在需要的地方打上断点启动调试,在下方的Debug窗口中就可以看到GDB/LLDB窗口,在其中输入命令即可。
![faa4830e2e9e01fd2788c206578ba6b8.png](https://i-blog.csdnimg.cn/blog_migrate/f6db93b94f43fc28048e4f78a57f9fbe.jpeg)
Visual Studio Code
VSCode本身并不自带GCC和GDB的相关功能,因此使用VSCode进行调试同样需要额外安装GDB/LLDB调试工具。不过,VSCode提供了一个可视化的C/C++调试插件,相比命令行调试会直观很多。该插件可直接在插件商店里搜索并安装。
![18fd61f80ee20f5d0e8cc77b9ce4d749.png](https://i-blog.csdnimg.cn/blog_migrate/c1385b089d8193677a44d23ae02d4641.png)
和其他大型IDE类似,VSCode将一个目录视为一个项目,因此在创建项目时,你需要指定一个空的文件夹来存放项目文件(这也是部分用户装了辅助插件却被提示要手动配置调试文件的一大重要原因)。在第一次运行程序时,选择Run->Start Debugging,在弹出的选项框中选择一个要使用的调试环境及编译环境,VSCode就会自动在当前的项目目录下创建相应的配置文件。(当然也可以手动指定配置文件,具体配置方法可参考VSCode官方文档:C++ programming with Visual Studio Code)
![1587730f1759fe448e00e7fca23b745c.png](https://i-blog.csdnimg.cn/blog_migrate/1859628cad2dded4f0c5bb5a3d9b67ea.png)
![d30fd7dd471c464155f0d75b66b3f8bc.png](https://i-blog.csdnimg.cn/blog_migrate/f4afde03066a48bc8474ef5bd12a2727.png)
随后即可在调试界面中看到相关的调试信息。你也可以在下方的Debug Console中输入相关的GDB/LLDB命令来进行进一步的调试。
![85bea32c677e89780a7a108d74b2fa28.png](https://i-blog.csdnimg.cn/blog_migrate/2cc2dd7b25865c049ddb4e3b360d547a.jpeg)
GDB的基本使用
启动调试
若要使用GDB来调试程序,需要在使用GCC编译源文件的时候打开-g选项。
gcc -g [源文件名] -o [目标文件名]
Example:
gcc -g test.c -o test
若不打开调试选项,则在调试时无法添加断点。
在某些情况下,我们可能需要编译在64的机器上编译32位的程序,此时我们可以使用-m32选项得到使用32位汇编代码编译的可执行文件。
gcc -g [源文件名] -o [目标文件名] -m32
使用gdb打开生成的可执行文件即可开始调试。
gdb [目标文件名]
GDB还可以关联正在运行的程序进行调试。我们可以通过ps命令查询目标进程的PID,随后进入GDB使用attach命令关联进程。
ps -ef|grep [进程名]
gdb
(gdb)(lldb) attach [PID]
Linux用户在这一过程过可能会遇到权限不足的情况。解决方法:切换至root用户,进入/etc/stsctl.d/10-ptrace.conf中将kernel.yama.ptrace_scpoe = 1
改为kernel.yama.ptrace_scpoe = 0
即可。
事实上,GDB默认自带了一个类GUI的辅助调试界面,可以更加直观的看到各个指令下的各种参数,只需要在运行GDB时加上-tui参数即可打开。
gdb -tui [目标文件名]
添加断点
通常在调试过程中,我们需要在程序的某个位置添加断点,并让程序运行到这一位置时自动暂停以分析程序当前的运行状态。在GDB环境下,我们可以通过break命令来快速添加断点。
(gdb)(lldb) break [源文件名称]:[行号] #执行到某一行时中断
(gdb)(lldb) break [函数名] #执行到某个函数时中断
Example:
(gdb) break test.c:5
(gdb) break main
有时我们希望程序在特定条件下中断,这个时候我们可以使用break+if语句来设置条件断点。
(gdb) break [中断位置] if 触发条件
Example:
(gdb) break test.c:10 if a==5
此外,还可以使用condition语句达到同样的效果:
(gdb) break [中断位置]
(gdb) condition [断点号] [触发条件]
(lldb) breakpoint set --name [函数名] --condition '条件表达式'
Example:
(gdb) break test.c:10
(gdb) condition 1 a==5
我们可以使用info指令查看已设置断点的断点号及相关信息
(gdb) info breakpoints
(lldb) breakpoint list
通过clear和delete命令可以删除已创建的断点。
(gdb) clear [目标文件名]:[行号] #删除某一行处的断点
(gdb) clear [函数名] #删除某个函数处的断点
(gdb) delete #删除所有断点
(gdb) delete [断点号] #删除某一特定断点
(lldb) breakpoint delete [断点号]
Example:
(gdb) clear test:5
(gdb) delete 1
运行程序
对于不需要向main函数传递参数的程序,可以直接使用run指令开始运行程序。
(gdb)(lldb) run
对于需要向main函数传递参数的程序,可以直接在run后加上参数来运行程序。
(gdb)(lldb) run [参数]
Example:
(gdb) run para1 para2 para3
此外,还可以使用set args指令达到同样的效果:
(gdb) set args [参数]
(gdb) run
(lldb) settings set target.run-args [参数]
(lldb) run
Example:
(gdb) set args para1 para2 para3
(gdb) run
程序运行后,会一直运行至第一个断点处并暂停。若没有设置断点,则效果等同于直接运行程序。
当程序中断后,GDB提供了以下几种继续运行的指令。
(gdb) next #单步执行(不进入函数内部,等同于大部分IDE中的Step Over)
(gdb) step #单步进入(进入函数内部,等同于大部分IDE中的Step Into)
(gdb) continue #继续执行至下一个断点处
(gdb) until [行号] #继续执行直至某一行
(gdb) finish #运行至程序结尾
(lldb) thread until [行号]
(lldb) finish #运行至该函数结尾
查看变量及内存
在程序中断时,GDB提供了一系列指令来查看当前变量及内存中的各种信息。
通过print指令可以打印变量或表达式的值
(gdb) print [变量名/表达式] #查看当前函数下的变量/表达式(局部或全局变量均可)
(gdb) print '[源文件名/函数名]'::[变量名/表达式]
(gdb) print /[格式控制符] [变量名/表达式] #以固定格式打印当前函数下的变量/表达式
(gdb) print [指针名/数组名]@[数组大小] #查看数组信息
(lldb) frame variable [变量名] #查看局部变量
(lldb) print [变量名/表达式] #查看局部变量/表达式
(lldb) target variable [变量名] #查看全局变量
(lldb) frame variable --format [格式控制符] [变量名]
(lldb) parray [数组大小] [数组名]
其中,格式控制符有以下几种:
x | 按十六进制格式显示 |
d | 按十进制格式显示变量 |
u | 按十进制格式显示无符号整数 |
o | 按八进制格式显示变量 |
t | 按二进制格式显示变量 |
a | 按十六进制格式显示地址 |
c | 按字符格式显示变量 |
f | 按浮点数格式显示变量 |
Example:
(gdb) print a
(gdb) print 'main'::a
(gdb) print /x 'test.c'::b
(gdb) print 'main'::*p
(gdb) print *p@10
通过display命令可以使得每次程序中断时自动打印某个变量或表达式的值
(gdb) display [变量名/表达式]
此外,我们还可以通过watch指令追踪某一变量,使其值发生改变时中断程序
(gdb) watch [变量名]
(lldb) watchpoint set variable [变量名]
通过x(examine的缩写)命令,我们可以查看指定内存地址处指定长度的内存信息
(gdb) x /[内存单元个数][格式控制符][地址单元长度] [内存地址]
(lldb) memory read --size [地址单位长度(数字,单位:字节)] --format [格式控制符] --count [内存单元个数] [内存地址]
(lldb) x/[内存单元个数][格式控制符][地址单元长度] [内存地址] #x与/直接无空格
其中,格式控制符与前文相同,地址单元长度有以下四种:(lldb中若使用memory命令需直接写数字)
b | 单字节 |
h | 双字节 |
w | 四字节 |
g | 八字节 |
Example:
(gdb) x /4xw 0xbffff3c0
通过backtrace指令可以查看函数调用栈的存储情况及相关信息。
(gdb) backtrace
机器级程序调试
GDB还支持机器级代码的调试。当程序在某个函数中中断时,可以通过disassemble命令打印该函数对应的汇编代码
(gdb) disassemble #查看当前函数对应的汇编代码
(gdb) disassemble [函数名] #查看指定函数对应的汇编代码
(gdb) disassemble /m [函数名] #源码与与汇编代码对应显示
(lldb) disassemble --frame
(lldb) disassemble --name [函数名]
(lldb) disassemble /m --name [函数名]
Example:
(gdb) disassemble /m main
通过info命令可以查看当前寄存器中的信息
(gdb) info registers #查看通用寄存器信息
(gdb) info all-registers #查看所有寄存器信息
(gdb) info all-registers [寄存器名称]
(lldb) register read
(lldb) register read --all
(lldb) register read [寄存器名称]
Example:
(gdb) info all-registers rax rsp rbp
若要在机器级代码上w进行单步运行操作,可以通过stepi和nexti命令完成
(gdb) stepi #单步执行
(gdb) nexti #单步进入
(lldb) thread step-inst
(lldb) thread step-inst-over
对于更多命令的详细用法,可以通过help指令查询
(gdb) help [命令]