C/C++調(diào)試技巧-debugbreak
有日子沒有寫基礎點的文章了啊。最近在iOS上調(diào)試,發(fā)現(xiàn)iOS上沒有MSVC上已經(jīng)用慣了的__debugbreak,所以花了點時間,研究了一下如何在iOS/Android調(diào)試時使用__debugbreak,本著研究問題一次就研究清楚的態(tài)度,稍微看了兩眼,然后就有了這篇文章。
MSVC獨有神器 DebugBreak ( __debugbreak )
要說調(diào)試時最常用的手段,那應該就是打斷點調(diào)試了。但是如果每次錯誤都需要手工去定位斷點的位置,未免還是有點麻煩。用Unreal engine 4開發(fā)的同學應該都有經(jīng)歷,就是崩潰的時候總能觸發(fā)一次斷點,給個機會查看崩潰時的程序調(diào)用棧和變量值。這個就是依靠MSVC提供的 DebugBreak
函數(shù)實現(xiàn)的。
DebugBreak
函數(shù)相當于一個斷點,在可能發(fā)生崩潰的地方都加一個,一旦崩潰自動斷住,查看棧和數(shù)值,對于分析bug非常有幫助。
其他平臺的 DebugBreak ( __debugbreak ) 的
理想的 debug_break
函數(shù)的功能
在執(zhí)行此函數(shù)時觸發(fā)一個軟件斷點(e.g. Linux系統(tǒng)上的 SIGTRAP 信號)
在觸發(fā)斷點后,可以繼續(xù)執(zhí)行,比如GDB的 continue, next, step, stepi 命令
debug_break
函數(shù)不應該導致代碼優(yōu)化
GCC
GCC提供了內(nèi)置的 __builtin_trap()
函數(shù)??梢栽赿ebug模式下自動進入斷點,但是,GCC編譯器默認情況會把 __builtin_trap()
后面的代碼優(yōu)化掉。比如在i386上

會被GCC編譯成

printf被優(yōu)化掉了。
并且在 i386 / x86-64 體系結構上 __builtin_trap()
編譯成了匯編指令 ud2
, 在Linux上發(fā)出 SIGILL 信號而不是 SIGTRAP 信號。如果希望GDB在此斷住,還需要告訴GDB,在接收SIGILL信號之后進入斷點停止執(zhí)行。

除此之外,GCC/GDB還是有某些版本不認 __builtin_trap()
。
在ARM體系結構上,GCC的 __builtin_trap()
會被直接翻譯成 abort()
,比x86上的 ud2
更加不可靠。
幸運的是GCC已經(jīng)認識到這個需求,在GCC 7.3.1 預計會添加__builtin_break()
Clang
Clang/LLVM除了提供了內(nèi)置的 __builtin_trap
函數(shù)之外,還額外提供了 __builtin_debugtrap
函數(shù),這個函數(shù)在x86體系結構上會生成 int3
。

會被clang編譯成

int3
在Linux(x86)上可以發(fā)出一個 SIGTRAP
信號,用GDB/LLDB可以正常調(diào)試。
Linux SIGTRAP 的觸發(fā)方式
在前文已經(jīng)提過,在i386 / x86-64體系結構上,int3
指令可以發(fā)出一個SIGTRAP
信號。
在ARM體系結構上,也有等價的指令。在32位ARM上,對于ARM mode,.inst 0xe7f001f0
匯編可以發(fā)出SIGTRAP
信號,對于Thumb mode,.inst 0xde01
可以發(fā)出 SIGTRAP
信號。
不過GDB在32位ARM上,接收匯編直接發(fā)出的SIGTRAP
信號可能出現(xiàn)不能繼續(xù)調(diào)試的情況,幸好還有個workaround

在64位ARM上,.inst 0xd4200000.
匯編可以產(chǎn)生SIGTRAP
信號。
簡單總結一下,就是在Linux系統(tǒng)中,可以通過內(nèi)嵌匯編的方式在特定位置強行觸發(fā)斷點。
debugbreak庫
上文基本簡述了x86和ARM實現(xiàn)斷點的方式,有個人封裝了個庫,來簡化開發(fā),這個庫就是 debugbreak
用法

debugbreak庫在各個平臺上的實際情況。
