kernel: backtrace

This commit is contained in:
janis 2026-08-03 18:33:30 +02:00
parent 6b65dc594c
commit 723063d31e
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
11 changed files with 671 additions and 13 deletions

14
Cargo.lock generated
View file

@ -20,14 +20,28 @@ version = "0.1.0"
dependencies = [
"bit_field",
"bitflags",
"plain",
"rbtree",
"rustc-demangle",
"seq-macro",
]
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "rbtree"
version = "0.1.0"
[[package]]
name = "rustc-demangle"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
[[package]]
name = "seq-macro"
version = "0.3.6"

View file

@ -8,3 +8,6 @@ target = "x86_64-unknown-kernel.json"
[target.x86_64-unknown-kernel]
runner = "./run.sh"
rustflags = [
"-C", "force-frame-pointers=yes",
]

View file

@ -27,3 +27,5 @@ bitflags = "2.13.1"
seq-macro = "0.3.6"
rbtree = { path = "../crates/rbtree" }
plain = "0.2.3"
rustc-demangle = "0.1.28"

View file

@ -10,6 +10,8 @@ PHDRS {
SECTIONS {
. = 0xffffffff80000000;
__kernel_start = .;
.limine_requests : {
KEEP(*(.limine_requests_start))
KEEP(*(.limine_requests))
@ -36,6 +38,16 @@ SECTIONS {
*(COMMON)
} :data
__kernel_end = .;
.symtab 0: {
*(.symtab)
}
.strtab 0: {
*(.strtab)
}
/DISCARD/ : {
*(.eh_frame*)
*(.note .note.*)

View file

@ -1,4 +1,4 @@
use core::{cell::UnsafeCell, fmt::Debug, ptr::NonNull};
use core::{cell::UnsafeCell, ffi::CStr, fmt::Debug, ptr::NonNull};
#[repr(C)]
pub struct BaseRevision(UnsafeCell<[u64; 3]>);
@ -272,3 +272,60 @@ impl HhdmRequest {
self.response().map(|response| response.response.offset)
}
}
#[repr(C)]
#[derive(Debug)]
pub struct ExecutableFileResponse {
file: *const LimineFile,
}
unsafe impl Send for ExecutableFileResponse {}
unsafe impl Sync for ExecutableFileResponse {}
pub type ExecutableFileRequest = Request<ExecutableFileResponse>;
impl ExecutableFileRequest {
const ID: [u64; 2] = [0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69];
pub const fn new() -> Self {
Self::new_raw(Self::ID, 0, ())
}
pub fn file(&self) -> Option<&LimineFile> {
self.response()
.and_then(|response| unsafe { response.response.file.as_ref() })
}
}
#[repr(C)]
#[derive(Debug)]
pub struct LimineFile {
pub revision: u64,
pub addr: *const u8,
pub length: u64,
pub path: *const i8,
pub string: *const i8,
pub media_type: u32,
pub _padding: [u8; 4],
pub tftp_ipv4_addr: [u8; 4],
pub tftp_port: u32,
pub partition_index: u32,
pub mbr_disk_id: u32,
pub gpt_disk_uuid: [u8; 16],
pub gpt_part_uuid: [u8; 16],
pub part_uuid: [u8; 16],
}
impl LimineFile {
pub const MEDIA_TYPE_GENERIC: u32 = 0;
pub const MEDIA_TYPE_OPTICAL: u32 = 1;
pub const MEDIA_TYPE_TFTP: u32 = 2;
pub fn bytes(&self) -> &'static [u8] {
unsafe { core::slice::from_raw_parts(self.addr, self.length as usize) }
}
}
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static EXECUTABLE_FILE_REQUEST: ExecutableFileRequest = ExecutableFileRequest::new();

View file

@ -56,6 +56,8 @@ extern "C" fn _start() -> ! {
GDT.load();
IDT.load();
unsafe { (0 as *mut u8).write_volatile(0x42) };
kernel::serial_println!("entry point: 0x{:x}", _start as *const () as usize);
kernel::serial_println!(
"entry point phy: {:?}",

View file

@ -7,8 +7,11 @@ use crate::{serial_println, sync::OnceLock, x86_64::PAGE_SIZE};
pub static HHDM_BASE: OnceLock<u64> = OnceLock::new();
pub trait VirtAddrTranslationExt {
pub trait VirtAddrTranslationExt: Sized {
fn into_phy_addr(self) -> Option<PhyAddr>;
fn is_mapped(self) -> bool {
self.into_phy_addr().is_some()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
@ -51,6 +54,12 @@ impl Debug for PhyAddr {
}
}
impl From<u64> for VirtAddr {
fn from(value: u64) -> Self {
VirtAddr(value)
}
}
impl<T> From<&T> for VirtAddr {
fn from(ptr: &T) -> Self {
VirtAddr(ptr as *const T as u64)

View file

@ -1,5 +1,6 @@
#![cfg(target_arch = "x86_64")]
pub mod backtrace;
pub mod cpuid;
pub mod gdt;
pub mod idt;
@ -212,3 +213,67 @@ impl VirtAddrExt for VirtAddr {
E::decompose(self.0)
}
}
pub struct Context {
pub registers: Registers,
pub rip: u64,
pub rflags: RFlags,
pub rsp: u64,
/// The code segment selector.
pub cs: u16,
}
impl core::fmt::Display for Context {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(f, "RIP - {:>#018x} \tRSP - {:>#018x}", self.rip, self.rsp)?;
writeln!(
f,
"FLAGS - {:>#018x} \tCS - {:>#018x}",
self.rflags.bits(),
self.cs
)?;
writeln!(
f,
"RAX: {:>#018x} \tRCX: {:>#018x}",
self.registers.rax, self.registers.rcx
)?;
writeln!(
f,
"RDX: {:>#018x} \tRBX: {:>#018x}",
self.registers.rdx, self.registers.rbx
)?;
writeln!(
f,
"RSP: {:>#018x} \tRBP: {:>#018x}",
self.rsp, self.registers.rbp
)?;
writeln!(
f,
"RSI: {:>#018x} \tRDI: {:>#018x}",
self.registers.rsi, self.registers.rdi
)?;
writeln!(
f,
"R8 : {:>#018x} \tR9 : {:>#018x}",
self.registers.r8, self.registers.r9
)?;
writeln!(
f,
"R10: {:>#018x} \tR11: {:>#018x}",
self.registers.r10, self.registers.r11
)?;
writeln!(
f,
"R12: {:>#018x} \tR13: {:>#018x}",
self.registers.r12, self.registers.r13
)?;
writeln!(
f,
"R14: {:>#018x} \tR15: {:>#018x}",
self.registers.r14, self.registers.r15
)?;
Ok(())
}
}

View file

@ -0,0 +1,495 @@
use core::ffi::{CStr, c_void};
use crate::{
limine::LimineFile,
memory::{VirtAddr, VirtAddrTranslationExt},
serial_println,
sync::LazyLock,
x86_64::{Context, VirtAddrExt},
};
unsafe extern "C" {
static __kernel_start: c_void;
static __kernel_end: c_void;
}
pub struct Frame {
pub rip: u64,
pub rbp: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Elf64SymBind {
Local = 0,
Global = 1,
Weak = 2,
Loos = 10,
Hios = 12,
Loproc = 13,
Hiproc = 15,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Elf64SymType {
Notype = 0,
Object = 1,
Func = 2,
Section = 3,
File = 4,
Common = 5,
Tls = 6,
Loos = 10,
Hios = 12,
Loproc = 13,
Hiproc = 15,
}
#[repr(C)]
#[derive(Debug)]
struct Elf64Sym {
name: u32,
info: u8,
other: u8,
shndx: u16,
value: u64,
size: u64,
}
unsafe impl plain::Plain for Elf64Sym {}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SectionHeaderType {
Null = 0,
Progbits = 1,
Symtab = 2,
Strtab = 3,
Rela = 4,
Hash = 5,
Dynamic = 6,
Note = 7,
Nobits = 8,
Rel = 9,
Shlib = 10,
Dynsym = 11,
InitArray = 14,
FiniArray = 15,
PreinitArray = 16,
Group = 17,
SymtabShndx = 18,
Num = 19,
GnuAttributes = 0x6ffffff5,
GnuHash = 0x6ffffff6,
GnuLiblist = 0x6ffffff7,
Checksum = 0x6ffffff8,
Losunwind = 0x6ffffffa,
Amd64Unwind = 0x7000001,
OsSpecific(u32),
ProcSpecific(u32),
UserSpecific(u32),
Unknown = 0xffffffff,
}
impl SectionHeaderType {
fn from_u32(value: u32) -> Self {
match value {
0 => Self::Null,
1 => Self::Progbits,
2 => Self::Symtab,
3 => Self::Strtab,
4 => Self::Rela,
5 => Self::Hash,
6 => Self::Dynamic,
7 => Self::Note,
8 => Self::Nobits,
9 => Self::Rel,
10 => Self::Shlib,
11 => Self::Dynsym,
14 => Self::InitArray,
15 => Self::FiniArray,
16 => Self::PreinitArray,
17 => Self::Group,
18 => Self::SymtabShndx,
19 => Self::Num,
0x6ffffff5 => Self::GnuAttributes,
0x6ffffff6 => Self::GnuHash,
0x6ffffff7 => Self::GnuLiblist,
0x6ffffff8 => Self::Checksum,
0x6ffffffa => Self::Losunwind,
0x7000001 => Self::Amd64Unwind,
0x60000000..=0x6fffffff => Self::OsSpecific(value),
0x70000000..=0x7fffffff => Self::ProcSpecific(value),
0x80000000..=0x8fffffff => Self::UserSpecific(value),
_ => Self::Unknown,
}
}
fn into_u32(self) -> u32 {
match self {
Self::Null => 0,
Self::Progbits => 1,
Self::Symtab => 2,
Self::Strtab => 3,
Self::Rela => 4,
Self::Hash => 5,
Self::Dynamic => 6,
Self::Note => 7,
Self::Nobits => 8,
Self::Rel => 9,
Self::Shlib => 10,
Self::Dynsym => 11,
Self::InitArray => 14,
Self::FiniArray => 15,
Self::PreinitArray => 16,
Self::Group => 17,
Self::SymtabShndx => 18,
Self::Num => 19,
Self::GnuAttributes => 0x6ffffff5,
Self::GnuHash => 0x6ffffff6,
Self::GnuLiblist => 0x6ffffff7,
Self::Checksum => 0x6ffffff8,
Self::Losunwind => 0x6ffffffa,
Self::Amd64Unwind => 0x7000001,
Self::OsSpecific(value) | Self::ProcSpecific(value) | Self::UserSpecific(value) => {
value
}
Self::Unknown => 0xffffffff,
}
}
}
#[repr(C)]
#[derive(Debug)]
struct SectionHeader {
name: u32,
sh_type: u32,
flags: u64,
addr: u64,
offset: u64,
size: u64,
link: u32,
info: u32,
addralign: u64,
entsize: u64,
}
unsafe impl plain::Plain for SectionHeader {}
impl SectionHeader {
fn bytes<'a>(&self, bytes: &'a [u8]) -> Option<&'a [u8]> {
let offset = self.offset as usize;
let size = self.size as usize;
if offset + size > bytes.len() {
return None;
}
Some(&bytes[offset..offset + size])
}
}
#[repr(C)]
#[derive(Debug)]
struct Elf64Ehdr {
ident: [u8; 16],
kind: u16,
machine: u16,
version: u32,
entry: u64,
phoff: u64,
shoff: u64,
flags: u32,
ehsize: u16,
phentsize: u16,
phnum: u16,
shentsize: u16,
shnum: u16,
shstrndx: u16,
}
unsafe impl plain::Plain for Elf64Ehdr {}
struct SectionHeaders<'a> {
headers: &'a [SectionHeader],
}
impl<'a> SectionHeaders<'a> {
fn symtab_hdr(&self) -> Option<&'a SectionHeader> {
self.headers
.iter()
.find(|header| SectionHeaderType::from_u32(header.sh_type) == SectionHeaderType::Symtab)
}
}
#[derive(Default)]
struct Strtab<'a> {
bytes: &'a [u8],
}
impl<'a> Strtab<'a> {
fn len(&self) -> usize {
self.bytes.len()
}
fn get_str(&self, offset: u32) -> Option<&'a str> {
let offset = offset as usize;
if offset >= self.bytes.len() {
return None;
}
let bytes = &self.bytes[offset..];
let nul_pos = bytes.iter().position(|&b| b == 0)?;
core::str::from_utf8(&bytes[..nul_pos]).ok()
}
}
impl Elf64Ehdr {
fn section_headers<'a>(&self, bytes: &'a [u8]) -> SectionHeaders<'a> {
let shoff = self.shoff as usize;
// let shentsize = self.shentsize as usize;
let shnum = self.shnum as usize;
unsafe {
SectionHeaders {
headers: core::slice::from_raw_parts(
bytes.as_ptr().byte_add(shoff).cast::<SectionHeader>(),
shnum,
),
}
}
}
fn shstrtab<'a>(&self, bytes: &'a [u8]) -> Option<Strtab<'a>> {
let shstrndx = self.shstrndx as usize;
let section_headers = self.section_headers(bytes);
if shstrndx >= section_headers.headers.len() {
return None;
}
let shstrtab_header = &section_headers.headers[shstrndx];
serial_println!("shstrtab_header: {:#?}", shstrtab_header);
let offset = shstrtab_header.offset as usize;
let size = shstrtab_header.size as usize;
if offset + size > bytes.len() {
return None;
}
Some(Strtab {
bytes: &bytes[offset..offset + size],
})
}
fn find_shdr_by_name<'a>(&self, bytes: &'a [u8], name: &str) -> Option<&'a SectionHeader> {
let shstrtab = self.shstrtab(bytes)?;
let section_headers = self.section_headers(bytes);
section_headers
.headers
.iter()
.find(|header| shstrtab.get_str(header.name) == Some(name))
}
fn strtab<'a>(&self, bytes: &'a [u8]) -> Option<Strtab<'a>> {
let strtab = self.find_shdr_by_name(bytes, ".strtab")?;
let offset = strtab.offset as usize;
let size = strtab.size as usize;
if offset + size > bytes.len() {
return None;
}
Some(Strtab {
bytes: &bytes[offset..offset + size],
})
}
fn symtab<'a>(&self, bytes: &'a [u8]) -> Option<&'a [Elf64Sym]> {
let section_headers = self.section_headers(bytes);
let symtab_header = section_headers.symtab_hdr()?;
let offset = symtab_header.offset as usize;
let size = symtab_header.size as usize;
if offset + size > bytes.len() {
return None;
}
let num_symbols = size / core::mem::size_of::<Elf64Sym>();
unsafe {
Some(core::slice::from_raw_parts(
bytes.as_ptr().byte_add(offset).cast::<Elf64Sym>(),
num_symbols,
))
}
}
}
impl Elf64Sym {
fn symbols() -> &'static [Elf64Sym] {
static SYMBOLS: LazyLock<&'static [Elf64Sym]> = LazyLock::new(|| {
if let Some(bytes) = crate::limine::EXECUTABLE_FILE_REQUEST
.file()
.map(LimineFile::bytes)
{
if let Ok(ehdr) = plain::from_bytes::<Elf64Ehdr>(bytes) {
if let Some(symtab) = ehdr.symtab(bytes) {
return symtab;
}
}
}
&[]
});
SYMBOLS.as_ref()
}
fn find(addr: u64) -> Option<&'static Elf64Sym> {
Self::symbols()
.iter()
.find(|&sym| sym.range().contains(&addr))
.map(|v| v as _)
}
fn find_type(addr: u64, kind: Elf64SymType) -> Option<&'static Elf64Sym> {
Self::symbols()
.iter()
.find(|&sym| sym.kind() == kind && sym.range().contains(&addr))
.map(|v| v as _)
}
fn bind(&self) -> Elf64SymBind {
match self.info >> 4 {
0 => Elf64SymBind::Local,
1 => Elf64SymBind::Global,
2 => Elf64SymBind::Weak,
10 => Elf64SymBind::Loos,
12 => Elf64SymBind::Hios,
13 => Elf64SymBind::Loproc,
15 => Elf64SymBind::Hiproc,
_ => panic!("Invalid bind value"),
}
}
fn kind(&self) -> Elf64SymType {
match self.info & 0xf {
0 => Elf64SymType::Notype,
1 => Elf64SymType::Object,
2 => Elf64SymType::Func,
3 => Elf64SymType::Section,
4 => Elf64SymType::File,
5 => Elf64SymType::Common,
6 => Elf64SymType::Tls,
10 => Elf64SymType::Loos,
12 => Elf64SymType::Hios,
13 => Elf64SymType::Loproc,
15 => Elf64SymType::Hiproc,
_ => panic!("Invalid type value"),
}
}
fn range(&self) -> core::ops::Range<u64> {
self.value..(self.value.saturating_add(self.size))
}
fn name(&self) -> Option<&str> {
static STRTAB: LazyLock<Strtab<'static>> = LazyLock::new(|| {
if let Some(bytes) = crate::limine::EXECUTABLE_FILE_REQUEST
.file()
.map(LimineFile::bytes)
&& let Ok(ehdr) = plain::from_bytes::<Elf64Ehdr>(bytes)
{
serial_println!("ELF64 Header: {:#?}", ehdr);
return ehdr.strtab(bytes).unwrap_or_default();
}
Strtab::default()
});
let len = STRTAB.len();
if self.name == 0 || self.name as usize >= len {
return None;
}
STRTAB.get_str(self.name)
}
}
pub fn backtrace(ctx: &Context) -> impl Iterator<Item = Frame> {
Backtrace {
frame: Some(Frame {
rip: ctx.rip,
rbp: ctx.registers.rbp,
}),
}
}
pub fn debug_backtrace(ctx: &Context) {
serial_println!("{ctx}");
for frame in backtrace(ctx) {
if let Some(sym) = Elf64Sym::find(frame.rip) {
if sym.kind() == Elf64SymType::Func
&& sym.shndx != 0
&& let Some(name) = sym.name()
{
serial_println!(
"0x{:016x} - {} + 0x{:x}",
frame.rip,
rustc_demangle::demangle(name),
frame.rip - sym.value
);
} else {
serial_println!("{sym:#?}");
serial_println!(
"0x{:016x} - <unknown> + 0x{:x}",
frame.rip,
frame.rip - sym.value
);
}
} else {
serial_println!("0x{:016x} - <unknown>", frame.rip);
}
}
}
struct Backtrace {
frame: Option<Frame>,
}
impl Iterator for Backtrace {
type Item = Frame;
fn next(&mut self) -> Option<Self::Item> {
let frame = self.frame.take()?;
let next_frame = {
let rsp = VirtAddr::from(frame.rbp);
if rsp.is_canonical() && rsp.is_mapped() {
// The stack frame layout is as follows:
// | Our Function |
// +-----------------+
// | Previous RBP | <- RBP points here
// +-----------------+
// | Return Address | <- RBP + 8 points here
// +-----------------+
// | Parent Function |
//
let rip = unsafe { *(rsp.as_ptr::<u64>().add(1)) };
let rbp = unsafe { *(rsp.as_ptr::<u64>()) };
Some(Frame { rip, rbp })
} else {
None
}
};
self.frame = next_frame;
Some(frame)
}
}

