| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- Exception Implementation in the Mono Runtime
- Dietmar Maurer ([email protected])
- (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.
-
|