GDB调试如何在return设置断点

本文详细解析了在GDB调试过程中,如何在包含多个跳转的return语句后设置断点,通过单步调试和指令级分析,教你如何定位并成功设置断点在预期代码行。

GDB调试时常用设置断点的方法有:

1. 按照源文件代码,在某行设置断点,如:b **.cpp:40

2. 指定函数符号,在函数入口设置断点,如:b CBiPlayer::_CanAutoStart

这次遇到一个问题,用方法1在一条 return 语句设置断点,无法设置,查看断点信息,实际上断点是后续代码。

代码如:

001 // bi_player.cpp
...
180 CBiPlayer::_CanAutoStart(int)
181 {
...
189   if (m_mng) { ... }
...
199   if(!bAutoStart)
200     return ECANNOTAUTOSTART;
201
202   if(mng->GetActorNumber() < AutoStartMinPlayerCount())
203     return ECANNOTAUTOSTART;
204
...
207   if(!IsAllowAutoStart(modeid))
208     return ECANNOTAUTOSTART;
209
210   return mng->CanStartGame(param);
211 }

设置断点:b bi_player.cpp:200  实际是:bi_player.cpp:202

设置断点:b bi_player.cpp:203  实际是:bi_player.cpp:205

设置断点:b bi_player.cpp:208  实际是:bi_player.cpp:210

为什么呢?具体原因以后另开文章分析下,这里直接分析解决当前问题。

我们知道代码最终都会汇编成汇编代码,看看return这句汇编是啥样的。

查看汇编方法:

disassemble bi_player.cpp:CBiPlayer::_CanAutoStart

即使是对汇编熟悉的人,在遇到复杂函数时估计也难以下手。

为了定位某条语句的汇编代码,我用了比较简单的办法,单步调试:

首先,断点到函数入口:CBiPlayer::_CanAutoStart(这个断点可以设置在return前的语句,更快定位);

然后,用单步调试(step/next),和指令调试(stepi/nexti)找到return语句对应指令。

为了在调试过程显示代码行,需要设置:

display/i $pc

下面展示调试过程

(gdb) b CBiPlayer::_CanAutoStart
cBreakpoint 7 at 0x7f0279068a30: CBiPlayer::_CanAutoStart. (2 locations)
(gdb) c
Continuing.

Thread 1 hit Breakpoint 7, CBiPlayer::_CanAutoStart (this=0x225487a8, param=0) at /home/workspace/bi/core/bi_player.cpp:189
189 if (m_mng)
1: x/i $pc
=> 0x7f027c56c5a0 <CBiPlayer::_CanAutoStart(int)>:      push   r15
(gdb) c

Thread 1 hit Breakpoint 7, CBiPlayer::_CanAutoStart (this=0x225487a8, param=0) at /home/workspace/bi/core/bi_player.cpp:189
189 if (m_mng)
1: x/i $pc
=> 0x7f027c56c5a0 <CBiPlayer::_CanAutoStart(int)>:      push   r15

...


#====== 1. 断点到return前的条件语句


199 if(!bAutoStart)
1: x/i $pc
=> 0x7f027c56c6b1 <CBiPlayer::_CanAutoStart(int)+273>:  test   bl,bl


#====== 2. 开始指令级调试,发现 test 后是一条 je 跳转到 0x7f027c56c738 <CBiPlayer::_CanAutoStart(int)+408>


(gdb) si
0x00007f027c56c6b3      199 if(!bAutoStart)
1: x/i $pc
=> 0x7f027c56c6b3 <CBiPlayer::_CanAutoStart(int)+275>:  je     0x7f027c56c738 <CBiPlayer::_CanAutoStart(int)+408>

#====== 3. 继续指令级调试(空命令,代表继续上一次的命令

(gdb)
202 if(mng->GetActorNumber() < AutoStartMinPlayerCount())
1: x/i $pc
=> 0x7f027c56c6b9 <CBiPlayer::_CanAutoStart(int)+281>:  mov    rax,QWORD PTR [r12]
(gdb)
0x00007f027c56c6bd      202 if(mng->GetActorNumber() < AutoStartMinPlayerCount())
1: x/i $pc
=> 0x7f027c56c6bd <CBiPlayer::_CanAutoStart(int)+285>:  mov    rdi,r12
(gdb)
0x00007f027c56c6c0      202 if(mng->GetActorNumber() < AutoStartMinPlayerCount())
1: x/i $pc
=> 0x7f027c56c6c0 <CBiPlayer::_CanAutoStart(int)+288>:  call   QWORD PTR [rax+0x218]

...


#====== 4. 这里也是 jl 跳转到 0x7f027c56c738 <CBiPlayer::_CanAutoStart(int)+408>


(gdb)
0x00007f027c56c6d7      202 if(mng->GetActorNumber() < AutoStartMinPlayerCount())
1: x/i $pc
=> 0x7f027c56c6d7 <CBiPlayer::_CanAutoStart(int)+311>:  jl     0x7f027c56c738 <CBiPlayer::_CanAutoStart(int)+408>


...

(gdb)
0x00007f027c56c6ec      207 if(!IsAllowAutoStart(modeid))
1: x/i $pc
=> 0x7f027c56c6ec <CBiPlayer::_CanAutoStart(int)+332>:  mov    rdi,rbp

...



#======= 5. 同上 je 跳转到 0x7f027c56c738 <CBiPlayer::_CanAutoStart(int)+408>


