2010-04-15 21 views
5

我有一些奇怪的自修改代碼,但它的根源是一個非常簡單的問題:我希望能夠執行一個jmp(或一個call),然後從該任意點拋出一個異常,並使其被包含jmp/call的try/catch塊捕獲。內聯asm跳轉後拋出一個C++異常

但是,當我這樣做時(在gcc 4.4.1 x86_64中),異常結果爲terminate(),就像從try/catch之外拋出異常一樣。我真的不明白這與從一些遙遠的圖書館內部拋出異常有什麼不同,但顯然是因爲它不起作用。

我該如何執行jmpcall,但仍然會拋出一個異常回到原來的try/catch?爲什麼這個try/catch繼續處理這些異常,就像這個函數被正常調用一樣?

代碼:

#include <iostream> 
#include <stdexcept> 

using namespace std; 

void thrower() 
{ 
    cout << "Inside thrower" << endl; 
    throw runtime_error("some exception"); 
} 

int main() 
{ 
    cout << "Top of main" << endl; 

    try { 
     asm volatile (
      "jmp *%0" // same thing happens with a call instead of a jmp 
      : 
      : "r"((long)thrower) 
      : 
     ); 
    } catch (exception &e) { 
     cout << "Caught : " << e.what() << endl; 
    } 
    cout << "Bottom of main" << endl << endl; 
} 

預期輸出:

Top of main 
Inside thrower 
Caught : some exception 
Bottom of main 

實際輸出:

Top of main 
Inside thrower 
terminate called after throwing an instance of 'std::runtime_error' 
    what(): some exception 
Aborted 
+0

最初我在linux中的信號處理程序的上下文中提出過類似的問題。這真的只是籠罩了這個問題,所以我刪除了那個,問了這個更簡化的版本。整個信號處理器只是掩蓋了我真正的問題。 – SoapBox 2010-04-15 02:39:39

+1

大致有兩種不同的方式來實現異常處理。一個是裝飾代碼和調用網站,以便在任何地方推動上下文,另一個是索引呼叫和返回點,以便異常處理程序可以查找去哪裏。無論哪種方式,內聯asm缺少一些東西。 – spraff 2012-02-03 15:19:06

回答

1

如果你正在使用GCC 4.4.7(及以上)的X86-64的Linux,與異常處理機制(這可能是默認的),我有辦法來解決這一問題。

假設你的內聯彙編代碼是一個函數inline_add。它會調用另一個功能add,這可能會引發異常。這裏是代碼:

extern "C" int add(int a, int b) { 
    throw "in add"; 
} 

int inline_add(int a, int b) { 
    int r = 0; 
    __asm__ __volatile__ (
     "movl %1, %%edi\n\t" 
     "movl %2, %%esi\n\t" 
     "call add\n\t" 
     "movl %%eax, %0\n\t" 
     :"=r"(r) 
     :"r"(a), "r"(b) 
     :"%eax" 
    ); 
    return r; 
} 

如果調用inline_add這樣的:

try { 
    inline_add(1, 1); 
} catch (...) { 
    std::cout << "in catch" << std::endl; 
} 

它會崩潰,因爲gcc沒有爲inline_add提供異常幀。當涉及到一個例外時,它就不得不去做。 (見here爲「與C兼容性」)

所以我們需要假爲它異常幀,但是這將是很難用gcc組裝破解,我們只是使用功能與適當的異常框架將其包圍

我們定義一個函數是這樣的:

void build_exception_frame(bool b) { 
    if (b) { 
     throw 0; 
    } 
} 

,並呼籲inline_add這樣的:

try { 
    inline_add(1, 1); 
    build_exception_frame(false); 
} catch (...) { 
    std::cout << "in catch" << std::endl; 
} 

,它只是WO RKS。

build_exception_frame應該來電話後,或將無法正常工作

更進一步,以防止optimiziong GCC可能採取build_exception_frame,我們需要補充一點:

void build_exception_frame(bool b) __attribute__((optimize("O0"))); 

您可以檢查gcc生成的彙編代碼來驗證代碼。

似乎gcc提供了整個try/catch的異常幀,只要有一個函數可能拋出並且位置很重要。

需要了解gcc以後的工作方式。

如果有人知道這一點,請儘可能讓我知道。謝謝。

1

你看,如果你試圖通過GCC生成的彙編代碼{}塊只包含一個函數調用?我非常肯定,C++編譯器在這一點上不僅僅是跳轉,因爲它需要能夠在異常發生時回溯到堆棧。

如果您能夠模仿構建函數調用時gcc所需的步驟,那麼您的代碼可能是可行的。

更新:this question可以提供更多的信息。特別是,安騰ABI的投擲和捕捉部分可能有用:link

+0

這個問題沒有提供更多的信息。我對理解異常處理(在Mac OS X上)的微弱嘗試使我陷入了一些奇怪的'libunwind'代碼,對此我找不到開源代碼。它是用C++(!)編寫的,並且具有Apple命名約定。要指出的是,它不是GCC的嚴格功能。看看GCC源代碼,你會發現它依賴於OS提供的libunwind。 – Potatoswatter 2010-04-15 02:48:12

+0

不,它看起來像asm只是對函數的調用(我在調用中放置了內聯註釋,除了調用本身之外,在註釋之間沒有指令)。 – SoapBox 2010-04-15 02:49:47

+0

@SoapBox:您需要比較異常調度表,以查看在try {'和'throw'處插入*的函數的操作。 – Potatoswatter 2010-04-15 02:59:00

3

您是否看過您的實現如何處理異常?它涉及在表格中查找PC地址,找出程序正在做什麼,以及所有呼叫者在做什麼。至少在Mac OS X GCC上。

我看過的唯一的其他系統,Metrowerks Codewarrior for Mac(回頭的時候)使用了類似的系統,雖然稍微透明一些。編譯器可以透明地插入任何類型的異常上下文變化的函數。

你沒有做這種便攜式的祈禱。

+0

有趣......但是如果我在同一個try/catch中嵌入了一個合法的工作調用「thrower()」,那麼inline-asm版本仍然不起作用,這聽起來像是你應該說的。我猜這個異常處理程序可能正在查看調用本身的地址,這不是它所知道的。這是迄今爲止最有希望的答案.... – SoapBox 2010-04-15 02:55:53

1

一旦你做了內聯彙編,你就有了實現定義的行爲(7.4/1)。

你應該嘗試設置一個堆棧幀;細節是特定於平臺的,我不知道如何在x86_64上完成。