背景
调试linux程序一般有两种,一种是检查程序的日志输出,但如果问题与IO有关就不能通过日志获得调试信息了;
虽然gdb不常用且命令复杂,但却是不可或缺呀。
常用调试命令
分类 | 序号 | 命令 | 说明 |
运行 | 1 | r | run简写,运行程序,遇到断点暂停 |
2 | c | continue简写,继续执行直到下一个断点或程序结束 | |
3 | n | next简写,单步跟踪,遇函数调用时不进入函数体 | |
4 | s | step简写,单步进入,遇函数调用时进入函数体 | |
5 | until | 如果进入循环体,until将继续执行直到跳出循环体 | |
6 | until +n | 继续运行,到第n行暂停 | |
7 | finish | 继续执行,直到当前函数返回,并打印函数返回时的堆栈地址及返回值等相关信息 | |
8 | call 函数(参数) | 手动调用程序中的可见函数并传参 | |
9 | q | quit简写,退出gdb | |
断点 | 10 | b line/func if a > b | 设置条件断点,断点位置是某文件的某行或某个函数入口处,如果条件成立则断点设置成功 |
11 | delete n | 删除第n个断点 | |
12 | disable n | 暂停第n个断点 | |
13 | enable n | 开启第n个断点 | |
14 | clear n | 删除第n行上的断点 | |
15 | info b | 查看断点设置情况 | |
16 | delete breakpoints | 删除所有断点 | |
查看源码 | 17 | l | 接着显示上次显示的后面的源码 |
18 | l n | 显示第n行附近的源码 | |
19 | l func | 显示函数func附近的源码 | |
打印表达式 | 20 | print a | 打印变量a的值 |
21 | print ++a | a加1后打印 | |
22 | print name | 打印字符串name的值 | |
23 | priint func(n) | 调用函数func并传参n,n可以是一个常量,也可以是一个程序中已定义的变量 | |
24 | display 表达式 | 常用于单步调试 | |
25 | watch 表达式 | 监视表达式,当表达式的值改变时运行暂停并打印监视点的值 | |
26 | whatis | 查询变量或函数 | |
27 | info function | 打印所有函数信息 | |
28 | info locals | 显示当前堆栈的所有变量 | |
查询运行状态 | 29 | where/bt | 查看当前堆栈列表 |
30 | bt backtrace | 显示当前调用堆栈 | |
31 | up/down | 改变堆栈深度 | |
32 | set args 参数 | 设置运行参数 | |
33 | show args | 查看运行参数 | |
34 | info program | 查看运行状态 | |
分割窗口 | 35 | layout src | 显示源码窗口 |
36 | layout asm | 显示反汇编窗口 | |
37 | layout regs | 显示源码/反汇编和CPU寄存器窗口 | |
38 | layout split | 显示源码和反汇编窗口 | |
39 | ctrl + l | 清空窗口 |
调试流程总结
被调试的源码(来源:https://blog.csdn.net/haoel/article/details/2879)
#include <iostream> using namespace std; int func(int n){ int sum=0; for(int i=0; i<n; i++){ sum+=i; } return sum; } int main(){ long result = 0; for(int i=1; i<=100; i++){ result += i; } cout<<"result[1-100] = "<<result<<endl; cout<<"result[1-250] = "<<func(250)<<endl; }
编译
orejia@debian9:gdb$ g++ -g main.cc -o main
进入调试
orejia@debian9:gdb$ gdb main
命令执行顺序 | 阶段 | 命令 | 说明 | 备注 |
1 | 查看代码 | l | 打印源码 | list简写,默认一次向下打印10行代码 |
2 | <enter> | 回车 | 回车重复执行上一次命令 | |
3 | 设置断点 | b main.cc:16 | 设置断点 | break简写,在文件main.cc的第16行设置断点,如果程序只有一个文件可省略文件名 |
4 | b main.cc:func | 设置断点 | 在函数func入口处设置断点 | |
b 7 if sum > 4 | 设置条件断点 | 当 sum 大于 4时,程序在第7行暂停运行 | ||
5 | info b | 查看断点信息 | info命令可打印与调试过程有关的信息, | |
6 | 运行调试 | r | 启动程序 | 代码开始执行,遇到断点暂停 |
7 | n | 单步执行 | next简写 | |
8 | c | 继续执行 | continue简写,遇到下一一个断点将再次暂时 | |
9 | p i | 查看变量 | print i 打印变量i的当前值 | |
10 | bt | 查看函数堆栈 | breaktrace简写 | |
11 | finish | 跳出当前函数 | 跳转到当前所在函数的被调用位置并继续执行,直到遇到下一个断点 | |
12 | 退出调试 | q | 退出 | 退出gdb调试命令行 |
core文件
前期设置
解除core文件限制,将其添加至/etc/profile
ulimit -c unlimited
修改core存储位置(默认与可执行程序在同级目录,但因为不知名原因有时会找不到,因此建议设置一个固定位置)
/proc/sys/kernel/core/pattern代表core文件名,可以给它加上路径前缀以及可执行文件名、pid 、时间等后缀参数
sudo echo "/data/coredump/core-%e-%p-%t" > /proc/sys/kernel/core_pattern"
coredump可能原因:
- 数组下标访问越界
- 对常量区数据(如字符指针)进行写操作
- 调用不安全的字符串操作函数(strcpy ctrcat等)
- 局部数据量太大导致堆栈溢出
- 使用空指针
- 不规范的指针类型强制转换
- 多线程访问共享数据时未加锁
- 调用线程不安全函数
调试core文件
gdb ./test core
进入gdb后,执行bt显示堆栈
cmake使用gdb
SET(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
SET(CMAKE_C_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall -g -ggdb")
参考
官方英文文档:https://sourceware.org/gdb/
gdb常用命令:https://www.cnblogs.com/tangtangde12580/p/8045980.html
core文件详解:https://blog.csdn.net/wkd_007/article/details/79757289
gdb调试手册:https://blog.csdn.net/MOU_IT/article/details/88322477