Skip to content

Commit 1374eb0

Browse files
committed
Unwind: separate the "set frame reg" and "define new frame" operations to fix metadata bug related to stack checks.
In #3859 we discovered that the unwind metadata info on Windows is placed slightly incorrectly in the presence of explicit stack checks in function prologues. In particular, if the stack check fails, then we start the process of backtracing from a state where we have actually updated rbp (the frame pointer) but we have not emitted the metadata saying we have done so. The fix is to emit the `SetFPReg` Win32 unwind op exactly at the offset of the `mov rbp, rsp` instruction, not after the stack check. However, actually achieving this output given our current unwind design (which abstracts over SysV and Win32) is slightly tricky, because: - We previously had a single `DefineNewFrame` op that both indicated where the FP was set, and gave the size of the clobbers; - We don't know the size of the clobbers until we generate the clobber-save sequence; and - The clobber-save sequence is generated after any explicit stack check. To resolve the problem, this PR splits the `DefineNewFrame` op into two pieces: `SetFrameReg` and `DefineNewFrame`. On x86-64 and aarch64 this is emitted in the appropriate place. (`s390x` does not use a frame register, so has no need for this op.) This should resolve the issue we were seeing with Windows-2022 and unwinding. (Thanks to @alexcrichton, @iximeow and @peterhuene for help debugging this!)
1 parent 29ebfa4 commit 1374eb0

File tree

7 files changed

+228
-193
lines changed

7 files changed

+228
-193
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ jobs:
216216
- os: ubuntu-latest
217217
- os: macos-latest
218218
- os: windows-2019
219-
- os: windows-2019
219+
- os: windows-2022
220+
- os: windows-2022
220221
target: x86_64-pc-windows-gnu
221222
- os: ubuntu-latest
222223
target: aarch64-unknown-linux-gnu

cranelift/codegen/src/isa/aarch64/abi.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,13 @@ impl ABIMachineSpec for AArch64MachineDeps {
673673
shift12: false,
674674
},
675675
});
676+
677+
if flags.unwind_info() {
678+
insts.push(Inst::Unwind {
679+
inst: UnwindInst::SetFrameReg,
680+
});
681+
}
682+
676683
insts
677684
}
678685

@@ -734,8 +741,8 @@ impl ABIMachineSpec for AArch64MachineDeps {
734741
// clobbers, just below the saved FP/LR pair.
735742
insts.push(Inst::Unwind {
736743
inst: UnwindInst::DefineNewFrame {
737-
offset_downward_to_clobbers: clobber_size as u32,
738744
offset_upward_to_caller_sp: 16, // FP, LR
745+
offset_downward_to_clobbers: clobber_size as u32,
739746
},
740747
});
741748
}

