Implement emitting Windows unwind information for fastcall functions. (#1155)
* Implement emitting Windows unwind information for fastcall functions. This commit implements emitting Windows unwind information for x64 fastcall calling convention functions. The unwind information can be used to construct a Windows function table at runtime for JIT'd code, enabling stack walking and unwinding by the operating system. * Address code review feedback. This commit addresses code review feedback: * Remove unnecessary unsafe code. * Emit the unwind information always as little endian. * Fix comments. A dependency from cranelift-codegen to the byteorder crate was added. The byteorder crate is a no-dependencies crate with a reasonable abstraction for writing binary data for a specific endianness. * Address code review feedback. * Disable default features for the `byteorder` crate. * Add a comment regarding the Windows ABI unwind code numerical values. * Panic if we encounter a Windows function with a prologue greater than 256 bytes in size.
This commit is contained in:
@@ -20,6 +20,7 @@ log = "0.4.6"
|
||||
memmap = "0.7.0"
|
||||
num_cpus = "1.8.0"
|
||||
region = "2.1.2"
|
||||
byteorder = { version = "1.3.2", default-features = false }
|
||||
|
||||
[features]
|
||||
basic-blocks = []
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
test unwind
|
||||
set opt_level=speed_and_size
|
||||
set is_pic
|
||||
target x86_64 haswell
|
||||
|
||||
; check that there is no unwind information for a system_v function
|
||||
function %not_fastcall() system_v {
|
||||
ebb0:
|
||||
return
|
||||
}
|
||||
; sameln: No unwind information.
|
||||
|
||||
; check the unwind information with a function with no args
|
||||
function %no_args() windows_fastcall {
|
||||
ebb0:
|
||||
return
|
||||
}
|
||||
; sameln: UnwindInfo {
|
||||
; nextln: version: 1,
|
||||
; nextln: flags: 0,
|
||||
; nextln: prologue_size: 8,
|
||||
; nextln: unwind_code_count_raw: 3,
|
||||
; nextln: frame_register: 5,
|
||||
; nextln: frame_register_offset: 0,
|
||||
; nextln: unwind_codes: [
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 8,
|
||||
; nextln: op: SmallStackAlloc,
|
||||
; nextln: info: 3,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 4,
|
||||
; nextln: op: SetFramePointer,
|
||||
; nextln: info: 0,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 1,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 5,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: ],
|
||||
; nextln: }
|
||||
|
||||
; check a function with medium-sized stack alloc
|
||||
function %medium_stack() windows_fastcall {
|
||||
ss0 = explicit_slot 100000
|
||||
ebb0:
|
||||
return
|
||||
}
|
||||
; sameln: UnwindInfo {
|
||||
; nextln: version: 1,
|
||||
; nextln: flags: 0,
|
||||
; nextln: prologue_size: 17,
|
||||
; nextln: unwind_code_count_raw: 4,
|
||||
; nextln: frame_register: 5,
|
||||
; nextln: frame_register_offset: 0,
|
||||
; nextln: unwind_codes: [
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 17,
|
||||
; nextln: op: LargeStackAlloc,
|
||||
; nextln: info: 0,
|
||||
; nextln: value: U16(
|
||||
; nextln: 12504,
|
||||
; nextln: ),
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 4,
|
||||
; nextln: op: SetFramePointer,
|
||||
; nextln: info: 0,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 1,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 5,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: ],
|
||||
; nextln: }
|
||||
|
||||
; check a function with large-sized stack alloc
|
||||
function %large_stack() windows_fastcall {
|
||||
ss0 = explicit_slot 524288
|
||||
ebb0:
|
||||
return
|
||||
}
|
||||
; sameln: UnwindInfo {
|
||||
; nextln: version: 1,
|
||||
; nextln: flags: 0,
|
||||
; nextln: prologue_size: 17,
|
||||
; nextln: unwind_code_count_raw: 5,
|
||||
; nextln: frame_register: 5,
|
||||
; nextln: frame_register_offset: 0,
|
||||
; nextln: unwind_codes: [
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 17,
|
||||
; nextln: op: LargeStackAlloc,
|
||||
; nextln: info: 1,
|
||||
; nextln: value: U32(
|
||||
; nextln: 524320,
|
||||
; nextln: ),
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 4,
|
||||
; nextln: op: SetFramePointer,
|
||||
; nextln: info: 0,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 1,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 5,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: ],
|
||||
; nextln: }
|
||||
|
||||
; check a function that has CSRs
|
||||
function %lots_of_registers(i64, i64) windows_fastcall {
|
||||
ebb0(v0: i64, v1: i64):
|
||||
v2 = load.i32 v0+0
|
||||
v3 = load.i32 v0+8
|
||||
v4 = load.i32 v0+16
|
||||
v5 = load.i32 v0+24
|
||||
v6 = load.i32 v0+32
|
||||
v7 = load.i32 v0+40
|
||||
v8 = load.i32 v0+48
|
||||
v9 = load.i32 v0+56
|
||||
v10 = load.i32 v0+64
|
||||
v11 = load.i32 v0+72
|
||||
v12 = load.i32 v0+80
|
||||
v13 = load.i32 v0+88
|
||||
v14 = load.i32 v0+96
|
||||
store.i32 v2, v1+0
|
||||
store.i32 v3, v1+8
|
||||
store.i32 v4, v1+16
|
||||
store.i32 v5, v1+24
|
||||
store.i32 v6, v1+32
|
||||
store.i32 v7, v1+40
|
||||
store.i32 v8, v1+48
|
||||
store.i32 v9, v1+56
|
||||
store.i32 v10, v1+64
|
||||
store.i32 v11, v1+72
|
||||
store.i32 v12, v1+80
|
||||
store.i32 v13, v1+88
|
||||
store.i32 v14, v1+96
|
||||
return
|
||||
}
|
||||
; sameln: UnwindInfo {
|
||||
; nextln: version: 1,
|
||||
; nextln: flags: 0,
|
||||
; nextln: prologue_size: 19,
|
||||
; nextln: unwind_code_count_raw: 10,
|
||||
; nextln: frame_register: 5,
|
||||
; nextln: frame_register_offset: 0,
|
||||
; nextln: unwind_codes: [
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 19,
|
||||
; nextln: op: SmallStackAlloc,
|
||||
; nextln: info: 4,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 15,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 15,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 13,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 14,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 11,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 13,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 9,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 12,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 7,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 7,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 6,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 6,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 5,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 3,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 4,
|
||||
; nextln: op: SetFramePointer,
|
||||
; nextln: info: 0,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: UnwindCode {
|
||||
; nextln: offset: 1,
|
||||
; nextln: op: PushNonvolatileRegister,
|
||||
; nextln: info: 5,
|
||||
; nextln: value: None,
|
||||
; nextln: },
|
||||
; nextln: ],
|
||||
; nextln: }
|
||||
@@ -54,6 +54,7 @@ mod test_safepoint;
|
||||
mod test_shrink;
|
||||
mod test_simple_gvn;
|
||||
mod test_simple_preopt;
|
||||
mod test_unwind;
|
||||
mod test_verifier;
|
||||
|
||||
/// The result of running the test in a file.
|
||||
@@ -135,6 +136,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult<Box<dyn subtest::
|
||||
"verifier" => test_verifier::subtest(parsed),
|
||||
"preopt" => test_preopt::subtest(parsed),
|
||||
"safepoint" => test_safepoint::subtest(parsed),
|
||||
"unwind" => test_unwind::subtest(parsed),
|
||||
_ => Err(format!("unknown test command '{}'", parsed.command)),
|
||||
}
|
||||
}
|
||||
|
||||
198
cranelift/filetests/src/test_unwind.rs
Normal file
198
cranelift/filetests/src/test_unwind.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
//! Test command for verifying the unwind emitted for each function.
|
||||
//!
|
||||
//! The `unwind` test command runs each function through the full code generator pipeline.
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
|
||||
|
||||
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use cranelift_codegen;
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_reader::TestCommand;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::io::Cursor;
|
||||
|
||||
struct TestUnwind;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> {
|
||||
assert_eq!(parsed.command, "unwind");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestUnwind))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestUnwind {
|
||||
fn name(&self) -> &'static str {
|
||||
"unwind"
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn needs_isa(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> {
|
||||
let isa = context.isa.expect("unwind needs an ISA");
|
||||
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
|
||||
|
||||
comp_ctx.compile(isa).expect("failed to compile function");
|
||||
|
||||
let mut mem = Vec::new();
|
||||
comp_ctx.emit_unwind_info(isa, &mut mem);
|
||||
|
||||
let mut text = String::new();
|
||||
if mem.is_empty() {
|
||||
writeln!(text, "No unwind information.").unwrap();
|
||||
} else {
|
||||
print_unwind_info(&mut text, &mem);
|
||||
}
|
||||
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_unwind_info(text: &mut String, mem: &[u8]) {
|
||||
let info = UnwindInfo::from_cursor(&mut Cursor::new(mem)).expect("failed to read unwind info");
|
||||
|
||||
// Assert correct alignment and padding of the unwind information
|
||||
assert!(mem.len() % 4 == 0);
|
||||
assert_eq!(
|
||||
mem.len(),
|
||||
4 + ((info.unwind_code_count_raw as usize) * 2)
|
||||
+ if (info.unwind_code_count_raw & 1) == 1 {
|
||||
2
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
|
||||
writeln!(text, "{:#?}", info).unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnwindInfo {
|
||||
pub version: u8,
|
||||
pub flags: u8,
|
||||
pub prologue_size: u8,
|
||||
pub unwind_code_count_raw: u8,
|
||||
pub frame_register: u8,
|
||||
pub frame_register_offset: u8,
|
||||
pub unwind_codes: Vec<UnwindCode>,
|
||||
}
|
||||
|
||||
impl UnwindInfo {
|
||||
fn from_cursor(cursor: &mut Cursor<&[u8]>) -> std::io::Result<Self> {
|
||||
let version_and_flags = cursor.read_u8()?;
|
||||
let prologue_size = cursor.read_u8()?;
|
||||
let unwind_code_count_raw = cursor.read_u8()?;
|
||||
let frame_register_and_offset = cursor.read_u8()?;
|
||||
let mut unwind_codes = Vec::new();
|
||||
|
||||
let mut i = 0;
|
||||
while i < unwind_code_count_raw {
|
||||
let code = UnwindCode::from_cursor(cursor)?;
|
||||
|
||||
i += match &code.value {
|
||||
UnwindValue::None => 1,
|
||||
UnwindValue::U16(_) => 2,
|
||||
UnwindValue::U32(_) => 3,
|
||||
};
|
||||
|
||||
unwind_codes.push(code);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
version: version_and_flags & 0x3,
|
||||
flags: (version_and_flags & 0xF8) >> 3,
|
||||
prologue_size,
|
||||
unwind_code_count_raw,
|
||||
frame_register: frame_register_and_offset & 0xF,
|
||||
frame_register_offset: (frame_register_and_offset & 0xF0) >> 4,
|
||||
unwind_codes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnwindCode {
|
||||
pub offset: u8,
|
||||
pub op: UnwindOperation,
|
||||
pub info: u8,
|
||||
pub value: UnwindValue,
|
||||
}
|
||||
|
||||
impl UnwindCode {
|
||||
fn from_cursor(cursor: &mut Cursor<&[u8]>) -> std::io::Result<Self> {
|
||||
let offset = cursor.read_u8()?;
|
||||
let op_and_info = cursor.read_u8()?;
|
||||
let op = UnwindOperation::from(op_and_info & 0xF);
|
||||
let info = (op_and_info & 0xF0) >> 4;
|
||||
|
||||
let value = match op {
|
||||
UnwindOperation::LargeStackAlloc => match info {
|
||||
0 => UnwindValue::U16(cursor.read_u16::<LittleEndian>()?),
|
||||
1 => UnwindValue::U32(cursor.read_u32::<LittleEndian>()?),
|
||||
_ => panic!("unexpected stack alloc info value"),
|
||||
},
|
||||
UnwindOperation::SaveNonvolatileRegister => {
|
||||
UnwindValue::U16(cursor.read_u16::<LittleEndian>()?)
|
||||
}
|
||||
UnwindOperation::SaveNonvolatileRegisterFar => {
|
||||
UnwindValue::U32(cursor.read_u32::<LittleEndian>()?)
|
||||
}
|
||||
UnwindOperation::SaveXmm128 => UnwindValue::U16(cursor.read_u16::<LittleEndian>()?),
|
||||
UnwindOperation::SaveXmm128Far => UnwindValue::U32(cursor.read_u32::<LittleEndian>()?),
|
||||
_ => UnwindValue::None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
offset,
|
||||
op,
|
||||
info,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum UnwindOperation {
|
||||
PushNonvolatileRegister,
|
||||
LargeStackAlloc,
|
||||
SmallStackAlloc,
|
||||
SetFramePointer,
|
||||
SaveNonvolatileRegister,
|
||||
SaveNonvolatileRegisterFar,
|
||||
SaveXmm128,
|
||||
SaveXmm128Far,
|
||||
PushMachineFrame,
|
||||
}
|
||||
|
||||
impl From<u8> for UnwindOperation {
|
||||
fn from(value: u8) -> Self {
|
||||
// The numerical value is specified as part of the Windows x64 ABI
|
||||
match value {
|
||||
0 => Self::PushNonvolatileRegister,
|
||||
1 => Self::LargeStackAlloc,
|
||||
2 => Self::SmallStackAlloc,
|
||||
3 => Self::SetFramePointer,
|
||||
4 => Self::SaveNonvolatileRegister,
|
||||
5 => Self::SaveNonvolatileRegisterFar,
|
||||
6 => Self::SaveXmm128,
|
||||
7 => Self::SaveXmm128Far,
|
||||
8 => Self::PushMachineFrame,
|
||||
_ => panic!("unsupported unwind operation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum UnwindValue {
|
||||
None,
|
||||
U16(u16),
|
||||
U32(u32),
|
||||
}
|
||||
Reference in New Issue
Block a user