Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 20 additions & 1 deletion src/arch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
228 changes: 228 additions & 0 deletions src/unwinder/arch/loongarch64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use core::{fmt, ops};
use gimli::{LoongArch, Register};

use super::maybe_cfi;

pub const MAX_REG_RULES: usize = 74;

#[repr(C)]
#[derive(Clone, Default)]
pub struct Context {
pub gp: [usize; 32],
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(),
&format_args!("{:#x}", self.gp[i]),
);
}
for i in 0..=31 {
fmt.field(
LoongArch::register_name(Register((i + 32) as _)).unwrap(),
&format_args!("{:#x}", self.fp[i]),
);
}
fmt.finish()
}
}

impl ops::Index<Register> for Context {
type Output = usize;

fn index(&self, reg: Register) -> &usize {
match reg {
Register(0..=31) => &self.gp[reg.0 as usize],
Register(32..=63) => &self.fp[(reg.0 - 32) as usize],
_ => unimplemented!(),
}
}
}

impl ops::IndexMut<gimli::Register> for Context {
fn index_mut(&mut self, reg: Register) -> &mut usize {
match reg {
Register(0..=31) => &mut self.gp[reg.0 as usize],
Register(32..=63) => &mut self.fp[(reg.0 - 32) as usize],
_ => unimplemented!(),
}
}
}

macro_rules! save_regs {
(gp$(, $fp:ident)?) => {
core::arch::naked_asm!(
maybe_cfi!(".cfi_startproc"),
"
move $r12, $r3
addi.d $r3, $r3, -0x210
",
maybe_cfi!(".cfi_def_cfa_offset 0x210"),
"
st.d $r1, $r3, 0x200
",
maybe_cfi!(".cfi_offset 1, -16"), // ra
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does loongarch assembler not support more friendly names in cfi_offset?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried .cfi_offset $ra, -16 (note the $ sign)

"
st.d $r0, $r3, 0x0 // zero
st.d $r1, $r3, 0x8 // ra
st.d $r2, $r3, 0x10 // tp
// sp is saved later
st.d $r21, $r3, 0xa8 // reserved
st.d $r22, $r3, 0xb0 // fp
st.d $r23, $r3, 0xb8 // s0
st.d $r24, $r3, 0xc0 // s1
st.d $r25, $r3, 0xc8 // s2
st.d $r26, $r3, 0xd0 // s3
st.d $r27, $r3, 0xd8 // s4
st.d $r28, $r3, 0xe0 // s5
st.d $r29, $r3, 0xe8 // s6
st.d $r30, $r3, 0xf0 // s7
st.d $r31, $r3, 0xf8 // s8
",
save_regs!(maybe_save_fp($($fp)?)),
"
move $r12, $r4
move $r4, $r3 // argument a0 pointing the ctx base

addi.d $r13, $r3, 0x210
st.d $r13, $r3, 0x18 // save sp

jirl $r1, $r12, 0 // call f

ld.d $r1, $r3, 0x200
addi.d $r3, $r3, 0x210 // restore ra and sp
",
maybe_cfi!(".cfi_def_cfa_offset 0"),
maybe_cfi!(".cfi_restore 1"), // ra
"
ret
",
maybe_cfi!(".cfi_endproc"),
)
};
(maybe_save_fp(fp)) => {
"
fst.d $f24, $r3, 0x1c0 // fs0
fst.d $f25, $r3, 0x1c8 // fs1
fst.d $f26, $r3, 0x1d0 // fs2
fst.d $f27, $r3, 0x1d8 // fs3
fst.d $f28, $r3, 0x1e0 // fs4
fst.d $f29, $r3, 0x1e8 // fs5
fst.d $f30, $r3, 0x1f0 // fs6
fst.d $f31, $r3, 0x1f8 // fs7
"
};
(maybe_save_fp()) => {
""
};
}

