Skip to content

Commit 1748d1c

Browse files
committed
backtrace support
1 parent 21a3245 commit 1748d1c

6 files changed

Lines changed: 205 additions & 1 deletion

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"

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {

src/arch/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,7 @@ mod arm64;
212212

213213
#[cfg(target_arch = "aarch64")]
214214
pub use self::arm64::Aarch64 as ArchImpl;
215+
#[cfg(target_arch = "aarch64")]
216+
pub(crate) use self::arm64::{
217+
EMERG_STACK_END, IMAGE_BASE, KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER,
218+
};

src/backtrace/mod.rs

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

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)