GDB-Beginner


主要参考:

1. GDB 是什么?

GDB,全称是 GNU Debugger,是 Unix 系统中最受欢迎的 C/C++ 调试器。

2. GDB 安装

Ubuntu 系统可以使用以下指令直接安装:

sudo apt install gdb

3. 准备源文件

这里准备了一个包含 bug 的 c++ 文件:

// main.cc
// Andrew Gilpin
// agg1@cec.wustl.edu

// This file contains the example program used in the gdb debugging
// tutorial. The tutorial can be found on the web at
// http://students.cec.wustl.edu/~agg1/tutorial/
using namespace std;
#include <iostream>

int number_instantiated = 0;

template <class T>
class Node {
public:
  Node (const T &value, Node<T> *next = 0) : value_(value), next_(next) {
    cout << "Creating Node, "
         << ++number_instantiated
         << " are in existence right now" << endl;
  }
  ~Node () {
    cout << "Destroying Node, "
         << --number_instantiated
         << " are in existence right now" << endl;
    next_ = 0;
  }

  Node<T>* next () const { return next_; }
  void next (Node<T> *new_next) { next_ = new_next; };
  const T& value () const { return value_; }
  void value (const T &value) { value_ = value; }

private:
  Node ();
  T value_;
  Node<T> *next_;
};
  
template <class T>
class LinkedList {
public:
  LinkedList () : head_(0) {};
  ~LinkedList () { delete_nodes (); };

  // returns 0 on success, -1 on failure
  int insert (const T &new_item) {
    return ((head_ = new Node<T>(new_item, head_)) != 0) ? 0 : -1;
  }

  // returns 0 on success, -1 on failure
  int remove (const T &item_to_remove) {
    Node<T> *marker = head_;
    Node<T> *temp = 0;  // temp points to one behind as we iterate

    while (marker != 0) {
      if (marker->value() == item_to_remove) {
        if (temp == 0) { // marker is the first element in the list
          if (marker->next() == 0) {
            head_ = 0;
            delete marker; // marker is the only element in the list
            marker = 0;
          } else {
            head_ = new Node<T>(marker->value(), marker->next());
            delete marker;
            marker = 0;
          }
          return 0;
        } else {
          temp->next (marker->next());
          delete temp;
          temp = 0;
          return 0;
        }
      }
      marker = 0;  // reset the marker
      temp = marker;
      marker = marker->next();
    }

    return -1;	// failure
  }

  void print (void) {
    Node<T> *marker = head_;
    while (marker != 0) {
      cout << marker->value() << endl;
      marker = marker->next();
    }
  }

private:
  void delete_nodes (void) {
    Node<T> *marker = head_;
    while (marker != 0) {
      Node<T> *temp = marker;
      delete marker;
      marker = temp->next();
    }
  }
        
  Node<T> *head_;
};

int main (int argc, char **argv) {
  LinkedList<int> *list = new LinkedList<int> ();

  list->insert (1);
  list->insert (2);
  list->insert (3);
  list->insert (4);

  cout << "The fully created list is:" << endl;
  list->print ();

  cout << endl << "Now removing elements:" << endl;
  list->remove (4);
  list->print ();
  cout << endl;

  list->remove (1);
  list->print ();
  cout << endl;

  list->remove (2);
  list->print ();
  cout << endl;

  list->remove (3);
  list->print ();

  delete list;

  return 0;
}

以及对应的 Makefile

CXX = g++
FLAGS = -ggdb -Wall

main: main.cc
	${CXX} ${FLAGS} -o main main.cc

clean:
	rm -f main

我们首先使用 make 编译程序:

make

但是现在运行程序会报 segmentation fault:

(base) zsc@BerryLap:~/workspace/gdb-learn$ ./main
Creating Node, 1 are in existence right now
Creating Node, 2 are in existence right now
Creating Node, 3 are in existence right now
Creating Node, 4 are in existence right now
The fully created list is:
4
3
2
1

Now removing elements:
Creating Node, 5 are in existence right now
Destroying Node, 4 are in existence right now
4
3
2
1

Segmentation fault

4. Debugging symbols

gdb can only use debugging symbols that are generated by g++. For Sun CC users, there is the dbx debugger which is very similar to gdb.

gdb is most effective when it is debugging a program that has debugging symbols linked in to it. With g++, this is accomplished using the -g command line argument. For even more information, the -ggdb switch can be used which includes debugging symbols which are specific to gdb. The makefile for this tutorial uses the -ggdb switch.

5. 调试

5.1 载入程序

现在,我们已经有了一个可执行程序 main 然后我们想调试这个程序。

首先,载入调试器:gdb <the program you want to debug>

gdb main

会得到下面的输出:

(base) zsc@BerryLap:~/workspace/gdb-learn$ gdb main
GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1
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 main...
(gdb)

GDB 现在在等待用户输入指令。输入 run 执行程序:

