GDB 的简单使用
一、启动调试
直接使用gcc或g++命令编译生成的可执行文件无法直接用于调试,因为它们缺少必要的调试信息(代码行号、符号表等)。因此需要使用 -g
选项编译源文件,生成满足GDB要求的可调试的可执行文件,然后通过 gdb 可执行文件名
启动调试。
atreus@iZwz9fsfltolu74amg1v0rZ:~/Code$ g++ -g remove.cpp -o remove
atreus@iZwz9fsfltolu74amg1v0rZ:~/Code$ gdb remove
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from remove...
(gdb) quit
atreus@iZwz9fsfltolu74amg1v0rZ:~/Code$
二、常用调试命令
1.list(显示程序源代码)
list
命令(可以用 l
代替)用于显示程序源代码,默认只显示十行,此默认行数值可以通过 show listsize
查看,并通过 set listsize 一次显示的行数
修改。list
的主要用法如下:
list
:显示当前行后面的代码,默认从头开始打印。list -
:显示当前行前面的代码。list 文件名:行号
:打印指定文件中指定行号附近的代码。list 文件名:函数名
:打印指定文件中指定函数起始处附近的代码。list 文件名:起始位置,结束位置
:打印从起始位置到结束位置的代码。
需要注意的是,文件名参数可以省略,且省略时GDB会按照函数名自动查找或优先处理当前文件。此外在存在函数重载或函数重写时函数名并不唯一,此时可以指定函数的参数类型或函数所属的类类型以区分不同函数,这两点也适用于其他GDB命令。
(gdb) show listsize # 查看默认显示行数
Number of source lines gdb will list by default is 10.
(gdb) list main # 查看main函数附近的代码
1 #include "remove.h"
2
3 using namespace std;
4
5 int main() {
6 Solution solution;
7 vector<int> nums = {0, 1, 2, 2, 3, 0, 4, 2};
8
9 solution.removeElement(nums, 2);
10 for (auto num : nums) {
(gdb) list remove.h:6,23 # 查看remove.h文件中从第6行到第23行的代码
6 class Solution {
7 public:
8 /**
9 * 删除nums中值为val的元素并调整nums的大小
10 */
11 void removeElement(vector<int> &nums, int val) {
12 int slow = 0, fast = 0;
13
14 while (fast < nums.size()) {
15 if (nums.at(fast) != val) {
16 nums.at(slow++) = nums.at(fast);
17 }
18 fast++;
19 }
20
21 nums.resize(slow); // 根据慢指针位置调整大小
22 }
23 };
(gdb) list removeElement(std::vector<int, std::allocator<int>> &, int) # 指定函数的参数类型,可以解决函数重载
6 class Solution {
7 public:
8 /**
9 * 删除nums中值为val的元素并调整nums的大小
10 */
11 void removeElement(vector<int> &nums, int val) {
12 int slow = 0, fast = 0;
13
14 while (fast < nums.size()) {
15 if (nums.at(fast) != val) {
(gdb) list Solution::removeElement # 指定函数的所属类,可以解决函数重写
6 class Solution {
7 public:
8 /**
9 * 删除nums中值为val的元素并调整nums的大小
10 */
11 void removeElement(vector<int> &nums, int val) {
12 int slow = 0, fast = 0;
13
14 while (fast < nums.size()) {
15 if (nums.at(fast) != val) {
(gdb) list - # 向前查看代码
1 #include <iostream>
2 #include <vector>
3
4 using namespace std;
5
(gdb)
2.break、tbreak、delete、disable、enable 和 info break(断点操作)
break
命令(可以用 b
代替)用于设置断点,它的主要用法如下:
break 文件名:行号
:在指定文件的指定行号设置断点。break 文件名:函数名
:在指定文件的指定函数起始处设置断点。
此外还可以通过 break 断点位置 if 条件
命令设置条件断点,当指定条件满足时程序会被中断到指定位置。
tbreak
也用于设置断点,只不过它设置的断点为临时断点,所谓临时断点,就是指该断点触发一次后就会自动删除。
delete
命令(可以用 d
代替)用于删除断点,主要用法如下:
delete 断点编号
:删除一个或多个指定编号的断点,指定多个时以空格分隔即可。delete
:不指定断点编号则会删除所有断点。
断点不但可以删除,也可以被禁用,所谓禁用就是使目标断点暂时失去作用,必要时可以再将其激活,恢复断点原有的功能。断点通过 disable
命令禁用,禁用后通过 enable
激活,二者用法与 delete
类似。
info break
命令用于查看当前程序中的断点信息。
(gdb) break remove.h:14 # 在remove.h的第14行处添加断点
Breakpoint 1 at 0x1558: file remove.h, line 14.
(gdb) break removeElement # 在removeElement函数起始处添加断点
Breakpoint 2 at 0x1532: file remove.h, line 11.
(gdb) break main # 在main函数起始处添加断点
Breakpoint 3 at 0x1309: file remove.cpp, line 5.
(gdb) info break # 查看当前所有的断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001558 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:14
2 breakpoint keep y 0x0000000000001532 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:11
3 breakpoint keep y 0x0000000000001309 in main() at remove.cpp:5
(gdb) delete 3 # 删除编号为3的断点
(gdb) info break # 查看当前所有的断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001558 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:14
2 breakpoint keep y 0x0000000000001532 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:11
(gdb) tbreak main # 在main函数起始处添加临时断点
Temporary breakpoint 4 at 0x1309: file remove.cpp, line 5.
(gdb) info break # 查看当前所有的断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001558 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:14
2 breakpoint keep y 0x0000000000001532 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:11
4 breakpoint del y 0x0000000000001309 in main() at remove.cpp:5
(gdb) disable 4 # 禁用编号为4的断点
(gdb) info break # 查看当前所有的断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001558 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:14
2 breakpoint keep y 0x0000000000001532 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:11
4 breakpoint del n 0x0000000000001309 in main() at remove.cpp:5
(gdb)
(gdb) break remove.h:15 if fast = 3 # 当fast等于3时在remove.h第15行处中断
Breakpoint 1 at 0x1574: file remove.h, line 15.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001574 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:15
stop only if fast = 3
(gdb) run
Starting program: /home/atreus/Code/gdb_demo/remove
Breakpoint 1, Solution::removeElement (this=0x7fffffffe163, nums=std::vector of length 8, capacity 8 = {...}, val=2) at remove.h:15
warning: Source file is more recent than executable.
15 if (nums.at(fast) != val) {
(gdb) display fast
1: fast = 3
(gdb)
3.run 和 continue(运行源程序)
run
命令(可以用 r
代替)用于启动源程序,默认会运行到第一个断点处,如果没有设置断点,会直接执行完可执行程序。
continue
(可以用 c
代替)会在程序被中断后重新恢复程序的执行,直到触发断点或程序结束。
(gdb) tbreak main
Temporary breakpoint 1 at 0x1309: file remove.cpp, line 5.
(gdb) tbreak remove.h:removeElement
Temporary breakpoint 2 at 0x1532: file remove.h, line 11.
(gdb) run # 启动源程序
Starting program: /home/atreus/Code/gdb_demo/remove
Temporary breakpoint 1, main () at remove.cpp:5
5 int main() {
(gdb) continue # 恢复源程序执行
Continuing.
Temporary breakpoint 2, Solution::removeElement (this=0x7fffffffe170, nums=std::vector of length -35184372086969, capacity -11728124003598 = {...}, val=0) at remove.h:11
warning: Source file is more recent than executable.
11 void removeElement(vector<int> &nums, int val) {
(gdb) # 继续执行
Continuing.
0
1
3
0
4
[Inferior 1 (process 398883) exited normally]
(gdb)
4.next 和 step(单步调试)
next
命令(可以用 n
代替)表示单步步过(stepping over)。
step
命令(可以用 s
代替)表示单步步入(stepping into),它会在遇到函数调用时进入到函数内部去执行。
(gdb) break main
Breakpoint 1 at 0x1309: file remove.cpp, line 5.
(gdb) list remove.cpp:1,15
1 #include "remove.h"
2
3 using namespace std;
4
5 int main() {
6 Solution solution;
7 vector<int> nums = {0, 1, 2, 2, 3, 0, 4, 2};
8
9 solution.removeElement(nums, 2);
10 for (auto num : nums) {
11 cout << num << endl;
12 }
13
14 return 0;
15 }
(gdb) run
Starting program: /home/atreus/Code/remove
Breakpoint 1, main () at remove.cpp:5
5 int main() {
(gdb) next # 单步步过
7 vector<int> nums = {0, 1, 2, 2, 3, 0, 4, 2};
(gdb)
9 solution.removeElement(nums, 2);
(gdb) step # 单步步入
Solution::removeElement (this=0x7fffffffe190, nums=std::vector of length -35184372086977, capacity -11728124003606 = {...}, val=0) at remove.h:11
11 void removeElement(vector<int> &nums, int val) {
(gdb)
5.finish 和 return(函数返回)
finish
和 return
均用于从当前函数中返回,二者主要区别如下:
finish
表示执行完当前函数并返回到函数调用处,即会一直执行到函数正常再退出。return
表示直接结束当前函数执行并返回到函数调用处,还可以指定函数返回值,这也就意味着即使当前函数还有剩余代码,但也不会执行了。
(gdb) list 1,13
1 #include <stdio.h>
2
3 int func() {
4 int ret = 0;
5
6 return ret;
7 }
8
9 int main() {
10 printf("%d\n", func());
11
12 return 0;
13 }
(gdb) break 3
Breakpoint 1 at 0x1149: file main.c, line 3.
(gdb) run
Starting program: /home/atreus/Code/main
Breakpoint 1, func () at main.c:3
3 int func() {
(gdb) next
4 int ret = 0;
(gdb) return -1 # 以-1返回值直接返回
Make func return now? (y or n) y
#0 0x000055555555516f in main () at main.c:10
10 printf("%d\n", func());
(gdb) next
-1
12 return 0;
(gdb)
(gdb) run
Starting program: /home/atreus/Code/main
Breakpoint 1, func () at main.c:3
3 int func() {
(gdb) next
4 int ret = 0;
(gdb) finish # 继续执行完当前函数
Run till exit from #0 func () at main.c:4
0x000055555555516f in main () at main.c:10
10 printf("%d\n", func());
Value returned is $1 = 0
(gdb) next
0
12 return 0;
(gdb)
6.print、display 和 ptype(查看变量信息)
print
(可以用 p
代替)用于在调试过程中查看变量的值或内存地址,ptype
命令用于输出变量的类型。
display
也可以查看监视的变量或者内存地址,而且会在每次程序中断下来时进行输出,通过 delete display
清除需要自动输出的变量。
(gdb) break remove.h:14
Breakpoint 1 at 0x1558: file remove.h, line 14.
(gdb) run
Starting program: /home/atreus/Code/remove
Breakpoint 1, Solution::removeElement (this=0x7fffffffe183, nums=std::vector of length 8, capacity 8 = {...}, val=2) at remove.h:14
14 while (fast < nums.size()) {
(gdb) print this # 打印this指针的值
$1 = (Solution * const) 0x7fffffffe183
(gdb) print fast # 打印fast变量的值
$2 = 0
(gdb) print &fast # 打印fast变量的地址
$3 = (int *) 0x7fffffffe15c
(gdb) print nums.at(fast) # 打印指定表达式的值
$4 = 0
(gdb) print nums.at(fast) == 0
$5 = true
(gdb) display slow # 在每次中断时输出fast的值
1: slow = 0
(gdb) display fast
2: fast = 0
(gdb) next
15 if (nums.at(fast) != val) {
1: slow = 0
2: fast = 0
(gdb)
16 nums.at(slow++) = nums.at(fast);
1: slow = 0
2: fast = 0
(gdb)
18 fast++;
1: slow = 1
2: fast = 0
(gdb) delete display 1
(gdb) next
14 while (fast < nums.size()) {
2: fast = 1
(gdb) ptype fast # 打印fast变量的类型
type = int
(gdb) ptype nums # 打印nums的类型,它是一个vevtor容器
type = class std::vector<int> [with _Tp = int, _Alloc = std::allocator<int>] : protected std::_Vector_base<_Tp, _Alloc> {
private:
static bool _S_nothrow_relocate(std::true_type);
static bool _S_nothrow_relocate(std::false_type);
static bool _S_use_relocate(void);
static pointer _S_do_relocate(pointer, pointer, pointer, _Alloc &, std::true_type);
static pointer _S_do_relocate(pointer, pointer, pointer, _Alloc &, std::false_type);
static pointer _S_relocate(pointer, pointer, pointer, _Alloc &);
public:
vector(void);
vector(const _Alloc &);
vector(size_type, const _Alloc &);
vector(size_type, reference, const _Alloc &);
vector(const std::vector<int> &);
vector(std::vector<int> &&);
vector(const std::vector<int> &, const _Alloc &);
private:
vector(std::vector<int> &&, const _Alloc &, std::true_type);
vector(std::vector<int> &&, const _Alloc &, std::false_type);
public:
vector(std::vector<int> &&, const _Alloc &);
vector(std::initializer_list<_Tp>, const _Alloc &);
~vector();
std::vector<int> & operator=(const std::vector<int> &);
std::vector<int> & operator=(std::vector<int> &&);
std::vector<int> & operator=(std::initializer_list<_Tp>);
void assign(size_type, reference);
void assign(std::initializer_list<_Tp>);
iterator begin(void);
const_iterator begin(void) const;
iterator end(void);
const_iterator end(void) const;
reverse_iterator rbegin(void);
const_reverse_iterator rbegin(void) const;
reverse_iterator rend(void);
const_reverse_iterator rend(void) const;
const_iterator cbegin(void) const;
const_iterator cend(void) const;
const_reverse_iterator crbegin(void) const;
const_reverse_iterator crend(void) const;
size_type size(void) const;
size_type max_size(void) const;
void resize(size_type);
void resize(size_type, reference);
void shrink_to_fit(void);
size_type capacity(void) const;
bool empty(void) const;
void reserve(size_type);
reference operator[](size_type);
reference operator[](size_type) const;
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) ptype nums.resize()
type = void
(gdb)
7.watch(添加数据断点)
watch
用于监视一个变量或者一段内存,当这个变量或该内存处的值发生变化时,gdb就会中断下来,通过 watch
添加的断点一般被称为数据断点,有时候也被称为硬件断点。
但是与 break
添加的普通断点不同的是,数据断点必须在程序运行时设置,在执行 run
命令之前对变量设置数据断点会提示无法在当前上下文中找到对应符号。
(gdb) break remove.h:12
Breakpoint 1 at 0x154a: file remove.h, line 12.
(gdb) run
Starting program: /home/atreus/Code/gdb_demo/remove
Breakpoint 1, Solution::removeElement (this=0x7fffffffe163, nums=std::vector of length 8, capacity 8 = {...}, val=2) at remove.h:12
12 int slow = 0, fast = 0;
(gdb) watch slow # 为slow变量添加一个数据断点,每当它的值发生变化时将程序中断下来
Hardware watchpoint 2: slow
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x000055555555554a in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:12
breakpoint already hit 1 time
2 hw watchpoint keep y slow
(gdb) continue
Continuing.
Hardware watchpoint 2: slow
Old value = 1431656218
New value = 0
Solution::removeElement (this=0x7fffffffe163, nums=std::vector of length 8, capacity 8 = {...}, val=2) at remove.h:12
12 int slow = 0, fast = 0;
(gdb)
Continuing.
Hardware watchpoint 2: slow
Old value = 0
New value = 1
Solution::removeElement (this=0x7fffffffe163, nums=std::vector of length 8, capacity 8 = {...}, val=2) at remove.h:16
16 nums.at(slow++) = nums.at(fast);
(gdb)
8.backtrace 和 frame(函数调用栈)
backtrace
(可以用 bt
代替)命令用于查看当前线程的函数调用栈。
frame
用于切换当前栈帧,info frame
用于打印当前栈帧信息。
(gdb) break 3
Breakpoint 1 at 0x114d: file recursion.cpp, line 3.
(gdb) run
Starting program: /home/atreus/Code/gdb_demo/recursion
Breakpoint 1, func (x=10) at recursion.cpp:3
3 return;
(gdb) backtrace # 查看当前的函数调用栈
#0 func (x=10) at recursion.cpp:3
#1 0x000055555555514b in func (x=9) at recursion.cpp:5
#2 0x000055555555514b in func (x=8) at recursion.cpp:5
#3 0x000055555555514b in func (x=7) at recursion.cpp:5
#4 0x000055555555514b in func (x=6) at recursion.cpp:5
#5 0x000055555555514b in func (x=5) at recursion.cpp:5
#6 0x000055555555514b in func (x=4) at recursion.cpp:5
#7 0x000055555555514b in func (x=3) at recursion.cpp:5
#8 0x000055555555514b in func (x=2) at recursion.cpp:5
#9 0x000055555555514b in func (x=1) at recursion.cpp:5
#10 0x000055555555514b in func (x=0) at recursion.cpp:5
#11 0x0000555555555162 in main () at recursion.cpp:10
(gdb) backtrace 3 # 查看当前的函数调用栈栈顶的三个栈帧
#0 func (x=10) at recursion.cpp:3
#1 0x000055555555514b in func (x=9) at recursion.cpp:5
#2 0x000055555555514b in func (x=8) at recursion.cpp:5
(More stack frames follow...)
(gdb) backtrace -3 # 查看当前的函数调用栈栈底的三个栈帧
#9 0x000055555555514b in func (x=1) at recursion.cpp:5
#10 0x000055555555514b in func (x=0) at recursion.cpp:5
#11 0x0000555555555162 in main () at recursion.cpp:10
(gdb) frame 5 # 切换到5号栈帧
#5 0x000055555555514b in func (x=5) at recursion.cpp:5
5 func(x + 1);
(gdb) info frame # 打印当前栈帧信息
Stack level 5, frame at 0x7fffffffe140:
rip = 0x55555555514b in func (recursion.cpp:5); saved rip = 0x55555555514b
called by frame at 0x7fffffffe160, caller of frame at 0x7fffffffe120
source language c++.
Arglist at 0x7fffffffe118, args: x=5
Locals at 0x7fffffffe118, Previous frame's sp is 0x7fffffffe140
Saved registers:
rbp at 0x7fffffffe130, rip at 0x7fffffffe138
(gdb)
附、调试代码
remove.h:
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
/**
* 删除nums中值为val的元素并调整nums的大小
*/
void removeElement(vector<int> &nums, int val) {
int slow = 0, fast = 0;
while (fast < nums.size()) {
if (nums.at(fast) != val) {
nums.at(slow++) = nums.at(fast);
}
fast++;
}
nums.resize(slow); // 根据慢指针位置调整大小
}
};
remove.cpp:
#include "remove.h"
using namespace std;
int main() {
Solution solution;
vector<int> nums = {0, 1, 2, 2, 3, 0, 4, 2};
solution.removeElement(nums, 2);
for (auto num : nums) {
cout << num << endl;
}
return 0;
}
recursion.cpp
void func(int x) {
if (x == 10) {
return;
} else {
func(x + 1);
}
}
int main() {
func(0);
return 0;
}