断点 breakpoint
,即为了调试的需要,在程序中设置一些特殊标志,代码执行到这些具有特殊标志的位置时会暂停。一旦程序暂停,我们就可以查看或者修改程序运行的一些信息,比如内存信息、堆栈信息等,并且可以去检查程序运行的一些结果,去判断程序运行是否符合期望等。
总而言之,断点就是程序中断(暂停运行)的地方。gdb
提供了一些与断点有关的命令,比如设置断点、查看断点、条件断点等,尤其是设置断点的方法和技巧。
gdb
中的断点可以分为好个几种类,比如普通断点、条件断点、数据断点等,下面将详细介绍每一种断点的使用方式。
1. 在代码的某一行设置断点
先使用 gdb
启动 demo
程序,进入调试状态,在 Shell
中执行以下命令:
gdb demo
假设要在 main
函数中的某个位置设置一个断点,先来回顾源代码,查看 main
函数相关代码,如下所示,demo.cpp
内容如下:
#include <iostream>
int main()
{
int a=10, b=3;
int c = a + b;
std::cout << c << std::endl;
return 0;
}
在源代码某一行设置断点的语法如下:
break 文件名:行号
我们希望在代码的第 6 行处设置断点,即希望程序运行到第 6 行时能够命中断点并暂停,则可以在 gdb
命令行窗口中输入以下命令:
break demo.cpp:6
这样就可以设置一个断点,break
也可以简写为 b
。
然后执行命令 r
启动程序。因为我们在第 6 行设置了第一个断点,所以代码运行到第 6 行时会暂停下来,这时就可以使用一些 gdb
命令来查看信息了。比如,输入 list
(缩写为 l
)来查看断点附近的代码,使用 print
(缩写为 p
)来查看变量的值。
完整过程如下:
$ gdb demo
...
Reading symbols from demo...done.
(gdb) break demo.cpp:6
Breakpoint 1 at 0x8a0: file demo.cpp, line 6.
(gdb) b demo.cpp:7
Breakpoint 2 at 0x8ab: file demo.cpp, line 7.
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Breakpoint 1, main () at demo.cpp:6
6 int c = a + b;
(gdb) print a
$1 = 10
(gdb) p b
$2 = 3
(gdb) p c
$3 = 0
(gdb) c
Continuing.
Breakpoint 2, main () at demo.cpp:7
7 std::cout << c << std::endl;
(gdb) p c
$4 = 13
2. 为函数设置断点
2.1 单个唯一函数
为函数设置断点的语法如下:
break 函数名
为某个函数设置断点后,只要代码中调用该函数,就会命中并暂停。demo.cpp
代码如下:
#include <iostream>
int add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10, b = 3;
int c = add(a, b);
std::cout << c << std::endl;
return 0;
}
如果函数不存在,gdb
会给出提示。如果是一个合法的函数名,则会提示设置断点成功。输入 c
继续执行,所以会在 add
函数的第一行中断,
$ gdb demo
...
Reading symbols from demo...done.
(gdb) b add
Breakpoint 1 at 0x894: file demo.cpp, line 5.
(gdb) b add2
Function "add2" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Breakpoint 1, add (x=10, y=3) at demo.cpp:5
5 return x + y;
(gdb) p x
$1 = 10
(gdb) p y
$2 = 3
(gdb) c
Continuing.
13
[Inferior 1 (process 30073) exited normally]
(gdb)
2.2 多个同名函数
如果有多个函数名相同,只是参数不同,为同名函数设置断点会怎样呢?gdb
会为所有同名函数都设置断点,这一点其实很重要,尤其是在 C++
的函数重载中,因为只看代码很难区分到底会调用哪一个函数。但是为函数设置断点后,就不用担心到底会执行哪一个函数。因为每个函数都会被设置断点,无论是哪一个函数被调用,都会命中。
demo.cpp
代码如下:
#include <iostream>
#include <string>
int add(int x, int y)
{
return x + y;
}
std::string add(std::string x, std::string y)
{
return x + y;
}
int main()
{
int a = 10, b = 3;
int c = add(a, b);
std::string s1 = "hello";
std::string s2 = "world";
std::string s = add(s1, s2);
std::cout << c << std::endl;
std::cout << s << std::endl;
return 0;
}
使用 gdb
调试
$ gdb demo
...
Reading symbols from demo...done.
(gdb) b add
Breakpoint 1 at 0xdc4: add. (2 locations)
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Breakpoint 1, add (x=10, y=3) at demo.cpp:6
6 return x + y;
(gdb) c
Continuing.
Breakpoint 1, add (x="hello", y="world") at demo.cpp:10
10 {
(gdb) p x
$1 = "hello"
(gdb) list
5 {
6 return x + y;
7 }
8
9 std::string add(std::string x, std::string y)
10 {
11 return x + y;
12 }
13
14 int main()
(gdb)
会看到提示有两个地方设置了函数断点,然后在 gdb
中输入 r
开始运行程序。首先命中第一个 add(int, int)
函数。然后输入 c
,继续运行,马上在第二个 add(std::string, std::string)
函数中断。
如果多个类是继承关系,由于虚函数也是同名函数,所以当为函数设置断点时,无论是什么类型的函数,只要函数名满足条件,都会被设置断点。
如果只想为特定的函数设置断点,则需要添加限定符,以便区分到底是为哪个函数设置断点。如下 demo.cpp
代码
#include <iostream>
#include <string>
class test_1
{
public:
test_1() {}
virtual ~test_1() {}
virtual void test_fun()
{
printf("test_1 test_fun\n");
}
};
class test_2 : public test_1
{
public:
test_2() {}
virtual ~test_2() {}
virtual void test_fun()
{
printf("test_2 test_fun\n");
}
};
void test_fun(int i)
{
printf("i is %d\n", i);
}
void test_fun(const char *str)
{
printf("str is %s\n", str);
}
int main(int argc, char *argv[])
{
test_fun(10);
test_fun("test");
test_1 *test = new test_1();
test->test_fun();
test_1 *test2 = new test_2();
test2->test_fun();
}
又新增了 test_1
和 test_2
两个类,并且 test_2
从 test_1
继承而来,代码中包含 4 个 test_fun
函数,如果在 gdb
中执行命令 b test_fun
,则 4 个函数都会被设置断点。假设我们只想对 test_1
中的test_fun
和 test_fun(int)
设置断点,则分别执行命令:
b test_1::test_fun
b test_fun(int)
就会只对这两个函数设置断点,另外两个函数则不会被设置断点,
(gdb) b test_1::test_fun
Breakpoint 1 at 0xc02: file demo.cpp, line 11.
(gdb) b test_fun(int)
Breakpoint 2 at 0xa65: file demo.cpp, line 27.
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Breakpoint 2, test_fun (i=10) at demo.cpp:27
27 printf("i is %d\n", i);
(gdb) c
Continuing.
i is 10
str is test
Breakpoint 1, test_1::test_fun (this=0x555555769280) at demo.cpp:11
11 printf("test_1 test_fun\n");
(gdb)
2.3 使用正则表达式设置函数断点
如果想为多个函数设置断点,但是这些函数名又各不相同,则不能使用前面提到的方法。但是如果这些函数名遵循一定的规则或者模式,则可以使用正则表达式来为这些函数设置断点,比如使用 *
等。
对代码稍作改动,添加一个函数,名称为 test_fun_x
,这时代码包含多个以 test_fun
开头的函数名,就可以使用正则表达式来为满足规则的函数设置断点,语法如下:
rb 正则表达式 rbreak 正则表达式
为所有以 test_fun
开头的函数设置断点,在 gdb
中输入以下命令:
rb test_fun*
这样就为所有以test_fun开头的函数设置了断点,
(gdb) rb test_fun*
Breakpoint 1 at 0xc28: file demo.cpp, line 11.
void test_1::test_fun();
Breakpoint 2 at 0xcc4: file demo.cpp, line 21.
void test_2::test_fun();
Breakpoint 3 at 0xa8a: file demo.cpp, line 31.
void test_fun(char const*);
Breakpoint 4 at 0xa65: file demo.cpp, line 27.
void test_fun(int);
Breakpoint 5 at 0xab1: file demo.cpp, line 36.
void test_fun_A(char const*);
Breakpoint 6 at 0xbab: file demo.cpp, line 47.
static void _GLOBAL__sub_I__Z8test_funi();
2.4 通过偏移量设置断点
当前代码执行到某一行时,如果要为当前代码行的前面某一行或者后面某一行设置断点,就可以使用这个功能来达到快速设置断点的目的。
语法如下:
b +偏移量
b -偏移量
比如当前代码执行至第 73 行,如果要在第 78 行设置断点,可以执行以下命令:
b +5
如果要在第 68 行处设置断点,可以执行以下命令:
b -5
使用如下:
(gdb) c
Continuing.
Breakpoint 4, test_fun (i=10) at demo.cpp:27
27 printf("i is %d\n", i);
(gdb) b +3
Note: breakpoint 3 also set at pc 0x555555554a8a.
Breakpoint 7 at 0x555555554a8a: file demo.cpp, line 30.
(gdb) b +2
Note: breakpoints 3 and 7 also set at pc 0x555555554a8a.
Breakpoint 8 at 0x555555554a8a: file demo.cpp, line 29.
(gdb)
2.5 设置条件断点
所谓条件断点,就是当满足一定条件时断点才会命中。普通的断点只要代码执行到断点处就会命中并暂停下来,而条件断点必须要满足设置的条件,才能够命中并暂停。条件断点的语法如下:
b 断点 条件
其中的 断点
可以是前面按照代码行的方式设置的断点,也可以是函数断点。条件
一般是一个 bool
表达式,比如 if i == 5
。条件断点在一些特殊的调试场合是非常有效的,比如在循环中,循环变量达到某个值时问题才会出现。如果循环变量很大,每次单步执行,是不太可能的。
比如有一个上千次的循环,但是当循环变量达到 900 时才会出问题,这时就可以设置一个条件断点,使得循环变量达到 900 时才会中断。先来查看测试代码,其中包括一个循环,如代码清单所示。
#include <iostream>
#include <string>
void test_loop()
{
for (int i = 0; i < 1000; i++)
{
printf("i is %d\n", i);
}
printf("exit the loop\n");
}
int main(int argc, char *argv[])
{
test_loop();
}
我们可以在代码的第 8 行设置一个条件断点,当 i
等于 900 的时候命中。在 gdb
中输入以下命令:
(gdb) b demo.cpp:8 if i==900
Breakpoint 1 at 0x7e2: file demo.cpp, line 8.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000007e2 in test_loop() at demo.cpp:8
stop only if i==900
(gdb)
然后输入 r
开始执行程序,并且会在 i
等于900的时候命中断点并暂停。此时输入 print i
查看变量 i
的当前值,发现确实是 900。
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
i is 0
i is 1
i is 2
i is 3
...
Breakpoint 1, test_loop () at demo.cpp:8
8 printf("i is %d\n", i);
(gdb) p i
$1 = 900
(gdb)
也可以为函数断点设置条件,比如函数 void cond_fun_test(int a,const char *str)
,如下代码清单所示,
#include <iostream>
#include <string>
void fun_test(int a, const char *str)
{
printf("a is %d, str is %s\n", a, str);
}
int main(int argc, char *argv[])
{
fun_test(10, "test");
}
假设我们希望在调用 fun_test
函数并且参数 a 等于 10 时,程序暂停,则可以使用以下命令:
b fun_test if a==10
如果希望 str
等于 test
时暂停,则可以使用下面的命令来设置条件断点:
b fun_test if str="test"
完整示例如下:
(gdb) b fun_test if a==10
Breakpoint 1 at 0x799: file demo.cpp, line 6.
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Breakpoint 1, fun_test (a=10, str=0x5555555548d9 "test") at demo.cpp:6
6 printf("a is %d, str is %s\n", a, str);
(gdb) p a
$1 = 10
(gdb) c
Continuing.
a is 10, str is test
[Inferior 1 (process 17825) exited normally]
(gdb) b fun_test if str=="test"
Note: breakpoint 1 also set at pc 0x555555554799.
Breakpoint 2 at 0x555555554799: file demo.cpp, line 6.
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Breakpoint 1, fun_test (a=10, str=0x5555555548d9 "test") at demo.cpp:6
6 printf("a is %d, str is %s\n", a, str);
(gdb) p str
$2 = 0x5555555548d9 "test"
(gdb) c
Continuing.
a is 10, str is test
[Inferior 1 (process 17835) exited normally]
(gdb)
2.6 在指令地址上设置断点
如果调试程序没有符号信息,而我们又想在某些地方设置断点时,则可以使用在指令地址上设置断点的功能。语法如下:
b *指令地址
先使用无调试符号的方式生成可执行文件。对 Makefile
稍做修改,去除 -g
选项,使得生成的可执行文件不包含调试符号信息。
启动 gdb
并调试 ,然后在测试函数 fun_test
上设置一个断点。因为没有调试符号信息,所以第一步先获得 fun_test
函数的地址,执行下述命令:
p fun_test
该命令会获得函数 fun_test
的函数地址,这里是 0x400a0b
。然后为地址 0x400a0b
设置断点,命令如下:
b *0x400a0b
在 gdb
中输入 r
,运行程序,就会在函数 fun_test
中暂停
备注:自己测试结果如下,不清楚哪里出现问题。
(gdb) p fun_test
$1 = {<text variable, no debug info>} 0x78a <fun_test(int, char const*)>
(gdb) print fun_test
$2 = {<text variable, no debug info>} 0x78a <fun_test(int, char const*)>
(gdb) b *0x78a
Breakpoint 1 at 0x78a
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x78a
(gdb)
2.7 设置临时断点
临时断点是指这个断点是临时的,只命中一次,然后会被自动删除,后续即使代码被多次调用也不会再次命中。语法如下:
tbreak 断点
tb 断点
示例代码
#include <iostream>
#include <string>
void fun_test(int a, const char *str)
{
printf("a is %d, str is %s\n", a, str);
}
int main(int argc, char *argv[])
{
for (int i = 0; i < 10; i++)
{
fun_test(10, "test");
}
}
我们在一个循环中调用函数 fun_test
,但是只想在 fun_test
函数中命中一次,此时就可以设置一个临时断点。在 gdb
中执行下述命令:
tb fun_test
完整过程如下:
Reading symbols from demo...done.
(gdb) tb fun_test
Temporary breakpoint 1 at 0x799: file demo.cpp, line 6.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint del y 0x0000000000000799 in fun_test(int, char const*) at demo.cpp:6
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo
Temporary breakpoint 1, fun_test (a=10, str=0x5555555548e9 "test")
at demo.cpp:6
6 printf("a is %d, str is %s\n", a, str);
(gdb) c
Continuing.
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
a is 10, str is test
[Inferior 1 (process 14553) exited normally]
(gdb) q