From: Zoltan Varga Date: Sat, 5 Jul 2014 02:43:03 +0000 (+0200) Subject: Add support for precise unwind info in epilogs on amd64: X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=496579788e581e2ceb7f0673bd198075ac6e517e;p=mono.git Add support for precise unwind info in epilogs on amd64: * Implement DW_CFA_remember_state/restore_state opcodes. * Add a DW_CFA_mono_advance_loc opcode to advance to a location stored outside of the unwind info. Use this to allow the sharing of unwind info between methods. This is need to prevent crashes during unwinding when the ip of the last frame is in the epilog. This can be reproduced by running sgen-new-threads-dont-join-stw.exe in a loop. --- diff --git a/mono/mini/aot-compiler.c b/mono/mini/aot-compiler.c index 9802f3ef8e4..2a0547f729d 100644 --- a/mono/mini/aot-compiler.c +++ b/mono/mini/aot-compiler.c @@ -5325,14 +5325,27 @@ emit_exception_debug_info (MonoAotCompile *acfg, MonoCompile *cfg) if (use_unwind_ops) { guint32 encoded_len; guint8 *encoded; + guint32 unwind_desc; /* * This is a duplicate of the data in the .debug_frame section, but that * section cannot be accessed using the dl interface. */ encoded = mono_unwind_ops_encode (cfg->unwind_ops, &encoded_len); - encode_value (get_unwind_info_offset (acfg, encoded, encoded_len), p, &p); - g_free (encoded); + + unwind_desc = get_unwind_info_offset (acfg, encoded, encoded_len); + g_assert (unwind_desc < 0xffff); + if (cfg->has_unwind_info_for_epilog) { + /* + * The lower 16 bits identify the unwind descriptor, the upper 16 bits contain the offset of + * the start of the epilog from the end of the method. + */ + g_assert (cfg->code_size - cfg->epilog_begin < 0xffff); + encode_value (((cfg->code_size - cfg->epilog_begin) << 16) | unwind_desc, p, &p); + g_free (encoded); + } else { + encode_value (unwind_desc, p, &p); + } } else { encode_value (jinfo->used_regs, p, &p); } diff --git a/mono/mini/aot-runtime.c b/mono/mini/aot-runtime.c index dc046daf20e..3c9064b885d 100644 --- a/mono/mini/aot-runtime.c +++ b/mono/mini/aot-runtime.c @@ -2700,7 +2700,8 @@ mono_aot_get_unwind_info (MonoJitInfo *ji, guint32 *unwind_info_len) mono_aot_unlock (); } - p = amodule->unwind_info + ji->used_regs; + /* The upper 16 bits of ji->used_regs might contain the epilog offset */ + p = amodule->unwind_info + (ji->used_regs & 0xffff); *unwind_info_len = decode_value (p, &p); return p; } diff --git a/mono/mini/exceptions-amd64.c b/mono/mini/exceptions-amd64.c index 212416e06cf..5a030740915 100644 --- a/mono/mini/exceptions-amd64.c +++ b/mono/mini/exceptions-amd64.c @@ -590,6 +590,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, guint8 *cfa; guint32 unwind_info_len; guint8 *unwind_info; + guint8 *epilog; frame->type = FRAME_TYPE_MANAGED; @@ -602,6 +603,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, printf ("%s %p %p\n", ji->d.method->name, ji->code_start, ip); mono_print_unwind_info (unwind_info, unwind_info_len); */ + epilog = (guint8*)ji->code_start + ji->code_size - (ji->used_regs >> 16); regs [AMD64_RAX] = new_ctx->rax; regs [AMD64_RBX] = new_ctx->rbx; @@ -619,7 +621,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, mono_unwind_frame (unwind_info, unwind_info_len, ji->code_start, (guint8*)ji->code_start + ji->code_size, - ip, regs, MONO_MAX_IREGS + 1, + ip, &epilog, regs, MONO_MAX_IREGS + 1, save_locations, MONO_MAX_IREGS, &cfa); new_ctx->rax = regs [AMD64_RAX]; diff --git a/mono/mini/exceptions-arm.c b/mono/mini/exceptions-arm.c index 020794582b1..d7fd3318f3d 100644 --- a/mono/mini/exceptions-arm.c +++ b/mono/mini/exceptions-arm.c @@ -424,7 +424,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, mono_unwind_frame (unwind_info, unwind_info_len, ji->code_start, (guint8*)ji->code_start + ji->code_size, - ip, regs, MONO_MAX_IREGS + 8, + ip, NULL, regs, MONO_MAX_IREGS + 8, save_locations, MONO_MAX_IREGS, &cfa); for (i = 0; i < 16; ++i) diff --git a/mono/mini/exceptions-mips.c b/mono/mini/exceptions-mips.c index ad6f2552c0e..34197145048 100644 --- a/mono/mini/exceptions-mips.c +++ b/mono/mini/exceptions-mips.c @@ -413,7 +413,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, mono_unwind_frame (unwind_info, unwind_info_len, ji->code_start, (guint8*)ji->code_start + ji->code_size, - ip, regs, MONO_MAX_IREGS, + ip, NULL, regs, MONO_MAX_IREGS, save_locations, MONO_MAX_IREGS, &cfa); for (i = 0; i < MONO_MAX_IREGS; ++i) diff --git a/mono/mini/exceptions-ppc.c b/mono/mini/exceptions-ppc.c index d2429325d94..6c3878e97b8 100644 --- a/mono/mini/exceptions-ppc.c +++ b/mono/mini/exceptions-ppc.c @@ -550,7 +550,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, mono_unwind_frame (unwind_info, unwind_info_len, ji->code_start, (guint8*)ji->code_start + ji->code_size, - ip, regs, ppc_lr + 1, + ip, NULL, regs, ppc_lr + 1, save_locations, MONO_MAX_IREGS, &cfa); /* we substract 4, so that the IP points into the call instruction */ diff --git a/mono/mini/exceptions-s390x.c b/mono/mini/exceptions-s390x.c index 4820c52a355..74c8a0f37a3 100644 --- a/mono/mini/exceptions-s390x.c +++ b/mono/mini/exceptions-s390x.c @@ -478,9 +478,9 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, memcpy(®s, &ctx->uc_mcontext.gregs, sizeof(regs)); mono_unwind_frame (unwind_info, unwind_info_len, ji->code_start, - (guint8 *) ji->code_start + ji->code_size, - ip, regs, 16, save_locations, - MONO_MAX_IREGS, &cfa); + (guint8 *) ji->code_start + ji->code_size, + ip, NULL, regs, 16, save_locations, + MONO_MAX_IREGS, &cfa); memcpy (&new_ctx->uc_mcontext.gregs, ®s, sizeof(regs)); MONO_CONTEXT_SET_IP(new_ctx, regs[14] - 2); MONO_CONTEXT_SET_BP(new_ctx, cfa); diff --git a/mono/mini/exceptions-x86.c b/mono/mini/exceptions-x86.c index c2ff867dfd6..d335d7ea5c6 100644 --- a/mono/mini/exceptions-x86.c +++ b/mono/mini/exceptions-x86.c @@ -816,7 +816,7 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, mono_unwind_frame (unwind_info, unwind_info_len, ji->code_start, (guint8*)ji->code_start + ji->code_size, - ip, regs, MONO_MAX_IREGS + 1, + ip, NULL, regs, MONO_MAX_IREGS + 1, save_locations, MONO_MAX_IREGS, &cfa); new_ctx->eax = regs [X86_EAX]; diff --git a/mono/mini/mini-amd64.c b/mono/mini/mini-amd64.c index c72e39c868d..fbebe0ce71c 100644 --- a/mono/mini/mini-amd64.c +++ b/mono/mini/mini-amd64.c @@ -7151,7 +7151,7 @@ mono_arch_emit_epilog (MonoCompile *cfg) CallInfo *cinfo; gint32 lmf_offset = cfg->lmf_var ? ((MonoInst*)cfg->lmf_var)->inst_offset : -1; gint32 save_area_offset = cfg->arch.reg_save_area_offset; - + max_epilog_size = get_max_epilog_size (cfg); while (cfg->code_len + max_epilog_size > (cfg->code_size - 16)) { @@ -7162,6 +7162,14 @@ mono_arch_emit_epilog (MonoCompile *cfg) code = cfg->native_code + cfg->code_len; + cfg->has_unwind_info_for_epilog = TRUE; + + /* Mark the start of the epilog */ + mono_emit_unwind_op_mark_loc (cfg, code, 0); + + /* Save the uwind state which is needed by the out-of-line code */ + mono_emit_unwind_op_remember_state (cfg, code); + if (mono_jit_trace_calls != NULL && mono_trace_eval (method)) code = mono_arch_instrument_epilog (cfg, mono_trace_leave_method, code, TRUE); @@ -7199,8 +7207,11 @@ mono_arch_emit_epilog (MonoCompile *cfg) for (i = 0; i < AMD64_NREG; ++i) { if (AMD64_IS_CALLEE_SAVED_REG (i) && (cfg->arch.saved_iregs & (1 << i))) { /* Restore only used_int_regs, not arch.saved_iregs */ - if (cfg->used_int_regs & (1 << i)) + if (cfg->used_int_regs & (1 << i)) { amd64_mov_reg_membase (code, i, cfg->frame_reg, save_area_offset, 8); + mono_emit_unwind_op_same_value (cfg, code, i); + async_exc_point (code); + } save_area_offset += 8; } } @@ -7231,14 +7242,20 @@ mono_arch_emit_epilog (MonoCompile *cfg) } if (cfg->arch.omit_fp) { - if (cfg->arch.stack_alloc_size) + if (cfg->arch.stack_alloc_size) { amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, cfg->arch.stack_alloc_size); + } } else { amd64_leave (code); + mono_emit_unwind_op_same_value (cfg, code, AMD64_RBP); } + mono_emit_unwind_op_def_cfa (cfg, code, AMD64_RSP, 8); async_exc_point (code); amd64_ret (code); + /* Restore the unwind state to be the same as before the epilog */ + mono_emit_unwind_op_restore_state (cfg, code); + cfg->code_len = code - cfg->native_code; g_assert (cfg->code_len < cfg->code_size); diff --git a/mono/mini/mini-exceptions.c b/mono/mini/mini-exceptions.c index 833704485b3..706577b5024 100644 --- a/mono/mini/mini-exceptions.c +++ b/mono/mini/mini-exceptions.c @@ -2747,5 +2747,6 @@ mono_jinfo_get_unwind_info (MonoJitInfo *ji, guint32 *unwind_info_len) if (ji->from_aot) return mono_aot_get_unwind_info (ji, unwind_info_len); else - return mono_get_cached_unwind_info (ji->used_regs, unwind_info_len); + /* The upper 16 bits of ji->used_regs might contain the epilog offset */ + return mono_get_cached_unwind_info (ji->used_regs & 0xffff, unwind_info_len); } diff --git a/mono/mini/mini-unwind.h b/mono/mini/mini-unwind.h index 6b6ba7ad2f9..a2c9f49805d 100644 --- a/mono/mini/mini-unwind.h +++ b/mono/mini/mini-unwind.h @@ -60,6 +60,13 @@ #define DW_CFA_lo_user 0x1c #define DW_CFA_hi_user 0x3f +/* + * Mono extension, advance loc to a location stored outside the unwind info. + * This is required to make the unwind descriptors sharable, since otherwise each one would contain + * an advance_loc with a different offset just before the unwind ops for the epilog. + */ +#define DW_CFA_mono_advance_loc DW_CFA_lo_user + /* Represents one unwind instruction */ typedef struct { guint8 op; /* One of DW_CFA_... */ @@ -84,6 +91,16 @@ typedef struct { #define mono_emit_unwind_op_same_value(cfg,ip,reg) mono_emit_unwind_op (cfg, (ip) - (cfg)->native_code, DW_CFA_same_value, (reg), 0) /* Reg is saved at cfa+offset */ #define mono_emit_unwind_op_offset(cfg,ip,reg,offset) mono_emit_unwind_op (cfg, (ip) - (cfg)->native_code, DW_CFA_offset, (reg), (offset)) +/* Save the unwind state into an implicit stack */ +#define mono_emit_unwind_op_remember_state(cfg,ip) mono_emit_unwind_op (cfg, (ip) - (cfg)->native_code, DW_CFA_remember_state, 0, 0) +/* Restore the unwind state from the state stack */ +#define mono_emit_unwind_op_restore_state(cfg,ip) mono_emit_unwind_op (cfg, (ip) - (cfg)->native_code, DW_CFA_restore_state, 0, 0) +/* + * Mark the current location as a location stored outside the unwind info, which will be passed + * explicitly to mono_unwind_frame () in the MARK_LOCATIONS argument. This allows the unwind info + * to be shared among multiple methods. + */ +#define mono_emit_unwind_op_mark_loc(cfg,ip,n) mono_emit_unwind_op (cfg, (ip) - (cfg)->native_code, DW_CFA_mono_advance_loc, 0, (n)) /* Similar macros usable when a cfg is not available, like for trampolines */ #define mono_add_unwind_op_def_cfa(op_list,code,buf,reg,offset) do { (op_list) = g_slist_append ((op_list), mono_create_unwind_op ((code) - (buf), DW_CFA_def_cfa, (reg), (offset))); } while (0) @@ -127,7 +144,8 @@ mono_unwind_ops_encode (GSList *unwind_ops, guint32 *out_len) MONO_INTERNAL; void mono_unwind_frame (guint8 *unwind_info, guint32 unwind_info_len, - guint8 *start_ip, guint8 *end_ip, guint8 *ip, mgreg_t *regs, int nregs, + guint8 *start_ip, guint8 *end_ip, guint8 *ip, guint8 **mark_locations, + mgreg_t *regs, int nregs, mgreg_t **save_locations, int save_locations_len, guint8 **out_cfa) MONO_INTERNAL; diff --git a/mono/mini/mini.c b/mono/mini/mini.c index 0610353aaa2..cae9683bdcb 100644 --- a/mono/mini/mini.c +++ b/mono/mini/mini.c @@ -4638,8 +4638,21 @@ create_jit_info (MonoCompile *cfg, MonoMethod *method_to_compile) } else if (cfg->unwind_ops) { guint32 info_len; guint8 *unwind_info = mono_unwind_ops_encode (cfg->unwind_ops, &info_len); + guint32 unwind_desc; - jinfo->used_regs = mono_cache_unwind_info (unwind_info, info_len); + unwind_desc = mono_cache_unwind_info (unwind_info, info_len); + + if (cfg->has_unwind_info_for_epilog) { + /* + * The lower 16 bits identify the unwind descriptor, the upper 16 bits contain the offset of + * the start of the epilog from the end of the method. + */ + g_assert (unwind_desc < 0xffff); + g_assert (cfg->code_size - cfg->epilog_begin < 0xffff); + jinfo->used_regs = ((cfg->code_size - cfg->epilog_begin) << 16) | unwind_desc; + } else { + jinfo->used_regs = unwind_desc; + } g_free (unwind_info); } diff --git a/mono/mini/mini.h b/mono/mini/mini.h index 97c715d7033..70706102641 100644 --- a/mono/mini/mini.h +++ b/mono/mini/mini.h @@ -1499,6 +1499,7 @@ typedef struct { guint has_atomic_exchange_i4 : 1; guint has_atomic_cas_i4 : 1; guint check_pinvoke_callconv : 1; + guint has_unwind_info_for_epilog : 1; gpointer debug_info; guint32 lmf_offset; guint16 *intvars; diff --git a/mono/mini/unwind.c b/mono/mini/unwind.c index 8a7d46eae49..d9f4778c0e7 100644 --- a/mono/mini/unwind.c +++ b/mono/mini/unwind.c @@ -307,10 +307,23 @@ mono_print_unwind_info (guint8 *unwind_info, int unwind_info_len) offset = decode_sleb128 (p, &p) * DWARF_DATA_ALIGN; printf ("CFA: [%x] offset_extended_sf: %s at cfa-0x%x\n", pos, mono_arch_regname (mono_dwarf_reg_to_hw_reg (reg)), -offset); break; + case DW_CFA_same_value: + reg = decode_uleb128 (p, &p); + printf ("CFA: [%x] same_value: %s\n", pos, mono_arch_regname (mono_dwarf_reg_to_hw_reg (reg))); + break; case DW_CFA_advance_loc4: pos += read32 (p); p += 4; break; + case DW_CFA_remember_state: + printf ("CFA: [%x] remember_state\n", pos); + break; + case DW_CFA_restore_state: + printf ("CFA: [%x] restore_state\n", pos); + break; + case DW_CFA_mono_advance_loc: + printf ("CFA: [%x] mono_advance_loc\n", pos); + break; default: g_assert_not_reached (); } @@ -348,9 +361,31 @@ mono_unwind_ops_encode (GSList *unwind_ops, guint32 *out_len) /* Convert the register from the hw encoding to the dwarf encoding */ reg = mono_hw_reg_to_dwarf_reg (op->reg); + if (op->op == DW_CFA_mono_advance_loc) { + /* This advances loc to its location */ + loc = op->when; + } + /* Emit an advance_loc if neccesary */ while (op->when > loc) { - if (op->when - loc < 32) { + if (op->when - loc > 65536) { + *p ++ = DW_CFA_advance_loc4; + *(guint32*)p = (guint32)(op->when - loc); + g_assert (read32 (p) == (guint32)(op->when - loc)); + p += 4; + loc = op->when; + } else if (op->when - loc > 256) { + *p ++ = DW_CFA_advance_loc2; + *(guint16*)p = (guint16)(op->when - loc); + g_assert (read16 (p) == (guint32)(op->when - loc)); + p += 2; + loc = op->when; + } else if (op->when - loc >= 32) { + *p ++ = DW_CFA_advance_loc1; + *(guint8*)p = (guint8)(op->when - loc); + p += 1; + loc = op->when; + } else if (op->when - loc < 32) { *p ++ = DW_CFA_advance_loc | (op->when - loc); loc = op->when; } else { @@ -373,6 +408,10 @@ mono_unwind_ops_encode (GSList *unwind_ops, guint32 *out_len) *p ++ = op->op; encode_uleb128 (reg, p, &p); break; + case DW_CFA_same_value: + *p ++ = op->op; + encode_uleb128 (reg, p, &p); + break; case DW_CFA_offset: if (reg > 63) { *p ++ = DW_CFA_offset_extended_sf; @@ -383,6 +422,15 @@ mono_unwind_ops_encode (GSList *unwind_ops, guint32 *out_len) encode_uleb128 (op->val / DWARF_DATA_ALIGN, p, &p); } break; + case DW_CFA_remember_state: + case DW_CFA_restore_state: + *p ++ = op->op; + break; + case DW_CFA_mono_advance_loc: + /* Only one location is supported */ + g_assert (op->val == 0); + *p ++ = op->op; + break; default: g_assert_not_reached (); break; @@ -416,6 +464,12 @@ print_dwarf_state (int cfa_reg, int cfa_offset, int ip, int nregs, Loc *location printf ("\n"); } +typedef struct { + Loc locations [NUM_REGS]; + guint8 reg_saved [NUM_REGS]; + int cfa_reg, cfa_offset; +} UnwindState; + /* * Given the state of the current frame as stored in REGS, execute the unwind * operations in unwind_info until the location counter reaches POS. The result is @@ -423,11 +477,13 @@ print_dwarf_state (int cfa_reg, int cfa_offset, int ip, int nregs, Loc *location * If SAVE_LOCATIONS is non-NULL, it should point to an array of size SAVE_LOCATIONS_LEN. * On return, the nth entry will point to the address of the stack slot where register * N was saved, or NULL, if it was not saved by this frame. + * MARK_LOCATIONS should contain the locations marked by mono_emit_unwind_op_mark_loc (), if any. * This function is signal safe. */ void mono_unwind_frame (guint8 *unwind_info, guint32 unwind_info_len, - guint8 *start_ip, guint8 *end_ip, guint8 *ip, mgreg_t *regs, int nregs, + guint8 *start_ip, guint8 *end_ip, guint8 *ip, guint8 **mark_locations, + mgreg_t *regs, int nregs, mgreg_t **save_locations, int save_locations_len, guint8 **out_cfa) { @@ -436,6 +492,8 @@ mono_unwind_frame (guint8 *unwind_info, guint32 unwind_info_len, int i, pos, reg, cfa_reg, cfa_offset, offset; guint8 *p; guint8 *cfa_val; + UnwindState state_stack [1]; + int state_stack_pos; memset (reg_saved, 0, sizeof (reg_saved)); @@ -443,6 +501,7 @@ mono_unwind_frame (guint8 *unwind_info, guint32 unwind_info_len, pos = 0; cfa_reg = -1; cfa_offset = -1; + state_stack_pos = 0; while (pos <= ip - start_ip && p < unwind_info + unwind_info_len) { int op = *p & 0xc0; @@ -489,10 +548,42 @@ mono_unwind_frame (guint8 *unwind_info, guint32 unwind_info_len, locations [reg].loc_type = LOC_OFFSET; locations [reg].offset = offset * DWARF_DATA_ALIGN; break; + case DW_CFA_same_value: + reg = decode_uleb128 (p, &p); + locations [reg].loc_type = LOC_SAME; + break; + case DW_CFA_advance_loc1: + pos += *p; + p += 1; + break; + case DW_CFA_advance_loc2: + pos += read16 (p); + p += 2; + break; case DW_CFA_advance_loc4: pos += read32 (p); p += 4; break; + case DW_CFA_remember_state: + g_assert (state_stack_pos == 0); + memcpy (&state_stack [0].locations, &locations, sizeof (locations)); + memcpy (&state_stack [0].reg_saved, ®_saved, sizeof (reg_saved)); + state_stack [0].cfa_reg = cfa_reg; + state_stack [0].cfa_offset = cfa_offset; + state_stack_pos ++; + break; + case DW_CFA_restore_state: + g_assert (state_stack_pos == 1); + state_stack_pos --; + memcpy (&locations, &state_stack [0].locations, sizeof (locations)); + memcpy (®_saved, &state_stack [0].reg_saved, sizeof (reg_saved)); + cfa_reg = state_stack [0].cfa_reg; + cfa_offset = state_stack [0].cfa_offset; + break; + case DW_CFA_mono_advance_loc: + g_assert (mark_locations [0]); + pos = mark_locations [0] - start_ip; + break; default: g_assert_not_reached (); }