C++代码调试(gdb)
Linux系统gdb调试功能
gdb常用功能
支持的功能 | 描述 |
---|---|
断点管理 | 设置断点、查看断点等 |
调试执行 | 逐语句、逐过程执行 |
查看数据 | 在调试状态下查看变量数据、内存数据等 |
运行时修改变量值 | 在调试状态下修改某个变量的值 |
显示源代码 | 查看源代码信息 |
搜索源代码 | 对源代码进行查找 |
调用堆栈管理 | 查看堆栈信息 |
线程管理 | 调用多线程程序,查看线程信息 |
进程管理 | 调试多个进程 |
崩溃转储(core dump)分析 | 分析core dump文件 |
调试启动方式 | 用不同的方式调试进程,比如加载参数启动、附加到进程等 |
调试执行
-
list / l
- 查看代码
- 查看断点附近的代码
-
r / run
- 运行
-
set args
- 设置参数(类似main函数的传参)
set args admin password
- gdb attach [pid] // TODO:细看一下
ps aux | grep test
gdb attach [pid]
-
将gdb附加到已经跑起来的进程
-
b / break 设置断点
break 文件名:行号
- 普通断点
break 文件名:行号
- 条件断点
- 数据断点
为函数设置断点
- break 函数名
- 如果有多个函数名相同,只是参数不同,则gdb会为所有同名函数都设置断点(虚函数也一样)
# 为add_member函数打断点
break add_member
-
p / print
- 查看变量的值
-
rb / rbreak
- 使用正则表达式设置函数断点
# rb/rbreak
rb test_fun*
-
b +(-)偏移量
- 当前代码执行到某一行时,可为当前代码行的前面或者后面某一行设置断点
b +5
b -5
-
b 断点 条件
- 设置条件断点
b demo:79 if i==900
# 为函数断点设置条件
# void cond_fun_test(int a,const char *str)
b cond_fun_test if a==10
-
b *指令地址
- 在指令地址上设置断点(针对没有符号信息的调试程序)
# 先获取函数地址
p main
# 设置断点
b * 0x400aa6
-
tb / tbreak 断点
- 设置临时断点,只命中一次,然后会被自动删除,后续即使代码被多次调用也不会被调用
# tb/tbreak
tb test_fun_x
-
info b / info break(breakpoints) / i b
- 查看断点状态
- 也可以只查看某一个具体的断点,方法为在这些命令后面加上断点编号
启用/禁用断点
- disable 断点编号
- 禁用断点
- enable 断点编号
- 启用断点
- enable once 断点编号
- 启用断点一次
- 与临时断点相似,临时断点只会命中一次,命中一次之后就会自动删除;启用断点一次的不同之处在于断点启用之后,虽然只会命中一次,但是不会被删除,而是被禁用
- enable delete 断点编号
- 启用断点并删除,即如果断点被启用,当下次命中该断点后,会自动删除。相当于把一个被禁用的断点转换为临时断点
- enable count 数量 断点编号
- 启用断点并命中N次,即启用断点后可以命中N次,但是命中N次后,该断点就会被自动禁用,不会再次命中
- ignore 断点编号 次数
- 忽略断点前N次命中
ignore 1 7
查看断点
- info breakpoints
- info break
- info b
- i b
删除断点
-
delete
- 删除所有断点
-
delete 断点编号
- 删除指定断点
-
delete范围
- 删除指定范围的断点(可删除多个范围的断点)
delete 5-7 10-12
-
clear 函数名
- 删除指定函数的断点(如果有多个同名函数断点,则这些断点都会被删除)
-
clear 行号
- 删除指定行号的断点
- 删除断点命令clear和delete是有区别的。delete是全局的,不收栈帧的影响,clear命令受到当前栈帧的影响,删除的是将要执行的下一处指令的断点。delete命令可以删除所有断点,包括观察点和捕获点;clear命令不能删除观察点和捕获点
程序运行
- 继续运行
# c/continue
- 继续运行并跳过当前断点N次
# continue 次数
- 继续运行直到当前函数执行完成
# finish
- 单步执行(会进入函数)
# s/step
- 逐过程执行
# n/next
- 查看/修改变量的值
# 查看: p 变量名
# 修改: p 变量名=值
- 自动显示变量的值
每次程序暂停都可以自动显示变量值
如果display命令后面跟多个变量名,则必须要求这些变量的长度相同(比如都是整型变量)。如果长度不相同,则需要分开使用
# display 变量名,可以跟多个变量名
display {var1, var2, var3}
# info display
# undisplay 编号:取消自动变量的显示
- 显示源码
# l/list
# 每次显示20行代码
set listsize 20
# 查看指定函数的代码
list add_member
# 查看指定行的代码
l 100
- 查看内存
# x 选项 地址, 默认以十六进制显示,可设定显示宽度:x /4o
# /s /d /4d
x /s str
# 不局限于查看变量的内存信息,无论是函数地址、变量地址,还是其他地址,只要地址合法而且可以访问,都可以使用x命令来查看
- 查看寄存器
# i r
- 查看堆栈
如果bt后面跟的是一个正数,则从0开始计数。如果是一个负数,则从最大的栈帧编号开始倒序计数,但是最后显示时还是按照从小到大的编号顺序显示,只是显示的栈帧不同
# bt
# 查看指定数量的栈帧:bt 2
- 切换栈帧
# f/frame 栈帧号
# 还可以使用命令up和down来切换帧。up和down都是基于当前帧来计数的
- 查看当前帧的所有局部变量
# 查看当前帧的所有局部变量
info locals
# 查看当前帧所有的函数参数
info args
# 查看帧的详细信息
info frame 栈帧号
- 线程管理
#include <vector>
#include <thread>
#include <iostream>
#include <cstring>
#include <stdlib.h>
int count = 0;
void
do_work(void* arg)
{
std::cout << "线程函数开始" << std::endl;
//模拟做一些事情
int local_data = count;
count++;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "线程函数结束" << std::endl;
}
int
start_threads(int thread_num)
{
std::vector<std::thread> threads;
//启动10个线程
for (int i = 0; i < thread_num; ++i)
{
threads.push_back(std::thread(do_work, &i));
std::cout << "启动新线程:" << i << std::endl;
}
//等待所有线程结束
for (auto& thread : threads)
{
thread.join();
}
std::cout << "love" << std::endl;
}
int
main(int argc, char* argv[])
{
start_threads(10);
}
# 查看当前进程的所有线程信息
info threads
# 切换线程
thread 线程id
# 为线程设置断点
# break 断点 thread 线程id
b 155 thread 2
# 为线程执行命令
# thread apply 线程号 命令
thread apply 2 3 i locals
thread apply all bt
- 设置观察点
# watch
watch count
watch count==5
# 读取观察点:当该变量或者表达式被读取时,程序会发生中断
rwatch 变量或表达式
# 读写观察点:无论这个变量是被读取还是被写入,程序都会发生中断,即只要遇到这个变量就会发生中断
awatch 变量或表达式
# 查看所有观察点
info watchpoints
- 捕获点
捕获点(catchpoint)指的是程序在发生某事件时,gdb能够捕获这些事件并使程序停止执行。该功能可以支持很多事件,比如C++异常、载入动态库等(可以捕获的事件:throw、catch、exec、fork、vfork、load/unload)
# catch 事件
catch throw
# tcatch 临时捕获
- 搜索源代码
# search 正则表达式
# forward-search 正则表达式
# reverse-search 正则表达式(反向搜索)
- 查看变量类型
# ptype 可选参数 变量或者类型
# 可选参数:/r /m /M /t /o
# 可选参数用来控制显示信息,变量或者类型可以是任意的变量,也可以是定义的数据类型,比如类、结构体、枚举等
# whatis
- 跳转执行
# 不按照代码的流程逐行执行,而是按照我们期望的方式执行。命令中的位置可以是代码行或者某个函数的地址
# jump 位置
jump add_member
- 窗口管理
gdb可以同时显示几个窗口,比如源代码窗口(显示程序源码的窗口)、命令窗口(gdb命令输入和结果输出的窗口,始终可见)、寄存器窗口(显示寄存器的值)、汇编窗口等。
# layout命令可以设置显示哪个窗口、是否切分窗口等
# 显示下一个窗口
layout next
# 显示前一个窗口
layout prev
# 只显示源代码窗口
layout src
# 只显示汇编窗口
layout asm
# 显示源代码和汇编窗口
layout split
# 显示寄存器窗口,与汇编,源码窗口一起显示
layout regs
# 设置窗口为活动窗口,以便能够相应上下滚动键
focus next | prev | src | asm | regs | split
# 刷新屏幕
refresh
# 更新源码窗口
update
- 调用shell命令
# shell 命令
- 显示所有栈帧的局部变量
backtrace full
使用gdb内嵌函数
-
比如C函数(例:sizeof、strcmp)
-
甚至可以在gdb中直接进行开发
- 可以在gdb中直接调用文件操作的函数,打开一个文件并向其中写入一些内容,最后关闭文件
-
查看结构体/类的值
- 使用 p *new_node可以显示整个结构体的成员信息
# 设置字符串的显示规则,即遇到结束符时停止显示
set print null-stop
show print null-stop
set print pretty
show print pretty
gdb模式
- set logging on
- set logging off
- show logging
update
- 调用shell命令
```shell
# shell 命令
- 显示所有栈帧的局部变量
backtrace full
使用gdb内嵌函数
-
比如C函数(例:sizeof、strcmp)
-
甚至可以在gdb中直接进行开发
- 可以在gdb中直接调用文件操作的函数,打开一个文件并向其中写入一些内容,最后关闭文件
-
查看结构体/类的值
- 使用 p *new_node可以显示整个结构体的成员信息
# 设置字符串的显示规则,即遇到结束符时停止显示
set print null-stop
show print null-stop
set print pretty
show print pretty
gdb模式
- set logging on
- set logging off
- show logging
- set logging file 日志文件