C 語法上使用 goto 或是 setjmp/longjmp 實現例外處理。goto 只能做本地跳轉,亦即在同一函式內部進行跳轉。setjmp/longjmp 則能做到非本地跳轉。setjmp 會保留當前進程的狀態,以供之後的 longjmp 回到 setjmp 所在位置。setjmp/longjmp 並非以 stack unwind 實現[(http://people.cs.nctu.edu.tw/~chenwj/log/LLVM/baldrick-2012-07-05.txt)]。
* [[http://www.embecosm.com/appnotes/ean9/html/ch04s01s02.html|Implementing the setjmp and longjmp functions]]
* [[http://blog.reverberate.org/2013/05/deep-wizardry-stack-unwinding.html|Deep Wizardry: Stack Unwinding]]All setjmp is doing is saving a bunch of registers including rsp (the stack pointer) and the return address into the jmp_buf array that was passed as a parameter.
* setjmp 主要保存被調用方保存 (callee-save) 暫存器、棧指針和返回位址於 jmp_buf。jmp_buf 可以是 unsigned char 陣列,大小足夠保存前述資訊即可。
* longjmp 從傳入的 jmp_buf 中回復暫存器和棧指針,載入 setjmp 設置的返回位址。
C++ 語法上使用 try/catch/throw 實現例外處理,C++ 內部主要使用 table-driven 的方式實現。簡單來講,編譯和鏈結時期會建立一張表,一段範圍的計數器值會對應到例外處理語句。當執行時期發生例外時,運行期函式庫會表跳轉至對應的例外處理語句。
* [[http://stackoverflow.com/questions/490773/how-is-the-c-exception-handling-runtime-implemented|How is the C plusplus exception handling runtime implemented?]]
* [[http://www.codeproject.com/KB/cpp/exceptionhandler.aspx|How a C plusplus compiler implements exception handling]]
術語 [[wp>Call_stack#Unwinding|stack unwinding]],又稱棧回溯 (棧回退),此為一搜尋例外處理代碼的過程。當例外被丟出時,棧會以 callee 到 caller 的方向消退,以搜尋例外處理代碼處理該例外。棧回溯的過程中,當脫離某一個棧的範圍時,針對配置在該棧上的物件,運行期函式庫會呼叫其解構子。棧回溯的效果就如同除了例外處理代碼 (即 catch) 所在的函式以外,往下曾經被調用過的函式沒有作用,彷彿未被調用。
* [[http://stackoverflow.com/questions/2331316/what-is-stack-unwinding|What is stack unwinding?]]
* The objects allocated on the stack are "unwound" when the scope is exited (here the scope is of the function func.) This is done by the compiler inserting calls to destructors of automatic (stack) variables.
* [[http://ucla.jamesyxu.com/?p=231|Decoding .debug_frame information from DWARF-2]]
* DWARF2 contains .debug_frame information for helping debuggers figure out how to unwind frames (i.e. how to restore the stack to the previous frame from any instruction executing using the current frame.
* [[http://stackoverflow.com/questions/16597350/what-is-an-exception-handling-personality-function|What is an exception handling personality function?]]
* [[http://monoinfinito.wordpress.com/category/programming/c/exceptions/|C++ exceptions under the hood]]
* [[https://stackoverflow.com/questions/87220/how-does-gcc-implement-stack-unrolling-for-c-exceptions-on-linux|How does gcc implement stack unrolling for C++ exceptions on linux?]]
* 例外處理需要和 [[binutils]] 和 [[debugger]] 配合。
====== 系列文章 ======
* [[http://www.educity.cn/se/experteyes/No141.htm|前言]]
* [[http://www.educity.cn/se/experteyes/No136.htm|第1集 初次与异常处理编程相邂逅]]
* [[http://www.educity.cn/se/experteyes/No137.htm|第2集 C++中异常处理的游戏规则]]
* [[http://www.educity.cn/se/experteyes/No138.htm|第3集 C++中catch(…)如何使用]]
* [[http://www.educity.cn/se/experteyes/No139.htm|第4集 C++的异常处理和面向对象的紧密关系]]
* [[http://www.educity.cn/se/experteyes/No140.htm|第5集 C++的异常rethrow]]
* [[http://www.educity.cn/se/experteyes/No142.htm|第6集 对象的成员函数中抛出的异常]]
* [[http://www.educity.cn/se/experteyes/No143.htm|第7集 构造函数中抛出的异常]]
* [[http://www.educity.cn/se/experteyes/No144.htm|第8集 析构函数中抛出的异常]]
* [[http://www.educity.cn/se/experteyes/No145.htm|第9集 C++的异常对象如何被传递]]
* [[http://www.educity.cn/se/experteyes/No146.htm|第10集 C++的异常对象按传值的方式被复制和传递]]
* [[http://www.educity.cn/se/experteyes/No147.htm|第11集 C++的异常对象按引用方式被传递]]
* [[http://www.educity.cn/se/experteyes/No148.htm|第12集 C++的异常对象按指针方式被传递]]
* [[http://www.educity.cn/se/experteyes/No149.htm|第13集 C++异常对象三种方式传递的综合比较]]
* [[http://www.educity.cn/se/experteyes/No151.htm|第15集 C语言中的异常处理机制]]
* [[http://www.educity.cn/se/experteyes/No152.htm|第16集 C语言中一种更优雅的异常处理机制]]
* [[http://www.educity.cn/se/experteyes/No153.htm|第17集 全面了解setjmp与longjmp的使用]]
* [[http://www.educity.cn/se/experteyes/No154.htm|第18集 玩转setjmp与longjmp]]
* [[http://www.educity.cn/se/experteyes/No155.htm|第19集 setjmp与longjmp机制,很难与C++和睦相处]]
* [[http://www.educity.cn/se/experteyes/No156.htm|第20集 C++中如何兼容并支持C语言中提供的异常处理机制]]
* [[http://www.educity.cn/se/experteyes/No157.htm|第21集 Windows系列操作系统平台中所提供的异常处理机制]]
* [[http://www.educity.cn/se/experteyes/No158.htm|第22集 更进一步认识SEH]]
* [[http://www.educity.cn/se/experteyes/No159.htm|第23集 SEH的强大功能之一]]
* [[http://www.educity.cn/se/experteyes/No160.htm|第24集 SEH的强大功能之二]]
* [[http://www.educity.cn/se/experteyes/No161.htm|第25集 SEH的综合]]
* [[http://www.educity.cn/se/experteyes/No162.htm|第26集 SEH 可以在 C++ 程序中使用]]
* [[http://www.educity.cn/se/experteyes/No163.htm|第27集 SEH 与 C++ 异常模型的混合使用]]
* [[http://www.educity.cn/se/experteyes/No164.htm|第28集 如何把SEH类型的系统异常转化为C++类型的异常?]]
* [[http://www.educity.cn/se/experteyes/No165.htm|第29集 Java语言中的异常处理模型]]
* [[http://www.educity.cn/se/experteyes/No166.htm|第30集 Java 异常处理模型之细节分析]]
* [[http://baiy.cn/doc/cpp/inside_exception.htm|C++异常机制的实现方式和开销分析]]
* [[http://luse.blogspot.tw/2009/05/c.html|淺談C++例外處理 (前篇)]]
* [[http://luse.blogspot.tw/2009/10/c.html|淺談C++例外處理 (中篇)]]
* [[http://luse.blogspot.tw/2010/01/c.html|淺談C++例外處理 (後篇)]]
====== 代碼 ======
* [[https://stackoverflow.com/questions/48228562/how-to-debug-libstdc-and-libgcc|How to debug libstdc++ and libgcc?]]
* [[https://gcc.gnu.org/wiki/Dwarf2EHNewbiesHowto|Dwarf2 Exception Handler HOWTO]]
* [[http://dandylife.net/blog/archives/686|ELF Sections for Exception Handling]]
* [[http://wiki.dwarfstd.org/index.php?title=Exception_Handling|Exception Handling]]
* [[https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html|Itanium C++ ABI: Exception Handling]]
* 例外處理相關的 ABI 分為上下兩層。上層和語言相關,為 [[https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#cxx-abi|C++ ABI]],實現 ''__cxa'' 一類的接口。下層和語言無關,為 [[https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-abi|Base ABI]],實現 ''_Unwind'' 一類的接口。C++ ABI 於 GCC 和 LLVM 分別於 [[https://github.com/gcc-mirror/gcc/tree/master/libstdc%2B%2B-v3|libstdc++]] 和 [[https://github.com/llvm-mirror/libcxx|libc++]] 實現; Base ABI 於 GCC 和 LLVM 分別於 [[https://github.com/gcc-mirror/gcc/tree/master/libgcc|libgcc]] 和 [[https://github.com/llvm-mirror/libunwind|libunwind]] 實現。
* [[https://llvm.org/devmtg/2015-10/slides/KlecknerMajnemer-ExceptionHandling.pdf|Exception handling in LLVM, from Itanium to MSVC (2015)]]
* [[http://www.cnblogs.com/catch/p/3604516.html|c++ 异常处理(1)]]
* [[http://www.cnblogs.com/catch/p/3619379.html|c++ 异常处理(2)]]
* [[https://www.quora.com/How-does-gcc-implement-C++-exception-handling|How does gcc implement C++ exception handling?]]
* [[http://www.airs.com/blog/archives/460|.eh_frame]]: 用來還原暫存器和棧的狀態。.eh_frame 基本和 .debug_frame 相同,後者只有在 ''-g'' 選項下才會生成; 前者用於 C++ 例外處理。此部分參考 [[debugger]]。
* [[http://www.airs.com/blog/archives/462|.eh_frame_hdr]]: 指向 .eh_frame,讓 libstdc++ 和 libgcc 知道 .eh_frame 的位置 ([[https://stackoverflow.com/questions/14091231/what-do-the-eh-frame-and-eh-frame-hdr-sections-store-exactly|What do the .eh_frame and .eh_frame_hdr sections store, exactly?]])。
* [[http://www.airs.com/blog/archives/464|.gcc_except_table]]: 用來得知例外何時可以被處理。規格可以參考 [[https://itanium-cxx-abi.github.io/cxx-abi/exceptions.pdf|Exception Handling Tables]]。
* [[http://www.cs.dartmouth.edu/~sergey/battleaxe/hackito_2011_oakley_bratus.pdf|Exploiting the Hard-Working DWARF]]
* libgcc: [[https://github.com/gcc-mirror/gcc/blob/master/libgcc/unwind-dw2.c|unwind-dw2.c]]
* 關於 CIE 和 FDE,參考 [[debugger]]。
* ''uw_frame_state_for'': 在 ''_Unwind_RaiseException'' 被調用。將當前 context 的 caller 更新至 fs (frame state)。
/* Given the _Unwind_Context CONTEXT for a stack frame, look up the FDE for
its caller and decode it into FS. This function also sets the
args_size and lsda members of CONTEXT, as they are really information
about the caller's frame. */
static _Unwind_Reason_Code
uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs)
{
// 尋找 caller 的 fde。
fde = _Unwind_Find_FDE (context->ra + _Unwind_IsSignalFrame (context) - 1,
&context->bases);
// 找到 fde 基於的 cie,並執行 cie 中的指令。
cie = get_cie (fde);
insn = extract_cie_info (cie, context, fs);
/* First decode all the insns in the CIE. */
end = (const unsigned char *) next_fde ((const struct dwarf_fde *) cie);
execute_cfa_program (insn, end, context, fs);
// 執行 fde 中指令,更新 fs。
execute_cfa_program (insn, end, context, fs);
}
* ''uw_update_context'': 在 ''_Unwind_RaiseException'' 被調用。將 fs (frame state) 所表示 context 的 caller,更新至 context。
/* CONTEXT describes the unwind state for a frame, and FS describes the FDE
of its caller. Update CONTEXT to refer to the caller as well. Note
that the args_size and lsda members are not updated here, but later in
uw_frame_state_for. */
static void
uw_update_context (struct _Unwind_Context *context, _Unwind_FrameState *fs)
{
// 主要由 uw_update_context_1 更新 context。
uw_update_context_1 (context, fs);
if (fs->regs.reg[DWARF_REG_TO_UNWIND_COLUMN (fs->retaddr_column)].how
== REG_UNDEFINED)
/* uw_frame_state_for uses context->ra == 0 check to find outermost
stack frame. */
context->ra = 0;
else
{
// 將 context 的 caller 的 return address 更新至 context->ra。
// 至此,context 完全更新到原本 context 的 caller 的內容。
context->ra = __builtin_extract_return_addr
(_Unwind_GetPtr (context, fs->retaddr_column));
}
}
* [[https://monoinfinito.wordpress.com/series/exception-handling-in-c/|C++ exception handling internals]]
* [[https://monoinfinito.wordpress.com/2013/02/05/c-exceptions-under-the-hood/|C++ exceptions under the hood]]
* {{https://image.slidesharecdn.com/hes2011-joakley-sbratus-exploiting-the-hard-working-dwarf-110415112206-phpapp01/95/hes2011-james-oakley-and-sergey-bratusexploitingthehardworkingdwarf-50-728.jpg?500}}
* [[https://monoinfinito.wordpress.com/2013/02/12/c-exceptions-under-the-hood-ii-a-tiny-abi/|C++ exceptions under the hood II: a tiny ABI]]
* [[https://monoinfinito.wordpress.com/2013/02/19/c-exceptions-under-the-hood-3-an-abi-to-appease-the-linker/|C++ exceptions under the hood 3: an ABI to appease the linker]]
* C++ 的 ''throw'' 會被轉成 ''__cxa_allocate_exception'' 和 ''__cxa_throw''。
* libstdc++: [[https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/eh_alloc.cc|eh_alloc.cc]]和 [[https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/eh_throw.cc|eh_throw.cc]]。
* libgcc: [[https://github.com/gcc-mirror/gcc/blob/master/libgcc/unwind.inc|unwind.inc]]
* ''__cxa_throw'' -> ''_Unwind_RaiseException''
extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
{
_Unwind_RaiseException (&header->exc.unwindHeader);
// Some sort of unwinding error. Note that terminate is a handler.
__cxa_begin_catch (&header->exc.unwindHeader);
std::terminate ();
}
* [[https://monoinfinito.wordpress.com/2013/02/26/c-exceptions-under-the-hood-4-catching-what-you-throw/|C++ exceptions under the hood 4: catching what you throw]]
* C++ 的 ''catch'' 會被轉成 ''__cxa_begin_catch'' 和 ''__cxa_end_catch'',''__cxa_begin_catch'' 和 ''__cxa_end_catch'' 又被稱作 landing pad。
* libstdc++: [[https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/eh_catch.cc|eh_catch.cc]] 和 [[https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/eh_personality.cc|eh_personality.cc]]。
* [[https://monoinfinito.wordpress.com/2013/03/05/c-exceptions-under-the-hood-5-magic-around-__cxa_begin_catch-and-__cxa_end_catch/|C++ exceptions under the hood 5: magic around __cxa_begin_catch and __cxa_end_catch]]
* 調用函式之後,有底下幾種情況:
* 正常返回。
* 丟出例外,有 ''catch'' 可以處理該例外,跳到 ''__cxa_begin_catch'' 和 ''__cxa_end_catch''。處理例外之後,正常返回。
* 丟出例外,沒有 ''catch'' 可以處理該例外,跳到 ''_Unwind_Resume''。
* 有 try-catch 的函式,其開頭會有底下匯編指示符:
* [[https://sourceware.org/binutils/docs/as/CFI-directives.html|.cfi_personality]]: 定義 personality function,一般為 ''__gxx_personality_v0''。
* [[https://sourceware.org/binutils/docs/as/CFI-directives.html|.cfi_lsda]]: 定義 LSDA (Language Specific Data Area)。 ''__gxx_personality_v0'' 透過 LSDA 知道其所屬函式是否可以處理當前的例外。
* [[https://monoinfinito.wordpress.com/2013/03/12/c-exceptions-under-the-hood-6-gcc_except_table-and-the-personality-function/|C++ exceptions under the hood 6: gcc_except_table and the personality function]]
* 有 try-catch 的函式,在緊鄰其結尾處,會有一個 ''.gcc_except_table'' 段。''_Unwind_RaiseException'' 調用 ''__gxx_personality_v0'' 檢查 ''.gcc_except_table'' 段中的 LSDA 得知該函式是否可以處理當前的例外。
* [[https://monoinfinito.wordpress.com/2013/03/19/c-exceptions-under-the-hood-7-a-nice-personality/|C++ exceptions under the hood 7: a nice personality]]
* libstdc++: [[https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/eh_personality.cc|eh_personality.cc]] 定義 ''__gxx_personality_v0''。
_Unwind_Reason_Code __gxx_personality_v0 (
int version, _Unwind_Action actions, uint64_t exceptionClass,
_Unwind_Exception* unwind_exception, _Unwind_Context* context)
* version/exceptionClass: 語言/ABI/編譯器相關版本。
* actions: 當前 personality 是執行第一查找 (lookup) 或是第二輪清理 (cleanup)。
* unwind_exception: 當前處理的例外。
* context: 當前棧相關資訊,如: LSDA。
* [[https://monoinfinito.wordpress.com/2013/03/26/c-exceptions-under-the-hood-8-two-phase-handling/|C++ exceptions under the hood 8: two-phase handling]]
* ''_Unwind_RaiseException'' 調用 personality function,檢查當前棧是否可以處理目前的例外。personality function 接受 ''_UA_SEARCH_PHASE'' 表示此為第一輪查找,返回 ''_URC_HANDLER_FOUND'' 表示當前棧可以處理目前的例外,返回 ''_URC_CONTINUE_UNWIND'' 表示繼續棧回朔。
_Unwind_Reason_Code LIBGCC2_UNWIND_ATTRIBUTE
_Unwind_RaiseException(struct _Unwind_Exception *exc)
{
/* Phase 1: Search. Unwind the stack, calling the personality routine
with the _UA_SEARCH_PHASE flag set. Do not modify the stack yet. */
while (1)
{
_Unwind_FrameState fs;
/* Set up fs to describe the FDE for the caller of cur_context. The
first time through the loop, that means __cxa_throw. */
code = uw_frame_state_for (&cur_context, &fs);
if (code == _URC_END_OF_STACK)
/* Hit end of stack with no handler found. */
return _URC_END_OF_STACK;
if (code != _URC_NO_REASON)
/* Some error encountered. Usually the unwinder doesn't
diagnose these and merely crashes. */
return _URC_FATAL_PHASE1_ERROR;
/* Unwind successful. Run the personality routine, if any. */
if (fs.personality)
{
code = (*fs.personality) (1, _UA_SEARCH_PHASE, exc->exception_class,
exc, &cur_context);
if (code == _URC_HANDLER_FOUND)
break;
else if (code != _URC_CONTINUE_UNWIND)
return _URC_FATAL_PHASE1_ERROR;
}
/* Update cur_context to describe the same frame as fs. */
uw_update_context (&cur_context, &fs);
}
* 對於註解的理解,可以參考底下的 GDB 輸出。''_Unwind_RaiseException'' 第一次進入迴圈之時,其 caller 即為 ''__cxa_throw''。
(gdb) bt
#0 __gxx_personality_v0 (version=1, actions=1, exceptionClass=0,
unwind_exception=0x555555756040 , context=0x7fffffffdc20) at mycppabi.cpp:76
#1 0x00007ffff7bce28b in _Unwind_RaiseException () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#2 0x0000555555554a99 in __cxa_throw (thrown_exception=0x555555756060 ,
tinfo=0x555555755d88 , dest=0x0) at mycppabi.cpp:54
#3 0x0000555555554901 in raise () at throw.cpp:8
#4 0x000055555555490f in try_but_dont_catch () at throw.cpp:14
#5 0x0000555555554977 in catchit () at throw.cpp:25
#6 0x0000555555554a12 in seppuku () at throw.cpp:37
#7 0x00005555555548d8 in main () at main.c:5
* 一但 personality function 返回''_URC_HANDLER_FOUND'',''_Unwind_RaiseException'' 調用 ''_Unwind_RaiseException_Phase2''。personality function 接受 ''_UA_CLEANUP_PHASE'' 表示此為第二輪清理。
static _Unwind_Reason_Code
_Unwind_RaiseException_Phase2(struct _Unwind_Exception *exc,
struct _Unwind_Context *context,
unsigned long *frames_p)
{
while (1)
{
_Unwind_FrameState fs;
int match_handler;
code = uw_frame_state_for (context, &fs);
/* Identify when we've reached the designated handler context. */
match_handler = (uw_identify_context (context) == exc->private_2
? _UA_HANDLER_FRAME : 0);
if (code != _URC_NO_REASON)
/* Some error encountered. Usually the unwinder doesn't
diagnose these and merely crashes. */
return _URC_FATAL_PHASE2_ERROR;
/* Unwind successful. Run the personality routine, if any. */
if (fs.personality)
{
code = (*fs.personality) (1, _UA_CLEANUP_PHASE | match_handler,
exc->exception_class, exc, context);
if (code == _URC_INSTALL_CONTEXT)
break;
if (code != _URC_CONTINUE_UNWIND)
return _URC_FATAL_PHASE2_ERROR;
}
/* Don't let us unwind past the handler context. */
gcc_assert (!match_handler);
uw_update_context (context, &fs);
frames++;
}
* [[https://monoinfinito.wordpress.com/2013/04/02/c-exceptions-under-the-hood-9-catching-our-first-exception/|C++ exceptions under the hood 9: catching our first exception]]
* personality function 於第一輪查找,應返回 ''_URC_HANDLER_FOUND'' 表示可處理例外的 catch 已被找到; 第二輪清理,應返回 ''_URC_INSTALL_CONTEXT'' 表示應當恢復執行。此時必須要指定於哪一個 catch (即 landing pad) 恢復執行。
* [[https://monoinfinito.wordpress.com/2013/04/09/c-exceptions-under-the-hood-10-_unwind_-and-call-frame-info/|C++ exceptions under the hood 10: _Unwind_ and call frame info]]
* ''__gxx_personality_v0'' 透過 LSDA 知道其所屬函式是否可以處理當前的例外。
_Unwind_Reason_Code __gxx_personality_v0 (
int version, _Unwind_Action actions, uint64_t exceptionClass,
_Unwind_Exception* unwind_exception, _Unwind_Context* context)
{
if (actions & _UA_SEARCH_PHASE)
{
printf("Personality function, lookup phase\n");
return _URC_HANDLER_FOUND;
} else if (actions & _UA_CLEANUP_PHASE) {
printf("Personality function, cleanup\n");
const uint8_t* lsda = (const uint8_t*)_Unwind_GetLanguageSpecificData(context);
uintptr_t ip = _Unwind_GetIP(context) - 1;
uintptr_t funcStart = _Unwind_GetRegionStart(context);
uintptr_t ipOffset = ip - funcStart;
return _URC_INSTALL_CONTEXT;
} else {
printf("Personality function, error\n");
return _URC_FATAL_PHASE1_ERROR;
}
}
* [[https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-context|1.5 Context Management]]
* ''_Unwind_GetLanguageSpecificData'': 取得當前棧的 LSDA。我們可以在 LSDA 找到 landing pad。
* ''_Unwind_GetIP'': 取得當前 PC 位址。基本上就是調用丟出例外函式之後所在的位址。
(lldb) expr -f hex -- ip
(uintptr_t) $4 = 0x000000010000085c
(lldb) dis -a ip -m
11 try {
** 12 raise();
0x100000854 <+4>: subq $0x30, %rsp
0x100000858 <+8>: callq 0x100000820 ; raise at throw.cpp:6
0x10000085d <+13>: jmp 0x100000862 ; <+18> at throw.cpp:13
* ''_Unwind_GetRegionStart'': 取得當前棧的起始位址。可以於 gdb 反匯編該位址,看到函式 ''try_but_dont_catch''。
* [[https://monoinfinito.wordpress.com/2013/04/11/c-exceptions-under-the-hood-11-reading-a-cfi-table/|C++ exceptions under the hood 11: reading a CFI table]]
* {{https://image.slidesharecdn.com/hes2011-joakley-sbratus-exploiting-the-hard-working-dwarf-110415112206-phpapp01/95/hes2011-james-oakley-and-sergey-bratusexploitingthehardworkingdwarf-46-728.jpg?500}}
* personality function 讀取緊鄰函式之後,''.gcc_except_table'' 段中的 LSDA,以得到該函式 landing pad 相關訊息。
.LFE1:
.globl __gxx_personality_v0
.section .gcc_except_table,"a",@progbits
.align 4
.LLSDA1:
.byte 0xff # @LPStart format (omit)
.byte 0x9b # @TType format (indirect pcrel sdata4)
.uleb128 .LLSDATT1-.LLSDATTD1 # @TType base offset
.LLSDATTD1:
.byte 0x1 # call-site format (uleb128)
.uleb128 .LLSDACSE1-.LLSDACSB1 # Call-site table length
.LLSDACSB1:
.uleb128 .LEHB0-.LFB1 # region 0 start
.uleb128 .LEHE0-.LEHB0 # length
.uleb128 .L8-.LFB1 # landing pad
.uleb128 0x1 # action
.uleb128 .LEHB1-.LFB1 # region 1 start
.uleb128 .LEHE1-.LEHB1 # length
.uleb128 0 # landing pad
.uleb128 0 # action
.uleb128 .LEHB2-.LFB1 # region 2 start
.uleb128 .LEHE2-.LEHB2 # length
.uleb128 .L9-.LFB1 # landing pad
.uleb128 0 # action
.uleb128 .LEHB3-.LFB1 # region 3 start
.uleb128 .LEHE3-.LEHB3 # length
.uleb128 0 # landing pad
.uleb128 0 # action
.LLSDACSE1:
.byte 0x1 # Action record table
.byte 0
.align 4
.long DW.ref._ZTI14Fake_Exception-.
.LLSDATT1:
.text
.size _Z18try_but_dont_catchv, .-_Z18try_but_dont_catchv
.section .rodata
* 以 ''try_but_dont_catch'' 為例,LSDA 將其分成數個區域 (region)。每一個區域都有對應的 call site table entry。
void try_but_dont_catch() {
try {
/* region 0 begin */
raise();
/* region 0 end */
} catch(Fake_Exception&) {
/* region 2 begin */
printf("Caught a Fake_Exception!\n");
/* region 2 end */
}
/* region 1 begin */
printf("try_but_dont_catch handled the exception\n");
/* region 1 end */
}
* [[https://monoinfinito.wordpress.com/2013/04/16/c-exceptions-under-the-hood-12-and-suddenly-reflexion-in-c/|C++ exceptions under the hood 12: and suddenly, reflexion in C++]]
* personality function 讀取緊鄰函式之後,''.gcc_except_table'' 段中的 LSDA。透過 ''_Unwind_SetIP'' 設置要執行的 landing pad (即 catch)。這裡因為沒有透過 ''_Unwind_SetGR'' 將例外傳給 landing pad,landing pad 只能告知 ''_Unwind'' 無法處理該例外。當前實現,''_Unwind'' 會陷入尋找可以處理例外的 landing pad 的無窮迴圈。
// lsda 是 LSDA 基地址,加上 LSDA header 和 call site table header,得到 call site table。
const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header)
+ sizeof(LSDA_Call_Site_Header);
for (size_t i=0; i < cs_in_table; ++i)
{
const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)];
LSDA_Call_Site cs(offset);
if (cs.cs_lp)
{
uintptr_t func_start = _Unwind_GetRegionStart(context);
_Unwind_SetIP(context, func_start + cs.cs_lp);
break;
}
}
* [[https://monoinfinito.wordpress.com/2013/04/25/c-exceptions-under-the-hood-13-setting-the-context-for-a-landing-pad/|C++ exceptions under the hood 13: setting the context for a landing pad]]
while (lsda < lsda_cs_table_end)
{
LSDA_CS cs(&lsda);
if (cs.lp)
{
int r0 = __builtin_eh_return_data_regno(0);
int r1 = __builtin_eh_return_data_regno(1);
_Unwind_SetGR(context, r0, (uintptr_t)(unwind_exception));
// Note the following code hardcodes the exception type;
// we'll fix that later on
_Unwind_SetGR(context, r1, (uintptr_t)(1));
uintptr_t func_start = _Unwind_GetRegionStart(context);
_Unwind_SetIP(context, func_start + cs.lp);
break;
}
}
* [[https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-context|1.5 Context Management]]
* 使用 ''_Unwind_SetGR'' 傳參給 personality function。第一個參數傳入例外,第二個參數傳入例外的型別。此處我們傳入的型別使得 ''catch(Fake_Exception&)'' 實際上變成 ''catch(...)''。
* [[https://monoinfinito.wordpress.com/2013/04/23/c-exceptions-under-the-hood-14-multiple-landing-pads-the-teachings-of-the-guru/|C++ exceptions under the hood 14: multiple landing pads & the teachings of the guru]]
* LSDA 基本描述 try-catch 的範圍。
void foo() {
L0:
try {
do_something();
L1:
} catch (const Exception1& ex) {
...
} catch (const Exception2& ex) {
...
} catch (const ExceptionN& ex) {
...
} catch (...) {
}
L2:
}
* call site table entry 依序由底下欄位組成:
* start: try block 起始位址在函式中的偏移量,即 L0 – addr_of(foo)。
* len: try block 的長度,即 L1 - L0。
* lp: landing pad 起始位址在函式中的偏移量,即 L1 – addr_of(foo)。
* action: 例外型別相關資訊。
* [[https://monoinfinito.wordpress.com/2013/05/02/c-exceptions-under-the-hood-15-finding-the-right-landing-pad/|C++ exceptions under the hood 15: finding the right landing pad]]
* 當 ip (透過 '' _Unwind_GetIP'' 取得) 落在 start 和 start + len 之間,我們可以知道當前棧可以處理例外。
while (lsda < lsda_cs_table_end)
{
LSDA_CS cs(&lsda);
// If there's no LP we can't handle this exception; move on
if (not cs.lp) continue;
uintptr_t func_start = _Unwind_GetRegionStart(context);
// Calculate the range of the instruction pointer valid for this
// landing pad; if this LP can handle the current exception then
// the IP for this stack frame must be in this range
uintptr_t try_start = func_start + cs.start;
uintptr_t try_end = func_start + cs.start + cs.len;
// Check if this is the correct LP for the current try block
if (throw_ip < try_start) continue;
if (throw_ip > try_end) continue;
// We found a landing pad for this exception; resume execution
int r0 = __builtin_eh_return_data_regno(0);
int r1 = __builtin_eh_return_data_regno(1);
_Unwind_SetGR(context, r0, (uintptr_t)(unwind_exception));
// Note the following code hardcodes the exception type;
// we'll fix that later on
_Unwind_SetGR(context, r1, (uintptr_t)(1));
_Unwind_SetIP(context, func_start + cs.lp);
break;
}
* [[https://monoinfinito.wordpress.com/2013/05/07/c-exceptions-under-the-hood-16-finding-the-right-catch-in-a-landing-pad/|C++ exceptions under the hood 16: finding the right catch in a landing pad]]
* ''.gcc_except_table'' 段中有 call site table,action table 和 type table。type table 紀錄型別資訊。
.LLSDACSE1:
.byte 0x1 # Action record table
.byte 0
.align 4
.long DW.ref._ZTI14Fake_Exception-.
.LLSDATT1: # Type table 結尾。
* 其中,''DW.ref._ZTI14Fake_Exception-.'' 紀錄著 ''_ZTI14Fake_Exception'' 型別資訊所在位址。
_ZTI14Fake_Exception:
.quad _ZTVN10__cxxabiv117__class_type_infoE+16
.quad _ZTS14Fake_Exception
.weak _ZTS14Fake_Exception
.section .rodata._ZTS14Fake_Exception,"aG",@progbits,_ZTS14Fake_Exception,comdat
.align 16
.type _ZTS14Fake_Exception, @object
.size _ZTS14Fake_Exception, 17
* [[https://monoinfinito.wordpress.com/2013/05/14/c-exceptions-under-the-hood-17-reflecting-on-an-exception-type-and-reading-gcc_except_table/|C++ exceptions under the hood 17: reflecting on an exception type and reading .gcc_except_table]]
* LSDA 標頭紀錄 type table 的偏移 (''@TType base offset'')。
.LLSDA1:
.byte 0xff # @LPStart format (omit)
.byte 0x9b # @TType format (indirect pcrel sdata4)
.uleb128 .LLSDATT1-.LLSDATTD1 # @TType base offset
* call site table entry 紀錄 action table 的索引 (非零值減去 1 才是真正的索引。0 表示沒有 action)。
.LLSDACSB1:
.uleb128 .LEHB0-.LFB1 # region 0 start
.uleb128 .LEHE0-.LEHB0 # length
.uleb128 .L8-.LFB1 # landing pad
.uleb128 0x1 # action
* 對於帶有 catch 的 landing pad,其 action table entry 紀錄 type table 的反向索引 (即要乘上 -1)。
.LLSDACSE1:
.byte 0x1 # Action record table
.byte 0
.align 4
.long DW.ref._ZTI14Fake_Exception-. # catch 可以處理例外的型別。
.LLSDATT1:
* [[https://monoinfinito.wordpress.com/2013/05/16/c-exceptions-under-the-hood-18-getting-the-right-stack-frame/|C++ exceptions under the hood 18: getting the right stack frame]]
* ''__cxa_throw'' 會將例外的型別傳遞給 ''_Unwind_RaiseException''。
extern "C" __cxa_refcounted_exception*
__cxxabiv1::
__cxa_init_primary_exception(void *obj, std::type_info *tinfo,
void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
_GLIBCXX_NOTHROW
{
__cxa_refcounted_exception *header
= __get_refcounted_exception_header_from_obj (obj);
header->exc.exceptionType = tinfo;
return header;
}
extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
{
__cxa_refcounted_exception *header =
__cxa_init_primary_exception(obj, tinfo, dest);
_Unwind_RaiseException (&header->exc.unwindHeader);
}
* ''_Unwind_RaiseException'' 調用 personality function 檢視 LSDA 中的型別,和當前例外的型別對照,決定 landing pad 是否能處理該例外。
* [[https://monoinfinito.wordpress.com/2013/05/23/c-exceptions-under-the-hood-19-getting-the-right-catch-in-a-landing-pad/|C++ exceptions under the hood 19: getting the right catch in a landing pad]]
* 描述如何檢視 ''.gcc_except_table'' 中的 call site table,action table 和 type table 以找到 landing pad 可以處理的例外型別。
* [[https://monoinfinito.wordpress.com/2013/05/28/c-exceptions-under-the-hood-20-running-destructors-while-unwinding/|C++ exceptions under the hood 20: running destructors while unwinding]]
* action table entry 的 type index 如果為 0,表示該 landing pad 負責清理 (例如執行解構子),最後調用 ''_Unwind_Resume''。
* [[https://monoinfinito.wordpress.com/2013/06/04/c-exceptions-under-the-hood-21-a-summary-and-some-final-thoughts/|C++ exceptions under the hood 21: a summary and some final thoughts]]
* [[https://monoinfinito.wordpress.com/2013/06/11/c-exceptions-under-the-hood-appendix-i-the-true-cost-of-an-exception/|C++ exceptions under the hood appendix I: the true cost of an exception]]
* [[https://monoinfinito.wordpress.com/2013/06/13/c-exceptions-under-the-hood-appendix-ii-metaclasses-and-rtti-on-c/|C++ exceptions under the hood appendix II: metaclasses and RTTI on C++]]
* [[https://monoinfinito.wordpress.com/2013/07/25/c-exceptions-under-the-hood-appendix-iii-rtti-and-exceptions-orthogonality/|C++ exceptions under the hood appendix III: RTTI and exceptions orthogonality]]
====== 外部連結 ======
* [[wp>setjmp.h]]
* [[wp>Exception handling]]
* [[http://www.codeproject.com/Articles/2126/How-a-C-compiler-implements-exception-handling|How a C++ compiler implements exception handling]]