Skip to content

Commit

Permalink
Merge pull request #2002 from Unity-Technologies/6000.0/handle-unwind…
Browse files Browse the repository at this point in the history
…-codes-in-methods-with-large-stacks

[6000.0] Handle unwind codes in methods with large stacks
  • Loading branch information
scott-ferguson-unity committed Mar 21, 2024
2 parents 8fe0a8d + fa61cd0 commit c869ed8
Showing 1 changed file with 59 additions and 31 deletions.
90 changes: 59 additions & 31 deletions mono/mini/exceptions-arm64.c
Original file line number Diff line number Diff line change
Expand Up @@ -630,16 +630,26 @@ mono_arch_do_ip_adjustment (MonoContext *ctx)

#ifdef MONO_ARCH_HAVE_UNWIND_TABLE

static void
static gboolean
mono_arch_unwind_add_assert (guint32 unwind_code_size, guint32 max_offset, guint32 unwind_codes_buffer_size, guint32 offset) {
g_assert(offset <= max_offset);
if (unwind_codes_buffer_size + unwind_code_size > MONO_ARM64_MAX_UNWIND_CODE_SIZE)
g_error ("Larger allocation needed for the unwind information.");

if (offset > max_offset) {
g_debug("Unwind code max offset exceeded %d > %d", offset, max_offset);
return FALSE;
}

if (unwind_codes_buffer_size + unwind_code_size > MONO_ARM64_MAX_UNWIND_CODE_SIZE) {
g_debug("Larger allocation needed for the unwind information.");
return FALSE;
}

return TRUE;
}

