更多精彩内容 |
---|
个人内容分类汇总 |
C++软件调试、异常定位 |
GDB官方教程文档(英文) |
100个gdb小技巧 |
GDB是GNU调试器的缩写,是一种用于调试程序的工具。
它可以帮助程序员在程序运行时检查程序的状态,查找程序中的错误和问题,并提供一些调试工具来帮助程序员更好地理解程序的行为。
GDB支持多种编程语言,包括C、C++、Go、Fortran和汇编语言等。
它可以在命令行界面或者图形界面下使用,并且可以在多种操作系统上运行,包括Linux、Unix、Windows等。
GDB的主要作用包括以下几个方面:
- 检查程序状态:GDB可以帮助程序员在程序运行时检查程序的状态,包括变量的值、函数的调用栈、程序的执行流程等。
- 查找程序错误和问题:GDB可以帮助程序员更快地找到程序中的错误和问题,提高程序的质量和稳定性。
- 设置断点:GDB可以设置断点,让程序在特定的位置停下来,以便程序员检查程序的状态。
- 提供调试工具:GDB还提供了一些调试工具,如单步执行、查看内存、查看寄存器等,帮助程序员更好地理解程序的行为。
总之,GDB是一种非常强大的调试工具,可以帮助程序员更快地找到程序中的错误和问题,提高程序的质量和稳定性。
学习GDB可以帮助程序员更好地调试程序,找到程序中的错误和问题,提高程序的质量和稳定性。
在开发大型软件时,程序中可能存在许多错误和问题,这些问题可能会导致程序崩溃或者出现不可预期的行为。
使用GDB可以帮助程序员更快地找到这些问题,并且提供一些调试工具来帮助程序员更好地理解程序的行为。
此外,学习GDB还可以提高程序员的调试能力,让他们更加熟练地使用调试工具,提高工作效率。
因此,学习GDB是非常有必要的。
- linux C/C++开发调试环境准备;
- GDB调试基本功能介绍;
- 调试执行;
- 断点管理;
- 线程管理;
- 调用栈管理;
- 查看修改变量;
- 其它基本功能;
- 多线程死锁调试;
- 动态库调试;
- 内存检查
- 内存泄漏;
- 堆栈溢出;
- 删除后继续使用;
- 远程调试;
- Core dump调试分析;
- 发行版调试。
GDB:GNU 工程调试器 (sourceware.org)
顶部(使用 GDB 调试) (sourceware.org)
在ubuntu中使用下 列命令安装C++开发调试环境
sudo apt install g++ gcc make gdb
测试代码
#include
using namespace std;
int func(int x)
{
int sum = 0;
for(int i = 0; i < x; i++)
{
sum += i;
}
return sum;
}
int main(int argc, char** argv)
{
for(int i = 1; i < argc; i++)
{
cout << "传入参数:" << i <<" " << argv[i] << endl;
}
int a = 0;
int x = 0;
if(argc > 1)
{
x = atoi(argv[1]);
}
a = func(x);
cout << x << " " << a << endl;
return 0;
}
gdb --args
set args
gdb --args
类似,不过是在 GDB 内部执行的命令(gdb启动后),用于修改当前正在调试的程序的命令行参数。gdb set args
可以多次修改参数,而使用 gdb --args
只能在启动时设置一次。r
run
命令用于启动程序;什么是附加到进程调试
附加到进程调试是一种调试技术,它允许开发人员在程序运行时观察和分析程序的内部状态。
通常,在调试过程中,开发人员会在代码中设置断点,以便在程序执行到特定位置时停止并检查其状态。但是,有时候问题可能只在程序运行的特定环境中出现,或者只在特定条件下才能复现。这时,附加到进程调试就非常有用了。
附加到进程调试的过程通常包括以下几个步骤:
- 打开调试器:首先,开发人员需要打开一个调试器,例如Visual Studio、GDB或LLDB等。调试器是一个用于观察和控制程序执行的工具。
- 选择进程:开发人员需要选择要附加调试的进程。这可以是正在运行的本地程序,也可以是远程计算机上的程序。
- 设置断点:开发人员可以在代码中设置断点,以便在程序执行到特定位置时停止。断点可以设置在函数调用、条件语句、循环等位置。
- 开始调试:一旦选择了进程并设置了断点,开发人员就可以开始调试了。调试器会监视程序的执行,并在断点处停止程序。
- 观察和分析:当程序停止在断点处时,开发人员可以观察和分析程序的内部状态,例如变量的值、函数的调用堆栈等。这有助于理解程序的行为和找出问题所在。
- 继续执行:在观察和分析完程序的内部状态后,开发人员可以选择继续执行程序,直到下一个断点或程序结束。
附加到进程调试是一种强大的调试技术,可以帮助开发人员快速定位和解决程序中的问题。它在软件开发和故障排除过程中非常常见和重要。
下面三条命令的主要区别在于语法的不同,但它们的功能是相同的。你可以使用gdb的各种命令来控制调试过程,例如"break"设置断点,"run"开始执行程序,"next"单步执行等等。
请注意,附加到正在运行的进程可能会对其产生一些影响,因此请谨慎使用。此外,附加到进程可能需要root权限或者对进程的所有者具有适当的权限。
是想要附加的进程的进程ID。附加成功后,gdb将会连接到该进程,并允许你使用gdb的调试功能来检查进程的状态、设置断点、单步执行等操作。
gdb attach
gdb --pid=
gdb -p
测试代码
#include
int main()
{
int num;
std::cout << "请输入一个整数:";
std::cin >> num;
int a;
a = num + 1;
int b;
b = num * 10;
std::cout << a <<" " << b << std::endl;
std::cout << "您输入的整数是:" << num << std::endl;
return 0;
}
演示如下
g++ test2.cpp -g
编译代码,加上-g
生成包含调试详细的可执行程序;cat -n test2.cpp
查看源代码;ps -aux | grep a.out
查看名称为a.out的进程的pid号; sudo gdb -q -p 9834
将gdb附加到pid号为9834的进程进行调试,需要使用超级用户权限;b test2.cpp:8
在test2.cpp文件的第8行打一个断点;常用过程执行命令
start
:启动程序并暂停在main
函数的第一行,让程序在调试器中开始执行,而不是直接运行。next
(简写为n
):执行下一行代码,并将控制权移动到下一行,遇见函数调用会跳过,如果调用的函数中有断点则会进入。
n
:简写形式,执行下一行代码。next
:执行下一行代码。next n
:执行下n行代码。nexti
:执行下一条机器指令。nexti n
:执行下n条机器指令。step [count]
(简写为s
):执行下一行代码,并将控制权移动到下一行。如果当前行是函数调用,会进入该函数并在第一行暂停。
finish
:执行完当前函数的剩余代码,并将控制权返回到调用该函数的地方。continue
(简写为c
):继续执行程序,直到遇到下一个断点或程序结束。测试代码
#include
using namespace std;
int sumFun(int size)
{
int sum = 0;
for(int i = 0; i < size; i++)
{
sum += i;
}
return sum;
}
int main()
{
int size = 10;
int sum = 0;
sum = sumFun(size);
cout << "计算从0到" << size <<" 的和为:" << endl;
cout << sum << endl;
size = 100;
sum = sumFun(size);
cout << "计算从0到" << size <<" 的和为:" << endl;
cout << sum << endl;
return 0;
}
演示
quit
:缩写q
这是最常用的退出命令,它会立即终止gdb会话并退出到终端。Ctrl + D
:这是一个快捷键组合,按下Ctrl和D键后,gdb会话会立即终止并退出到终端。detach
:用于将gdb与正在调试的程序分离,使得程序可以在后台继续运行,而不受gdb的控制。这在某些情况下非常有用,比如当你想让程序在调试过程中继续执行一段时间,而不需要gdb的干预。测试代码
#include
using namespace std;
int fun1()
{
int a = 10;
int b = 20;
throw a;
return b;
}
int fun2(int a)
{
int b = 0;
b = a + 10;
return b;
}
int main()
{
cout <<"调用函数2" << endl;
int a = 10;
cout << fun2(a) << endl;
cout << "调用函数1" << endl;
try
{
fun1();
}
catch(...)
{
cout <<"出现异常" << endl;
}
return 0;
}
设置断点命令
break
:设置断点,缩写为b
。
break
命令加上源代码的行号可以在指定行上设置断点。例如,break 10
会在第10行设置一个断点;break
命令加上函数名可以在指定函数入口处设置断点。例如,break myFunction
会在myFunction
函数的入口处设置一个断点,如果是类或者命名空间则需要加上作用域;break
命令加上条件可以在满足条件时设置断点。例如,break myFunction if i == 10
会在myFunction
函数的入口处设置一个断点,但只有当变量i
的值等于10时才会触发断点;break
命令加上内存地址可以在指定内存地址处设置断点。例如,break *0x12345678
会在内存地址0x12345678
处设置一个断点,当调试汇编程序,或者没有调试信息的程序时,经常需要在程序地址上打断点,可以使用disassemble
命令查看程序的反汇编后的地址;break
命令加上指令地址可以在指定指令处设置断点。例如,break *0x12345678
会在指令地址0x12345678
处设置一个断点;tbreak
:命令用于设置临时断点,缩写为tb
。与break命令不同,临时断点只会在程序执行到该断点时触发一次,然后自动被删除。您可以使用tbreak命令后跟函数或行号来设置临时断点。rbreak
:设置正则表达式断点,缩写为rb
;
rbreak [regexp]
,其中,regexp是一个正则表达式,用于匹配要设置断点的函数名。rbreak命令会在所有可执行文件中搜索匹配的函数名,并在每个匹配的函数入口处设置断点;catch
:catch命令用于设置异常断点。
catch [exception] [command]
;trace
:用于设置跟踪点。
演示
gdb断点管理常用命令
delete
:删除断点。会从断点列表中删除指定的断点,但不会清除程序中的断点位置。如果再次运行程序,断点仍然会被触发。
缩写为d
;
删除所有断点:使用delete
命令后不加任何参数,即可删除所有已设置的断点。
删除指定编号的断点:每个断点都有一个唯一的编号,可以使用delete
命令后加上断点编号来删除指定的断点。例如delete 1
。
删除所有在指定函数中设置的断点:可以使用delete
命令后加上函数名来删除所有在该函数中设置的断点。例如 delete function_name
。
删除所有在指定文件中设置的断点:可以使用delete
命令后加上文件名来删除所有在该文件中设置的断点。例如:delete file_name
。
删除所有在指定行号处设置的断点:可以使用delete
命令后加上行号来删除所有在该行号处设置的断点。例如delete line_number
。
需要注意的是,删除断点时需要确保当前正在调试的程序处于暂停状态,否则删除操作可能无效。可以使用info breakpoints
命令来查看当前已设置的断点,并获取它们的编号以及其他相关信息。
clear
:清除断点。会从程序中清除指定的断点位置,这意味着即使再次运行程序,断点也不会被触发。
clear
命令后不跟任何参数,可以清除所有已设置的断点。clear main.cpp:10
可以清除main.cp文件中第10行的断点。clear my_function
可以清除名为my_function的函数的断点。clear main.cpp
可以清除main.cp文件中的所有断点。disable
:禁用断点、观察点或线程。
语法:disable [breakpoint|watchpoint|thread] <编号>
;
breakpoint
表示断点;watchpoint
表示观察点;thread
表示线程;<编号>
是要禁用的断点、观察点或线程的编号。可以使用disable
命令后跟断点号来禁用特定的断点,例如disable 1
。
enable
:用于启用断点。
enable
命令后跟断点号来启用特定的断点,例如enable 1
。info breakpoints
:显示当前设置的所有断点。
i b
;info breakpoints
命令可以查看当前设置的所有断点的详细信息,包括断点号、断点类型、断点位置等。演示
gdb中的tui是指Text User Interface,它是gdb的一个可选功能,用于在终端中以图形化的方式显示源代码、汇编代码和调试信息。
tui提供了一个类似于文本编辑器的界面,可以在调试过程中更方便地查看和操作代码。
使用tui可以在终端中同时显示源代码和调试信息,以及当前执行的代码行。
它可以帮助开发人员更直观地理解代码的执行流程,快速定位问题。
启动tui功能的方式:
gdb -tui 可执行程序
;tui enable
;Ctrl x a
;关闭tui功能的方式:在gdb命令行中输入命令tui disable
;
启用tui后,可以使用以下命令来操作:
layout src
:显示源代码窗口。layout asm
:显示汇编代码窗口。layout regs
:显示寄存器窗口。layout split
:将源代码窗口和汇编代码窗口分割显示。layout next
:切换到下一个布局。layout prev
:切换到上一个布局。focus next
:将焦点切换到下一个窗口。focus prev
:将焦点切换到上一个窗口。winheight [+ | -]count
”命令调整窗口大小(winheight
缩写为win
。win_name
可以是src
、cmd
、asm
和regs
)。
win src -5
;此外,还可以使用其他gdb命令来设置断点、单步执行代码等。
需要注意的是,gdb的tui功能在不同的终端和操作系统上可能会有一些差异,具体的使用方法和快捷键可能会有所不同。可以通过在gdb命令行中输入help tui
来获取更详细的帮助信息。
测试代码
#include
using namespace std;
int g_a = 10;
char g_b = 'a';
int func(int x)
{
int sum = 0;
for(int i = 0; i < x; i++)
{
sum += i;
}
return sum;
}
struct Test{
int a = 10;
char b = 'c';
double c = 123.321;
};
int main(int argc, char** argv)
{
for(int i = 1; i < argc; i++)
{
cout << "传入参数:" << i <<" " << argv[i] << endl;
}
int a = 0;
int x = 0;
char str[] = "hello";
if(argc > 1)
{
x = atoi(argv[1]);
}
a = func(x);
cout << x << " " << a << endl;
Test t;
cout << "结构体" << endl;
return 0;
}
常用命令
info locals
:显示当前栈帧的局部变量的值。info args
:gdb将会显示出当前函数的参数列表及其对应的值。这些参数包括函数的形参名称以及实际传递给函数的值。info variables
:这个命令将显示当前作用域中所有变量的列表,包括全局变量。print variable_name
:这个命令将显示指定变量的值。(缩写为p)set print null-stop
:设置字符串的显示规则,显示字符串时遇见\0
就停止输出;set print pretty
:显示结构体,结构体换行输出;set print array on
:显示数组;p 变量名=value
:修改变量值为value;set var 变量名=value
:修改变量值为value;set main::str = "hello"
:修改字符串的值,需要注意内存越界;演示
测试代码
#include
using namespace std;
int main()
{
int a = 10;
char c = 'a';
return 0;
}
查看内存
x/[格式 长度] 地址
格式字母有:
大小字母有
b
:以字节为单位显示数据。h
:以半字(2字节)为单位显示数据。w
:以字(4字节)为单位显示数据。g
:以双字(8字节)为单位显示数据。设置内存
set {type}address=value
address
:存储地址;type
:变量类型;value
:需要设置的值。演示
测试代码
#include
using namespace std;
int fun(const char* name, int age)
{
cout << name <<" " << age << endl;
return 0;
}
int main()
{
fun("hello", 123);
return 0;
}
使用命令
info registers
:用于显示当前正在被调试程序的寄存器状态。它会列出各个寄存器的名称和当前的值。info r rdi
:查看单个寄存器rdi;函数参数的寄存器
寄存器 | 函数参数 |
---|---|
rdi | 第一个参数 |
rsi | 第二个参数 |
rdx | 第三个参数 |
rcx | 第四个参数 |
r8 | 第五个参数 |
r9 | 第六个参数 |
演示:没有调试符号时可以通过查看寄存器来进行调试。
修改寄存器命令
set var $pc=value
:修改pc寄存器,pc寄存器是一种用于存储即将被执行的指令地址的寄存器。它通常用于计算机的中央处理器(CPU)中,在执行程序时起到指示下一条要执行的指令的作用。
演示
b fun
在fun函数打一个断点;r
命令运行程序,停在断点处;info line 8
命令查看第8行代码的地址;set var &pc=0x5555555551e3
将pc寄存器执行的下一条指令修改为第8行代码;n
命令单步执行,程序直接跳转到第8行,跳过了int a = 10
这一行代码。测试代码
main.cpp
/********************************************************************************
* 文件名: main.cpp
* 创建时间: 2023-07-26 20:14:04
* 开发者: MHF
* 邮箱: [email protected]
* 功能:
*********************************************************************************/
#include
#include "test.h"
using namespace std;
int main()
{
int a = 10;
int b = 20;
int sum = 0;
test t;
t.setValue(a, b);
sum = t.getSum();
cout << sum << endl;
return 0;
}
test.h
#pragma one
class test
{
private:
int m_a = 0;
int m_b = 0;
public:
test(/* args */);
~test();
void setValue(int a, int b);
int getSum();
};
test.cpp
#include "test.h"
test::test(/* args */)
{
}
test::~test()
{
}
void test::setValue(int a, int b)
{
m_a = a;
m_b = b;
}
int test::getSum()
{
return m_a + m_b;
}
使用命令
list
:缩写为l
,它用于显示源代码,并帮助程序员在调试过程中理解代码的执行流程。list [function] [start-line [end-line]]
list
:显示当前函数或方法的源代码,默认从下一行开始,显示十行代码。list main
:显示名为"main"的函数的源代码,默认从下一行开始,显示十行代码。list main 10
:显示名为"main"的函数的源代码,从第10行开始,显示十行代码。list main.cpp:15
:显示指定文件的指定行代码。list file:fun
:显示指定文件中的指定函数代码;list class:fun
:显示指定类的成员函数代码。set listsize xx
:设置每次显示的代码行数。演示
search str
:在当前文件中查找包含str字符串的行;forward-search str
:向后查找;reverse-search str
:向前查找。测试代码
#include
using namespace std;
void fun1()
{
int a = 10;
int* b = nullptr;
*b = a;
}
void fun2()
{
char c = 'a';
cout << c << endl;
fun1();
}
int main()
{
fun2();
return 0;
}
使用命令
backtrace
命令:显示当前的函数调用栈。可以在程序崩溃或中止时使用,用来追踪函数调用链以确定程序执行到哪里。缩写为bt
;up
和down
命令:在当前函数调用链中向上或向下切换。
up
: 切换到调用当前函数的函数down
:切换回当前函数up n
:向上选择函数堆栈帧,其中n是层数down n
:切向下选择函数堆栈帧,其中n是层数frame
命令:切换到指定的栈帧(Stack Frame)。
backtrace
查看栈帧列表frame 2
切换到第3个栈帧info frame
命令:显示当前栈帧的详细信息,包括函数调用点、参数、局部变量等。
info frame
:查看当前栈帧信息info frame 3
:查看id为3的栈帧信息演示
watch: watch 命令允许您监视一个变量或一个表达式的值,并在其值发生更改时暂停程序的执行。您可以使用 watch 命令来跟踪特定变量的变化,以便在发生错误或特定事件时检查它们的值。
watch a
;watch a == 10
:观察点可以带有条件。条件可以使观察点仅在满足特定条件时触发。例如,要在变量x
的值为10时触发观察点;watch a thread 1
:指定只有线程1写变量a时触发;rwatch:rwatch 命令也是用于监视变量或表达式的值,但它只在读取(而不是写入)该值时触发断点。这对于跟踪对某个变量的读取很有用,以确认某些代码段是否访问了它。
awatch: awatch 命令可用于监视变量或表达式的读取和写入操作。当变量或表达式的值发生变化时,程序将在读取或写入操作上暂停执行。
info watch:查看观察点;
delete:删除观察点;
disable:禁用观察点;
enable:弃用观察点;
测试代码
#include
using namespace std;
int main()
{
int a = 10;
char c = 'a';
return 0;
}
演示
捕获点是一个特殊的断点,命令语法为:catch event
;
即捕获到event这个事件的时候,程序就会中断下类;
支持的捕获事件有:(可以在gdb中使用help catch
命令查看)
事件 | 说明 |
---|---|
catch assert | Catch在引发时失败的Ada断言。 |
catch catch | 当程序捕获异常时触发捕获点。可以用来跟踪异常的捕获过程。 |
catch exception | Catch Ada异常,当引发时。 |
catch exec | 捕获对exec的调用。 |
catch fork | 捕获对fork的调用。 |
catch handlers | 处理时捕获Ada异常。 |
catch load | 捕获共享库的负载。 |
catch rethrow | 重新引发时捕获异常。 |
catch signal | 通过信号的名称和/或编号捕捉信号。 |
catch syscall | 通过系统调用的名称、组和/或号码捕获系统调用。 |
catch throw | 抛出时捕获异常 |
catch unload | 捕获共享库的卸载。 |
catch vfork | 捕获对vfork的调用。 |
测试代码
#include
using namespace std;
void fun()
{
int a = 1;
cout << a << endl;
throw a;
}
int main()
{
try
{
fun();
}
catch(int a)
{
cout << "捕获:" << a << endl;
}
}
演示
测试代码
使用命令
generate-core-file:
与 gcore
类似,generate-core-file
命令也用于在 gdb
中生成核心转储文件,可以单独使用命令,也可以在generate-core-file
后跟文件路径文件名称。gcore:
在正在运行的进程中,使用 gcore
命令可以生成一个称为核心转储文件(core dump)的文件。这个核心转储文件包含了进程在崩溃时的内存状态、寄存器信息等,有助于开发人员分析程序崩溃的原因。使用示例