diff --git a/src/unwinder/arch/mod.rs b/src/unwinder/arch/mod.rs index 1d007ec..67d08c1 100644 --- a/src/unwinder/arch/mod.rs +++ b/src/unwinder/arch/mod.rs @@ -3,6 +3,11 @@ mod x86_64; #[cfg(target_arch = "x86_64")] pub use x86_64::*; +#[cfg(target_arch = "x86")] +mod x86; +#[cfg(target_arch = "x86")] +pub use x86::*; + #[cfg(target_arch = "riscv64")] mod riscv64; #[cfg(target_arch = "riscv64")] @@ -15,6 +20,7 @@ pub use aarch64::*; #[cfg(not(any( target_arch = "x86_64", + target_arch = "x86", target_arch = "riscv64", target_arch = "aarch64" )))] diff --git a/src/unwinder/arch/x86.rs b/src/unwinder/arch/x86.rs new file mode 100644 index 0000000..ec55416 --- /dev/null +++ b/src/unwinder/arch/x86.rs @@ -0,0 +1,120 @@ +use core::fmt; +use core::ops; +use gimli::{Register, X86}; + +#[repr(C)] +#[derive(Clone, Default)] +pub struct Context { + pub registers: [usize; 8], + pub ra: usize, + pub mcxsr: usize, + pub fcw: usize, +} + +impl fmt::Debug for Context { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fmt = fmt.debug_struct("Context"); + for i in 0..=7 { + fmt.field( + X86::register_name(Register(i as _)).unwrap(), + &self.registers[i], + ); + } + fmt.field("ra", &self.ra) + .field("mcxsr", &self.mcxsr) + .field("fcw", &self.fcw) + .finish() + } +} + +impl ops::Index for Context { + type Output = usize; + + fn index(&self, reg: Register) -> &usize { + match reg { + Register(0..=7) => &self.registers[reg.0 as usize], + X86::RA => &self.ra, + X86::MXCSR => &self.mcxsr, + _ => unimplemented!(), + } + } +} + +impl ops::IndexMut for Context { + fn index_mut(&mut self, reg: Register) -> &mut usize { + match reg { + Register(0..=7) => &mut self.registers[reg.0 as usize], + X86::RA => &mut self.ra, + X86::MXCSR => &mut self.mcxsr, + _ => unimplemented!(), + } + } +} + +#[naked] +pub extern "C-unwind" fn save_context() -> Context { + // No need to save caller-saved registers here. + unsafe { + asm!( + " + mov eax, [esp + 4] + mov [eax + 4], ecx + mov [eax + 8], edx + mov [eax + 12], ebx + mov [eax + 20], ebp + mov [eax + 24], esi + mov [eax + 28], edi + + /* Adjust the stack to account for the return address */ + lea edx, [esp + 4] + mov [eax + 16], edx + + mov edx, [esp] + mov [eax + 32], edx + stmxcsr [eax + 36] + fnstcw [eax + 40] + ret 4 + ", + options(noreturn) + ); + } +} + +#[naked] +pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { + unsafe { + asm!( + " + mov edx, [esp + 4] + + /* Restore stack */ + mov esp, [edx + 16] + + /* Restore callee-saved control registers */ + ldmxcsr [edx + 36] + fldcw [edx + 40] + + /* Restore return address */ + mov eax, [edx + 32] + push eax + + /* + * Restore general-purpose registers. Non-callee-saved registers are + * also restored because sometimes it's used to pass unwind arguments. + */ + mov eax, [edx + 0] + mov ecx, [edx + 4] + mov ebx, [edx + 12] + mov ebp, [edx + 20] + mov esi, [edx + 24] + mov edi, [edx + 28] + + /* EDX restored last */ + mov edx, [edx + 8] + + ret + ", + options(noreturn) + ); + } +}