Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Throwing a C++ exception after an inline-asm jump

I have some odd self modifying code, but at the root of it is a pretty simple problem: I want to be able to execute a jmp (or a call) and then from that arbitrary point throw an exception and have it caught by the try/catch block that contained the jmp/call.

But when I do this (in gcc 4.4.1 x86_64) the exception results in a terminate() as it would if the exception was thrown from outside of a try/catch. I don't really see how this is different than throwing an exception from inside of some far-flung library, yet it obviously is because it just doesn't work.

How can I execute a jmp or call but still throw an exception back to the original try/catch? Why doesn't this try/catch continue to handle these exceptions as it would if the function was called normally?

The code:

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

The expected output:

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

The actual output:

Top of main
Inside thrower
terminate called after throwing an instance of 'std::runtime_error'
  what():  some exception
Aborted
like image 387
SoapBox Avatar asked Apr 15 '10 02:04

SoapBox


4 Answers

Have you looked at how your implementation handles exceptions? It involves looking up PC addresses in tables to figure out what the program was doing the particular spot that threw, and what all callers were doing. At least on Mac OS X GCC.

The only other system I've looked at, Metrowerks Codewarrior for Mac (way back when) used a similar system, albeit somewhat more transparent. The compiler can transparently insert functions for any kind of exception context change.

You don't have a prayer of making this portable.

like image 121
Potatoswatter Avatar answered Nov 16 '22 16:11

Potatoswatter


If you are using gcc 4.4.7(and above) on x86-64 linux, with dwarf exception handle mechanism (which might be the default one), I have a way to work this out.

Suppose your inline assembly code is a function inline_add. It'll call another function add, which might throw a exception. Here's code:

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

If you call inline_add like this:

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

it'll crash, because gcc doesn't provide the exception frame for inline_add. When it comes to a exception, it has to crach. (see here for the 'Compatibility with C')

So we need to fake a exception frame for it, but it would be hard to hack with gcc assembly, we just use functions with proper exception frame to surround it

we define a function like this:

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

and call inline_add like this:

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

and it just works.

build_exception_frame should come after the call, or it won't work

Further more, to prevent optimiziong gcc might take on build_exception_frame, we need to add this:

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

You can check the assembly code that gcc generates to verify the code.

It seems that gcc provide exception frame for the whole try/catch, as long as there's one function might throw, and position matters.

Need to see how gcc works on that later.

If any knows about that, please be kind enough to let me know. Thanks.

like image 28
Lelouch He Avatar answered Nov 16 '22 14:11

Lelouch He


Once you do the inline assembly, you have implementation defined behavior (7.4/1).

You should try and set up a stack frame; details are platform specific and I don't know how to do it on x86_64.

like image 1
R Samuel Klatchko Avatar answered Nov 16 '22 16:11

R Samuel Klatchko


Have you looked at the assembly code generated by gcc if your try {} block simply contained a function call? I'm pretty sure that the C++ compiler does more than just a jump at that point, since it needs to be able to back-trace the stack when an exception occurs.

Your code might be workable if you were able to mimic the steps that gcc takes when structuring the function call.

Update: this question may provide more information. In particular, the throwing and catching portion of the Itanium ABI may be of use: link

like image 1
e.James Avatar answered Nov 16 '22 14:11

e.James