use crate::unwind::UnwindOps;
const SMALL_ALLOC_MAX_SIZE: u32 = 128;
const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
struct Writer<'a> {
buf: &'a mut [u8],
offset: usize,
}
impl<'a> Writer<'a> {
pub fn new(buf: &'a mut [u8]) -> Self {
Self { buf, offset: 0 }
}
fn write_u8(&mut self, v: u8) {
self.buf[self.offset] = v;
self.offset += 1;
}
fn write_u16_le(&mut self, v: u16) {
self.buf[self.offset..(self.offset + 2)].copy_from_slice(&v.to_le_bytes());
self.offset += 2;
}
fn write_u32_le(&mut self, v: u32) {
self.buf[self.offset..(self.offset + 4)].copy_from_slice(&v.to_le_bytes());
self.offset += 4;
}
}
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum UnwindCode {
PushRegister {
instruction_offset: u8,
reg: u8,
},
SaveReg {
instruction_offset: u8,
reg: u8,
stack_offset: u32,
},
SaveXmm {
instruction_offset: u8,
reg: u8,
stack_offset: u32,
},
StackAlloc {
instruction_offset: u8,
size: u32,
},
SetFPReg {
instruction_offset: u8,
},
}
impl UnwindCode {
fn emit(&self, writer: &mut Writer) {
enum UnwindOperation {
PushNonvolatileRegister = 0,
LargeStackAlloc = 1,
SmallStackAlloc = 2,
SetFPReg = 3,
SaveNonVolatileRegister = 4,
SaveNonVolatileRegisterFar = 5,
SaveXmm128 = 8,
SaveXmm128Far = 9,
}
match self {
Self::PushRegister {
instruction_offset,
reg,
} => {
writer.write_u8(*instruction_offset);
writer.write_u8((*reg << 4) | (UnwindOperation::PushNonvolatileRegister as u8));
}
Self::SaveReg {
instruction_offset,
reg,
stack_offset,
}
| Self::SaveXmm {
instruction_offset,
reg,
stack_offset,
} => {
let is_xmm = matches!(self, Self::SaveXmm { .. });
let (op_small, op_large) = if is_xmm {
(UnwindOperation::SaveXmm128, UnwindOperation::SaveXmm128Far)
} else {
(
UnwindOperation::SaveNonVolatileRegister,
UnwindOperation::SaveNonVolatileRegisterFar,
)
};
writer.write_u8(*instruction_offset);
let scaled_stack_offset = stack_offset / 16;
if scaled_stack_offset <= u16::MAX as u32 {
writer.write_u8((*reg << 4) | (op_small as u8));
writer.write_u16_le(scaled_stack_offset as u16);
} else {
writer.write_u8((*reg << 4) | (op_large as u8));
writer.write_u16_le(*stack_offset as u16);
writer.write_u16_le((stack_offset >> 16) as u16);
}
}
Self::StackAlloc {
instruction_offset,
size,
} => {
assert!(*size >= 8);
assert!((*size % 8) == 0);
writer.write_u8(*instruction_offset);
if *size <= SMALL_ALLOC_MAX_SIZE {
writer.write_u8(
((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
);
} else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
writer.write_u8(UnwindOperation::LargeStackAlloc as u8);
writer.write_u16_le((*size / 8) as u16);
} else {
writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
writer.write_u32_le(*size);
}
}
Self::SetFPReg { instruction_offset } => {
writer.write_u8(*instruction_offset);
writer.write_u8(UnwindOperation::SetFPReg as u8);
}
}
}
fn node_count(&self) -> usize {
match self {
Self::StackAlloc { size, .. } => {
if *size <= SMALL_ALLOC_MAX_SIZE {
1
} else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
2
} else {
3
}
}
Self::SaveXmm { stack_offset, .. } | Self::SaveReg { stack_offset, .. } => {
if *stack_offset <= u16::MAX as u32 {
2
} else {
3
}
}
_ => 1,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct UnwindInfo {
pub(crate) flags: u8,
pub(crate) prologue_size: u8,
pub(crate) frame_register: Option<u8>,
pub(crate) frame_register_offset: u8,
pub(crate) unwind_codes: Vec<UnwindCode>,
}
impl UnwindInfo {
pub fn emit_size(&self) -> usize {
let node_count = self.node_count();
assert!(self.flags == 0);
4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
}
pub fn emit(&self, buf: &mut [u8]) {
const UNWIND_INFO_VERSION: u8 = 1;
let node_count = self.node_count();
assert!(node_count <= 256);
let mut writer = Writer::new(buf);
writer.write_u8((self.flags << 3) | UNWIND_INFO_VERSION);
writer.write_u8(self.prologue_size);
writer.write_u8(node_count as u8);
if let Some(reg) = self.frame_register {
writer.write_u8((self.frame_register_offset << 4) | reg);
} else {
writer.write_u8(0);
}
for code in self.unwind_codes.iter().rev() {
code.emit(&mut writer);
}
if (node_count & 1) == 1 {
writer.write_u16_le(0);
}
assert_eq!(writer.offset, self.emit_size());
}
fn node_count(&self) -> usize {
self.unwind_codes
.iter()
.fold(0, |nodes, c| nodes + c.node_count())
}
}
const UNWIND_RBP_REG: u8 = 5;
pub(crate) fn create_unwind_info_from_insts(insts: &[(usize, UnwindOps)]) -> Option<UnwindInfo> {
let mut unwind_codes = vec![];
let mut frame_register_offset = 0;
let mut max_unwind_offset = 0;
for &(instruction_offset, ref inst) in insts {
let instruction_offset = ensure_unwind_offset(instruction_offset as u32)?;
match *inst {
UnwindOps::PushFP { .. } => {
unwind_codes.push(UnwindCode::PushRegister {
instruction_offset,
reg: UNWIND_RBP_REG,
});
}
UnwindOps::DefineNewFrame => {
frame_register_offset = ensure_unwind_offset(32)?;
unwind_codes.push(UnwindCode::SetFPReg { instruction_offset });
}
UnwindOps::SaveRegister { reg, bp_neg_offset } => match reg {
0..=15 => {
static FROM_DWARF: [u8; 16] =
[0, 2, 1, 3, 6, 7, 5, 4, 8, 9, 10, 11, 12, 13, 14, 15];
unwind_codes.push(UnwindCode::SaveReg {
instruction_offset,
reg: FROM_DWARF[reg as usize],
stack_offset: bp_neg_offset as u32,
});
}
17..=32 => {
unwind_codes.push(UnwindCode::SaveXmm {
instruction_offset,
reg: reg as u8 - 17,
stack_offset: bp_neg_offset as u32,
});
}
_ => {
unreachable!("unknown register index {}", reg);
}
},
UnwindOps::Push2Regs { .. } => {
unreachable!("no aarch64 on x64");
}
}
max_unwind_offset = instruction_offset;
}
Some(UnwindInfo {
flags: 0,
prologue_size: max_unwind_offset,
frame_register: Some(UNWIND_RBP_REG),
frame_register_offset,
unwind_codes,
})
}
fn ensure_unwind_offset(offset: u32) -> Option<u8> {
if offset > 255 {
panic!("function prologues cannot exceed 255 bytes in size for Windows x64");
}
Some(offset as u8)
}