Skip to content

Commit 4549bdb

Browse files
committed
backtrace support
1 parent 21a3245 commit 4549bdb

9 files changed

Lines changed: 256 additions & 3 deletions

File tree

.cargo/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[build]
22
target = "aarch64-unknown-none-softfloat"
3+
rustflags = ["-C", "force-frame-pointers=yes"]
34

45
[target.aarch64-unknown-none-softfloat]
56
runner = "scripts/qemu_runner.py"

libkernel/src/arch/arm64/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
//! AArch64 (ARM64) specific implementations.
22
33
pub mod memory;
4+
pub mod stacktrace;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! Aarch64 stack trace implementation
2+
3+
use crate::StackTrace;
4+
use core::arch::asm;
5+
6+
/// Aarch64 stack trace implementation
7+
pub struct StackTraceImpl {
8+
/// Frame pointer
9+
pub fp: usize,
10+
/// PC ptr
11+
pub pc_ptr: *const usize,
12+
}
13+
14+
impl StackTrace for StackTraceImpl {
15+
#[inline(always)]
16+
unsafe fn start() -> Option<Self> {
17+
unsafe {
18+
let fp: usize;
19+
asm!("mov {}, fp", out(reg) fp);
20+
let pc_ptr = fp.checked_add(size_of::<usize>())?;
21+
Some(Self {
22+
fp,
23+
pc_ptr: pc_ptr as *const usize,
24+
})
25+
}
26+
}
27+
28+
unsafe fn next(self) -> Option<Self> {
29+
unsafe {
30+
let fp = *(self.fp as *const usize);
31+
let pc_ptr = fp.checked_add(size_of::<usize>())?;
32+
Some(Self {
33+
fp,
34+
pc_ptr: pc_ptr as *const usize,
35+
})
36+
}
37+
}
38+
39+
fn fp(&self) -> usize {
40+
self.fp
41+
}
42+
43+
fn pc_ptr(&self) -> *const usize {
44+
self.pc_ptr
45+
}
46+
}

libkernel/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,30 @@ pub trait CpuOps: 'static {
112112
fn enable_interrupts();
113113
}
114114