macro_rules! restore_regs {
($ctx:expr, gp$(, $fp:ident)?) => {
core::arch::asm!(
restore_regs!(maybe_restore_fp($($fp)?)),
"
ld.d $r1, $r4, 0x8 // ra
ld.d $r2, $r4, 0x10 // tp
ld.d $r3, $r4, 0x18 // sp
// r4(a0) is restored later
ld.d $r5, $r4, 0x28 // a1
ld.d $r6, $r4, 0x30 // a2
ld.d $r7, $r4, 0x38 // a3
ld.d $r8, $r4, 0x40 // a4
ld.d $r9, $r4, 0x48 // a5
ld.d $r10, $r4, 0x50 // a6
ld.d $r11, $r4, 0x58 // a7
ld.d $r12, $r4, 0x60 // t0
ld.d $r13, $r4, 0x68 // t1
ld.d $r14, $r4, 0x70 // t2
ld.d $r15, $r4, 0x78 // t3
ld.d $r16, $r4, 0x80 // t4
ld.d $r17, $r4, 0x88 // t5
ld.d $r18, $r4, 0x90 // t6
ld.d $r19, $r4, 0x98 // t7
ld.d $r20, $r4, 0xa0 // t8
ld.d $r21, $r4, 0xa8 // reserved
ld.d $r22, $r4, 0xb0 // fp
ld.d $r23, $r4, 0xb8 // s0
ld.d $r24, $r4, 0xc0 // s1
ld.d $r25, $r4, 0xc8 // s2
ld.d $r26, $r4, 0xd0 // s3
ld.d $r27, $r4, 0xd8 // s4
ld.d $r28, $r4, 0xe0 // s5
ld.d $r29, $r4, 0xe8 // s6
ld.d $r30, $r4, 0xf0 // s7
ld.d $r31, $r4, 0xf8 // s8
",
"
ld.d $r4, $r4, 0x20
ret
",
in("$r4") $ctx,
options(noreturn)
)
};
(maybe_restore_fp(fp)) => {
"
fld.d $f0, $r4, 0x100 // fa0
fld.d $f1, $r4, 0x108 // fa1
fld.d $f2, $r4, 0x110 // fa2
fld.d $f3, $r4, 0x118 // fa3
fld.d $f4, $r4, 0x120 // fa4
fld.d $f5, $r4, 0x128 // fa5
fld.d $f6, $r4, 0x130 // fa6
fld.d $f7, $r4, 0x138 // fa7
fld.d $f8, $r4, 0x140 // ft0
fld.d $f9, $r4, 0x148 // ft1
fld.d $f10, $r4, 0x150 // ft2
fld.d $f11, $r4, 0x158 // ft3
fld.d $f12, $r4, 0x160 // ft4
fld.d $f13, $r4, 0x168 // ft5
fld.d $f14, $r4, 0x170 // ft6
fld.d $f15, $r4, 0x178 // ft7
fld.d $f16, $r4, 0x180 // ft8
fld.d $f17, $r4, 0x188 // ft9
fld.d $f18, $r4, 0x190 // ft10
fld.d $f19, $r4, 0x198 // ft11
fld.d $f20, $r4, 0x1a0 // ft12
fld.d $f21, $r4, 0x1a8 // ft13
fld.d $f22, $r4, 0x1b0 // ft14
fld.d $f23, $r4, 0x1b8 // ft15
fld.d $f24, $r4, 0x1c0 // fs0
fld.d $f25, $r4, 0x1c8 // fs1
fld.d $f26, $r4, 0x1d0 // fs2
fld.d $f27, $r4, 0x1d8 // fs3
fld.d $f28, $r4, 0x1e0 // fs4
fld.d $f29, $r4, 0x1e8 // fs5
fld.d $f30, $r4, 0x1f0 // fs6
fld.d $f31, $r4, 0x1f8 // fs7
"
};
(maybe_restore_fp()) => {
""
};
}

#[unsafe(naked)]
pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) {
#[allow(unused_unsafe)]
unsafe {
#[cfg(target_feature = "d")]
save_regs!(gp, fp);
#[cfg(not(target_feature = "d"))]
save_regs!(gp);
}
}

pub unsafe fn restore_context(ctx: &Context) -> ! {
unsafe {
#[cfg(target_feature = "d")]
restore_regs!(ctx, gp, fp);
#[cfg(not(target_feature = "d"))]
restore_regs!(ctx, gp);
}
}
8 changes: 7 additions & 1 deletion src/unwinder/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
Loading