2010-04-15 19 views
5

Mam dziwny kod modyfikujący, ale w jego głównym katalogu jest całkiem prosty problem: chcę móc wykonać jmp (lub call), a następnie od ten punkt arbitralny rzuca wyjątek i przechwytuje go blok próbny/przechwytujący, który zawierał jmp/call.Rzucanie wyjątku C++ po skoku inline-asm

Ale kiedy to zrobię (w gcc 4.4.1 x86_64) wyjątek spowoduje, że terminate() będzie tak, jakby wyjątek został wyrzucony spoza try/catch. Naprawdę nie widzę, jak to jest inne niż wyrzucenie wyjątku z wnętrza jakiejś odległej biblioteki, ale to oczywiście dlatego, że po prostu nie działa.

Jak mogę wykonać jmp lub call, ale nadal mogę podać wyjątek z powrotem do oryginalnego try/catch? Dlaczego ta próba/catch nadal nie obsługuje tych wyjątków, tak jak gdyby funkcja była wywoływana normalnie?

Kod:

#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; 
} 

oczekiwany wynik:

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

Rzeczywista moc:

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

Pierwotnie zadałem podobne pytanie w kontekście programów obsługi sygnałów w systemie Linux. To naprawdę tylko przesłaniało problem, więc usunąłem ten i poprosiłem o znacznie bardziej uproszczoną wersję. Cała obsługa sygnału po prostu maskowała mój prawdziwy problem. – SoapBox

+1

Istnieją dwa różne sposoby implementacji wyjątków. Jedną z nich jest udekorowanie kodu i stron wywoływania w celu przepchnięcia kontekstu dookoła, drugim jest zindeksowanie wywołań i punktów zwracanych, aby program obsługi wyjątków mógł sprawdzić, dokąd się udać. Tak czy inaczej, inline asm czegoś nie ma. – spraff

Odpowiedz

1

Jeśli używasz gcc 4.4.7 (i powyżej) na x86-64 Linux, z karłowatej mechanizmu rączki wyjątek (który może być domyślny), mam drogę do pracy na to uwagę .

Załóżmy, że Twój wbudowany kod zespołu jest funkcją inline_add. Będzie wywoływał inną funkcję add, która może rzucić wyjątek. Oto kod:

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; 
} 

Jeśli zadzwonisz inline_add takiego:

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

będzie ona upaść, bo gcc nie przewiduje wyjątku dla inline_add ramkę. Jeśli chodzi o wyjątek, to musi się podnieść. (Patrz here dla „Zgodność z C”)

Więc musimy udawać ramie wyjątek dla niego, ale trudno byłoby się włamać z montażem gcc, po prostu korzystać z funkcji z odpowiedniej ramce wyjątek go otaczają

możemy zdefiniować funkcję tak:

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

i nazywają inline_add takiego:

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

i to właśnie wo rks.

build_exception_frame powinny pochodzić po zakończeniu rozmowy, czy to nie będzie działać

Co więcej, aby zapobiec optimiziong gcc może podjąć build_exception_frame, musimy dodać to:

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

można sprawdzić kod zestawu generowany przez gcc w celu zweryfikowania kodu.

Wygląda na to, że gcc zapewnia ramkę wyjątku dla całego try/catch, o ile istnieje jedna funkcja, która może rzutować, a pozycja ma znaczenie.

Musisz sprawdzić, jak działa Gcc później.

Jeśli ktoś o tym wie, uprzejmie prosimy o powiadomienie mnie. Dzięki.

1

Pan spojrzał na kod montażu generowane przez GCC jeśli try Blok {} zawierał po prostu wywołanie funkcji? Jestem całkiem pewien, że kompilator C++ robi więcej niż tylko skok w tym punkcie, ponieważ musi mieć możliwość śledzenia wstecznego stosu, gdy wystąpi wyjątek.

Twój kod może działać, jeśli udało ci się naśladować kroki, które podejmuje gcc podczas konstruowania wywołania funkcji.

Aktualizacja:this question może dostarczyć więcej informacji. W szczególności może być użyteczna część wyrzucania i chwytania Itanium ABI: link

+0

To pytanie nie dostarcza znacznie więcej informacji. Moja słaba próba zrozumienia obsługi wyjątków (na Mac OS X) doprowadziła mnie do dziwnego kodu 'libunwind', dla którego nie mogłem znaleźć open source. Został napisany w C++ (!) I posiadał konwencje nazewnictwa Apple. Punkt będący, to nie jest ściśle funkcjonalność GCC. Spójrz na źródło GCC, a zobaczysz, że to zależy od dostarczonego przez system libunwind. – Potatoswatter

+0

Wygląda na to, że asm jest po prostu wywołaniem funkcji (umieszczam komentarze inline-asm wokół wywołania i nie ma instrukcji między komentarzami oprócz samego wywołania). – SoapBox

+0

@SoapBox: Musisz porównać tabele wyjątków wyjątków, aby zobaczyć, jakie funkcje * są * wstawiane w 'try {' i 'throw' działają dalej. – Potatoswatter

3

Czy sprawdziłeś, jak implementacja obsługuje wyjątki? Polega ona na wyszukiwaniu adresów komputerów w tabelach, aby dowiedzieć się, co program robi w danym miejscu, które rzuciło, i co robili wszyscy rozmówcy. Przynajmniej na Mac OS X GCC.

Jedyny inny system, na który patrzyłem, Metrowerks Codewarrior na Maca (droga powrotna) używał podobnego systemu, choć nieco bardziej przejrzystego. Kompilator może przezroczyście wstawiać funkcje dla dowolnego rodzaju zmiany kontekstu wyjątku.

Nie masz modlitwy, żeby zrobić to przenośne.

+0

Interesujące ... Ale jeśli osadzę uzasadnione, działające wezwanie do "thrower()" w tym samym try/catch, wersja inline-asm nadal nie działa, co brzmi jak mówisz, że powinno.Domyślam się, że program obsługi wyjątku mógłby przeglądać adres wywołania, który nie jest znany. To jest najbardziej obiecująca odpowiedź do tej pory .... – SoapBox

1

Po wykonaniu złożenia inline masz zdefiniowane zachowanie implementacji (7.4/1).

Powinieneś spróbować ustawić ramkę stosu; szczegóły są specyficzne dla platformy i nie wiem jak to zrobić na x86_64.

Powiązane problemy