diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f60659..e20aef2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - aarch64-unknown-linux-gnu - riscv64gc-unknown-linux-gnu - riscv32gc-unknown-linux-gnu + - loongarch64-unknown-linux-gnu runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 4f2a0e8..a9c9918 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This library serves two purposes: 1. Provide a pure Rust alternative to libgcc_eh or libunwind. 2. Provide easier unwinding support for `#![no_std]` targets. -Currently supports x86_64, x86, RV64, RV32 and AArch64. +Currently supports x86_64, x86, RV64, RV32, AArch64, and LoongArch64. ## Unwinder diff --git a/src/arch.rs b/src/arch.rs index 33b3811..1d96a5d 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -70,11 +70,30 @@ mod aarch64 { #[cfg(target_arch = "aarch64")] pub use aarch64::*; +#[cfg(target_arch = "loongarch64")] +mod loongarch64 { + use gimli::{LoongArch, Register}; + + pub struct Arch; + + #[allow(unused)] + impl Arch { + pub const SP: Register = LoongArch::SP; + pub const RA: Register = LoongArch::RA; + + pub const UNWIND_DATA_REG: (Register, Register) = (LoongArch::A0, LoongArch::A1); + pub const UNWIND_PRIVATE_DATA_SIZE: usize = 2; + } +} +#[cfg(target_arch = "loongarch64")] +pub use loongarch64::*; + #[cfg(not(any( target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", target_arch = "riscv32", - target_arch = "aarch64" + target_arch = "aarch64", + target_arch = "loongarch64" )))] compile_error!("Current architecture is not supported"); diff --git a/src/unwinder/arch/loongarch64.rs b/src/unwinder/arch/loongarch64.rs new file mode 100644 index 0000000..b94dddd --- /dev/null +++ b/src/unwinder/arch/loongarch64.rs @@ -0,0 +1,250 @@ +use core::fmt; +use core::ops; +use gimli::{LoongArch, Register}; + +use super::maybe_cfi; + +// LoongArch64's DWARF_FRAME_REGISTERS in GCC is 74 +pub const MAX_REG_RULES: usize = 74; + +// https://doc.rust-lang.org/beta/rustc/platform-support/loongarch-none.html +// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html +// loongarch64: Rust supports only LP64D and LP64S +#[repr(C)] +#[derive(Clone, Default)] +pub struct Context { + pub gp: [usize; 32], + #[cfg(target_feature = "d")] + pub fp: [usize; 32], +} + +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..=31 { + fmt.field( + LoongArch::register_name(Register(i as _)).unwrap(), + &self.gp[i], + ); + } + #[cfg(target_feature = "d")] + for i in 0..=31 { + fmt.field( + LoongArch::register_name(Register((i + 32) as _)).unwrap(), + &self.fp[i], + ); + } + fmt.finish() + } +} + +impl ops::Index for Context { + type Output = usize; + + fn index(&self, reg: Register) -> &usize { + match reg { + Register(0..=31) => &self.gp[reg.0 as usize], + #[cfg(target_feature = "d")] + Register(32..=63) => &self.fp[(reg.0 - 32) as usize], + _ => unimplemented!(), + } + } +} + +impl ops::IndexMut for Context { + fn index_mut(&mut self, reg: Register) -> &mut usize { + match reg { + Register(0..=31) => &mut self.gp[reg.0 as usize], + #[cfg(target_feature = "d")] + Register(32..=63) => &mut self.fp[(reg.0 - 32) as usize], + _ => unimplemented!(), + } + } +} + +macro_rules! code { + (save_gp) => { + " + st.d $zero, $sp, 0x0 + st.d $ra, $sp, 0x8 + st.d $tp, $sp, 0x10 + st.d $t0, $sp, 0x18 + st.d $r21, $sp, 0xa8 // reserved + st.d $fp, $sp, 0xb0 + st.d $s0, $sp, 0xb8 + st.d $s1, $sp, 0xc0 + st.d $s2, $sp, 0xc8 + st.d $s3, $sp, 0xd0 + st.d $s4, $sp, 0xd8 + st.d $s5, $sp, 0xe0 + st.d $s6, $sp, 0xe8 + st.d $s7, $sp, 0xf0 + st.d $s8, $sp, 0xf8 + " + }; + (save_fp) => { + " + fst.d $fs0, $sp, 0x1c0 + fst.d $fs1, $sp, 0x1c8 + fst.d $fs2, $sp, 0x1d0 + fst.d $fs3, $sp, 0x1d8 + fst.d $fs4, $sp, 0x1e0 + fst.d $fs5, $sp, 0x1e8 + fst.d $fs6, $sp, 0x1f0 + fst.d $fs7, $sp, 0x1f8 + " + }; + (restore_gp) => { + " + ld.d $ra, $a0, 0x8 + ld.d $tp, $a0, 0x10 + ld.d $sp, $a0, 0x18 + ld.d $a1, $a0, 0x28 + ld.d $a2, $a0, 0x30 + ld.d $a3, $a0, 0x38 + ld.d $a4, $a0, 0x40 + ld.d $a5, $a0, 0x48 + ld.d $a6, $a0, 0x50 + ld.d $a7, $a0, 0x58 + ld.d $t0, $a0, 0x60 + ld.d $t1, $a0, 0x68 + ld.d $t2, $a0, 0x70 + ld.d $t3, $a0, 0x78 + ld.d $t4, $a0, 0x80 + ld.d $t5, $a0, 0x88 + ld.d $t6, $a0, 0x90 + ld.d $t7, $a0, 0x98 + ld.d $t8, $a0, 0xa0 + ld.d $r21, $a0, 0xa8 // reserved + ld.d $fp, $a0, 0xb0 + ld.d $s0, $a0, 0xb8 + ld.d $s1, $a0, 0xc0 + ld.d $s2, $a0, 0xc8 + ld.d $s3, $a0, 0xd0 + ld.d $s4, $a0, 0xd8 + ld.d $s5, $a0, 0xe0 + ld.d $s6, $a0, 0xe8 + ld.d $s7, $a0, 0xf0 + ld.d $s8, $a0, 0xf8 + " + }; + (restore_fp) => { + " + fld.d $fa0, $a0, 0x100 + fld.d $fa1, $a0, 0x108 + fld.d $fa2, $a0, 0x110 + fld.d $fa3, $a0, 0x118 + fld.d $fa4, $a0, 0x120 + fld.d $fa5, $a0, 0x128 + fld.d $fa6, $a0, 0x130 + fld.d $fa7, $a0, 0x138 + fld.d $ft0, $a0, 0x140 + fld.d $ft1, $a0, 0x148 + fld.d $ft2, $a0, 0x150 + fld.d $ft3, $a0, 0x158 + fld.d $ft4, $a0, 0x160 + fld.d $ft5, $a0, 0x168 + fld.d $ft6, $a0, 0x170 + fld.d $ft7, $a0, 0x178 + fld.d $ft8, $a0, 0x180 + fld.d $ft9, $a0, 0x188 + fld.d $ft10, $a0, 0x190 + fld.d $ft11, $a0, 0x198 + fld.d $ft12, $a0, 0x1a0 + fld.d $ft13, $a0, 0x1a8 + fld.d $ft14, $a0, 0x1b0 + fld.d $ft15, $a0, 0x1b8 + fld.d $fs0, $a0, 0x1c0 + fld.d $fs1, $a0, 0x1c8 + fld.d $fs2, $a0, 0x1d0 + fld.d $fs3, $a0, 0x1d8 + fld.d $fs4, $a0, 0x1e0 + fld.d $fs5, $a0, 0x1e8 + fld.d $fs6, $a0, 0x1f0 + fld.d $fs7, $a0, 0x1f8 + " + }; +} + +#[unsafe(naked)] +pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) { + #[cfg(target_feature = "d")] + core::arch::naked_asm!( + maybe_cfi!(".cfi_startproc"), + " + move $t0, $sp + addi.d $sp, $sp, -0x210 + ", + maybe_cfi!(".cfi_def_cfa_offset 0x210"), + " + st.d $ra, $sp, 0x200 + ", + maybe_cfi!(".cfi_offset ra, -16"), + code!(save_gp), + code!(save_fp), + " + move $t0, $a0 + move $a0, $sp + jirl $ra, $t0, 0 + ld.d $ra, $sp, 0x200 + addi.d $sp, $sp, 0x210 + ", + maybe_cfi!(".cfi_def_cfa_offset 0"), + maybe_cfi!(".cfi_restore ra"), + "ret", + maybe_cfi!(".cfi_endproc"), + ); + #[cfg(not(target_feature = "d"))] + core::arch::naked_asm!( + maybe_cfi!(".cfi_startproc"), + " + move $t0, $sp + addi.d $sp, $sp, -0x110 + ", + maybe_cfi!(".cfi_def_cfa_offset 0x110"), + " + st.d $ra, $sp, 0x100 + ", + maybe_cfi!(".cfi_offset ra, -16"), + code!(save_gp), + " + move $t0, $a0 + move $a0, $sp + jirl $ra, $t0, 0 + ld.d $ra, $sp, 0x100 + addi.d $sp, $sp, 0x110 + ", + maybe_cfi!(".cfi_def_cfa_offset 0"), + maybe_cfi!(".cfi_restore ra"), + "ret", + maybe_cfi!(".cfi_endproc"), + ); +} + +pub unsafe fn restore_context(ctx: &Context) -> ! { + #[cfg(target_feature = "d")] + unsafe { + core::arch::asm!( + code!(restore_fp), + code!(restore_gp), + " + ld.d $a0, $a0, 0x20 + ret + ", + in("$a0") ctx, + options(noreturn) + ); + } + #[cfg(not(target_feature = "d"))] + unsafe { + core::arch::asm!( + code!(restore_gp), + " + ld.d $a0, $a0, 0x20 + ret + ", + in("$a0") ctx, + options(noreturn) + ); + } +} diff --git a/src/unwinder/arch/mod.rs b/src/unwinder/arch/mod.rs index 3cdbfce..3c96f93 100644 --- a/src/unwinder/arch/mod.rs +++ b/src/unwinder/arch/mod.rs @@ -23,12 +23,18 @@ mod aarch64; #[cfg(target_arch = "aarch64")] pub use aarch64::*; +#[cfg(target_arch = "loongarch64")] +mod loongarch64; +#[cfg(target_arch = "loongarch64")] +pub use loongarch64::*; + #[cfg(not(any( target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", target_arch = "riscv32", - target_arch = "aarch64" + target_arch = "aarch64", + target_arch = "loongarch64" )))] compile_error!("Current architecture is not supported");