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
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With