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 日志文件
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