gdb 笔记(03)— 某一行设置断点、为函数(单个唯一函数、多个同名函数、使用正则)设置断点、设置条件断点、设置临时断点

断点 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_1test_2 两个类,并且 test_2test_1 继承而来,代码中包含 4 个 test_fun 函数,如果在 gdb 中执行命令 b test_fun ,则 4 个函数都会被设置断点。假设我们只想对 test_1 中的test_funtest_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 中暂停
3-37
备注:自己测试结果如下,不清楚哪里出现问题。

(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
  • 8
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值