C/C++调试技巧-debugbreak
有日子没有写基础点的文章了啊。最近在iOS上调试,发现iOS上没有MSVC上已经用惯了的__debugbreak,所以花了点时间,研究了一下如何在iOS/Android调试时使用__debugbreak,本着研究问题一次就研究清楚的态度,稍微看了两眼,然后就有了这篇文章。
MSVC独有神器 DebugBreak ( __debugbreak )
要说调试时最常用的手段,那应该就是打断点调试了。但是如果每次错误都需要手工去定位断点的位置,未免还是有点麻烦。用Unreal engine 4开发的同学应该都有经历,就是崩溃的时候总能触发一次断点,给个机会查看崩溃时的程序调用栈和变量值。这个就是依靠MSVC提供的 DebugBreak
函数实现的。
DebugBreak
函数相当于一个断点,在可能发生崩溃的地方都加一个,一旦崩溃自动断住,查看栈和数值,对于分析bug非常有帮助。
其他平台的 DebugBreak ( __debugbreak ) 的
理想的 debug_break
函数的功能
在执行此函数时触发一个软件断点(e.g. Linux系统上的 SIGTRAP 信号)
在触发断点后,可以继续执行,比如GDB的 continue, next, step, stepi 命令
debug_break
函数不应该导致代码优化
GCC
GCC提供了内置的 __builtin_trap()
函数。可以在debug模式下自动进入断点,但是,GCC编译器默认情况会把 __builtin_trap()
后面的代码优化掉。比如在i386上

会被GCC编译成

printf被优化掉了。
并且在 i386 / x86-64 体系结构上 __builtin_trap()
编译成了汇编指令 ud2
, 在Linux上发出 SIGILL 信号而不是 SIGTRAP 信号。如果希望GDB在此断住,还需要告诉GDB,在接收SIGILL信号之后进入断点停止执行。

除此之外,GCC/GDB还是有某些版本不认 __builtin_trap()
。
在ARM体系结构上,GCC的 __builtin_trap()
会被直接翻译成 abort()
,比x86上的 ud2
更加不可靠。
幸运的是GCC已经认识到这个需求,在GCC 7.3.1 预计会添加__builtin_break()
Clang
Clang/LLVM除了提供了内置的 __builtin_trap
函数之外,还额外提供了 __builtin_debugtrap
函数,这个函数在x86体系结构上会生成 int3
。

会被clang编译成

int3
在Linux(x86)上可以发出一个 SIGTRAP
信号,用GDB/LLDB可以正常调试。
Linux SIGTRAP 的触发方式
在前文已经提过,在i386 / x86-64体系结构上,int3
指令可以发出一个SIGTRAP
信号。
在ARM体系结构上,也有等价的指令。在32位ARM上,对于ARM mode,.inst 0xe7f001f0
汇编可以发出SIGTRAP
信号,对于Thumb mode,.inst 0xde01
可以发出 SIGTRAP
信号。
不过GDB在32位ARM上,接收汇编直接发出的SIGTRAP
信号可能出现不能继续调试的情况,幸好还有个workaround

在64位ARM上,.inst 0xd4200000.
汇编可以产生SIGTRAP
信号。
简单总结一下,就是在Linux系统中,可以通过内嵌汇编的方式在特定位置强行触发断点。
debugbreak库
上文基本简述了x86和ARM实现断点的方式,有个人封装了个库,来简化开发,这个库就是 debugbreak
用法

debugbreak库在各个平台上的实际情况。