cranelift/codegen/src/isa/unwind.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ pub enum UnwindInfo {
118118
/// push rbp
119119
/// unwind UnwindInst::PushFrameRegs { offset_upward_to_caller_sp: 16 }
120120
/// mov rbp, rsp
121-
/// unwind UnwindInst::DefineNewFrame { offset_upward_to_caller_sp: 16,
122-
/// offset_downward_to_clobbers: 16 }
121+
/// unwind UnwindInst::SetFrameReg { offset_upward_to_caller_sp: 16 }
122+
/// unwind UnwindInst::DefineNewFrame { offset_downward_to_clobbers: 16 }
123123
/// sub rsp, 32
124124
/// mov [rsp+16], r12
125125
/// unwind UnwindInst::SaveReg { reg: R12, clobber_offset: 0 }
@@ -143,15 +143,20 @@ pub enum UnwindInst {
143143
},
144144
/// The frame-pointer register for this architecture has just been
145145
/// set to the current stack location. We wish to define a new
146-
/// frame that is anchored on this new FP value. Offsets are provided
147-
/// upward to the caller's stack frame and downward toward the clobber
148-
/// area. We expect this pseudo-op to come after `PushFrameRegs`.
146+
/// frame that is anchored on this new FP value. We expect this
147+
/// pseudo-op to come after `PushFrameRegs`.
148+
SetFrameReg,
149+
/// We now know the size of clobbers, if any, and can define a
150+
/// frame in which we will give their offsets. Offsets are
151+
/// provided upward to the caller's stack frame and downward
152+
/// toward the clobber area. We expect this pseudo op to come
153+
/// after `SetFrameReg`.
149154
DefineNewFrame {
150-
/// The offset from the current SP and FP value upward to the value of
151-
/// SP at the callsite that invoked us.
155+
/// The offset from the current SP and FP value upward to the
156+
/// value of SP at the callsite that invoked us.
152157
offset_upward_to_caller_sp: u32,
153-
/// The offset from the current SP and FP value downward to the start of
154-
/// the clobber area.
158+
/// The offset from the current SP and FP value downward to
159+
/// the start of the clobber area.
155160
offset_downward_to_clobbers: u32,
156161
},
157162
/// The stack pointer was adjusted to allocate the stack.

cranelift/codegen/src/isa/unwind/systemv.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,21 @@ pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<regalloc::Reg>>(
202202
));
203203
}
204204
}
205-
&UnwindInst::DefineNewFrame {
206-
offset_upward_to_caller_sp,
207-
offset_downward_to_clobbers,
208-
} => {
205+
&UnwindInst::SetFrameReg => {
209206
// Define CFA in terms of FP. Note that we assume it was already
210207
// defined correctly in terms of the current SP, and FP has just
211208
// been set to the current SP, so we do not need to change the
212209
// offset, only the register. (This is done only if the target
213210
// defines a frame pointer register.)
214-
if let Some(fp) = mr.fp() {
215-
instructions.push((instruction_offset, CallFrameInstruction::CfaRegister(fp)));
216-
}
211+
instructions.push((
212+
instruction_offset,
213+
CallFrameInstruction::CfaRegister(mr.fp().unwrap()),
214+
));
215+
}
216+
&UnwindInst::DefineNewFrame {
217+
offset_upward_to_caller_sp,
218+
offset_downward_to_clobbers,
219+
} => {
217220
// Record initial CFA offset. This will be used with later
218221
// StackAlloc calls if we do not have a frame pointer.
219222
cfa_offset = offset_upward_to_caller_sp;

cranelift/codegen/src/isa/unwind/winx64.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,14 @@ pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<regalloc::Reg>>(
277277
reg: UNWIND_RBP_REG,
278278
});
279279
}
280+
&UnwindInst::SetFrameReg => {
281+
unwind_codes.push(UnwindCode::SetFPReg { instruction_offset });
282+
}
280283
&UnwindInst::DefineNewFrame {
281284
offset_downward_to_clobbers,
282285
..
283286
} => {
284287
frame_register_offset = ensure_unwind_offset(offset_downward_to_clobbers)?;
285-
unwind_codes.push(UnwindCode::SetFPReg { instruction_offset });
286288
}
287289
&UnwindInst::StackAlloc { size } => {
288290
unwind_codes.push(UnwindCode::StackAlloc {

cranelift/codegen/src/isa/x64/abi.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,13 @@ impl ABIMachineSpec for X64ABIMachineSpec {
463463
// `mov %rsp, %rbp`
464464
// RSP is now 0 % 16
465465
insts.push(Inst::mov_r_r(OperandSize::Size64, r_rsp, w_rbp));
466+
467+
if flags.unwind_info() {
468+
insts.push(Inst::Unwind {
469+
inst: UnwindInst::SetFrameReg,
470+
});
471+
}
472+
466473
insts
467474
}
468475

@@ -513,8 +520,8 @@ impl ABIMachineSpec for X64ABIMachineSpec {
513520
// part of our actual frame but do not concern the unwinder.
514521
insts.push(Inst::Unwind {
515522
inst: UnwindInst::DefineNewFrame {
516-
offset_downward_to_clobbers: clobbered_size,
517523
offset_upward_to_caller_sp: 16, // RBP, return address
524+
offset_downward_to_clobbers: clobbered_size,
518525
},
519526
});
520527
}

0 commit comments

Comments
 (0)