[譯]C++異常的幕後2:一個小ABI
如果我們準備嘗試理解為什麼異常是複雜的,以及它們如何工作,我們可以讀大量的手冊,也可以嘗試自己編寫異常的處理。實際上,我驚訝於這個議題好資料的缺乏:我找到的幾乎所有東西要麼非常詳細,要麼非常基礎,只有一兩個例外。當然,有一些要實現的規範(最值得注意的是C++的ABI,但我們還有CFI、DWARF與libstdc),但只閱讀規範不足以真正理解底層。
讓我們從最明顯的開始:重新發明輪子!我們知道一個事實,C不能處理異常,因此讓我們嘗試使用一個C連結器連結一個丟擲的C++程式,看會發生什麼。我想出了這樣一個簡單的東西:
1 2 3 4 5 6 |
|
不要忘了extern,否則g++將重整我們小函式的名字,我們將不能將它與我們的C程式連結。當然,我們需要一個頭檔案來將C++世界與C世界“連結”(沒有雙關語)起來:
|
|
以及一個非常簡單的main:
|
|
如果現在我們嘗試編譯並連結這個程式碼會發生什麼?
|
|
注意:你可以從我的 github庫 裡下載這個專案的完整原始碼。
目前還好。G++與gcc都陶醉在它們的小世界裡。然而一旦我們嘗試連結它們,混亂接踵而至:
> gcc main.o throw.o -o app throw.o: In function `foo()': Throw.cpp:4: undefined reference to `__cxa_allocate_exception' throw.cpp:4: undefined reference to `__cxa_throw' throw.o:(.rodata._ZTI9Exception[typeinfo for Exception]+0x0): undefined reference to `vtable for __cxxabiv1::__class_type_info' collect2: ld returned 1 exit status
確實,gcc抱怨缺少C++符號。雖然這些是非常特殊的C++符號。檢查最後一行錯誤:缺少用於cxxabiv1的vtable。定義在libstdc++中,cxxabi援引用於C++的應用程式二進位制介面。因此現在我們瞭解到異常處理是在帶有C++ABI定義介面的標準C++庫的輔助下完成。
C++ ABI定義了一個標準二進位制格式,因此我們可以在一個程式裡將物件連結起來;如果我們使用兩個編譯器編譯一個.o檔案,這些編譯器使用不同的ABI,我們將不能把.o檔案連結進應用程式。ABI也將定義其他一些格式,例如執行棧回滾或異常丟擲的介面。在這個情形裡,ABI在C++與我們程式裡其他某些處理棧回滾的庫之間定義了一個介面(不一定二進位制格式,只是一個介面),即ABI定義了C++特定的內容,因此它可與非C++庫交談:這使得在C++裡能捕捉從其他語言丟擲的異常,除了別的之外。
無論如何,連結器將我們指向幕後異常處理的第一層:一個我們必須自己實現的介面,cxxabi。在下一篇我們將開始我們自己的小ABI,就像定義在C++ ABI裡那樣。