(gdb)
0x00007f027c56c6f7      207 if(!IsAllowAutoStart(modeid))
1: x/i $pc
=> 0x7f027c56c6f7 <CBiPlayer::_CanAutoStart(int)+343>:  je     0x7f027c56c738 <CBiPlayer::_CanAutoStart(int)+408>
(gdb)
210 return mng->CanStartGame(param);
1: x/i $pc
=> 0x7f027c56c6f9 <CBiPlayer::_CanAutoStart(int)+345>:  mov    rax,QWORD PTR [r12]
(gdb) b 0x00007f027c56c738
Function "0x00007f027c56c738" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n


#======= 6. 从上面分析,return 都是最后跳转到相同的指令,可以在该指令处设置断点。然后会发现,这次断点成功设置在了 第 200 行。(一行高级语言汇编后可能对应多行指令)


(gdb) b *0x00007f027c56c738
Breakpoint 1 at 0x7f027c56c738: file /home/workspace/bi/core/bi_player.cpp, line 200.
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00007f027c56c6b1 in CBiPlayer::_CanAutoStart(int) at /home/workspace/bi/core/bi_player.cpp:199
        stop only if !bAutoStart
2       breakpoint     keep y   0x00007f027c56c6b9 in CBiPlayer::_CanAutoStart(int) at /home/workspace/bi/core/bi_player.cpp:202
        stop only if mng->GetActorNumber() < AutoStartMinPlayerCount()
3       breakpoint     keep y   0x00007f027c56c6e8 in CBiPlayer::_CanAutoStart(int) at /home/workspace/bi/core/bi_player.cpp:207
        stop only if !IsAllowAutoStart(modeid)
7       breakpoint     keep y   <MULTIPLE>
        breakpoint already hit 2 times
7.1                         y   0x00007f0279068a30 in std::string::_M_rep() const at /opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:3366
7.2                         y   0x00007f027c56c5a0 in CBiPlayer::_CanAutoStart(int) at /home/workspace/bi/core/bi_player.cpp:189
8       breakpoint     keep y   0x00007f027c56c738 in CBiPlayer::_CanAutoStart(int) at /home/workspace/bi/core/bi_player.cpp:200

再来看看反汇编 0x00007f027c56c738 附近的指令:

   0x00007f027c56c730 <+400>:   pop    r15
   0x00007f027c56c732 <+402>:   ret
   0x00007f027c56c733 <+403>:   nop    DWORD PTR [rax+rax*1+0x0]
   0x00007f027c56c738 <+408>:   mov    r12d,0x1
   0x00007f027c56c73e <+414>:   mov    rdi,QWORD PTR [rsp+0x18]
   0x00007f027c56c743 <+419>:   test   rdi,rdi

把枚举值0x1设置给r12d作为返回值。

好了,简单总结下,优化后的代码如何在return语句设置断点:

1. 在return前一条语句设置断点;

2. 调试,命中断点后,采用指令级调试,si/ni;

3. 分析return语句最后的跳转目标指令;

4. 在跳转目标指令设置断点;

5. 继续调试,命中跳转目标指令处断点。

### 在GDB中为大型程序设置断点进行调试GDB中,为大型程序设置断点进行调试是一个复杂但非常重要的技能。以下是一些关键方法和技巧,可以帮助用户更高效地完成这项任务。 #### 1. 指定行号设置断点 通过指定代码中的具体行号来设置断点是一种常见的方式。例如,在代码的第39行设置断点可以使用以下命令[^1]: ```bash (gdb) b 39 ``` 这将在文件 `gdbDebug.cpp` 的第39行设置一个断点。 #### 2. 指定函数设置断点 对于大型程序,直接通过函数设置断点通常更为方便。例如,如果需要在函数 `Demo1()` 中设置断点,可以使用以下命令[^2]: ```bash (gdb) b Demo1 ``` #### 3. 查看已设置断点调试过程中,可以通过以下命令查看所有已设置断点信息[^2]: ```bash (gdb) i b ``` 这将列出所有断点的详细信息,包括断点编号、类型、状态、地址以及对应的文件和行号。 #### 4. 条件断点 在某些情况下,可能需要在满足特定条件时才触发断点。例如,假设变量 `b` 的值为0时需要暂停程序执行,可以设置条件断点如下[^3]: ```bash (gdb) break test.c:23 if b == 0 ``` #### 5. 传递参数给程序 在调试大型程序时,可能需要向程序传递参数。这可以通过以下命令实现[^4]: ```bash (gdb) set args 参数1 参数2 ``` 随后运行程序时,这些参数将被传递给目标程序。 #### 6. 示例代码调试 以下是一个简单的C语言程序及其调试示例: ```c #include <stdio.h> int main(int argc, char *argv[]) { int j = argc, i; printf("argc=%d\n", argc); for (i = 0; i < j; i++) { printf("argc=%d, argv[]=%s\n", i, argv[i]); } return 0; } ``` 假设需要调试此程序,并观察 `argc` 和 `argv` 的值变化,可以按照以下步骤操作: 1. 启动GDB并加载程序: ```bash gdb ./program_name ``` 2. 设置断点: ```bash (gdb) b main ``` 3. 传递参数: ```bash (gdb) set args 参数1 参数2 ``` 4. 运行程序: ```bash (gdb) run ``` #### 7. 删除断点 如果不再需要某个断点,可以通过以下命令删除它: ```bash (gdb) delete 1 ``` 这里 `1` 是断点的编号。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值