Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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 .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[build]
target = "aarch64-unknown-none-softfloat"
rustflags = ["-C", "force-frame-pointers=yes"]

[target.aarch64-unknown-none-softfloat]
runner = "scripts/qemu_runner.py"
1 change: 1 addition & 0 deletions libkernel/src/arch/arm64/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! AArch64 (ARM64) specific implementations.

pub mod memory;
pub mod stacktrace;
46 changes: 46 additions & 0 deletions libkernel/src/arch/arm64/stacktrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Aarch64 stack trace implementation

use crate::StackTrace;
use core::arch::asm;

/// Aarch64 stack trace implementation
pub struct StackTraceImpl {
/// Frame pointer
pub fp: usize,
/// PC ptr
pub pc_ptr: *const usize,
}

impl StackTrace for StackTraceImpl {
#[inline(always)]
unsafe fn start() -> Option<Self> {
unsafe {
let fp: usize;
asm!("mov {}, fp", out(reg) fp);
let pc_ptr = fp.checked_add(size_of::<usize>())?;
Some(Self {
fp,
pc_ptr: pc_ptr as *const usize,
})
}
}

unsafe fn next(self) -> Option<Self> {
unsafe {
let fp = *(self.fp as *const usize);
let pc_ptr = fp.checked_add(size_of::<usize>())?;
Some(Self {
fp,
pc_ptr: pc_ptr as *const usize,
})
}
}

fn fp(&self) -> usize {
self.fp
}

fn pc_ptr(&self) -> *const usize {
self.pc_ptr
}
}
24 changes: 24 additions & 0 deletions libkernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ pub trait CpuOps: 'static {
fn enable_interrupts();
}

/// Generic stack trace methods
pub trait StackTrace: Sized {
/// Start a stack trace, if supported.
///
/// # Safety
/// Will mess up stack
unsafe fn start() -> Option<Self> {
None
}

/// Continue to next frame in stack, returning `None` if there are no more frames or if the next frame is invalid.
///
/// # Safety
/// Will mess up stack
unsafe fn next(self) -> Option<Self> {
None
}

/// Frame pointer
fn fp(&self) -> usize;
/// PC PTR
fn pc_ptr(&self) -> *const usize;
}

