Refactor unwind generation in Cranelift.

This commit makes the following changes to unwind information generation in
Cranelift:

* Remove frame layout change implementation in favor of processing the prologue
  and epilogue instructions when unwind information is requested.  This also
  means this work is no longer performed for Windows, which didn't utilize it.
  It also helps simplify the prologue and epilogue generation code.

* Remove the unwind sink implementation that required each unwind information
  to be represented in final form. For FDEs, this meant writing a
  complete frame table per function, which wastes 20 bytes or so for each
  function with duplicate CIEs.  This also enables Cranelift users to collect the
  unwind information and write it as a single frame table.

* For System V calling convention, the unwind information is no longer stored
  in code memory (it's only a requirement for Windows ABI to do so).  This allows
  for more compact code memory for modules with a lot of functions.

* Deletes some duplicate code relating to frame table generation.  Users can
  now simply use gimli to create a frame table from each function's unwind
  information.

Fixes #1181.
This commit is contained in:
Peter Huene
2020-03-30 19:48:02 -07:00
parent 7da6101732
commit f7e9f86ba9
42 changed files with 2678 additions and 3161 deletions

View File

@@ -4,13 +4,13 @@
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
use byteorder::{ByteOrder, LittleEndian};
use cranelift_codegen;
use cranelift_codegen::binemit::{FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc};
use cranelift_codegen::ir;
use cranelift_codegen::{self, ir, isa::unwind::UnwindInfo};
use cranelift_reader::TestCommand;
use gimli::{
write::{Address, EhFrame, EndianVec, FrameTable},
LittleEndian,
};
use std::borrow::Cow;
use std::fmt::Write;
struct TestUnwind;
@@ -42,175 +42,526 @@ impl SubTest for TestUnwind {
comp_ctx.compile(isa).expect("failed to compile function");
struct Sink(Vec<u8>);
impl FrameUnwindSink for Sink {
fn len(&self) -> FrameUnwindOffset {
self.0.len()
}
fn bytes(&mut self, b: &[u8]) {
self.0.extend_from_slice(b);
}
fn reloc(&mut self, _: Reloc, _: FrameUnwindOffset) {
unimplemented!();
}
fn set_entry_offset(&mut self, _: FrameUnwindOffset) {
unimplemented!();
}
}
let mut sink = Sink(Vec::new());
comp_ctx
.emit_unwind_info(isa, FrameUnwindKind::Fastcall, &mut sink)
.expect("can emit unwind info");
let mut text = String::new();
if sink.0.is_empty() {
writeln!(text, "No unwind information.").unwrap();
} else {
print_unwind_info(&mut text, &sink.0);
match comp_ctx.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())
}
None => {}
}
run_filecheck(&text, context)
}
}
fn print_unwind_info(text: &mut String, mem: &[u8]) {
let info = UnwindInfo::from_slice(mem);
mod windowsx64 {
use byteorder::{ByteOrder, LittleEndian};
use std::fmt::Write;
// 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
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,
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);
}
);
writeln!(text, "{:#?}", info).unwrap();
}
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 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>,
}
#[derive(Debug)]
struct UnwindCode {
offset: u8,
op: UnwindOperation,
info: u8,
value: UnwindValue,
}
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();
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 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,
let value = match op {
UnwindOperation::LargeStackAlloc => match info {
0 => UnwindValue::U16(LittleEndian::read_u16(&mem[2..])),
1 => UnwindValue::U32(LittleEndian::read_u32(&mem[2..])),
_ => panic!("unexpected stack alloc info value"),
},
UnwindOperation::SaveNonvolatileRegister => {
UnwindValue::U16(LittleEndian::read_u16(&mem[2..]))
}
UnwindOperation::SaveNonvolatileRegisterFar => {
UnwindValue::U32(LittleEndian::read_u32(&mem[2..]))
}
UnwindOperation::SaveXmm128 => UnwindValue::U16(LittleEndian::read_u16(&mem[2..])),
UnwindOperation::SaveXmm128Far => {
UnwindValue::U32(LittleEndian::read_u32(&mem[2..]))
}
_ => UnwindValue::None,
};
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 {
pub offset: u8,
pub op: UnwindOperation,
pub info: u8,
pub 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 value = match op {
UnwindOperation::LargeStackAlloc => match info {
0 => UnwindValue::U16(LittleEndian::read_u16(&mem[2..])),
1 => UnwindValue::U32(LittleEndian::read_u32(&mem[2..])),
_ => panic!("unexpected stack alloc info value"),
},
UnwindOperation::SaveNonvolatileRegister => {
UnwindValue::U16(LittleEndian::read_u16(&mem[2..]))
Self {
offset,
op,
info,
value,
}
UnwindOperation::SaveNonvolatileRegisterFar => {
UnwindValue::U32(LittleEndian::read_u32(&mem[2..]))
}
}
#[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"),
}
UnwindOperation::SaveXmm128 => UnwindValue::U16(LittleEndian::read_u16(&mem[2..])),
UnwindOperation::SaveXmm128Far => UnwindValue::U32(LittleEndian::read_u32(&mem[2..])),
_ => UnwindValue::None,
};
Self {
offset,
op,
info,
value,
}
}
#[derive(Debug)]
enum UnwindValue {
None,
U16(u16),
U32(u32),
}
}
#[derive(Debug)]
enum UnwindOperation {
PushNonvolatileRegister = 0,
LargeStackAlloc = 1,
SmallStackAlloc = 2,
SetFramePointer = 3,
SaveNonvolatileRegister = 4,
SaveNonvolatileRegisterFar = 5,
SaveXmm128 = 8,
SaveXmm128Far = 9,
PushMachineFrame = 10,
}
mod systemv {
fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {
Cow::Owned(format!("r{}", register.0))
}
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"),
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, &register_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")?;
}
},
}
}
}
}
#[derive(Debug)]
enum UnwindValue {
None,
U16(u16),
U32(u32),
}