static void
mono_arch_unwind_add_nop (guint8* unwind_codes, guint32* unwind_code_size) {
mono_arch_unwind_add_assert(1, 0, *unwind_code_size, 0);
if (!mono_arch_unwind_add_assert(1, 0, *unwind_code_size, 0))
return;

// nop: 11100011: no unwind operation is required.
unwind_codes[(*unwind_code_size)++] = 0b11100011;
Expand All @@ -653,58 +663,65 @@ mono_arch_unwind_add_nops (guint8* unwind_codes, guint32* unwind_code_size, int

static void
mono_arch_unwind_add_set_fp (guint8* unwind_codes, guint32* unwind_code_size) {
mono_arch_unwind_add_assert (1, 0, *unwind_code_size, 0);
if (!mono_arch_unwind_add_assert(1, 0, *unwind_code_size, 0))
return;

// set_fp: 11100001: set up x29 with mov x29,sp
unwind_codes[(*unwind_code_size)++] = 0b11100001;
}

static void
mono_arch_unwind_add_pac_sign_lr (guint8* unwind_codes, guint32* unwind_code_size) {
mono_arch_unwind_add_assert (1, 0, *unwind_code_size, 0);
if (!mono_arch_unwind_add_assert(1, 0, *unwind_code_size, 0))
return;

// pac_sign_lr: 11111100: sign the return address in lr with pacibsp
unwind_codes[(*unwind_code_size)++] = 0b11111100;
}

static void
mono_arch_unwind_add_end (guint8* unwind_codes, guint32* unwind_code_size) {
mono_arch_unwind_add_assert (1, 0, *unwind_code_size, 0);
if (!mono_arch_unwind_add_assert (1, 0, *unwind_code_size, 0))
return;

// end: 11100100: end of unwind code. Implies ret in epilog.
unwind_codes[(*unwind_code_size)++] = 0b11100100;
}

static void
mono_arch_unwind_add_end_c (guint8* unwind_codes, guint32* unwind_code_size) {
mono_arch_unwind_add_assert (1, 0, *unwind_code_size, 0);
if (!mono_arch_unwind_add_assert(1, 0, *unwind_code_size, 0))
return;

// end_c: 11100101: end of unwind code in current chained scope.
unwind_codes[(*unwind_code_size)++] = 0b11100101;
}

#define WIN_ARM64_SAVE_REG_MAX_OFFSET 504
#define WIN_ARM64_SAVE_REGP_MAX_OFFSET 512

static void
mono_arch_unwind_add_save_fplr (guint8* unwind_codes, guint32* unwind_code_size, gint32 offset) {

mono_arch_unwind_add_assert (1, 504, *unwind_code_size, offset);
if (!mono_arch_unwind_add_assert(1, WIN_ARM64_SAVE_REG_MAX_OFFSET, *unwind_code_size, offset))
return;

// save_fplr: 01zzzzzz: save <x29,lr> pair at [sp+#Z*8], offset <= 504.
unwind_codes[(*unwind_code_size)++] = 0b01000000 | offset / 8;
}

static void
mono_arch_unwind_add_save_fplr_x (guint8* unwind_codes, guint32* unwind_code_size, gint32 offset) {

mono_arch_unwind_add_assert (1, 512, *unwind_code_size, offset);
if (!mono_arch_unwind_add_assert(1, WIN_ARM64_SAVE_REGP_MAX_OFFSET, *unwind_code_size, offset))
return;

// save_fplr_x: 10zzzzzz: save <x29, lr> pair at[sp - (#Z + 1) * 8]!, pre - indexed offset >= -512
unwind_codes[(*unwind_code_size)++] = 0b10000000 | (offset-8) / 8;
}

static void
mono_arch_unwind_add_save_reg (guint8* unwind_codes, guint32* unwind_code_size, guint32 reg, guint32 offset, gboolean reverse) {

mono_arch_unwind_add_assert (2, 504, *unwind_code_size, offset);
if (!mono_arch_unwind_add_assert(2, WIN_ARM64_SAVE_REG_MAX_OFFSET, *unwind_code_size, offset))
return;

guint8 reg_offset = reg - ARMREG_R19;
int order = reverse ? 1 : 0;
Expand All @@ -718,8 +735,8 @@ mono_arch_unwind_add_save_reg (guint8* unwind_codes, guint32* unwind_code_size,

static void
mono_arch_unwind_add_save_regp (guint8* unwind_codes, guint32* unwind_code_size, guint32 reg, guint32 offset, gboolean reverse) {

mono_arch_unwind_add_assert (2, 504, *unwind_code_size, offset);
if (!mono_arch_unwind_add_assert(2, WIN_ARM64_SAVE_REG_MAX_OFFSET, *unwind_code_size, offset))
return;

guint8 reg_offset = reg - ARMREG_R19;
int order = reverse ? 1 : 0;
Expand All @@ -732,17 +749,18 @@ mono_arch_unwind_add_save_regp (guint8* unwind_codes, guint32* unwind_code_size,

static void
mono_arch_unwind_add_save_next(guint8* unwind_codes, guint32* unwind_code_size) {

mono_arch_unwind_add_assert (1, 0, *unwind_code_size, 0);
if (!mono_arch_unwind_add_assert(1, 0, *unwind_code_size, 0))
return;

// save_next: 11100110: save next non-volatile Int or FP register pair.
unwind_codes[(*unwind_code_size)++] = 0b11100110;
}

static void
mono_arch_unwind_add_alloc_s (guint8* unwind_codes, guint32* unwind_code_size, guint32 size) {
if (!mono_arch_unwind_add_assert(1, 511, *unwind_code_size, size))
return;

mono_arch_unwind_add_assert (1, 511, *unwind_code_size, size);
g_assert(size % MONO_ARCH_FRAME_ALIGNMENT == 0);

// alloc_s: 000xxxxx: allocate small stack with size < 512 (2^5 * 16).
Expand All @@ -752,7 +770,9 @@ mono_arch_unwind_add_alloc_s (guint8* unwind_codes, guint32* unwind_code_size, g
static void
mono_arch_unwind_add_alloc_m (guint8* unwind_codes, guint32* unwind_code_size, guint32 size, gboolean reverse) {

mono_arch_unwind_add_assert (2, 0x7FFF, *unwind_code_size, size);
if (!mono_arch_unwind_add_assert(2, 0x7FFF, *unwind_code_size, size))
return;

g_assert(size % MONO_ARCH_FRAME_ALIGNMENT == 0);

int order = reverse ? 1 : 0;
Expand All @@ -767,8 +787,9 @@ mono_arch_unwind_add_alloc_m (guint8* unwind_codes, guint32* unwind_code_size, g

static void
mono_arch_unwind_add_alloc_l (guint8* unwind_codes, guint32* unwind_code_size, guint32 size, gboolean reverse) {
if (!mono_arch_unwind_add_assert(4, 0x0FFFFFFF, *unwind_code_size, size))
return;

mono_arch_unwind_add_assert (4, 0x0FFFFFFF, *unwind_code_size, size);
g_assert(size % MONO_ARCH_FRAME_ALIGNMENT == 0);

int order = reverse ? 1 : 0;
Expand Down Expand Up @@ -798,19 +819,19 @@ static int
mono_arch_unwind_add_reg_offset (MonoUnwindOp* unwind_op_data, GSList* next, gint stack_offset, guint8 *unwind_codes, guint32 *unwind_code_size, MonoUnwindOp** last_saved_regp, gboolean processing_prolog_codes) {

if (unwind_op_data->reg == ARMREG_SP) {
g_assert("I don't believe there is a way to encode this in the unwind info");
g_debug("I don't believe there is a way to encode this in the unwind info");
return 0;
}

if (unwind_op_data->reg == ARMREG_FP || unwind_op_data->reg == ARMREG_LR) {
// FP and LR are handled seperately
// FP and LR are handled seperately, see mono_arch_uwwind_add_frame_setup
return 0;
}

if (unwind_op_data->reg < ARMREG_R19 && unwind_op_data->reg > ARMREG_R28) {
// Only the presreved registers have unwind codes
// We shouldn't hit here, but emit a nop if we do
g_assert(unwind_op_data->reg >= ARMREG_R19 && unwind_op_data->reg <= ARMREG_R28);
g_debug("Non preserved register stored in unwind info %d", unwind_op_data->reg);
mono_arch_unwind_add_nop(unwind_codes, unwind_code_size);
return 1;
}
Expand All @@ -824,7 +845,10 @@ mono_arch_unwind_add_reg_offset (MonoUnwindOp* unwind_op_data, GSList* next, gin
gint next_reg_dir = processing_prolog_codes ? 1 : -1;
guint8 op = processing_prolog_codes ? DW_CFA_offset : DW_CFA_mono_restore_offset_win64_arm64;

if (next_unwind_op_data && next_unwind_op_data->op == op && next_unwind_op_data->reg == unwind_op_data->reg + next_reg_dir && next_unwind_op_data->val == unwind_op_data->val + next_reg_dir * sizeof(host_mgreg_t)) {
// Mono emits paired reg loads/stores (stp/ldp) for adjacent regs, but still emits seperate MonoUnwindOp foreach
// Detect those cases and emit a single unwind code so to match the emitted assembly
// The 256 value is from mini-arm64.c::emit_store_regset_cfa, when we exeed that mono won't emit regp stores/loads
if (offset < 256 && next_unwind_op_data && next_unwind_op_data->op == op && next_unwind_op_data->reg == unwind_op_data->reg + next_reg_dir && next_unwind_op_data->val == unwind_op_data->val + next_reg_dir * sizeof(host_mgreg_t)) {

if (*last_saved_regp && (*last_saved_regp)->reg == unwind_op_data->reg - 2*next_reg_dir && unwind_op_data->val == (*last_saved_regp)->val + 2*next_reg_dir*sizeof(host_mgreg_t))
mono_arch_unwind_add_save_next (unwind_codes, unwind_code_size);
Expand All @@ -837,8 +861,14 @@ mono_arch_unwind_add_reg_offset (MonoUnwindOp* unwind_op_data, GSList* next, gin

*last_saved_regp = NULL;

// The Windows ARM64 unwind codes are setup assuming that the preserved registers are stored at the bottom of the frame, but Mono places them at the top.
// So we end up with offsets we can't encode, so we'll just emit nops for those (we emit nops to so we correctly encode the size of the prolog)
// Saving the preserved regs allows unwinding in the prolog/epilog which insn't needed for correct stack traces, so there doesn't seem to be a critical issue
if (arm_is_strx_imm(offset)) {
mono_arch_unwind_add_save_reg (unwind_codes, unwind_code_size, unwind_op_data->reg, offset, processing_prolog_codes);
if (offset < WIN_ARM64_SAVE_REG_MAX_OFFSET)
mono_arch_unwind_add_save_reg (unwind_codes, unwind_code_size, unwind_op_data->reg, offset, processing_prolog_codes);
else
mono_arch_unwind_add_nop (unwind_codes, unwind_code_size); // There is no way to encode an offset this large
}
else {
// Mono emits this an a mov xip0, #offset; str rn,[sp+xip0]
Expand Down Expand Up @@ -868,11 +898,9 @@ static void
mono_arch_uwwind_add_frame_setup(guint cfa_offset, guint8* unwind_codes, guint32* unwind_code_size, gboolean reverse) {

// Frame Setup
// This matches the logic in mini-arm64.c::mono_arch_emit_prolog
if (arm_is_ldpx_imm(-cfa_offset)) {
if (cfa_offset > 0)
mono_arch_unwind_add_save_fplr_x(unwind_codes, unwind_code_size, cfa_offset);
else
mono_arch_unwind_add_save_fplr(unwind_codes, unwind_code_size, 0);
mono_arch_unwind_add_save_fplr_x(unwind_codes, unwind_code_size, cfa_offset);
}
else {
mono_arch_unwind_add_emit_subx_sp_imm(cfa_offset, unwind_codes, unwind_code_size, reverse);
Expand Down

0 comments on commit c869ed8

Please sign in to comment.