* 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.
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);
}
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;
}
guint8 *cfa;
guint32 unwind_info_len;
guint8 *unwind_info;
+ guint8 *epilog;
frame->type = FRAME_TYPE_MANAGED;
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;
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];
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)
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)
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 */
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);
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];
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)) {
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);
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;
}
}
}
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);
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);
}
#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_... */
#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)
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;
} 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);
}
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;
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 ();
}
/* 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 {
*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;
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;
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
* 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)
{
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));
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;
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 ();
}