炸弹指令何处来?

新来的小伙伴Denny在使用JS脚本测试NDB时遇到拦路虎,node进程打印出下面这个信息后就崩溃闪退了。

geduer@ulan:/gewu/nanocode/nd3/ndi$ node ndunix.js
register 'jtag'
register 'swim'
register 'dapdirect_jtag'
register 'dapdirect_swd'
register 'swd'
0
NdAgent {}
1
11111111111111111




Trace/breakpoint trap (core dumped)

崩溃现场的最后一条信息最重要:Trace/breakpoint trap (core dumped)

直接翻译便是:追踪或断点陷阱 (内存已转储)

我看了这个信息后,也觉得有些奇怪。哪里来的“追踪或断点陷阱”呢?也没有挂调试器啊!

上调试器

为了搞清楚原因,我决定上调试器,在本来的命令行前面加上gdb --args,让整个过程在调试器下再来一遍。

geduer@ulan:/gewu/nanocode/nd3/ndi$ gdb --args node ndunix.js

执行r命令不久,果然又中断下来。

Thread 1 "node" received signal SIGTRAP, Trace/breakpoint trap.
0x0000007fe31c1808 in NdBoss::UiUpdateEngine (this=0x55557884b0) at ../ndi/NdBoss.cpp:1639
1639                    return this->m_pUiClient->exit_dispatch((IDebugClient*)this->m_pDbgClient);

c8aae8f727040363dbf24e8f297af6c4.png

执行bt观察执行经过,崩溃发生在主线程,是从脚本调用过来的。

(gdb) bt
#0  0x0000007fe31c1808 in NdBoss::UiUpdateEngine (this=0x55557884b0) at ../ndi/NdBoss.cpp:1639
#1  0x0000007fe31d1dd8 in NdFront::StartEngineForCommand (this=0x55558779f0) at ../ndi/NdFront.cpp:185
#2  0x0000007fe31d2008 in NdFront::Execute (this=0x55558779f0, lpszCmd=0x7fffffdf90 "r", ulFlags=0) at ../ndi/NdFront.cpp:279
#3  0x0000007fe324b1bc in NdAgent::Execute(Napi::CallbackInfo const&) () from /usr/share/nanocode/ndjs.node
#4  0x0000007fe3253280 in Napi::ObjectWrap<NdAgent>::InstanceMethodCallbackWrapper(napi_env__*, napi_callback_info__*) ()
   from /usr/share/nanocode/ndjs.node
#5  0x0000007ff5eeb694 in ?? () from /lib/aarch64-linux-gnu/libnode.so.108
#6  0x0000007ff62f512c in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) ()
   from /lib/aarch64-linux-gnu/libnode.so.108
#7  0x0000007ff62f5a70 in ?? () from /lib/aarch64-linux-gnu/libnode.so.108
#8  0x0000007ff62f5e98 in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) ()
   from /lib/aarch64-linux-gnu/libnode.so.108
#9  0x0000007ff61aa0ac in ?? () from /lib/aarch64-linux-gnu/libnode.so.108
#10 0x0000007fe3360411 in ?? ()

执行c尝试恢复执行,结果会再次中断下来。

(gdb) c
Continuing.


Thread 1 "node" received signal SIGTRAP, Trace/breakpoint trap.
0x0000007fe31c1808 in NdBoss::UiUpdateEngine (this=0x55557884b0) at ../ndi/NdBoss.cpp:1639
1639                    return this->m_pUiClient->exit_dispatch((IDebugClient*)this->m_pDbgClient);
(gdb) c
Continuing.


Thread 1 "node" received signal SIGTRAP, Trace/breakpoint trap.
0x0000007fe31c1808 in NdBoss::UiUpdateEngine (this=0x55557884b0) at ../ndi/NdBoss.cpp:1639
1639                    return this->m_pUiClient->exit_dispatch((IDebugClient*)this->m_pDbgClient);
(gdb) c
Continuing.


Thread 1 "node" received signal SIGTRAP, Trace/breakpoint trap.
0x0000007fe31c1808 in NdBoss::UiUpdateEngine (this=0x55557884b0) at ../ndi/NdBoss.cpp:1639
1639                    return this->m_pUiClient->exit_dispatch((IDebugClient*)this->m_pDbgClient);

执行disassemble观察汇编指令。

