GDB 的简单使用
- 一、启动调试
- 二、常用调试命令
- 1.list(显示程序源代码)
- 2.break、tbreak、delete、disable、enable 和 info break(断点操作)
- 3.run(运行源程序)
- 4.next 和 step(单步调试)
- 5.finish 和 return(函数返回)
- 6.print、display 和 ptype(查看变量信息)
- 附、调试代码
一、启动调试
直接使用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 文件名:函数名
:在指定文件的指定函数起始处设置断点。
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)
3.run(运行源程序)
run
命令(可以用 r
代替)用于启动源程序,默认会运行到第一个断点处,如果没有设置断点,会直接执行完可执行程序。
(gdb) tbreak main # 在main函数处设置断点
Temporary breakpoint 1 at 0x1309: file remove.cpp, line 5.
(gdb) break removeElement # 在removeElement函数处设置断点
Breakpoint 2 at 0x1532: file remove.h, line 11.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint del y 0x0000000000001309 in main() at remove.cpp:5
2 breakpoint keep y 0x0000000000001532 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:11
(gdb) run # 运行源程序
Starting program: /home/atreus/Code/remove
Temporary breakpoint 1, main () at remove.cpp:5
5 int main() {
(gdb) info break # 查看当前所有的断点信息,可以看到main函数处的临时断点已经被删除
Num Type Disp Enb Address What
2 breakpoint keep y 0x0000555555555532 in Solution::removeElement(std::vector<int, std::allocator<int> >&, int) at remove.h:11
(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
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
$1 = (Solution * const) 0x7fffffffe183
(gdb) print fast
$2 = 0
(gdb) print &fast
$3 = (int *) 0x7fffffffe15c
(gdb) print nums.at(fast)
$4 = 0
(gdb) print nums.at(fast) == 0
$5 = true
(gdb) display slow
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
type = int
(gdb) ptype nums
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)
附、调试代码
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;
}