* Replace resize+copy_from_slice with extend_from_slice Vec::resize initializes the new space, which is wasted effort if we're just going to call `copy_from_slice` on it immediately afterward. Using `extend_from_slice` is simpler, and very slightly faster. If the new size were bigger than the buffer we're copying from, then it would make sense to initialize the excess. But it isn't: it's always exactly the same size. * Move helpers from Context to CompiledCode These methods only use information from Context::compiled_code, so they should live on CompiledCode instead. * Remove an unnecessary #[cfg_attr] There are other uses of `#[allow(clippy::too_many_arguments)]` in this file, so apparently it doesn't need to be guarded by the "cargo-clippy" feature. * Fix a few comments Two of these were wrong/misleading: - `FunctionBuilder::new` does not clear the provided func_ctx. It does debug-assert that the context is already clear, but I don't think that's worth a comment. - `switch_to_block` does not "create values for the arguments." That's done by the combination of `append_block_params_for_function_params` and `declare_wasm_parameters`. * wasmtime-cranelift: Misc cleanups The main change is to use the `CompiledCode` reference we already had instead of getting it out of `Context` repeatedly. This removes a bunch of `unwrap()` calls. * wasmtime-cranelift: Factor out uncached compile
571 lines
20 KiB
Rust
571 lines
20 KiB
Rust
//! 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};
|
|
use cranelift_codegen::{self, ir, isa::unwind::UnwindInfo};
|
|
use cranelift_reader::TestCommand;
|
|
use gimli::{
|
|
write::{Address, EhFrame, EndianVec, FrameTable},
|
|
LittleEndian,
|
|
};
|
|
use std::borrow::Cow;
|
|
|
|
struct TestUnwind;
|
|
|
|
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
|
|
assert_eq!(parsed.command, "unwind");
|
|
if !parsed.options.is_empty() {
|
|
anyhow::bail!("No options allowed on {}", parsed);
|
|
}
|
|
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) -> anyhow::Result<()> {
|
|
let isa = context.isa.expect("unwind needs an ISA");
|
|
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
|
|
|
|
let code = comp_ctx.compile(isa).expect("failed to compile function");
|
|
|
|
let mut text = String::new();
|
|
match code.create_unwind_info(isa).expect("unwind info") {
|
|
Some(UnwindInfo::WindowsX64(info)) => {
|
|
let mut mem = vec![0; info.emit_size()];
|
|
info.emit(&mut mem);
|
|
windowsx64::dump(&mut text, &mem);
|
|
}
|
|
Some(UnwindInfo::SystemV(info)) => {
|
|
let mut table = FrameTable::default();
|
|
let cie = isa
|
|
.create_systemv_cie()
|
|
.expect("the ISA should support a System V CIE");
|
|
|
|
let cie_id = table.add_cie(cie);
|
|
table.add_fde(cie_id, info.to_fde(Address::Constant(0)));
|
|
|
|
let mut eh_frame = EhFrame(EndianVec::new(LittleEndian));
|
|
table.write_eh_frame(&mut eh_frame).unwrap();
|
|
systemv::dump(&mut text, &eh_frame.0.into_vec(), isa.pointer_bytes())
|
|
}
|
|
Some(ui) => {
|
|
anyhow::bail!("Unexpected unwind info type: {:?}", ui);
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
run_filecheck(&text, context)
|
|
}
|
|
}
|
|
|
|
mod windowsx64 {
|
|
use std::fmt::Write;
|
|
|
|
pub fn dump<W: Write>(text: &mut W, mem: &[u8]) {
|
|
let info = UnwindInfo::from_slice(mem);
|
|
|
|
writeln!(text, " version: {}", info.version).unwrap();
|
|
writeln!(text, " flags: {}", info.flags).unwrap();
|
|
writeln!(text, " prologue size: {}", info.prologue_size).unwrap();
|
|
writeln!(text, " frame register: {}", info.frame_register).unwrap();
|
|
writeln!(
|
|
text,
|
|
"frame register offset: {}",
|
|
info.frame_register_offset
|
|
)
|
|
.unwrap();
|
|
writeln!(text, " unwind codes: {}", info.unwind_codes.len()).unwrap();
|
|
|
|
for code in info.unwind_codes.iter().rev() {
|
|
writeln!(text).unwrap();
|
|
writeln!(text, " offset: {}", code.offset).unwrap();
|
|
writeln!(text, " op: {:?}", code.op).unwrap();
|
|
writeln!(text, " info: {}", code.info).unwrap();
|
|
match code.value {
|
|
UnwindValue::None => {}
|
|
UnwindValue::U16(v) => {
|
|
writeln!(text, " value: {} (u16)", v).unwrap()
|
|
}
|
|
UnwindValue::U32(v) => {
|
|
writeln!(text, " value: {} (u32)", v).unwrap()
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct UnwindInfo {
|
|
version: u8,
|
|
flags: u8,
|
|
prologue_size: u8,
|
|
#[allow(dead_code)]
|
|
unwind_code_count_raw: u8,
|
|
frame_register: u8,
|
|
frame_register_offset: u8,
|
|
unwind_codes: Vec<UnwindCode>,
|
|
}
|
|
|
|
impl UnwindInfo {
|
|
fn from_slice(mem: &[u8]) -> Self {
|
|
let version_and_flags = mem[0];
|
|
let prologue_size = mem[1];
|
|
let unwind_code_count_raw = mem[2];
|
|
let frame_register_and_offset = mem[3];
|
|
let mut unwind_codes = Vec::new();
|
|
|
|
let mut i = 0;
|
|
while i < unwind_code_count_raw {
|
|
let code = UnwindCode::from_slice(&mem[(4 + (i * 2) as usize)..]);
|
|
|
|
i += match &code.value {
|
|
UnwindValue::None => 1,
|
|
UnwindValue::U16(_) => 2,
|
|
UnwindValue::U32(_) => 3,
|
|
};
|
|
|
|
unwind_codes.push(code);
|
|
}
|
|
|
|
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 {
|
|
offset: u8,
|
|
op: UnwindOperation,
|
|
info: u8,
|
|
value: UnwindValue,
|
|
}
|
|
|
|
impl UnwindCode {
|
|
fn from_slice(mem: &[u8]) -> Self {
|
|
let offset = mem[0];
|
|
let op_and_info = mem[1];
|
|
let op = UnwindOperation::from(op_and_info & 0xF);
|
|
let info = (op_and_info & 0xF0) >> 4;
|
|
let unwind_le_bytes = |bytes| match (bytes, &mem[2..]) {
|
|
(2, &[b0, b1, ..]) => UnwindValue::U16(u16::from_le_bytes([b0, b1])),
|
|
(4, &[b0, b1, b2, b3, ..]) => {
|
|
UnwindValue::U32(u32::from_le_bytes([b0, b1, b2, b3]))
|
|
}
|
|
(_, _) => panic!("not enough bytes to unwind value"),
|
|
};
|
|
|
|
let value = match (&op, info) {
|
|
(UnwindOperation::LargeStackAlloc, 0) => unwind_le_bytes(2),
|
|
(UnwindOperation::LargeStackAlloc, 1) => unwind_le_bytes(4),
|
|
(UnwindOperation::LargeStackAlloc, _) => {
|
|
panic!("unexpected stack alloc info value")
|
|
}
|
|
(UnwindOperation::SaveNonvolatileRegister, _) => unwind_le_bytes(2),
|
|
(UnwindOperation::SaveNonvolatileRegisterFar, _) => unwind_le_bytes(4),
|
|
(UnwindOperation::SaveXmm128, _) => unwind_le_bytes(2),
|
|
(UnwindOperation::SaveXmm128Far, _) => unwind_le_bytes(4),
|
|
_ => UnwindValue::None,
|
|
};
|
|
|
|
Self {
|
|
offset,
|
|
op,
|
|
info,
|
|
value,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum UnwindOperation {
|
|
PushNonvolatileRegister = 0,
|
|
LargeStackAlloc = 1,
|
|
SmallStackAlloc = 2,
|
|
SetFramePointer = 3,
|
|
SaveNonvolatileRegister = 4,
|
|
SaveNonvolatileRegisterFar = 5,
|
|
SaveXmm128 = 8,
|
|
SaveXmm128Far = 9,
|
|
PushMachineFrame = 10,
|
|
}
|
|
|
|
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,
|
|
8 => Self::SaveXmm128,
|
|
9 => Self::SaveXmm128Far,
|
|
10 => Self::PushMachineFrame,
|
|
_ => panic!("unsupported unwind operation"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum UnwindValue {
|
|
None,
|
|
U16(u16),
|
|
U32(u32),
|
|
}
|
|
}
|
|
|
|
mod systemv {
|
|
fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {
|
|
Cow::Owned(format!("r{}", register.0))
|
|
}
|
|
|
|
pub fn dump<W: Write>(text: &mut W, bytes: &[u8], address_size: u8) {
|
|
let mut eh_frame = gimli::EhFrame::new(bytes, gimli::LittleEndian);
|
|
eh_frame.set_address_size(address_size);
|
|
let bases = gimli::BaseAddresses::default();
|
|
dump_eh_frame(text, &eh_frame, &bases, ®ister_name).unwrap();
|
|
}
|
|
|
|
// Remainder copied from https://github.com/gimli-rs/gimli/blob/1e49ffc9af4ec64a1b7316924d73c933dd7157c5/examples/dwarfdump.rs
|
|
use gimli::UnwindSection;
|
|
use std::borrow::Cow;
|
|
use std::collections::HashMap;
|
|
use std::fmt::{self, Debug, Write};
|
|
use std::result;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(super) enum Error {
|
|
GimliError(gimli::Error),
|
|
IoError,
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
#[inline]
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
|
|
Debug::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl From<gimli::Error> for Error {
|
|
fn from(err: gimli::Error) -> Self {
|
|
Self::GimliError(err)
|
|
}
|
|
}
|
|
|
|
impl From<fmt::Error> for Error {
|
|
fn from(_: fmt::Error) -> Self {
|
|
Self::IoError
|
|
}
|
|
}
|
|
|
|
pub(super) type Result<T> = result::Result<T, Error>;
|
|
|
|
pub(super) trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
|
|
|
|
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
|
|
Endian: gimli::Endianity + Send + Sync
|
|
{
|
|
}
|
|
|
|
pub(super) fn dump_eh_frame<R: Reader, W: Write>(
|
|
w: &mut W,
|
|
eh_frame: &gimli::EhFrame<R>,
|
|
bases: &gimli::BaseAddresses,
|
|
register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
|
|
) -> Result<()> {
|
|
let mut cies = HashMap::new();
|
|
|
|
let mut entries = eh_frame.entries(bases);
|
|
loop {
|
|
match entries.next()? {
|
|
None => return Ok(()),
|
|
Some(gimli::CieOrFde::Cie(cie)) => {
|
|
writeln!(w, "{:#010x}: CIE", cie.offset())?;
|
|
writeln!(w, " length: {:#010x}", cie.entry_len())?;
|
|
// TODO: CIE_id
|
|
writeln!(w, " version: {:#04x}", cie.version())?;
|
|
// TODO: augmentation
|
|
writeln!(w, " code_align: {}", cie.code_alignment_factor())?;
|
|
writeln!(w, " data_align: {}", cie.data_alignment_factor())?;
|
|
writeln!(w, " ra_register: {:#x}", cie.return_address_register().0)?;
|
|
if let Some(encoding) = cie.lsda_encoding() {
|
|
writeln!(w, " lsda_encoding: {:#02x}", encoding.0)?;
|
|
}
|
|
if let Some((encoding, personality)) = cie.personality_with_encoding() {
|
|
write!(w, " personality: {:#02x} ", encoding.0)?;
|
|
dump_pointer(w, personality)?;
|
|
writeln!(w)?;
|
|
}
|
|
if let Some(encoding) = cie.fde_address_encoding() {
|
|
writeln!(w, " fde_encoding: {:#02x}", encoding.0)?;
|
|
}
|
|
dump_cfi_instructions(
|
|
w,
|
|
cie.instructions(eh_frame, bases),
|
|
true,
|
|
register_name,
|
|
)?;
|
|
writeln!(w)?;
|
|
}
|
|
Some(gimli::CieOrFde::Fde(partial)) => {
|
|
let mut offset = None;
|
|
let fde = partial.parse(|_, bases, o| {
|
|
offset = Some(o);
|
|
cies.entry(o)
|
|
.or_insert_with(|| eh_frame.cie_from_offset(bases, o))
|
|
.clone()
|
|
})?;
|
|
|
|
writeln!(w)?;
|
|
writeln!(w, "{:#010x}: FDE", fde.offset())?;
|
|
writeln!(w, " length: {:#010x}", fde.entry_len())?;
|
|
writeln!(w, " CIE_pointer: {:#010x}", offset.unwrap().0)?;
|
|
// TODO: symbolicate the start address like the canonical dwarfdump does.
|
|
writeln!(w, " start_addr: {:#018x}", fde.initial_address())?;
|
|
writeln!(
|
|
w,
|
|
" range_size: {:#018x} (end_addr = {:#018x})",
|
|
fde.len(),
|
|
fde.initial_address() + fde.len()
|
|
)?;
|
|
if let Some(lsda) = fde.lsda() {
|
|
write!(w, " lsda: ")?;
|
|
dump_pointer(w, lsda)?;
|
|
writeln!(w)?;
|
|
}
|
|
dump_cfi_instructions(
|
|
w,
|
|
fde.instructions(eh_frame, bases),
|
|
false,
|
|
register_name,
|
|
)?;
|
|
writeln!(w)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn dump_pointer<W: Write>(w: &mut W, p: gimli::Pointer) -> Result<()> {
|
|
match p {
|
|
gimli::Pointer::Direct(p) => {
|
|
write!(w, "{:#018x}", p)?;
|
|
}
|
|
gimli::Pointer::Indirect(p) => {
|
|
write!(w, "({:#018x})", p)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::unneeded_field_pattern)]
|
|
fn dump_cfi_instructions<R: Reader, W: Write>(
|
|
w: &mut W,
|
|
mut insns: gimli::CallFrameInstructionIter<R>,
|
|
is_initial: bool,
|
|
register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
|
|
) -> Result<()> {
|
|
use gimli::CallFrameInstruction::*;
|
|
|
|
// TODO: we need to actually evaluate these instructions as we iterate them
|
|
// so we can print the initialized state for CIEs, and each unwind row's
|
|
// registers for FDEs.
|
|
//
|
|
// TODO: We should print DWARF expressions for the CFI instructions that
|
|
// embed DWARF expressions within themselves.
|
|
|
|
if !is_initial {
|
|
writeln!(w, " Instructions:")?;
|
|
}
|
|
|
|
loop {
|
|
match insns.next() {
|
|
Err(e) => {
|
|
writeln!(w, "Failed to decode CFI instruction: {}", e)?;
|
|
return Ok(());
|
|
}
|
|
Ok(None) => {
|
|
if is_initial {
|
|
writeln!(w, " Instructions: Init State:")?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
Ok(Some(op)) => match op {
|
|
SetLoc { address } => {
|
|
writeln!(w, " DW_CFA_set_loc ({:#x})", address)?;
|
|
}
|
|
AdvanceLoc { delta } => {
|
|
writeln!(w, " DW_CFA_advance_loc ({})", delta)?;
|
|
}
|
|
DefCfa { register, offset } => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_def_cfa ({}, {})",
|
|
register_name(register),
|
|
offset
|
|
)?;
|
|
}
|
|
DefCfaSf {
|
|
register,
|
|
factored_offset,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_def_cfa_sf ({}, {})",
|
|
register_name(register),
|
|
factored_offset
|
|
)?;
|
|
}
|
|
DefCfaRegister { register } => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_def_cfa_register ({})",
|
|
register_name(register)
|
|
)?;
|
|
}
|
|
DefCfaOffset { offset } => {
|
|
writeln!(w, " DW_CFA_def_cfa_offset ({})", offset)?;
|
|
}
|
|
DefCfaOffsetSf { factored_offset } => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_def_cfa_offset_sf ({})",
|
|
factored_offset
|
|
)?;
|
|
}
|
|
DefCfaExpression { expression: _ } => {
|
|
writeln!(w, " DW_CFA_def_cfa_expression (...)")?;
|
|
}
|
|
Undefined { register } => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_undefined ({})",
|
|
register_name(register)
|
|
)?;
|
|
}
|
|
SameValue { register } => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_same_value ({})",
|
|
register_name(register)
|
|
)?;
|
|
}
|
|
Offset {
|
|
register,
|
|
factored_offset,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_offset ({}, {})",
|
|
register_name(register),
|
|
factored_offset
|
|
)?;
|
|
}
|
|
OffsetExtendedSf {
|
|
register,
|
|
factored_offset,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_offset_extended_sf ({}, {})",
|
|
register_name(register),
|
|
factored_offset
|
|
)?;
|
|
}
|
|
ValOffset {
|
|
register,
|
|
factored_offset,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_val_offset ({}, {})",
|
|
register_name(register),
|
|
factored_offset
|
|
)?;
|
|
}
|
|
ValOffsetSf {
|
|
register,
|
|
factored_offset,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_val_offset_sf ({}, {})",
|
|
register_name(register),
|
|
factored_offset
|
|
)?;
|
|
}
|
|
Register {
|
|
dest_register,
|
|
src_register,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_register ({}, {})",
|
|
register_name(dest_register),
|
|
register_name(src_register)
|
|
)?;
|
|
}
|
|
Expression {
|
|
register,
|
|
expression: _,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_expression ({}, ...)",
|
|
register_name(register)
|
|
)?;
|
|
}
|
|
ValExpression {
|
|
register,
|
|
expression: _,
|
|
} => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_val_expression ({}, ...)",
|
|
register_name(register)
|
|
)?;
|
|
}
|
|
Restore { register } => {
|
|
writeln!(
|
|
w,
|
|
" DW_CFA_restore ({})",
|
|
register_name(register)
|
|
)?;
|
|
}
|
|
RememberState => {
|
|
writeln!(w, " DW_CFA_remember_state")?;
|
|
}
|
|
RestoreState => {
|
|
writeln!(w, " DW_CFA_restore_state")?;
|
|
}
|
|
ArgsSize { size } => {
|
|
writeln!(w, " DW_CFA_GNU_args_size ({})", size)?;
|
|
}
|
|
Nop => {
|
|
writeln!(w, " DW_CFA_nop")?;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|