(gdb) disassemble
Dump of assembler code for function _ZN6NdBoss14UiUpdateEngineEv:
   0x0000007fe31c17b8 <+0>:     stp     x29, x30, [sp, #-32]!
   0x0000007fe31c17bc <+4>:     mov     x29, sp
   0x0000007fe31c17c0 <+8>:     str     x0, [sp, #24]
   0x0000007fe31c17c4 <+12>:    ldr     x0, [sp, #24]
   0x0000007fe31c17c8 <+16>:    ldr     x0, [x0, #256]
   0x0000007fe31c17cc <+20>:    cmp     x0, #0x0
   0x0000007fe31c17d0 <+24>:    b.eq    0x7fe31c1808 <_ZN6NdBoss14UiUpdateEngineEv+80>  // b.none
   0x0000007fe31c17d4 <+28>:    ldr     x0, [sp, #24]
   0x0000007fe31c17d8 <+32>:    ldr     x0, [x0, #256]
   0x0000007fe31c17dc <+36>:    ldr     x0, [x0]
   0x0000007fe31c17e0 <+40>:    add     x0, x0, #0xe8
   0x0000007fe31c17e4 <+44>:    ldr     x2, [x0]
   0x0000007fe31c17e8 <+48>:    ldr     x0, [sp, #24]
   0x0000007fe31c17ec <+52>:    ldr     x3, [x0, #256]
   0x0000007fe31c17f0 <+56>:    ldr     x0, [sp, #24]
   0x0000007fe31c17f4 <+60>:    ldr     x0, [x0, #264]
   0x0000007fe31c17f8 <+64>:    mov     x1, x0
   0x0000007fe31c17fc <+68>:    mov     x0, x3
   0x0000007fe31c1800 <+72>:    blr     x2
   0x0000007fe31c1804 <+76>:    b       0x7fe31c180c <_ZN6NdBoss14UiUpdateEngineEv+84>
=> 0x0000007fe31c1808 <+80>:    brk     #0x3e8
   0x0000007fe31c180c <+84>:    ldp     x29, x30, [sp], #32
   0x0000007fe31c1810 <+88>:    ret
End of assembler dump.

程序指针卡在下面这一句:

=> 0x0000007fe31c1808 <+80>:    brk     #0x3e8

原来是很熟悉的断点指令。特别扎眼的是指令跟着的立即数:0x3e8,对于我来说,一眼就看出来它是1000。

brk 1000

这是多么有个性的一条指令啊!1000这样的值绝对不是偶然,一定是有人故意如此设计啊!

如果是故意,那么是谁放了这么一条具有炸弹性质的指令呢?

ee1226a3b3da79d9d047cef19737fae4.jpeg

考虑到没有调试器时也会以相同的信号崩溃,那么这条指令显然不是调试器插入的。那么是谁干的呢?

如果没有人动态篡改指令,那么这条指令就是编译器产生的。

使用l命令列出对应的源代码:

HRESULT NdBoss::UiUpdateEngine()
{
  if (m_pUiClient != NULL)
    return this->m_pUiClient->exit_dispatch((IDebugClient*)this->m_pDbgClient);
}

眼睛看着这几行代码,我的大脑在努力思考这几行代码的特别之处,是哪个特征触发编译器插入了一条炸弹指令呢?

找到原因

几秒钟之后,我的大脑灵机一动,想出了原因。增加两行代码,再次编译,问题果然不见了。

HRESULT NdBoss::UiUpdateEngine()
{
  if (m_pUiClient != NULL)
    return this->m_pUiClient->exit_dispatch((IDebugClient*)this->m_pDbgClient);
  else
    return E_FAIL;
}

看来问题的根源是本来代码不够严谨,函数声明了返回值,但是只有if分支有明确的返回值,缺少else分支,不全了就好了。

举一反三

为了进一步研究这个问题,我写了一小段代码,取名为gebrk.c。

geduer@ulan:~/gelabs/gebrk$ cat gebrk.c
#include <stdio.h>


int fn(int p)
{
        if(p<0)
                return p*p;
}


int main(int argc, const char*argv[])
{
        int a = fn(argc);
        printf("result is %d\n", a);


        return 0;
}

使用gcc编译上面的代码,没有任何警告,运行时也没有崩溃,打印出的结果是1。

geduer@ulan:~/gelabs/gebrk$ gcc gebrk.c
geduer@ulan:~/gelabs/gebrk$ ./a.out
result is 1

使用g++编译同样代码,有如下警告:

geduer@ulan:~/gelabs/gebrk$ g++ gebrk.c
gebrk.c: In function 'int fn(int)':
gebrk.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
    7 | }
      | ^

G++的警告信息让人困惑。

如果使用微软的vc编译器编译,给出的警告信息更好理解。

<source>(7) : warning C4715: 'fn': not all control paths return a value
Compiler returned: 0

运行g++编译出的可执行文件,可以重现Denny最初遇到的问题。

geduer@ulan:~/gelabs/gebrk$ ./a.out
Trace/breakpoint trap (core dumped)

使用gdb调试,也可以复现前面介绍的症状。

(gdb) disassemble fn
Dump of assembler code for function _Z2fni:
   0x0000005555550754 <+0>:     sub     sp, sp, #0x10
   0x0000005555550758 <+4>:     str     w0, [sp, #12]
   0x000000555555075c <+8>:     ldr     w0, [sp, #12]
   0x0000005555550760 <+12>:    cmp     w0, #0x0
   0x0000005555550764 <+16>:    b.ge    0x5555550774 <_Z2fni+32>  // b.tcont
   0x0000005555550768 <+20>:    ldr     w0, [sp, #12]
   0x000000555555076c <+24>:    mul     w0, w0, w0
   0x0000005555550770 <+28>:    b       0x5555550778 <_Z2fni+36>
   0x0000005555550774 <+32>:    brk     #0x3e8
   0x0000005555550778 <+36>:    add     sp, sp, #0x10
   0x000000555555077c <+40>:    ret
End of assembler dump.

特别值得一提的是,如果打开优化选项,那么不会再有崩溃。

geduer@ulan:~/gelabs/gebrk$ g++ -g -O1 gebrk.c
gebrk.c: In function 'int fn(int)':
gebrk.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
    7 | }
      | ^
geduer@ulan:~/gelabs/gebrk$ ./a.out
result is 1

归纳一下,导致崩溃的BRK炸弹是G++编译器故意加入的。加入的目的是帮助程序员发现代码中的设计缺欠,是一种辅助纠错的措施。因为此,打开优化选项后,这个“炸弹机制”会消失。

软件世界,精彩无限。LINUX研习班上海站正在招生中,三天时间,面对面交流,每人一套GDK8套件+挥码枪硬件调试器,上调试器,看活代码,既学操作系统原理,又学调试器和20多种重要工具,一举多得。

(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)

*************************************************

正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生

扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物

a4fd1138d8d970017676ebb74d0c497a.png

也欢迎关注格友公众号

6f021dc332c8b3f56b604b7c649098f0.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值