GDB 的简单使用

22 篇文章 0 订阅


一、启动调试

直接使用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(函数返回)

finishreturn 均用于从当前函数中返回,二者主要区别如下:

  • 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;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值