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. 继续调试,命中跳转目标指令处断点。