Refactor unwind; add FDE support. (#1320)
* Refactor unwind * add FDE support * use sink directly in emit functions * pref off all unwinding generation with feature
This commit is contained in:
@@ -20,6 +20,7 @@ hashbrown = { version = "0.6", optional = true }
|
|||||||
target-lexicon = "0.10"
|
target-lexicon = "0.10"
|
||||||
log = { version = "0.4.6", default-features = false }
|
log = { version = "0.4.6", default-features = false }
|
||||||
serde = { version = "1.0.94", features = ["derive"], optional = true }
|
serde = { version = "1.0.94", features = ["derive"], optional = true }
|
||||||
|
gimli = { version = "0.19.0", default-features = false, features = ["write"], optional = true }
|
||||||
smallvec = { version = "1.0.0" }
|
smallvec = { version = "1.0.0" }
|
||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
byteorder = { version = "1.3.2", default-features = false }
|
byteorder = { version = "1.3.2", default-features = false }
|
||||||
@@ -32,7 +33,7 @@ byteorder = { version = "1.3.2", default-features = false }
|
|||||||
cranelift-codegen-meta = { path = "meta", version = "0.54.0" }
|
cranelift-codegen-meta = { path = "meta", version = "0.54.0" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std", "basic-blocks"]
|
default = ["std", "basic-blocks", "unwind"]
|
||||||
|
|
||||||
# The "std" feature enables use of libstd. The "core" feature enables use
|
# The "std" feature enables use of libstd. The "core" feature enables use
|
||||||
# of some minimal std-like replacement libraries. At least one of these two
|
# of some minimal std-like replacement libraries. At least one of these two
|
||||||
@@ -47,6 +48,9 @@ core = ["hashbrown"]
|
|||||||
# can significantly increase the size of the library.
|
# can significantly increase the size of the library.
|
||||||
testing_hooks = []
|
testing_hooks = []
|
||||||
|
|
||||||
|
# This enables unwind info generation functionality.
|
||||||
|
unwind = ["gimli"]
|
||||||
|
|
||||||
# ISA targets for which we should build.
|
# ISA targets for which we should build.
|
||||||
# If no ISA targets are explicitly enabled, the ISA target for the host machine is enabled.
|
# If no ISA targets are explicitly enabled, the ISA target for the host machine is enabled.
|
||||||
x86 = []
|
x86 = []
|
||||||
|
|||||||
@@ -155,6 +155,36 @@ pub trait CodeSink {
|
|||||||
fn add_stackmap(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa);
|
fn add_stackmap(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type of the frame unwind information.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FrameUnwindKind {
|
||||||
|
/// Windows fastcall unwinding (as in .pdata).
|
||||||
|
Fastcall,
|
||||||
|
/// FDE entry for libunwind (similar to .eh_frame format).
|
||||||
|
Libunwind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Offset in frame unwind information buffer.
|
||||||
|
pub type FrameUnwindOffset = usize;
|
||||||
|
|
||||||
|
/// Sink for frame unwind information.
|
||||||
|
pub trait FrameUnwindSink {
|
||||||
|
/// Get the current position.
|
||||||
|
fn len(&self) -> FrameUnwindOffset;
|
||||||
|
|
||||||
|
/// Add bytes to the code section.
|
||||||
|
fn bytes(&mut self, _: &[u8]);
|
||||||
|
|
||||||
|
/// Reserves bytes in the buffer.
|
||||||
|
fn reserve(&mut self, _len: usize) {}
|
||||||
|
|
||||||
|
/// Add a relocation entry.
|
||||||
|
fn reloc(&mut self, _: Reloc, _: FrameUnwindOffset);
|
||||||
|
|
||||||
|
/// Specified offset to main structure.
|
||||||
|
fn set_entry_offset(&mut self, _: FrameUnwindOffset);
|
||||||
|
}
|
||||||
|
|
||||||
/// Report a bad encoding error.
|
/// Report a bad encoding error.
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn bad_encoding(func: &Function, inst: Inst) -> ! {
|
pub fn bad_encoding(func: &Function, inst: Inst) -> ! {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
//! single ISA instance.
|
//! single ISA instance.
|
||||||
|
|
||||||
use crate::binemit::{
|
use crate::binemit::{
|
||||||
relax_branches, shrink_instructions, CodeInfo, MemoryCodeSink, RelocSink, StackmapSink,
|
relax_branches, shrink_instructions, CodeInfo, FrameUnwindKind, FrameUnwindSink,
|
||||||
TrapSink,
|
MemoryCodeSink, RelocSink, StackmapSink, TrapSink,
|
||||||
};
|
};
|
||||||
use crate::dce::do_dce;
|
use crate::dce::do_dce;
|
||||||
use crate::dominator_tree::DominatorTree;
|
use crate::dominator_tree::DominatorTree;
|
||||||
@@ -201,8 +201,13 @@ impl Context {
|
|||||||
///
|
///
|
||||||
/// Only some calling conventions (e.g. Windows fastcall) will have unwind information.
|
/// Only some calling conventions (e.g. Windows fastcall) will have unwind information.
|
||||||
/// This is a no-op if the function has no unwind information.
|
/// This is a no-op if the function has no unwind information.
|
||||||
pub fn emit_unwind_info(&self, isa: &dyn TargetIsa, mem: &mut Vec<u8>) {
|
pub fn emit_unwind_info(
|
||||||
isa.emit_unwind_info(&self.func, mem);
|
&self,
|
||||||
|
isa: &dyn TargetIsa,
|
||||||
|
kind: FrameUnwindKind,
|
||||||
|
sink: &mut dyn FrameUnwindSink,
|
||||||
|
) {
|
||||||
|
isa.emit_unwind_info(&self.func, kind, sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the verifier on the function.
|
/// Run the verifier on the function.
|
||||||
|
|||||||
@@ -253,6 +253,11 @@ impl Function {
|
|||||||
/// Starts collection of debug information.
|
/// Starts collection of debug information.
|
||||||
pub fn collect_debug_info(&mut self) {
|
pub fn collect_debug_info(&mut self) {
|
||||||
self.dfg.collect_debug_info();
|
self.dfg.collect_debug_info();
|
||||||
|
self.collect_frame_layout_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts collection of frame layout information.
|
||||||
|
pub fn collect_frame_layout_info(&mut self) {
|
||||||
self.frame_layout = Some(FrameLayout::new());
|
self.frame_layout = Some(FrameLayout::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ use crate::settings::SetResult;
|
|||||||
use crate::timing;
|
use crate::timing;
|
||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use target_lexicon::{triple, Architecture, PointerWidth, Triple};
|
use target_lexicon::{triple, Architecture, PointerWidth, Triple};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -382,7 +381,12 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
|
|||||||
/// Emit unwind information for the given function.
|
/// Emit unwind information for the given function.
|
||||||
///
|
///
|
||||||
/// Only some calling conventions (e.g. Windows fastcall) will have unwind information.
|
/// Only some calling conventions (e.g. Windows fastcall) will have unwind information.
|
||||||
fn emit_unwind_info(&self, _func: &ir::Function, _mem: &mut Vec<u8>) {
|
fn emit_unwind_info(
|
||||||
|
&self,
|
||||||
|
_func: &ir::Function,
|
||||||
|
_kind: binemit::FrameUnwindKind,
|
||||||
|
_sink: &mut dyn binemit::FrameUnwindSink,
|
||||||
|
) {
|
||||||
// No-op by default
|
// No-op by default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
//! x86 ABI implementation.
|
//! x86 ABI implementation.
|
||||||
|
|
||||||
use super::super::settings as shared_settings;
|
use super::super::settings as shared_settings;
|
||||||
|
#[cfg(feature = "unwind")]
|
||||||
|
use super::fde::emit_fde;
|
||||||
use super::registers::{FPR, GPR, RU};
|
use super::registers::{FPR, GPR, RU};
|
||||||
use super::settings as isa_settings;
|
use super::settings as isa_settings;
|
||||||
|
#[cfg(feature = "unwind")]
|
||||||
use super::unwind::UnwindInfo;
|
use super::unwind::UnwindInfo;
|
||||||
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion};
|
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion};
|
||||||
|
#[cfg(feature = "unwind")]
|
||||||
|
use crate::binemit::{FrameUnwindKind, FrameUnwindSink};
|
||||||
use crate::cursor::{Cursor, CursorPosition, EncCursor};
|
use crate::cursor::{Cursor, CursorPosition, EncCursor};
|
||||||
use crate::ir;
|
use crate::ir;
|
||||||
use crate::ir::immediates::Imm64;
|
use crate::ir::immediates::Imm64;
|
||||||
@@ -18,7 +23,6 @@ use crate::regalloc::RegisterSet;
|
|||||||
use crate::result::CodegenResult;
|
use crate::result::CodegenResult;
|
||||||
use crate::stack_layout::layout_stack;
|
use crate::stack_layout::layout_stack;
|
||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::i32;
|
use core::i32;
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use target_lexicon::{PointerWidth, Triple};
|
use target_lexicon::{PointerWidth, Triple};
|
||||||
@@ -947,10 +951,25 @@ fn insert_common_epilogue(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_unwind_info(func: &ir::Function, isa: &dyn TargetIsa, mem: &mut Vec<u8>) {
|
#[cfg(feature = "unwind")]
|
||||||
|
pub fn emit_unwind_info(
|
||||||
|
func: &ir::Function,
|
||||||
|
isa: &dyn TargetIsa,
|
||||||
|
kind: FrameUnwindKind,
|
||||||
|
sink: &mut dyn FrameUnwindSink,
|
||||||
|
) {
|
||||||
|
match kind {
|
||||||
|
FrameUnwindKind::Fastcall => {
|
||||||
// Assumption: RBP is being used as the frame pointer
|
// Assumption: RBP is being used as the frame pointer
|
||||||
// In the future, Windows fastcall codegen should usually omit the frame pointer
|
// In the future, Windows fastcall codegen should usually omit the frame pointer
|
||||||
if let Some(info) = UnwindInfo::try_from_func(func, isa, Some(RU::rbp.into())) {
|
if let Some(info) = UnwindInfo::try_from_func(func, isa, Some(RU::rbp.into())) {
|
||||||
info.emit(mem);
|
info.emit(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrameUnwindKind::Libunwind => {
|
||||||
|
if func.frame_layout.is_some() {
|
||||||
|
emit_fde(func, isa, sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
354
cranelift/codegen/src/isa/x86/fde.rs
Normal file
354
cranelift/codegen/src/isa/x86/fde.rs
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
//! Support for FDE data generation.
|
||||||
|
|
||||||
|
use crate::binemit::{FrameUnwindOffset, FrameUnwindSink, Reloc};
|
||||||
|
use crate::ir::{FrameLayoutChange, Function};
|
||||||
|
use crate::isa::{CallConv, RegUnit, TargetIsa};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::convert::TryInto;
|
||||||
|
use gimli::write::{
|
||||||
|
Address, CallFrameInstruction, CommonInformationEntry, EhFrame, EndianVec,
|
||||||
|
FrameDescriptionEntry, FrameTable, Result, Writer,
|
||||||
|
};
|
||||||
|
use gimli::{Encoding, Format, LittleEndian, Register, X86_64};
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
pub type FDERelocEntry = (FrameUnwindOffset, Reloc);
|
||||||
|
|
||||||
|
const FUNCTION_ENTRY_ADDRESS: Address = Address::Symbol {
|
||||||
|
symbol: 0,
|
||||||
|
addend: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct FDEWriter {
|
||||||
|
vec: EndianVec<LittleEndian>,
|
||||||
|
relocs: Vec<FDERelocEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FDEWriter {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
vec: EndianVec::new(LittleEndian),
|
||||||
|
relocs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn into_vec_and_relocs(self) -> (Vec<u8>, Vec<FDERelocEntry>) {
|
||||||
|
(self.vec.into_vec(), self.relocs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writer for FDEWriter {
|
||||||
|
type Endian = LittleEndian;
|
||||||
|
fn endian(&self) -> Self::Endian {
|
||||||
|
LittleEndian
|
||||||
|
}
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.vec.len()
|
||||||
|
}
|
||||||
|
fn write(&mut self, bytes: &[u8]) -> Result<()> {
|
||||||
|
self.vec.write(bytes)
|
||||||
|
}
|
||||||
|
fn write_at(&mut self, offset: usize, bytes: &[u8]) -> Result<()> {
|
||||||
|
self.vec.write_at(offset, bytes)
|
||||||
|
}
|
||||||
|
fn write_address(&mut self, address: Address, size: u8) -> Result<()> {
|
||||||
|
match address {
|
||||||
|
Address::Constant(_) => self.vec.write_address(address, size),
|
||||||
|
Address::Symbol { .. } => {
|
||||||
|
assert_eq!(address, FUNCTION_ENTRY_ADDRESS);
|
||||||
|
let rt = match size {
|
||||||
|
4 => Reloc::Abs4,
|
||||||
|
8 => Reloc::Abs8,
|
||||||
|
_ => {
|
||||||
|
panic!("Unexpected address size at FDEWriter::write_address");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.relocs.push((self.vec.len().try_into().unwrap(), rt));
|
||||||
|
self.vec.write_udata(0, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_address_reg(isa: &dyn TargetIsa) -> Register {
|
||||||
|
assert!(isa.name() == "x86" && isa.pointer_bits() == 64);
|
||||||
|
X86_64::RA
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_reg(isa: &dyn TargetIsa, reg: RegUnit) -> Register {
|
||||||
|
assert!(isa.name() == "x86" && isa.pointer_bits() == 64);
|
||||||
|
// Mapping from https://github.com/bytecodealliance/cranelift/pull/902 by @iximeow
|
||||||
|
const X86_GP_REG_MAP: [gimli::Register; 16] = [
|
||||||
|
X86_64::RAX,
|
||||||
|
X86_64::RCX,
|
||||||
|
X86_64::RDX,
|
||||||
|
X86_64::RBX,
|
||||||
|
X86_64::RSP,
|
||||||
|
X86_64::RBP,
|
||||||
|
X86_64::RSI,
|
||||||
|
X86_64::RDI,
|
||||||
|
X86_64::R8,
|
||||||
|
X86_64::R9,
|
||||||
|
X86_64::R10,
|
||||||
|
X86_64::R11,
|
||||||
|
X86_64::R12,
|
||||||
|
X86_64::R13,
|
||||||
|
X86_64::R14,
|
||||||
|
X86_64::R15,
|
||||||
|
];
|
||||||
|
const X86_XMM_REG_MAP: [gimli::Register; 16] = [
|
||||||
|
X86_64::XMM0,
|
||||||
|
X86_64::XMM1,
|
||||||
|
X86_64::XMM2,
|
||||||
|
X86_64::XMM3,
|
||||||
|
X86_64::XMM4,
|
||||||
|
X86_64::XMM5,
|
||||||
|
X86_64::XMM6,
|
||||||
|
X86_64::XMM7,
|
||||||
|
X86_64::XMM8,
|
||||||
|
X86_64::XMM9,
|
||||||
|
X86_64::XMM10,
|
||||||
|
X86_64::XMM11,
|
||||||
|
X86_64::XMM12,
|
||||||
|
X86_64::XMM13,
|
||||||
|
X86_64::XMM14,
|
||||||
|
X86_64::XMM15,
|
||||||
|
];
|
||||||
|
let reg_info = isa.register_info();
|
||||||
|
let bank = reg_info.bank_containing_regunit(reg).unwrap();
|
||||||
|
match bank.name {
|
||||||
|
"IntRegs" => {
|
||||||
|
// x86 GP registers have a weird mapping to DWARF registers, so we use a
|
||||||
|
// lookup table.
|
||||||
|
X86_GP_REG_MAP[(reg - bank.first_unit) as usize]
|
||||||
|
}
|
||||||
|
"FloatRegs" => X86_XMM_REG_MAP[(reg - bank.first_unit) as usize],
|
||||||
|
_ => {
|
||||||
|
panic!("unsupported register bank: {}", bank.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_cfi(
|
||||||
|
isa: &dyn TargetIsa,
|
||||||
|
change: &FrameLayoutChange,
|
||||||
|
cfa_def_reg: &mut Register,
|
||||||
|
cfa_def_offset: &mut i32,
|
||||||
|
) -> Option<CallFrameInstruction> {
|
||||||
|
Some(match change {
|
||||||
|
FrameLayoutChange::CallFrameAddressAt { reg, offset } => {
|
||||||
|
let mapped = map_reg(isa, *reg);
|
||||||
|
let offset = (*offset) as i32;
|
||||||
|
if mapped != *cfa_def_reg && offset != *cfa_def_offset {
|
||||||
|
*cfa_def_reg = mapped;
|
||||||
|
*cfa_def_offset = offset;
|
||||||
|
CallFrameInstruction::Cfa(mapped, offset)
|
||||||
|
} else if offset != *cfa_def_offset {
|
||||||
|
*cfa_def_offset = offset;
|
||||||
|
CallFrameInstruction::CfaOffset(offset)
|
||||||
|
} else if mapped != *cfa_def_reg {
|
||||||
|
*cfa_def_reg = mapped;
|
||||||
|
CallFrameInstruction::CfaRegister(mapped)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrameLayoutChange::RegAt { reg, cfa_offset } => {
|
||||||
|
assert!(cfa_offset % -8 == 0);
|
||||||
|
let cfa_offset = *cfa_offset as i32;
|
||||||
|
let mapped = map_reg(isa, *reg);
|
||||||
|
CallFrameInstruction::Offset(mapped, cfa_offset)
|
||||||
|
}
|
||||||
|
FrameLayoutChange::ReturnAddressAt { cfa_offset } => {
|
||||||
|
assert!(cfa_offset % -8 == 0);
|
||||||
|
let cfa_offset = *cfa_offset as i32;
|
||||||
|
CallFrameInstruction::Offset(X86_64::RA, cfa_offset)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates FDE structure from FrameLayout.
|
||||||
|
pub fn emit_fde(func: &Function, isa: &dyn TargetIsa, sink: &mut dyn FrameUnwindSink) {
|
||||||
|
assert!(isa.name() == "x86");
|
||||||
|
|
||||||
|
// Expecting function with System V prologue
|
||||||
|
assert!(
|
||||||
|
func.signature.call_conv == CallConv::Fast
|
||||||
|
|| func.signature.call_conv == CallConv::Cold
|
||||||
|
|| func.signature.call_conv == CallConv::SystemV
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(func.frame_layout.is_some(), "expected func.frame_layout");
|
||||||
|
let frame_layout = func.frame_layout.as_ref().unwrap();
|
||||||
|
|
||||||
|
let mut ebbs = func.layout.ebbs().collect::<Vec<_>>();
|
||||||
|
ebbs.sort_by_key(|ebb| func.offsets[*ebb]); // Ensure inst offsets always increase
|
||||||
|
|
||||||
|
let encinfo = isa.encoding_info();
|
||||||
|
let mut last_offset = 0;
|
||||||
|
let mut changes = Vec::new();
|
||||||
|
for ebb in ebbs {
|
||||||
|
for (offset, inst, size) in func.inst_offsets(ebb, &encinfo) {
|
||||||
|
let address_offset = (offset + size) as usize;
|
||||||
|
assert!(last_offset <= address_offset);
|
||||||
|
if let Some(cmds) = frame_layout.instructions.get(&inst) {
|
||||||
|
for cmd in cmds.iter() {
|
||||||
|
changes.push((address_offset, cmd.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_offset = address_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = last_offset as u32;
|
||||||
|
|
||||||
|
let word_size = isa.pointer_bytes() as i32;
|
||||||
|
|
||||||
|
let encoding = Encoding {
|
||||||
|
format: Format::Dwarf32,
|
||||||
|
version: 1,
|
||||||
|
address_size: word_size as u8,
|
||||||
|
};
|
||||||
|
let mut frames = FrameTable::default();
|
||||||
|
|
||||||
|
let mut cfa_def_reg = return_address_reg(isa);
|
||||||
|
let mut cfa_def_offset = 0i32;
|
||||||
|
|
||||||
|
let mut cie = CommonInformationEntry::new(
|
||||||
|
encoding,
|
||||||
|
/* code_alignment_factor = */ 1,
|
||||||
|
/* data_alignment_factor = */ -word_size as i8,
|
||||||
|
return_address_reg(isa),
|
||||||
|
);
|
||||||
|
for ch in frame_layout.initial.iter() {
|
||||||
|
if let Some(cfi) = to_cfi(isa, ch, &mut cfa_def_reg, &mut cfa_def_offset) {
|
||||||
|
cie.add_instruction(cfi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cie_id = frames.add_cie(cie);
|
||||||
|
|
||||||
|
let mut fde = FrameDescriptionEntry::new(FUNCTION_ENTRY_ADDRESS, len);
|
||||||
|
|
||||||
|
for (addr, ch) in changes.iter() {
|
||||||
|
if let Some(cfi) = to_cfi(isa, ch, &mut cfa_def_reg, &mut cfa_def_offset) {
|
||||||
|
fde.add_instruction((*addr) as u32, cfi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.add_fde(cie_id, fde);
|
||||||
|
|
||||||
|
let mut eh_frame = EhFrame::from(FDEWriter::new());
|
||||||
|
frames.write_eh_frame(&mut eh_frame).unwrap();
|
||||||
|
|
||||||
|
let (bytes, relocs) = eh_frame.clone().into_vec_and_relocs();
|
||||||
|
|
||||||
|
let unwind_start = sink.len();
|
||||||
|
sink.bytes(&bytes);
|
||||||
|
|
||||||
|
for (off, r) in relocs {
|
||||||
|
sink.reloc(r, off + unwind_start);
|
||||||
|
}
|
||||||
|
let fde_offset = unsafe { ptr::read::<u32>(bytes.as_ptr() as *const u32) } as usize + 4;
|
||||||
|
sink.set_entry_offset(unwind_start + fde_offset);
|
||||||
|
|
||||||
|
// Need 0 marker for GCC unwind to end FDE "list".
|
||||||
|
sink.bytes(&[0, 0, 0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::binemit::{FrameUnwindOffset, Reloc};
|
||||||
|
use crate::cursor::{Cursor, FuncCursor};
|
||||||
|
use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind};
|
||||||
|
use crate::isa::{lookup, CallConv};
|
||||||
|
use crate::settings::{builder, Flags};
|
||||||
|
use crate::Context;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use target_lexicon::triple;
|
||||||
|
|
||||||
|
struct SimpleUnwindSink(pub Vec<u8>, pub usize, pub Vec<(Reloc, usize)>);
|
||||||
|
impl FrameUnwindSink for SimpleUnwindSink {
|
||||||
|
fn len(&self) -> FrameUnwindOffset {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
fn bytes(&mut self, b: &[u8]) {
|
||||||
|
self.0.extend_from_slice(b);
|
||||||
|
}
|
||||||
|
fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) {
|
||||||
|
self.2.push((r, off));
|
||||||
|
}
|
||||||
|
fn set_entry_offset(&mut self, off: FrameUnwindOffset) {
|
||||||
|
self.1 = off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_func() {
|
||||||
|
let isa = lookup(triple!("x86_64"))
|
||||||
|
.expect("expect x86 ISA")
|
||||||
|
.finish(Flags::new(builder()));
|
||||||
|
|
||||||
|
let mut context = Context::for_function(create_function(
|
||||||
|
CallConv::SystemV,
|
||||||
|
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
|
||||||
|
));
|
||||||
|
context.func.collect_frame_layout_info();
|
||||||
|
|
||||||
|
context.compile(&*isa).expect("expected compilation");
|
||||||
|
|
||||||
|
let mut sink = SimpleUnwindSink(Vec::new(), 0, Vec::new());
|
||||||
|
emit_fde(&context.func, &*isa, &mut sink);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
sink.0,
|
||||||
|
vec![
|
||||||
|
20, 0, 0, 0, // CIE len
|
||||||
|
0, 0, 0, 0, // CIE marker
|
||||||
|
1, // version
|
||||||
|
0, // augmentation string
|
||||||
|
1, // code aligment = 1
|
||||||
|
120, // data alignment = -8
|
||||||
|
16, // RA = r16
|
||||||
|
0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8
|
||||||
|
0x90, 0x01, // DW_CFA_offset r16, -8 * 1
|
||||||
|
0, 0, 0, 0, 0, 0, // padding
|
||||||
|
36, 0, 0, 0, // FDE len
|
||||||
|
28, 0, 0, 0, // CIE offset
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, // addr reloc
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0, // function length
|
||||||
|
0x42, // DW_CFA_advance_loc 2
|
||||||
|
0x0e, 0x10, // DW_CFA_def_cfa_offset 16
|
||||||
|
0x86, 0x02, // DW_CFA_offset r6, -8 * 2
|
||||||
|
0x43, // DW_CFA_advance_loc 3
|
||||||
|
0x0d, 0x06, // DW_CFA_def_cfa_register
|
||||||
|
0x4a, // DW_CFA_advance_loc 10
|
||||||
|
0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8
|
||||||
|
0, 0, 0, 0, // padding
|
||||||
|
0, 0, 0, 0, // End of FDEs
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(sink.1, 24);
|
||||||
|
assert_eq!(sink.2.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
|
||||||
|
let mut func =
|
||||||
|
Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
|
||||||
|
|
||||||
|
let ebb0 = func.dfg.make_ebb();
|
||||||
|
let mut pos = FuncCursor::new(&mut func);
|
||||||
|
pos.insert_ebb(ebb0);
|
||||||
|
pos.ins().return_(&[]);
|
||||||
|
|
||||||
|
if let Some(stack_slot) = stack_slot {
|
||||||
|
func.stack_slots.push(stack_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
func
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,19 @@
|
|||||||
mod abi;
|
mod abi;
|
||||||
mod binemit;
|
mod binemit;
|
||||||
mod enc_tables;
|
mod enc_tables;
|
||||||
|
#[cfg(feature = "unwind")]
|
||||||
|
mod fde;
|
||||||
mod registers;
|
mod registers;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
#[cfg(feature = "unwind")]
|
||||||
mod unwind;
|
mod unwind;
|
||||||
|
|
||||||
use super::super::settings as shared_settings;
|
use super::super::settings as shared_settings;
|
||||||
#[cfg(feature = "testing_hooks")]
|
#[cfg(feature = "testing_hooks")]
|
||||||
use crate::binemit::CodeSink;
|
use crate::binemit::CodeSink;
|
||||||
use crate::binemit::{emit_function, MemoryCodeSink};
|
use crate::binemit::{emit_function, MemoryCodeSink};
|
||||||
|
#[cfg(feature = "unwind")]
|
||||||
|
use crate::binemit::{FrameUnwindKind, FrameUnwindSink};
|
||||||
use crate::ir;
|
use crate::ir;
|
||||||
use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
|
use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
|
||||||
use crate::isa::Builder as IsaBuilder;
|
use crate::isa::Builder as IsaBuilder;
|
||||||
@@ -20,7 +25,6 @@ use crate::result::CodegenResult;
|
|||||||
use crate::timing;
|
use crate::timing;
|
||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use target_lexicon::{PointerWidth, Triple};
|
use target_lexicon::{PointerWidth, Triple};
|
||||||
|
|
||||||
@@ -157,8 +161,14 @@ impl TargetIsa for Isa {
|
|||||||
/// Emit unwind information for the given function.
|
/// Emit unwind information for the given function.
|
||||||
///
|
///
|
||||||
/// Only some calling conventions (e.g. Windows fastcall) will have unwind information.
|
/// Only some calling conventions (e.g. Windows fastcall) will have unwind information.
|
||||||
fn emit_unwind_info(&self, func: &ir::Function, mem: &mut Vec<u8>) {
|
#[cfg(feature = "unwind")]
|
||||||
abi::emit_unwind_info(func, self, mem);
|
fn emit_unwind_info(
|
||||||
|
&self,
|
||||||
|
func: &ir::Function,
|
||||||
|
kind: FrameUnwindKind,
|
||||||
|
sink: &mut dyn FrameUnwindSink,
|
||||||
|
) {
|
||||||
|
abi::emit_unwind_info(func, self, kind, sink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Unwind information for x64 Windows.
|
//! Unwind information for x64 Windows.
|
||||||
|
|
||||||
use super::registers::RU;
|
use super::registers::RU;
|
||||||
|
use crate::binemit::FrameUnwindSink;
|
||||||
use crate::ir::{Function, InstructionData, Opcode};
|
use crate::ir::{Function, InstructionData, Opcode};
|
||||||
use crate::isa::{CallConv, RegUnit, TargetIsa};
|
use crate::isa::{CallConv, RegUnit, TargetIsa};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
@@ -11,16 +12,20 @@ const SMALL_ALLOC_MAX_SIZE: u32 = 128;
|
|||||||
/// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits
|
/// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits
|
||||||
const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
|
const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
|
||||||
|
|
||||||
fn write_u16<T: ByteOrder>(mem: &mut Vec<u8>, v: u16) {
|
fn write_u8(sink: &mut dyn FrameUnwindSink, v: u8) {
|
||||||
let mut buf = [0; 2];
|
sink.bytes(&[v]);
|
||||||
T::write_u16(&mut buf, v);
|
|
||||||
mem.extend(buf.iter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_u32<T: ByteOrder>(mem: &mut Vec<u8>, v: u32) {
|
fn write_u16<T: ByteOrder>(sink: &mut dyn FrameUnwindSink, v: u16) {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
T::write_u16(&mut buf, v);
|
||||||
|
sink.bytes(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_u32<T: ByteOrder>(sink: &mut dyn FrameUnwindSink, v: u32) {
|
||||||
let mut buf = [0; 4];
|
let mut buf = [0; 4];
|
||||||
T::write_u32(&mut buf, v);
|
T::write_u32(&mut buf, v);
|
||||||
mem.extend(buf.iter());
|
sink.bytes(&buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The supported unwind codes for the x64 Windows ABI.
|
/// The supported unwind codes for the x64 Windows ABI.
|
||||||
@@ -36,7 +41,7 @@ enum UnwindCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UnwindCode {
|
impl UnwindCode {
|
||||||
fn emit(&self, mem: &mut Vec<u8>) {
|
fn emit(&self, sink: &mut dyn FrameUnwindSink) {
|
||||||
enum UnwindOperation {
|
enum UnwindOperation {
|
||||||
PushNonvolatileRegister,
|
PushNonvolatileRegister,
|
||||||
LargeStackAlloc,
|
LargeStackAlloc,
|
||||||
@@ -46,30 +51,37 @@ impl UnwindCode {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::PushRegister { offset, reg } => {
|
Self::PushRegister { offset, reg } => {
|
||||||
mem.push(*offset);
|
write_u8(sink, *offset);
|
||||||
mem.push(((*reg as u8) << 4) | (UnwindOperation::PushNonvolatileRegister as u8));
|
write_u8(
|
||||||
|
sink,
|
||||||
|
((*reg as u8) << 4) | (UnwindOperation::PushNonvolatileRegister as u8),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Self::StackAlloc { offset, size } => {
|
Self::StackAlloc { offset, size } => {
|
||||||
// Stack allocations on Windows must be a multiple of 8 and be at least 1 slot
|
// Stack allocations on Windows must be a multiple of 8 and be at least 1 slot
|
||||||
assert!(*size >= 8);
|
assert!(*size >= 8);
|
||||||
assert!((*size % 8) == 0);
|
assert!((*size % 8) == 0);
|
||||||
|
|
||||||
mem.push(*offset);
|
write_u8(sink, *offset);
|
||||||
if *size <= SMALL_ALLOC_MAX_SIZE {
|
if *size <= SMALL_ALLOC_MAX_SIZE {
|
||||||
mem.push(
|
write_u8(
|
||||||
|
sink,
|
||||||
((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
|
((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
|
||||||
);
|
);
|
||||||
} else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
|
} else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
|
||||||
mem.push(UnwindOperation::LargeStackAlloc as u8);
|
write_u8(sink, UnwindOperation::LargeStackAlloc as u8);
|
||||||
write_u16::<LittleEndian>(mem, (*size / 8) as u16);
|
write_u16::<LittleEndian>(sink, (*size / 8) as u16);
|
||||||
} else {
|
} else {
|
||||||
mem.push((1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
|
write_u8(sink, (1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
|
||||||
write_u32::<LittleEndian>(mem, *size);
|
write_u32::<LittleEndian>(sink, *size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::SetFramePointer { offset, sp_offset } => {
|
Self::SetFramePointer { offset, sp_offset } => {
|
||||||
mem.push(*offset);
|
write_u8(sink, *offset);
|
||||||
mem.push((*sp_offset << 4) | (UnwindOperation::SetFramePointer as u8));
|
write_u8(
|
||||||
|
sink,
|
||||||
|
(*sp_offset << 4) | (UnwindOperation::SetFramePointer as u8),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -231,48 +243,49 @@ impl UnwindInfo {
|
|||||||
.fold(0, |nodes, c| nodes + c.node_count())
|
.fold(0, |nodes, c| nodes + c.node_count())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit(&self, mem: &mut Vec<u8>) {
|
pub fn emit(&self, sink: &mut dyn FrameUnwindSink) {
|
||||||
const UNWIND_INFO_VERSION: u8 = 1;
|
const UNWIND_INFO_VERSION: u8 = 1;
|
||||||
|
|
||||||
let size = self.size();
|
let size = self.size();
|
||||||
let offset = mem.len();
|
let offset = sink.len();
|
||||||
|
|
||||||
// Ensure the memory is 32-bit aligned
|
// Ensure the memory is 32-bit aligned
|
||||||
assert_eq!(offset % 4, 0);
|
assert_eq!(offset % 4, 0);
|
||||||
|
|
||||||
mem.reserve(offset + size);
|
sink.reserve(offset + size);
|
||||||
|
|
||||||
let node_count = self.node_count();
|
let node_count = self.node_count();
|
||||||
assert!(node_count <= 256);
|
assert!(node_count <= 256);
|
||||||
|
|
||||||
mem.push((self.flags << 3) | UNWIND_INFO_VERSION);
|
write_u8(sink, (self.flags << 3) | UNWIND_INFO_VERSION);
|
||||||
mem.push(self.prologue_size);
|
write_u8(sink, self.prologue_size);
|
||||||
mem.push(node_count as u8);
|
write_u8(sink, node_count as u8);
|
||||||
|
|
||||||
if let Some(reg) = self.frame_register {
|
if let Some(reg) = self.frame_register {
|
||||||
mem.push((self.frame_register_offset << 4) | reg as u8);
|
write_u8(sink, (self.frame_register_offset << 4) | reg as u8);
|
||||||
} else {
|
} else {
|
||||||
mem.push(0);
|
write_u8(sink, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwind codes are written in reverse order (prologue offset descending)
|
// Unwind codes are written in reverse order (prologue offset descending)
|
||||||
for code in self.unwind_codes.iter().rev() {
|
for code in self.unwind_codes.iter().rev() {
|
||||||
code.emit(mem);
|
code.emit(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
// To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes
|
// To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes
|
||||||
if (node_count & 1) == 1 {
|
if (node_count & 1) == 1 {
|
||||||
write_u16::<LittleEndian>(mem, 0);
|
write_u16::<LittleEndian>(sink, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the correct number of bytes was emitted
|
// Ensure the correct number of bytes was emitted
|
||||||
assert_eq!(mem.len() - offset, size);
|
assert_eq!(sink.len() - offset, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::binemit::{FrameUnwindOffset, Reloc};
|
||||||
use crate::cursor::{Cursor, FuncCursor};
|
use crate::cursor::{Cursor, FuncCursor};
|
||||||
use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind};
|
use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind};
|
||||||
use crate::isa::{lookup, CallConv};
|
use crate::isa::{lookup, CallConv};
|
||||||
@@ -281,6 +294,18 @@ mod tests {
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use target_lexicon::triple;
|
use target_lexicon::triple;
|
||||||
|
|
||||||
|
struct SimpleUnwindSink(pub Vec<u8>);
|
||||||
|
impl FrameUnwindSink for SimpleUnwindSink {
|
||||||
|
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) {}
|
||||||
|
fn set_entry_offset(&mut self, _: FrameUnwindOffset) {}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrong_calling_convention() {
|
fn test_wrong_calling_convention() {
|
||||||
let isa = lookup(triple!("x86_64"))
|
let isa = lookup(triple!("x86_64"))
|
||||||
@@ -336,11 +361,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(unwind.size(), 12);
|
assert_eq!(unwind.size(), 12);
|
||||||
|
|
||||||
let mut mem = Vec::new();
|
let mut sink = SimpleUnwindSink(Vec::new());
|
||||||
unwind.emit(&mut mem);
|
unwind.emit(&mut sink);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem,
|
sink.0,
|
||||||
[
|
[
|
||||||
0x01, // Version and flags (version 1, no flags)
|
0x01, // Version and flags (version 1, no flags)
|
||||||
0x09, // Prologue size
|
0x09, // Prologue size
|
||||||
@@ -400,11 +425,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(unwind.size(), 12);
|
assert_eq!(unwind.size(), 12);
|
||||||
|
|
||||||
let mut mem = Vec::new();
|
let mut sink = SimpleUnwindSink(Vec::new());
|
||||||
unwind.emit(&mut mem);
|
unwind.emit(&mut sink);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem,
|
sink.0,
|
||||||
[
|
[
|
||||||
0x01, // Version and flags (version 1, no flags)
|
0x01, // Version and flags (version 1, no flags)
|
||||||
0x1B, // Prologue size
|
0x1B, // Prologue size
|
||||||
@@ -464,11 +489,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(unwind.size(), 16);
|
assert_eq!(unwind.size(), 16);
|
||||||
|
|
||||||
let mut mem = Vec::new();
|
let mut sink = SimpleUnwindSink(Vec::new());
|
||||||
unwind.emit(&mut mem);
|
unwind.emit(&mut sink);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem,
|
sink.0,
|
||||||
[
|
[
|
||||||
0x01, // Version and flags (version 1, no flags)
|
0x01, // Version and flags (version 1, no flags)
|
||||||
0x1B, // Prologue size
|
0x1B, // Prologue size
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ cranelift-reader = { path = "../cranelift-reader", version = "0.54.0" }
|
|||||||
cranelift-preopt = { path = "../cranelift-preopt", version = "0.54.0" }
|
cranelift-preopt = { path = "../cranelift-preopt", version = "0.54.0" }
|
||||||
file-per-thread-logger = "0.1.2"
|
file-per-thread-logger = "0.1.2"
|
||||||
filecheck = "0.4.0"
|
filecheck = "0.4.0"
|
||||||
|
gimli = { version = "0.19.0", default-features = false, features = ["read"] }
|
||||||
log = "0.4.6"
|
log = "0.4.6"
|
||||||
memmap = "0.7.0"
|
memmap = "0.7.0"
|
||||||
num_cpus = "1.8.0"
|
num_cpus = "1.8.0"
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
test fde
|
||||||
|
set opt_level=speed_and_size
|
||||||
|
set is_pic
|
||||||
|
target x86_64 haswell
|
||||||
|
|
||||||
|
; check that there is no libunwind information for a windows_fastcall function
|
||||||
|
function %not_fastcall() windows_fastcall {
|
||||||
|
ebb0:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
; sameln: No unwind information.
|
||||||
|
|
||||||
|
; check the libunwind information with a function with no args
|
||||||
|
function %no_args() system_v {
|
||||||
|
ebb0:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
; sameln: 0x00000000: CIE
|
||||||
|
; nextln: length: 0x00000014
|
||||||
|
; nextln: version: 0x01
|
||||||
|
; nextln: code_align: 1
|
||||||
|
; nextln: data_align: -8
|
||||||
|
; nextln: ra_register: 0x10
|
||||||
|
; nextln: DW_CFA_def_cfa (r7, 8)
|
||||||
|
; nextln: DW_CFA_offset (r16, 1)
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: Instructions: Init State:
|
||||||
|
; nextln:
|
||||||
|
; nextln:
|
||||||
|
; nextln: 0x00000018: FDE
|
||||||
|
; nextln: length: 0x00000024
|
||||||
|
; nextln: CIE_pointer: 0x00000000
|
||||||
|
; nextln: start_addr: 0x0000000000000000
|
||||||
|
; nextln: range_size: 0x0000000000000006 (end_addr = 0x0000000000000006)
|
||||||
|
; nextln: Instructions:
|
||||||
|
; nextln: DW_CFA_advance_loc (1)
|
||||||
|
; nextln: DW_CFA_def_cfa_offset (16)
|
||||||
|
; nextln: DW_CFA_offset (r6, 2)
|
||||||
|
; nextln: DW_CFA_advance_loc (3)
|
||||||
|
; nextln: DW_CFA_def_cfa_register (r6)
|
||||||
|
; nextln: DW_CFA_advance_loc (1)
|
||||||
|
; nextln: DW_CFA_def_cfa (r7, 8)
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln: DW_CFA_nop
|
||||||
|
; nextln:
|
||||||
|
; nextln: Entry: 24
|
||||||
|
; nextln: Relocs: [(Abs8, 32)]
|
||||||
@@ -42,6 +42,7 @@ mod test_cat;
|
|||||||
mod test_compile;
|
mod test_compile;
|
||||||
mod test_dce;
|
mod test_dce;
|
||||||
mod test_domtree;
|
mod test_domtree;
|
||||||
|
mod test_fde;
|
||||||
mod test_legalizer;
|
mod test_legalizer;
|
||||||
mod test_licm;
|
mod test_licm;
|
||||||
mod test_postopt;
|
mod test_postopt;
|
||||||
@@ -137,6 +138,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult<Box<dyn subtest::
|
|||||||
"preopt" => test_preopt::subtest(parsed),
|
"preopt" => test_preopt::subtest(parsed),
|
||||||
"safepoint" => test_safepoint::subtest(parsed),
|
"safepoint" => test_safepoint::subtest(parsed),
|
||||||
"unwind" => test_unwind::subtest(parsed),
|
"unwind" => test_unwind::subtest(parsed),
|
||||||
|
"fde" => test_fde::subtest(parsed),
|
||||||
_ => Err(format!("unknown test command '{}'", parsed.command)),
|
_ => Err(format!("unknown test command '{}'", parsed.command)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
415
cranelift/filetests/src/test_fde.rs
Normal file
415
cranelift/filetests/src/test_fde.rs
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
//! 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 cranelift_codegen;
|
||||||
|
use cranelift_codegen::binemit::{FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc};
|
||||||
|
use cranelift_codegen::ir;
|
||||||
|
use cranelift_reader::TestCommand;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
struct TestUnwind;
|
||||||
|
|
||||||
|
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> {
|
||||||
|
assert_eq!(parsed.command, "fde");
|
||||||
|
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 {
|
||||||
|
"fde"
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
if func.signature.call_conv != cranelift_codegen::isa::CallConv::SystemV {
|
||||||
|
return run_filecheck(&"No unwind information.", context);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
|
||||||
|
comp_ctx.func.collect_frame_layout_info();
|
||||||
|
|
||||||
|
comp_ctx.compile(isa).expect("failed to compile function");
|
||||||
|
|
||||||
|
struct SimpleUnwindSink(pub Vec<u8>, pub usize, pub Vec<(Reloc, usize)>);
|
||||||
|
impl FrameUnwindSink for SimpleUnwindSink {
|
||||||
|
fn len(&self) -> FrameUnwindOffset {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
fn bytes(&mut self, b: &[u8]) {
|
||||||
|
self.0.extend_from_slice(b);
|
||||||
|
}
|
||||||
|
fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) {
|
||||||
|
self.2.push((r, off));
|
||||||
|
}
|
||||||
|
fn set_entry_offset(&mut self, off: FrameUnwindOffset) {
|
||||||
|
self.1 = off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sink = SimpleUnwindSink(Vec::new(), 0, Vec::new());
|
||||||
|
comp_ctx.emit_unwind_info(isa, FrameUnwindKind::Libunwind, &mut sink);
|
||||||
|
|
||||||
|
let mut text = String::new();
|
||||||
|
if sink.0.is_empty() {
|
||||||
|
writeln!(text, "No unwind information.").unwrap();
|
||||||
|
} else {
|
||||||
|
print_unwind_info(&mut text, &sink.0, isa.pointer_bytes());
|
||||||
|
writeln!(text, "Entry: {}", sink.1).unwrap();
|
||||||
|
writeln!(text, "Relocs: {:?}", sink.2).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
run_filecheck(&text, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {
|
||||||
|
Cow::Owned(format!("r{}", register.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_unwind_info(text: &mut String, mem: &[u8], address_size: u8) {
|
||||||
|
let mut eh_frame = gimli::EhFrame::new(mem, gimli::LittleEndian);
|
||||||
|
eh_frame.set_address_size(address_size);
|
||||||
|
let bases = gimli::BaseAddresses::default();
|
||||||
|
dwarfdump::dump_eh_frame(text, &eh_frame, &bases, ®ister_name).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
mod dwarfdump {
|
||||||
|
// 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 {
|
||||||
|
Error::GimliError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<fmt::Error> for Error {
|
||||||
|
fn from(_: fmt::Error) -> Self {
|
||||||
|
Error::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")?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
|
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use cranelift_codegen;
|
use cranelift_codegen;
|
||||||
|
use cranelift_codegen::binemit::{FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc};
|
||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_reader::TestCommand;
|
use cranelift_reader::TestCommand;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
@@ -41,14 +42,30 @@ impl SubTest for TestUnwind {
|
|||||||
|
|
||||||
comp_ctx.compile(isa).expect("failed to compile function");
|
comp_ctx.compile(isa).expect("failed to compile function");
|
||||||
|
|
||||||
let mut mem = Vec::new();
|
struct Sink(Vec<u8>);
|
||||||
comp_ctx.emit_unwind_info(isa, &mut mem);
|
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);
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
if mem.is_empty() {
|
if sink.0.is_empty() {
|
||||||
writeln!(text, "No unwind information.").unwrap();
|
writeln!(text, "No unwind information.").unwrap();
|
||||||
} else {
|
} else {
|
||||||
print_unwind_info(&mut text, &mem);
|
print_unwind_info(&mut text, &sink.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
run_filecheck(&text, context)
|
run_filecheck(&text, context)
|
||||||
|
|||||||
Reference in New Issue
Block a user