#[cfg(test)]
#[allow(missing_docs)]
pub mod test {
Expand Down
61 changes: 60 additions & 1 deletion scripts/qemu_runner.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/usr/bin/env python3

import argparse
import re
import shutil
import subprocess
import sys

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

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



Expand Down Expand Up @@ -68,4 +72,59 @@

qemu_command += extra_args

subprocess.run(qemu_command, check=True)
if args.demangle:
addr2line = (
shutil.which("aarch64-none-elf-addr2line")
or shutil.which("llvm-addr2line")
or shutil.which("addr2line")
)
pc_pattern = re.compile(r"\bPC ([0-9a-fA-F]+)\b")
decode_cache = {}


def decode_pc(pc: str) -> str | None:
if addr2line is None:
return None

pc = pc.lower()
if pc in decode_cache:
return decode_cache[pc]

result = subprocess.run(
[addr2line, "-e", elf_executable, "-f", "-C", "-p", f"0x{pc}"],
check=False,
capture_output=True,
text=True,
)

decoded = result.stdout.strip() or None
decode_cache[pc] = decoded
return decoded


with subprocess.Popen(
qemu_command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
) as proc:
assert proc.stdout is not None

for line in proc.stdout:
sys.stdout.write(line)
sys.stdout.flush()

match = pc_pattern.search(line)
if match is None:
continue

decoded = decode_pc(match.group(1))
if decoded is not None:
print(f"\t=> {decoded}", flush=True)

ret = proc.wait()
if ret != 0:
raise subprocess.CalledProcessError(ret, qemu_command)
else:
subprocess.run(qemu_command, check=True)
7 changes: 6 additions & 1 deletion src/arch/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use cpu_ops::{local_irq_restore, local_irq_save};
use exceptions::ExceptionState;
use libkernel::{
CpuOps,
arch::arm64::memory::pg_tables::L0Table,
arch::arm64::{memory::pg_tables::L0Table, stacktrace::StackTraceImpl},
error::Result,
memory::{
address::{UA, VA},
Expand Down Expand Up @@ -45,6 +45,10 @@ mod proc;
pub mod psci;
pub mod ptrace;

pub(crate) use self::boot::memory::{KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER};
pub(crate) use self::exceptions::EMERG_STACK_END;
pub(crate) use self::memory::IMAGE_BASE;

pub struct Aarch64 {}

impl CpuOps for Aarch64 {
Expand Down Expand Up @@ -86,6 +90,7 @@ impl VirtualMemory for Aarch64 {
impl Arch for Aarch64 {
type UserContext = ExceptionState;
type PTraceGpRegs = Arm64PtraceGPRegs;
type ArchStackTrace = StackTraceImpl;

const PAGE_OFFSET: usize = PAGE_OFFSET;

Expand Down
12 changes: 11 additions & 1 deletion src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
use alloc::string::String;
use alloc::sync::Arc;
use libkernel::{
CpuOps,
CpuOps, StackTrace,
error::Result,
memory::{
address::{UA, VA},
Expand All @@ -37,6 +37,8 @@ pub trait Arch: CpuOps + VirtualMemory {
/// The type for GP regs copied via `PTRACE_GETREGSET`.
type PTraceGpRegs: UserCopyable + for<'a> From<&'a Self::UserContext>;

type ArchStackTrace: StackTrace;

/// The starting address for the logical mapping of all physical ram.
const PAGE_OFFSET: usize;

Expand Down Expand Up @@ -205,10 +207,18 @@ pub trait Arch: CpuOps + VirtualMemory {
dst: *mut u8,
len: usize,
) -> impl Future<Output = Result<usize>>;

unsafe fn stack_trace() -> Option<Self::ArchStackTrace> {
unsafe { Self::ArchStackTrace::start() }
}
}

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

#[cfg(target_arch = "aarch64")]
pub use self::arm64::Aarch64 as ArchImpl;
#[cfg(target_arch = "aarch64")]
pub(crate) use self::arm64::{
EMERG_STACK_END, IMAGE_BASE, KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER,
};
104 changes: 104 additions & 0 deletions src/backtrace/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use crate::arch::{
Arch, ArchImpl, EMERG_STACK_END, IMAGE_BASE, KERNEL_STACK_AREA, KERNEL_STACK_PG_ORDER,
};
use core::slice;
use libkernel::StackTrace;
use libkernel::memory::{PAGE_SIZE, address::VA, region::VirtMemoryRegion};
use log::error;
#[cfg(target_pointer_width = "32")]
use object::elf::FileHeader32 as FileHeader;
#[cfg(target_pointer_width = "64")]
use object::elf::FileHeader64 as FileHeader;
use object::{
NativeEndian, elf,
read::elf::{FileHeader as _, Sym as _},
};

const MAX_FRAMES: usize = 64;

fn emergency_stack_area() -> VirtMemoryRegion {
let stack_size = PAGE_SIZE << KERNEL_STACK_PG_ORDER;
VirtMemoryRegion::new(EMERG_STACK_END.sub_bytes(stack_size), stack_size)
}

fn is_valid_frame_pointer<T: StackTrace>(frame: &T) -> bool {
let fp_virt = VA::from_value(frame.fp());
let pc_virt = VA::from_value(frame.pc_ptr().addr());
let emergency_stack = emergency_stack_area();

let in_kernel_stack =
|va| KERNEL_STACK_AREA.contains_address(va) || emergency_stack.contains_address(va);

in_kernel_stack(fp_virt)
&& in_kernel_stack(pc_virt)
&& (frame.fp() as *const usize).is_aligned()
&& frame.pc_ptr().is_aligned()
}

pub fn print_backtrace() {
unsafe {
let kernel_ptr = IMAGE_BASE.cast::<u8>().as_ptr();
let elf_header: &FileHeader<NativeEndian> = object::pod::from_bytes(slice::from_raw_parts(
kernel_ptr,
size_of::<FileHeader<NativeEndian>>(),
))
.unwrap()
.0;

// This assumes that the linker places .shstrtab as last section. If it
// isn't, that just causes a recursive panic, not UB.
let kernel_size = elf_header.e_shoff(NativeEndian) as usize
+ usize::from(elf_header.e_shnum(NativeEndian))
* usize::from(elf_header.e_shentsize(NativeEndian));
let kernel_slice = slice::from_raw_parts(kernel_ptr, kernel_size);

let symbols = elf_header
.sections(NativeEndian, kernel_slice)
.unwrap()
.symbols(NativeEndian, kernel_slice, elf::SHT_SYMTAB)
.unwrap();

let mut frame = ArchImpl::stack_trace();

for _ in 0..MAX_FRAMES {
let Some(frame_) = frame else {
break;
};

if !is_valid_frame_pointer(&frame_) {
error!(" {:>016x}: INVALID FRAME", frame_.fp);
break;
}

let pc = *frame_.pc_ptr;
if pc == 0 {
error!(" {:>016x}: EMPTY RETURN", frame_.fp);
break;
}

error!(" FP {:>016x}: PC {:>016x}", frame_.fp, pc);

for sym in symbols.iter() {
if sym.st_type() != elf::STT_FUNC {
continue;
}
let sym_addr = sym.st_value.get(NativeEndian) as usize;
if !(pc >= sym_addr && pc < sym_addr + sym.st_size.get(NativeEndian) as usize) {
continue;
}

let sym_offset = pc - sym_addr;
if let Some(sym_name) = sym
.name(NativeEndian, symbols.strings())
.ok()
.and_then(|name| core::str::from_utf8(name).ok())
{
error!(" {sym_name} @ {sym_addr:>016X}+{sym_offset:>04X}");
} else {
error!(" {sym_addr:>016X}+{sym_offset:>04X}");
}
}
frame = frame_.next();
}
}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extern crate alloc;
extern crate moss_macros;

mod arch;
mod backtrace;
mod clock;
mod console;
mod drivers;
Expand Down Expand Up @@ -74,6 +75,8 @@ fn on_panic(info: &PanicInfo) -> ! {
error!("Kernel panicked at unknown location: {panic_msg}");
}

backtrace::print_backtrace();

ArchImpl::power_off();
}

Expand Down
Loading