View file

@ -4,7 +4,7 @@ use bit_field::BitField;
use crate::{
serial_println,
x86_64::{RFlags, Registers, halt_loop},
x86_64::{Context, RFlags, Registers, backtrace::debug_backtrace, halt_loop},
};
#[derive(Clone, Copy)]
@ -521,6 +521,13 @@ extern "C" fn global_interrupt_handler(
}
ExceptionVector::PAGE_FAULT => {
serial_println!("Page fault occurred! Error code: {:#x}", error_code);
debug_backtrace(&Context {
rip: frame.instruction_pointer,
registers: *registers,
rsp: frame.stack_pointer,
rflags: frame.flags,
cs: frame.code_segment,
});
halt_loop()
}
_ => {}

View file

@ -9,7 +9,6 @@ use bit_field::BitField;
use crate::{
memory::{PhyAddr, VirtAddr, VirtAddrTranslationExt},
serial_println,
x86_64::{VirtAddrExt, registers::Cr4},
};
@ -186,8 +185,6 @@ impl VirtAddrTranslationExt for VirtAddr {
pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
let cr3 = crate::x86_64::registers::Cr3::read();
let cr4 = crate::x86_64::registers::Cr4::read();
serial_println!("cr3: {:?}", cr3);
serial_println!("cr4: {:?}", cr4);
let frame = cr3.phy();
let page_table = unsafe {
@ -208,10 +205,7 @@ pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
page_table[virt.page_table_index::<{ VirtAddr::PML4 }>()]
};
serial_println!("pml4_entry: {:?}", pml4_entry);
let pdpt_entry = pml4_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PDPT }>()];
serial_println!("pdpt_entry: {:?}", pdpt_entry);
if pdpt_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
const PHY_MASK_1G: u64 = !((1 << 30) - 1);
@ -220,7 +214,6 @@ pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
}
let pd_entry = pdpt_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PD }>()];
serial_println!("pd_entry: {:?}", pd_entry);
if pd_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
const PHY_MASK_2M: u64 = !((1 << 21) - 1);
@ -229,7 +222,6 @@ pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
}
let pt_entry = pd_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PT }>()];
serial_println!("pt_entry: {:?}", pt_entry);
if !pt_entry.contains(PageTableEntryFlags::PRESENT) {
return None;