a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
110 lines
4.0 KiB
Plaintext
110 lines
4.0 KiB
Plaintext
Exception Implementation in the Mono Runtime
|
|
Dietmar Maurer (dietmar@ximian.com)
|
|
(C) 2001 Ximian, Inc.
|
|
|
|
Exception implementation (jit):
|
|
===============================
|
|
|
|
Stack unwinding:
|
|
================
|
|
|
|
We record the code address (start_address, size) of all methods. That way it is
|
|
possible to map an instruction pointer (IP) to the method information needed
|
|
for unwinding the stack:
|
|
|
|
We also save a Last Managed Frame (LMF) structure at each call from managed to
|
|
unmanaged code. That way we can recover from exceptions inside unmanaged code.
|
|
|
|
void handle_exception ((struct sigcontext *ctx, gpointer obj)
|
|
{
|
|
if (ctx->bp < mono_end_of_stack) {
|
|
/* unhandled exception */
|
|
abort ();
|
|
}
|
|
|
|
info = mono_jit_info_table_find (mono_jit_info_table, ctx->ip);
|
|
|
|
if (info) { // we are inside managed code
|
|
|
|
if (ch = find_catch_handler ())
|
|
execute_catch_handler (ch, ctx, obj);
|
|
|
|
execute_all_finally_handler ();
|
|
|
|
// restore register, including IP and Frame pointer
|
|
ctx = restore_caller_saved_registers_from_ctx (ji, ctx);
|
|
|
|
// continue unwinding
|
|
handle_exception (ctx, obj);
|
|
|
|
} else {
|
|
|
|
lmf = get_last_managed_frame ();
|
|
|
|
// restore register, including IP and Frame pointer
|
|
ctx = restore_caller_saved_registers_from_lmf (ji, lmf);
|
|
|
|
// continue unwinding
|
|
handle_exception (ctx, obj);
|
|
}
|
|
}
|
|
|
|
|
|
Code generation:
|
|
================
|
|
|
|
leave: is simply translated into a branch to the target. If the leave
|
|
instruction is inside a finally block (but not inside another handler)
|
|
we call the finally handler before we branch to the target.
|
|
|
|
finally/endfinally, filter/endfilter: is translated into subroutine ending with
|
|
a "return" statement. The subroutine does not save EBP, because we need access
|
|
to the local variables of the enclosing method. Its is possible that
|
|
instructions inside those handlers modify the stack pointer, thus we save the
|
|
stack pointer at the start of the handler, and restore it at the end. We have
|
|
to use a "call" instruction to execute such finally handlers. This makes it
|
|
also possible to execute them inside the stack unwinding code. The exception
|
|
object for filters is passed in a local variable (cfg->exvar).
|
|
|
|
throw: we first save all regs into a sigcontext struct and then call the stack
|
|
unwinding code.
|
|
|
|
catch handler: catch hanlders are always called from the stack unwinding
|
|
code. The exception object is passed in a local variable (cfg->exvar).
|
|
|
|
gcc support for Exceptions
|
|
==========================
|
|
|
|
gcc supports exceptions in files compiled with the -fexception option. gcc
|
|
generates DWARF exceptions tables in that case, so it is possible to unwind the
|
|
stack. The method to read those exception tables is contained in libgcc.a, and
|
|
in newer versions of glibc (glibc 2.2.5 for example), and it is called
|
|
__frame_state_for(). Another usable glibc function is backtrace_symbols() which
|
|
returns the function name corresponding to a code address.
|
|
|
|
We dynamically check if those features are available using g_module_symbol(),
|
|
and we use them only when available. If not available we use the LMF as
|
|
fallback.
|
|
|
|
Using gcc exception information prevents us from saving the LMF at each native
|
|
call, so this is a way to speed up native calls. This is especially valuable
|
|
for internal calls, because we can make sure that all internal calls are
|
|
compiled with -fexceptions (we compile the whole mono runtime with that
|
|
option).
|
|
|
|
All native function are able to call function without exception tables, and so
|
|
we are unable to restore all caller saved registers if an exception is raised
|
|
in such function. Well, its possible if the previous function already saves all
|
|
registers. So we only omit the the LMF if a function has an exception table
|
|
able to restore all caller saved registers.
|
|
|
|
One problem is that gcc almost never saves all caller saved registers, because
|
|
it is just unnecessary in normal situations. But there is a trick forcing gcc
|
|
to save all register, we just need to call __builtin_unwind_init() at the
|
|
beginning of a function. That way gcc generates code to save all caller saved
|
|
register on the stack.
|
|
|
|
|
|
|
|
|
|
|