(gdb) run
Starting program: /home/zsc/workspace/gdb-learn/main
Creating Node, 1 are in existence right now
Creating Node, 2 are in existence right now
Creating Node, 3 are in existence right now
Creating Node, 4 are in existence right now
The fully created list is:
4
3
2
1

Now removing elements:
Creating Node, 5 are in existence right now
Destroying Node, 4 are in existence right now
4
3
2
1


Program received signal SIGSEGV, Segmentation fault.
0x000055555555586c in Node<int>::next (this=0x0) at main.cc:28
28        Node<T>* next () const { return next_; }
(gdb)

5.2 调查崩溃原因

从前面的输出,我们可以看到程序在 28 行崩溃,但是想要知道更加详细的信息,可以使用 backtrace 指令:

28        Node<T>* next () const { return next_; }
(gdb) backtrace
#0  0x000055555555586c in Node<int>::next (this=0x0) at main.cc:28
#1  0x0000555555555763 in LinkedList<int>::remove (this=0x55555556aeb0, item_to_remove=@0x7fffffffdf6c: 1)
    at main.cc:77
#2  0x00005555555553b1 in main (argc=1, argv=0x7fffffffe088) at main.cc:120
(gdb)

根据输出信息,我们知道了当前的函数以及 local 变量的值,也可以知道那个函数调用了崩溃的函数。本例中,可以看出,调用 LinkedList<int>::remove 时, item_to_remove 在地址 0x7fffffffdf6c。如果我们想知道这个地址的变量的值,可以使用 x 指令:

(gdb) x 0x7fffffffdf6c
0x7fffffffdf6c: 0x00000001
(gdb)

因此,LinkedList<int>::removeitem_to_remove 为 1 的时候会崩溃。

5.3 设置断点

可以使用 break 指令设置断点(在 52 行设置断点):

(gdb) break 52
Breakpoint 1 at 0x5555555555f9: file main.cc, line 52.
(gdb)

可以设置条件断点:

(gdb) condition 1 item_to_remove==1
(gdb)

上述指令的意思是:在断点 1 处当 item_to_remove=1 的时候中断

5.4 单步执行

输入 run,可是代码会在断点处停止:

(gdb) condition 1 item_to_remove==1
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/zsc/workspace/gdb-learn/main
Creating Node, 1 are in existence right now
Creating Node, 2 are in existence right now
Creating Node, 3 are in existence right now
Creating Node, 4 are in existence right now
The fully created list is:
4
3
2
1

Now removing elements:
Creating Node, 5 are in existence right now
Destroying Node, 4 are in existence right now
4
3
2
1


Breakpoint 1, LinkedList<int>::remove (this=0x55555556aeb0, item_to_remove=@0x7fffffffdf6c: 1) at main.cc:52
52          Node<T> *marker = head_;
(gdb)

输入 step 可以单步执行(直接输入 enter 会执行上次的命令,所以后面输入 enter 就可以了):

(gdb) step
53          Node<T> *temp = 0;  // temp points to one behind as we iterate
(gdb)
55          while (marker != 0) {
(gdb)
56            if (marker->value() == item_to_remove) {
(gdb)
Node<int>::value (this=0x7ffff7f1021e <std::ostream::put(char)+94>) at main.cc:30
30        const T& value () const { return value_; }
(gdb)
LinkedList<int>::remove (this=0x55555556aeb0, item_to_remove=@0x7fffffffdf6c: 1) at main.cc:75
75            marker = 0;  // reset the marker
(gdb)
76            temp = marker;
(gdb)
77            marker = marker->next();
(gdb)
Node<int>::next (this=0x55555556b360) at main.cc:28
28        Node<T>* next () const { return next_; }
(gdb)

Program received signal SIGSEGV, Segmentation fault.
0x000055555555586c in Node<int>::next (this=0x0) at main.cc:28
28        Node<T>* next () const { return next_; }
(gdb)

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb)
The program is not being run.
(gdb)

6. 更多指令

可以参考

这里列出一些常用指令:

  • b main - Puts a breakpoint at the beginning of the program
  • b - Puts a breakpoint at the current line
  • b N - Puts a breakpoint at line N
  • b +N - Puts a breakpoint N lines down from the current line
  • b fn - Puts a breakpoint at the beginning of function “fn”
  • d N - Deletes breakpoint number N
  • info break - list breakpoints
  • r - Runs the program until a breakpoint or error
  • c - Continues running the program until the next breakpoint or error
  • f - Runs until the current function is finished
  • s - Runs the next line of the program
  • s N - Runs the next N lines of the program
  • n - Like s, but it does not step into functions
  • u N - Runs until you get N lines in front of the current line
  • p var - Prints the current value of the variable “var”
  • bt - Prints a stack trace
  • u - Goes up a level in the stack
  • d - Goes down a level in the stack
  • q - Quits gdb

文章作者: Shichao Zhang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Shichao Zhang !
  目录