115+
/// Generic stack trace methods
116+
pub trait StackTrace: Sized {
117+
/// Start a stack trace, if supported.
118+
///
119+
/// # Safety
120+
/// Will mess up stack
121+
unsafe fn start() -> Option<Self> {
122+
None
123+
}
124+
125+
/// Continue to next frame in stack, returning `None` if there are no more frames or if the next frame is invalid.
126+
///
127+
/// # Safety
128+
/// Will mess up stack
129+
unsafe fn next(self) -> Option<Self> {
130+
None
131+
}
132+
133+
/// Frame pointer
134+
fn fp(&self) -> usize;
135+
/// PC PTR
136+
fn pc_ptr(&self) -> *const usize;
137+
}
138+
115139
#[cfg(test)]
116140
#[allow(missing_docs)]
117141
pub mod test {

scripts/qemu_runner.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#!/usr/bin/env python3
22

33
import argparse
4+
import re
5+
import shutil
46
import subprocess
7+
import sys
58

69
parser = argparse.ArgumentParser(description="QEMU runner")
710

@@ -14,6 +17,7 @@
1417
parser.add_argument("--memory", default="2G")
1518
parser.add_argument("--debug", action="store_true", help="Enable QEMU debugging")
1619
parser.add_argument("--display", action="store_true", help="Add a display device to the VM")
20+
parser.add_argument("--demangle", action="store_true", help="Add a demangled backtrace")
1721

1822

1923

@@ -68,4 +72,59 @@
6872

6973
qemu_command += extra_args
7074

71-
subprocess.run(qemu_command, check=True)
75+
if args.demangle:
76+
addr2line = (
77+
shutil.which("aarch64-none-elf-addr2line")
78+
or shutil.which("llvm-addr2line")
79+
or shutil.which("addr2line")
80+
)
81+
pc_pattern = re.compile(r"\bPC ([0-9a-fA-F]+)\b")
82+
decode_cache = {}
83+
84+
85+
def decode_pc(pc: str) -> str | None:
86+
if addr2line is None:
87+
return None
88+
89+
pc = pc.lower()
90+
if pc in decode_cache:
91+
return decode_cache[pc]
92+
93+
result = subprocess.run(
94+
[addr2line, "-e", elf_executable, "-f", "-C", "-p", f"0x{pc}"],
95+
check=False,
96+
capture_output=True,
97+
text=True,
98+
)
99+
100+
decoded = result.stdout.strip() or None
101+
decode_cache[pc] = decoded
102+
return decoded
103+
104+
105+
with subprocess.Popen(
106+
qemu_command,
107+
stdout=subprocess.PIPE,
108+
stderr=subprocess.STDOUT,
109+
text=True,
110+
bufsize=1,
111+
) as proc:
112+
assert proc.stdout is not None
113+
114+
for line in proc.stdout:
115+
sys.stdout.write(line)
116+
sys.stdout.flush()
117+
118+
match = pc_pattern.search(line)
119+
if match is None:
120+
continue
121+
122+
decoded = decode_pc(match.group(1))
123+
if decoded is not None:
124+
print(f"\t=> {decoded}", flush=True)
125+
126+
ret = proc.wait()
127+
if ret != 0:
128+
raise subprocess.CalledProcessError(ret, qemu_command)
129+
else:
130+
subprocess.run(qemu_command, check=True)

src/arch/arm64/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use cpu_ops::{local_irq_restore, local_irq_save};
88
use exceptions::ExceptionState;
99
use libkernel::{
1010
CpuOps,
11-
arch::arm64::memory::pg_tables::L0Table,
11+
arch::arm64::{memory::pg_tables::L0Table, stacktrace::StackTraceImpl},
1212
error::Result,
1313
memory::{
1414
address::{UA, VA},
@@ -45,6 +45,10 @@ mod proc;
4545
pub mod psci;
4646
pub mod ptrace;
4747

48+
pub(crate) use self::boot::memory::{KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER};
49+
pub(crate) use self::exceptions::EMERG_STACK_END;
50+
pub(crate) use self::memory::IMAGE_BASE;
51+
4852
pub struct Aarch64 {}
4953

5054
impl CpuOps for Aarch64 {
@@ -86,6 +90,7 @@ impl VirtualMemory for Aarch64 {
8690
impl Arch for Aarch64 {
8791
type UserContext = ExceptionState;
8892
type PTraceGpRegs = Arm64PtraceGPRegs;
93+
type ArchStackTrace = StackTraceImpl;
8994

9095
const PAGE_OFFSET: usize = PAGE_OFFSET;
9196

src/arch/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{
2020
use alloc::string::String;
2121
use alloc::sync::Arc;
2222
use libkernel::{
23-
CpuOps,
23+
CpuOps, StackTrace,
2424
error::Result,
2525
memory::{
2626
address::{UA, VA},
@@ -37,6 +37,8 @@ pub trait Arch: CpuOps + VirtualMemory {
3737
/// The type for GP regs copied via `PTRACE_GETREGSET`.
3838
type PTraceGpRegs: UserCopyable + for<'a> From<&'a Self::UserContext>;
3939

40+
type ArchStackTrace: StackTrace;
41+
4042
/// The starting address for the logical mapping of all physical ram.
4143
const PAGE_OFFSET: usize;
4244

@@ -205,10 +207,18 @@ pub trait Arch: CpuOps + VirtualMemory {
205207
dst: *mut u8,
206208
len: usize,
207209
) -> impl Future<Output = Result<usize>>;
210+
211+
unsafe fn stack_trace() -> Option<Self::ArchStackTrace> {
212+
Self::ArchStackTrace::start()
213+
}
208214
}
209215

210216
#[cfg(target_arch = "aarch64")]
211217
mod arm64;
212218

213219
#[cfg(target_arch = "aarch64")]
214220
pub use self::arm64::Aarch64 as ArchImpl;
221+
#[cfg(target_arch = "aarch64")]
222+
pub(crate) use self::arm64::{
223+
EMERG_STACK_END, IMAGE_BASE, KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER,
224+
};

src/backtrace/mod.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use crate::arch::{
2+
Arch, ArchImpl, EMERG_STACK_END, IMAGE_BASE, KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER,
3+
};
4+
use core::slice;
5+
use libkernel::memory::{PAGE_SIZE, address::VA, region::VirtMemoryRegion};
6+
use log::error;
7+
#[cfg(target_pointer_width = "32")]
8+
use object::elf::FileHeader32 as FileHeader;
9+
#[cfg(target_pointer_width = "64")]
10+
use object::elf::FileHeader64 as FileHeader;
11+
use object::{
12+
NativeEndian, elf,
13+
read::elf::{FileHeader as _, Sym as _},
14+
};
15+
use libkernel::StackTrace;
16+
17+
const MAX_FRAMES: usize = 64;
18+
19+
fn emergency_stack_area() -> VirtMemoryRegion {
20+
let stack_size = PAGE_SIZE << KERNEL_STACK_PG_ORDER;
21+
VirtMemoryRegion::new(EMERG_STACK_END.sub_bytes(stack_size), stack_size)
22+
}
23+
24+
fn is_valid_frame_pointer<T: StackTrace>(frame: &T) -> bool {
25+
let fp_virt = VA::from_value(frame.fp());
26+
let pc_virt = VA::from_value(frame.pc_ptr().addr());
27+
let emergency_stack = emergency_stack_area();
28+
29+
let in_kernel_stack =
30+
|va| KERNEL_STACK_AREA.contains_address(va) || emergency_stack.contains_address(va);
31+
32+
in_kernel_stack(fp_virt)
33+
&& in_kernel_stack(pc_virt)
34+
&& (frame.fp() as *const usize).is_aligned()
35+
&& frame.pc_ptr().is_aligned()
36+
}
37+
38+
pub fn print_backtrace() {
39+
unsafe {
40+
let kernel_ptr = IMAGE_BASE.cast::<u8>().as_ptr();
41+
let elf_header: &FileHeader<NativeEndian> = object::pod::from_bytes(slice::from_raw_parts(
42+
kernel_ptr,
43+
size_of::<FileHeader<NativeEndian>>(),
44+
))
45+
.unwrap()
46+
.0;
47+
48+
// This assumes that the linker places .shstrtab as last section. If it
49+
// isn't, that just causes a recursive panic, not UB.
50+
let kernel_size = elf_header.e_shoff(NativeEndian) as usize
51+
+ usize::from(elf_header.e_shnum(NativeEndian))
52+
* usize::from(elf_header.e_shentsize(NativeEndian));
53+
let kernel_slice = slice::from_raw_parts(kernel_ptr, kernel_size);
54+
55+
let symbols = elf_header
56+
.sections(NativeEndian, kernel_slice)
57+
.unwrap()
58+
.symbols(NativeEndian, kernel_slice, elf::SHT_SYMTAB)
59+
.unwrap();
60+
61+
let mut frame = ArchImpl::stack_trace();
62+
63+
for _ in 0..MAX_FRAMES {
64+
let Some(frame_) = frame else {
65+
break;
66+
};
67+
68+
if !is_valid_frame_pointer(&frame_) {
69+
error!(" {:>016x}: INVALID FRAME", frame_.fp);
70+
break;
71+
}
72+
73+
let pc = *frame_.pc_ptr;
74+
if pc == 0 {
75+
error!(" {:>016x}: EMPTY RETURN", frame_.fp);
76+
break;
77+
}
78+
79+
error!(" FP {:>016x}: PC {:>016x}", frame_.fp, pc);
80+
81+
for sym in symbols.iter() {
82+
if sym.st_type() != elf::STT_FUNC {
83+
continue;
84+
}
85+
let sym_addr = sym.st_value.get(NativeEndian) as usize;
86+
if !(pc >= sym_addr && pc < sym_addr + sym.st_size.get(NativeEndian) as usize) {
87+
continue;
88+
}
89+
90+
let sym_offset = pc - sym_addr;
91+
if let Some(sym_name) = sym
92+
.name(NativeEndian, symbols.strings())
93+
.ok()
94+
.and_then(|name| core::str::from_utf8(name).ok())
95+
{
96+
error!(" {sym_name} @ {sym_addr:>016X}+{sym_offset:>04X}");
97+
} else {
98+
error!(" {sym_addr:>016X}+{sym_offset:>04X}");
99+
}
100+
}
101+
frame = frame_.next();
102+
}
103+
}
104+
}

src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ extern crate alloc;
4242
extern crate moss_macros;
4343

4444
mod arch;
45+
mod backtrace;
4546
mod clock;
4647
mod console;
4748
mod drivers;
@@ -74,6 +75,8 @@ fn on_panic(info: &PanicInfo) -> ! {
7475
error!("Kernel panicked at unknown location: {panic_msg}");
7576
}
7677

78+
backtrace::print_backtrace();
79+
7780
ArchImpl::power_off();
7881
}
7982

0 commit comments

Comments
 (0)