2 * exceptions-amd64.c: exception support for AMD64
5 * Dietmar Maurer (dietmar@ximian.com)
7 * (C) 2001 Ximian, Inc.
14 #include <sys/ucontext.h>
16 #include <mono/arch/amd64/amd64-codegen.h>
17 #include <mono/metadata/appdomain.h>
18 #include <mono/metadata/tabledefs.h>
19 #include <mono/metadata/threads.h>
20 #include <mono/metadata/debug-helpers.h>
21 #include <mono/metadata/exception.h>
22 #include <mono/metadata/gc-internal.h>
23 #include <mono/metadata/mono-debug.h>
24 #include <mono/metadata/mono-debug-debugger.h>
27 #include "mini-amd64.h"
29 #define ALIGN_TO(val,align) (((val) + ((align) - 1)) & ~((align) - 1))
32 static MonoW32ExceptionHandler fpe_handler;
33 static MonoW32ExceptionHandler ill_handler;
34 static MonoW32ExceptionHandler segv_handler;
36 static LPTOP_LEVEL_EXCEPTION_FILTER old_handler;
38 #define W32_SEH_HANDLE_EX(_ex) \
39 if (_ex##_handler) _ex##_handler((int)sctx)
42 * Unhandled Exception Filter
43 * Top-level per-process exception handler.
45 LONG CALLBACK seh_handler(EXCEPTION_POINTERS* ep)
52 res = EXCEPTION_CONTINUE_EXECUTION;
54 er = ep->ExceptionRecord;
55 ctx = ep->ContextRecord;
56 sctx = g_malloc(sizeof(MonoContext));
58 /* Copy Win32 context to UNIX style context */
69 switch (er->ExceptionCode) {
70 case EXCEPTION_ACCESS_VIOLATION:
71 W32_SEH_HANDLE_EX(segv);
73 case EXCEPTION_ILLEGAL_INSTRUCTION:
74 W32_SEH_HANDLE_EX(ill);
76 case EXCEPTION_INT_DIVIDE_BY_ZERO:
77 case EXCEPTION_INT_OVERFLOW:
78 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
79 case EXCEPTION_FLT_OVERFLOW:
80 case EXCEPTION_FLT_UNDERFLOW:
81 case EXCEPTION_FLT_INEXACT_RESULT:
82 W32_SEH_HANDLE_EX(fpe);
88 /* Copy context back */
102 void win32_seh_init()
104 old_handler = SetUnhandledExceptionFilter(seh_handler);
107 void win32_seh_cleanup()
109 if (old_handler) SetUnhandledExceptionFilter(old_handler);
112 void win32_seh_set_handler(int type, MonoW32ExceptionHandler handler)
116 fpe_handler = handler;
119 ill_handler = handler;
122 segv_handler = handler;
129 #endif /* PLATFORM_WIN32 */
132 * mono_arch_get_restore_context:
134 * Returns a pointer to a method which restores a previously saved sigcontext.
137 mono_arch_get_restore_context (void)
139 static guint8 *start = NULL;
140 static gboolean inited = FALSE;
146 /* restore_contect (MonoContext *ctx) */
148 start = code = mono_global_codeman_reserve (256);
150 /* get return address */
151 amd64_mov_reg_membase (code, AMD64_RAX, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, rip), 8);
153 /* Restore registers */
154 amd64_mov_reg_membase (code, AMD64_RBP, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, rbp), 8);
155 amd64_mov_reg_membase (code, AMD64_RBX, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, rbx), 8);
156 amd64_mov_reg_membase (code, AMD64_R12, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r12), 8);
157 amd64_mov_reg_membase (code, AMD64_R13, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r13), 8);
158 amd64_mov_reg_membase (code, AMD64_R14, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r14), 8);
159 amd64_mov_reg_membase (code, AMD64_R15, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r15), 8);
161 amd64_mov_reg_membase (code, AMD64_RSP, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, rsp), 8);
163 /* jump to the saved IP */
164 amd64_jump_reg (code, AMD64_RAX);
172 * mono_arch_get_call_filter:
174 * Returns a pointer to a method which calls an exception filter. We
175 * also use this function to call finally handlers (we pass NULL as
176 * @exc object in this case).
179 mono_arch_get_call_filter (void)
181 static guint8 *start;
182 static gboolean inited = FALSE;
190 start = code = mono_global_codeman_reserve (64);
192 /* call_filter (MonoContext *ctx, unsigned long eip) */
195 /* Alloc new frame */
196 amd64_push_reg (code, AMD64_RBP);
197 amd64_mov_reg_reg (code, AMD64_RBP, AMD64_RSP, 8);
199 /* Save callee saved regs */
201 for (i = 0; i < AMD64_NREG; ++i)
202 if (AMD64_IS_CALLEE_SAVED_REG (i)) {
203 amd64_push_reg (code, i);
209 amd64_push_reg (code, AMD64_RBP);
211 /* Make stack misaligned, the call will make it aligned again */
213 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, 8);
216 amd64_mov_reg_membase (code, AMD64_RBP, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, rbp), 8);
217 /* load callee saved regs */
218 amd64_mov_reg_membase (code, AMD64_RBX, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, rbx), 8);
219 amd64_mov_reg_membase (code, AMD64_R12, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r12), 8);
220 amd64_mov_reg_membase (code, AMD64_R13, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r13), 8);
221 amd64_mov_reg_membase (code, AMD64_R14, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r14), 8);
222 amd64_mov_reg_membase (code, AMD64_R15, AMD64_RDI, G_STRUCT_OFFSET (MonoContext, r15), 8);
224 /* call the handler */
225 amd64_call_reg (code, AMD64_RSI);
228 amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, 8);
231 amd64_pop_reg (code, AMD64_RBP);
233 /* Restore callee saved regs */
234 for (i = AMD64_NREG; i >= 0; --i)
235 if (AMD64_IS_CALLEE_SAVED_REG (i))
236 amd64_pop_reg (code, i);
241 g_assert ((code - start) < 64);
249 throw_exception (MonoObject *exc, guint64 rip, guint64 rsp,
250 guint64 rbx, guint64 rbp, guint64 r12, guint64 r13,
251 guint64 r14, guint64 r15, guint64 rethrow)
253 static void (*restore_context) (MonoContext *);
256 if (!restore_context)
257 restore_context = mono_arch_get_restore_context ();
268 if (!rethrow && mono_debugger_throw_exception ((gpointer)(rip - 8), (gpointer)rsp, exc)) {
270 * The debugger wants us to stop on the `throw' instruction.
271 * By the time we get here, it already inserted a breakpoint on
272 * eip - 8 (which is the address of the `mov %r15,%rdi ; callq throw').
277 * In case of a rethrow, the JIT is emitting code like this:
279 * mov 0xffffffffffffffd0(%rbp),%rax'
283 * Here, restore_context() wouldn't restore the %rax register correctly.
287 restore_context (&ctx);
288 g_assert_not_reached ();
291 /* adjust eip so that it point into the call instruction */
294 if (mono_object_isinst (exc, mono_defaults.exception_class)) {
295 MonoException *mono_ex = (MonoException*)exc;
297 mono_ex->stack_trace = NULL;
299 mono_handle_exception (&ctx, exc, (gpointer)rip, FALSE);
300 restore_context (&ctx);
302 g_assert_not_reached ();
306 get_throw_trampoline (gboolean rethrow)
311 start = code = mono_global_codeman_reserve (64);
316 amd64_mov_reg_reg (code, AMD64_RDI, AMD64_RDI, 8);
318 amd64_mov_reg_membase (code, AMD64_RSI, AMD64_RSP, 0, 8);
320 amd64_lea_membase (code, AMD64_RDX, AMD64_RSP, 8);
321 /* Callee saved regs */
322 amd64_mov_reg_reg (code, AMD64_RCX, AMD64_RBX, 8);
323 amd64_mov_reg_reg (code, AMD64_R8, AMD64_RBP, 8);
324 amd64_mov_reg_reg (code, AMD64_R9, AMD64_R12, 8);
326 amd64_push_imm (code, 0);
328 amd64_push_imm (code, rethrow);
329 amd64_push_reg (code, AMD64_R15);
330 amd64_push_reg (code, AMD64_R14);
331 amd64_push_reg (code, AMD64_R13);
333 amd64_mov_reg_imm (code, AMD64_R11, throw_exception);
334 amd64_call_reg (code, AMD64_R11);
335 amd64_breakpoint (code);
337 g_assert ((code - start) < 64);
343 * mono_arch_get_throw_exception:
345 * Returns a function pointer which can be used to raise
346 * exceptions. The returned function has the following
347 * signature: void (*func) (MonoException *exc);
351 mono_arch_get_throw_exception (void)
353 static guint8* start;
354 static gboolean inited = FALSE;
359 start = get_throw_trampoline (FALSE);
367 mono_arch_get_rethrow_exception (void)
369 static guint8* start;
370 static gboolean inited = FALSE;
375 start = get_throw_trampoline (TRUE);
383 mono_arch_get_throw_exception_by_name (void)
385 static guint8* start;
386 static gboolean inited = FALSE;
392 start = code = mono_global_codeman_reserve (64);
394 /* Not used on amd64 */
395 amd64_breakpoint (code);
401 * mono_arch_get_throw_corlib_exception:
403 * Returns a function pointer which can be used to raise
404 * corlib exceptions. The returned function has the following
405 * signature: void (*func) (guint32 ex_token, guint32 offset);
406 * Here, offset is the offset which needs to be substracted from the caller IP
407 * to get the IP of the throw. Passing the offset has the advantage that it
408 * needs no relocations in the caller.
411 mono_arch_get_throw_corlib_exception (void)
413 static guint8* start;
414 static gboolean inited = FALSE;
421 start = code = mono_global_codeman_reserve (64);
424 amd64_push_reg (code, AMD64_RSI);
426 /* Call exception_from_token */
427 amd64_mov_reg_reg (code, AMD64_RSI, AMD64_RDI, 8);
428 amd64_mov_reg_imm (code, AMD64_RDI, mono_defaults.exception_class->image);
429 amd64_mov_reg_imm (code, AMD64_R11, mono_exception_from_token);
430 amd64_call_reg (code, AMD64_R11);
432 /* Compute throw_ip */
433 amd64_pop_reg (code, AMD64_RSI);
435 amd64_pop_reg (code, AMD64_RDX);
436 amd64_alu_reg_reg (code, X86_SUB, AMD64_RDX, AMD64_RSI);
438 /* Put the throw_ip at the top of the misaligned stack */
439 amd64_push_reg (code, AMD64_RDX);
441 throw_ex = (guint64)mono_arch_get_throw_exception ();
443 /* Call throw_exception */
444 amd64_mov_reg_reg (code, AMD64_RDI, AMD64_RAX, 8);
445 amd64_mov_reg_imm (code, AMD64_R11, throw_ex);
446 /* The original IP is on the stack */
447 amd64_jump_reg (code, AMD64_R11);
449 g_assert ((code - start) < 64);
456 /* mono_arch_find_jit_info:
458 * This function is used to gather information from @ctx. It return the
459 * MonoJitInfo of the corresponding function, unwinds one stack frame and
460 * stores the resulting context into @new_ctx. It also stores a string
461 * describing the stack location into @trace (if not NULL), and modifies
462 * the @lmf if necessary. @native_offset return the IP offset from the
463 * start of the function or -1 if that info is not available.
466 mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, MonoJitInfo *res, MonoJitInfo *prev_ji, MonoContext *ctx,
467 MonoContext *new_ctx, char **trace, MonoLMF **lmf, int *native_offset,
472 gpointer ip = MONO_CONTEXT_GET_IP (ctx);
474 /* Avoid costly table lookup during stack overflow */
475 if (prev_ji && (ip > prev_ji->code_start && ((guint8*)ip < ((guint8*)prev_ji->code_start) + prev_ji->code_size)))
478 ji = mono_jit_info_table_find (domain, ip);
485 gboolean omit_fp = (ji->used_regs & (1 << 31)) > 0;
490 if (!ji->method->wrapper_type)
494 * Some managed methods like pinvoke wrappers might have save_lmf set.
495 * In this case, register save/restore code is not generated by the
496 * JIT, so we have to restore callee saved registers from the lmf.
498 if (ji->method->save_lmf) {
500 * We only need to do this if the exception was raised in managed
501 * code, since otherwise the lmf was already popped of the stack.
503 if (*lmf && ((*lmf) != jit_tls->first_lmf) && (MONO_CONTEXT_GET_SP (ctx) >= (gpointer)(*lmf)->rsp)) {
504 new_ctx->rbp = (*lmf)->ebp;
505 new_ctx->rbx = (*lmf)->rbx;
506 new_ctx->rsp = (*lmf)->rsp;
507 new_ctx->r12 = (*lmf)->r12;
508 new_ctx->r13 = (*lmf)->r13;
509 new_ctx->r14 = (*lmf)->r14;
510 new_ctx->r15 = (*lmf)->r15;
514 offset = omit_fp ? 0 : -1;
515 /* restore caller saved registers */
516 for (i = 0; i < AMD64_NREG; i ++)
517 if (AMD64_IS_CALLEE_SAVED_REG (i) && (ji->used_regs & (1 << i))) {
521 reg = *((guint64*)ctx->rsp + offset);
525 reg = *((guint64 *)ctx->SC_EBP + offset);
549 g_assert_not_reached ();
554 if (*lmf && ((*lmf) != jit_tls->first_lmf) && (MONO_CONTEXT_GET_SP (ctx) >= (gpointer)(*lmf)->rsp)) {
555 /* remove any unused lmf */
556 *lmf = (*lmf)->previous_lmf;
561 new_ctx->rsp += (ji->used_regs >> 16) & (0x7fff);
562 new_ctx->SC_EIP = *((guint64 *)new_ctx->rsp) - 1;
563 /* Pop return address */
567 /* Pop EBP and the return address */
568 new_ctx->SC_ESP = ctx->SC_EBP + (2 * sizeof (gpointer));
569 /* we substract 1, so that the IP points into the call instruction */
570 new_ctx->SC_EIP = *((guint64 *)ctx->SC_EBP + 1) - 1;
571 new_ctx->SC_EBP = *((guint64 *)ctx->SC_EBP);
574 /* Pop arguments off the stack */
576 MonoJitArgumentInfo *arg_info = g_newa (MonoJitArgumentInfo, mono_method_signature (ji->method)->param_count + 1);
578 guint32 stack_to_pop = mono_arch_get_argument_info (mono_method_signature (ji->method), mono_method_signature (ji->method)->param_count, arg_info);
579 new_ctx->SC_ESP += stack_to_pop;
590 if ((ji = mono_jit_info_table_find (domain, (gpointer)(*lmf)->rip))) {
592 memset (res, 0, sizeof (MonoJitInfo));
593 res->method = (*lmf)->method;
596 new_ctx->SC_RIP = (*lmf)->rip;
597 new_ctx->SC_RBP = (*lmf)->ebp;
598 new_ctx->SC_ESP = (*lmf)->rsp;
600 new_ctx->SC_RBX = (*lmf)->rbx;
601 new_ctx->SC_R12 = (*lmf)->r12;
602 new_ctx->SC_R13 = (*lmf)->r13;
603 new_ctx->SC_R14 = (*lmf)->r14;
604 new_ctx->SC_R15 = (*lmf)->r15;
606 *lmf = (*lmf)->previous_lmf;
608 return ji ? ji : res;
615 * mono_arch_handle_exception:
617 * @ctx: saved processor state
618 * @obj: the exception object
621 mono_arch_handle_exception (void *sigctx, gpointer obj, gboolean test_only)
623 ucontext_t *ctx = (ucontext_t*)sigctx;
626 mctx.rax = ctx->uc_mcontext.gregs [REG_RAX];
627 mctx.rbx = ctx->uc_mcontext.gregs [REG_RBX];
628 mctx.rcx = ctx->uc_mcontext.gregs [REG_RCX];
629 mctx.rdx = ctx->uc_mcontext.gregs [REG_RDX];
630 mctx.rbp = ctx->uc_mcontext.gregs [REG_RBP];
631 mctx.rsp = ctx->uc_mcontext.gregs [REG_RSP];
632 mctx.rsi = ctx->uc_mcontext.gregs [REG_RSI];
633 mctx.rdi = ctx->uc_mcontext.gregs [REG_RDI];
634 mctx.rip = ctx->uc_mcontext.gregs [REG_RIP];
635 mctx.r12 = ctx->uc_mcontext.gregs [REG_R12];
636 mctx.r13 = ctx->uc_mcontext.gregs [REG_R13];
637 mctx.r14 = ctx->uc_mcontext.gregs [REG_R14];
638 mctx.r15 = ctx->uc_mcontext.gregs [REG_R15];
640 mono_handle_exception (&mctx, obj, (gpointer)mctx.rip, test_only);
642 ctx->uc_mcontext.gregs [REG_RAX] = mctx.rax;
643 ctx->uc_mcontext.gregs [REG_RBX] = mctx.rbx;
644 ctx->uc_mcontext.gregs [REG_RCX] = mctx.rcx;
645 ctx->uc_mcontext.gregs [REG_RDX] = mctx.rdx;
646 ctx->uc_mcontext.gregs [REG_RBP] = mctx.rbp;
647 ctx->uc_mcontext.gregs [REG_RSP] = mctx.rsp;
648 ctx->uc_mcontext.gregs [REG_RSI] = mctx.rsi;
649 ctx->uc_mcontext.gregs [REG_RDI] = mctx.rdi;
650 ctx->uc_mcontext.gregs [REG_RIP] = mctx.rip;
651 ctx->uc_mcontext.gregs [REG_R12] = mctx.r12;
652 ctx->uc_mcontext.gregs [REG_R13] = mctx.r13;
653 ctx->uc_mcontext.gregs [REG_R14] = mctx.r14;
654 ctx->uc_mcontext.gregs [REG_R15] = mctx.r15;
660 mono_arch_ip_from_context (void *sigctx)
662 ucontext_t *ctx = (ucontext_t*)sigctx;
663 return (gpointer)ctx->uc_mcontext.gregs [REG_RIP];