Save failed stack trace code for future reference.

This commit is contained in:
2025-03-08 18:39:27 +00:00
parent 4b8388c66d
commit da6690fd8b
14 changed files with 335 additions and 122 deletions
-3
View File
@@ -17,8 +17,5 @@ runner = "scripts/run_debug.sh"
[target.'cfg(all(target_arch = "x86_64", target_os = "none", not(debug_assertions)))']
runner = "scripts/run_release.sh"
[target.x86_64-kernel]
rustflags = ["-C", "force-unwind-tables"]
[registries.gitea]
index = "sparse+https://git.zxq5.dev/api/packages/OsDev/cargo/" # Sparse index
Generated
-21
View File
@@ -99,18 +99,6 @@ dependencies = [
"syn",
]
[[package]]
name = "elf"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fnv"
version = "1.0.7"
@@ -123,10 +111,7 @@ version = "0.1.0"
dependencies = [
"cc",
"crossbeam",
"elf",
"fallible-iterator",
"futures-util",
"gimli",
"libm",
"limine",
"linked_list_allocator",
@@ -160,12 +145,6 @@ dependencies = [
"pin-utils",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "ident_case"
version = "1.0.1"
+5 -3
View File
@@ -19,9 +19,11 @@ futures-util = { version = "0.3.31", default-features = false, features = [
"alloc",
] }
linked_list_allocator = { version = "0.10.5", features = ["use_spin"] }
gimli = { version = "0.31.1", default-features = false, features = ["read"] }
elf = { version = "0.7.4", default-features = false, features = ["nightly"] }
fallible-iterator = "0.3.0"
# gimli = { version = "0.31.1", default-features = false, features = ["read"] }
# elf = { version = "0.7.4", default-features = false, features = ["nightly"] }
# fallible-iterator = "0.3.0"
# framehop = { version = "0.13.2", default-features = false }
# object = { version = "0.36.7", default-features = false, features = ["read"] }
[build-dependencies]
cc = "1.2.14"
+4 -4
View File
@@ -138,10 +138,10 @@ extern "x86-interrupt" fn page_fault_handler(
_stack_frame: InterruptStackFrame,
_error_code: PageFaultErrorCode,
) {
serial_println!("Exception: Page Fault");
serial_println!("Accessed Address: {:?}", Cr2::read());
serial_println!("Error Code: {:?}", _error_code);
serial_println!("{:#?}", _stack_frame);
// serial_println!("Exception: Page Fault");
// serial_println!("Accessed Address: {:?}", Cr2::read());
// serial_println!("Error Code: {:?}", _error_code);
// serial_println!("{:#?}", _stack_frame);
if let Some(frame_allocator) = FRAME_ALLOCATOR.get() {
let mut f = frame_allocator.lock();
+5 -2
View File
@@ -11,12 +11,15 @@ use x86_64::{
structures::paging::{OffsetPageTable, PageTable},
};
pub const STACK_VIRTUAL_SPACE: usize = 0x5555_5555_0000; // start address of the memory space where we store allocated stacks
pub const HEAP_VIRTUAL_SPACE: usize = 0x4444_4444_0000; // start address of heap allocated memory
/// Start address of the memory space where we store allocated stacks.
pub const STACK_VIRTUAL_SPACE: usize = 0x5555_5555_0000;
/// Start address of heap allocated memory.
pub const HEAP_VIRTUAL_SPACE: usize = 0x4444_4444_0000;
pub const HEAP_SIZE: usize = MiB(1).to_bytes();
pub static FRAME_ALLOCATOR: Once<Mutex<FoundryOSFrameAllocator>> = Once::new();
pub static OFFSET_PAGE_TABLE: Once<Mutex<OffsetPageTable>> = Once::new();
/// Returns a mutable reference to the current level 4 page table.
///
/// # Safety
+4 -5
View File
@@ -1,5 +1,5 @@
#![no_std]
#![feature(abi_x86_interrupt)]
#![feature(abi_x86_interrupt, breakpoint)]
#![warn(
clippy::correctness,
clippy::nursery,
@@ -35,7 +35,6 @@ use crate::{
use alloc::{boxed::Box, format};
use core::arch::asm;
use limine::BaseRevision;
use std::{debug, unwind::UNWINDER};
use x86_64::VirtAddr;
pub mod arch;
@@ -83,6 +82,7 @@ impl core::error::Error for NoError {}
/// Panicking before this is initialised is unwise. We should probably extract
/// very early init into it's own function because Stack Traces may require
/// allocations etc.
#[inline(never)]
pub fn boot() -> Result<(), Box<dyn core::error::Error>> {
if !BASE_REVISION.is_supported() {
return Err("Base revision not supported.".into());
@@ -149,10 +149,9 @@ pub fn boot() -> Result<(), Box<dyn core::error::Error>> {
x86_64::instructions::interrupts::enable();
debugln!("[Success]");
// Initialises the stack unwinder once and only once because this makes a
// heap allocation.
debug!(" Initializing Stack Unwinder... ");
UNWINDER.lock();
// Force evaluate the constructor.
// let _unwinder = &*UNWINDER;
debugln!("[Success]");
Ok(())
+73 -10
View File
@@ -3,9 +3,18 @@
extern crate alloc;
use foundry_os::arch::x86_64::processing::async_io::task::{Executor, Task};
use foundry_os::prelude::*;
use foundry_os::util::shell::shell;
use core::arch::asm;
use foundry_os::{
// arch::x86_64::processing::async_io::task::{Executor, Task},
prelude::*,
// std::unwind::UNWINDER,
// util::shell::shell,
};
// use framehop::{
// x86_64::{CacheX86_64, UnwindRegsX86_64},
// *,
// };
#[unsafe(no_mangle)]
extern "C" fn kmain() -> ! {
@@ -24,15 +33,69 @@ extern "C" fn kmain() -> ! {
test1();
let mut executor = Executor::new();
executor.spawn(Task::new(shell()));
executor.run()
// let mut executor = Executor::new();
// executor.spawn(Task::new(shell()));
// executor.run()
loop {}
}
fn test1() {
test2()
#[inline(never)]
pub fn test1() {
test2();
}
fn test2() {
panic!("Test");
#[inline(never)]
pub fn test2() {
test3();
}
const fn read_stack(addr: u64) -> Result<u64, ()> {
Ok(unsafe { *(addr as *const u64) })
}
#[inline(never)]
pub fn test3() {
// let mut cache = CacheX86_64::new();
// let unwinder = &*UNWINDER;
// let mut rip: u64;
// let mut rsp: u64;
// let mut rbp: u64;
// let mut read_stack_fn = read_stack;
// unsafe {
// asm!("lea {0}, [rip]",
// "mov {1}, rsp",
// "mov {2}, rbp",
// out(reg) rip,
// out(reg) rsp,
// out(reg) rbp);
// }
// let regs = UnwindRegsX86_64::new(rip, rsp, rbp);
// let mut frame_iter =
// unwinder.iter_frames(rip, regs, &mut cache, &mut read_stack_fn);
// // This is just me testing the unwinding. It seems to not return any
// stack // frames for some odd reason.
// loop {
// match frame_iter.next() {
// Ok(Some(frame)) => {
// debugln!("Got function with {:x?}",
// frame.address_for_lookup()); // match frame {
// // FrameAddress::InstructionPointer(rip) => (),
// // FrameAddress::ReturnAddress(ra) => {}
// // }
// }
// Ok(None) => {
// debugln!("Hit end of stack.");
// break;
// }
// Err(why) => {
// debugln!("{}", why);
// break;
// }
// }
// }
}
+2 -2
View File
@@ -1,7 +1,7 @@
pub use crate::{
arch::x86_64::drivers::framebuffer::colour::Colour,
debug, debugln, eprint, eprintln, print, print_log, print_oneshot, println,
println_log, serial_print, serial_println,
debug, debugln, eprint, eprintln, hcf, print, print_log, print_oneshot,
println, println_log, serial_print, serial_println,
std::debug::_debug,
std::io::{_print, _print_err, _print_log, _serial_write},
};
+62 -18
View File
@@ -11,14 +11,14 @@
//! * Add support for loading binary programs (this should probably be written
//! in a different module)
use alloc::format;
use alloc::{format, vec::Vec};
use elf::{
ElfBytes, ParseError,
endian::LittleEndian,
parse::{ParseAt, ParsingTable},
section::{SectionHeader, SectionHeaderTable},
string_table::StringTable,
symbol::SymbolTable,
symbol::{Symbol, SymbolTable},
};
use limine::request::KernelFileRequest;
@@ -38,6 +38,8 @@ pub static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new();
pub enum ElfError {
/// Returned if a section did not exist in [ElfReader::get_section_size].
SectionNotExists,
/// Returned if we failed to fetch the symbol table.
Symtab,
/// Parse errors returned by the `elf` crate.
OtherParseError(elf::ParseError),
}
@@ -49,8 +51,14 @@ impl From<elf::ParseError> for ElfError {
}
pub struct ElfReader {
/// The underlying bytes for the ELF file.
pub bytes: &'static [u8],
/// Structure returned by the `elf` crate having parsed the ELF header.
elf: ElfBytes<'static, LittleEndian>,
pub elf: ElfBytes<'static, LittleEndian>,
/// A sorted list of symbols to binary search.
pub sorted_syms: Vec<Symbol>,
/// The string table for looking up symbol names.
pub symbol_strtab: StringTable<'static>,
}
impl ElfReader {
@@ -65,23 +73,26 @@ impl ElfReader {
/// Both of these should be satisfied, but this function is marked unsafe
/// just in case, because we are derefererencing arbitrary pointers.
pub unsafe fn new() -> Result<Self, ElfError> {
let response = KERNEL_FILE_REQUEST
.get_response()
.expect("Didn't get the kernel file from Limine. That's odd.");
// We fetch these from Limine and use them to parse our own ELF file.
let file = response.file();
let file_start_ptr = file.addr();
let file_size = file.size() as usize;
// Safety: This slice should contain the whole bytes of the ELF file.
let elf_hdr_slice =
unsafe { core::slice::from_raw_parts(file_start_ptr, file_size) };
// Store this for use with other libraries.
let bytes = get_elf_slice();
let elf: ElfBytes<'static, LittleEndian> =
elf::ElfBytes::minimal_parse(elf_hdr_slice)?;
elf::ElfBytes::minimal_parse(bytes)?;
Ok(Self { elf })
let Some((symtab, strtab)) = elf.symbol_table()? else {
return Err(ElfError::Symtab);
};
// Sort the symtab for later use and store the strtab.
let mut symbols = symtab.into_iter().collect::<Vec<Symbol>>();
symbols.sort_by_key(|sym| sym.st_value);
Ok(Self {
elf,
sorted_syms: symbols,
symbol_strtab: strtab,
bytes,
})
}
pub fn get_symbol_table(
@@ -112,7 +123,7 @@ impl ElfReader {
/// Gets the section header of `section_name`.
pub fn get_section_header(
&self,
section_name: &'static str,
section_name: &str,
) -> Result<SectionHeader, ElfError> {
let section_hdr = self
.elf
@@ -121,4 +132,37 @@ impl ElfReader {
section_hdr.ok_or(ElfError::SectionNotExists)
}
pub fn search_symbol(&self, address: u64) -> Option<elf::symbol::Symbol> {
let entries = self.sorted_syms.iter().collect::<Vec<_>>();
let idx = entries
.as_slice()
.binary_search_by_key(&address, |sym| sym.st_value)
.ok()?;
Some(entries[idx].clone())
}
pub fn get_symbol_name(
&self,
sym: elf::symbol::Symbol,
) -> Option<&'static str> {
self.symbol_strtab.get(sym.st_name as usize).ok()
}
}
/// Gets a slice of the bytes of the kernel ELF file.
pub fn get_elf_slice() -> &'static [u8] {
let response = KERNEL_FILE_REQUEST
.get_response()
.expect("Didn't get the kernel file from Limine. That's odd.");
// We fetch these from Limine and use them to parse our own ELF file.
let file = response.file();
let file_start_ptr = file.addr();
let file_size = file.size() as usize;
// Safety: This slice should contain the whole bytes of the ELF file.
(unsafe { core::slice::from_raw_parts(file_start_ptr, file_size) }) as _
}
+2 -2
View File
@@ -1,7 +1,7 @@
pub mod application;
pub mod ascii;
pub mod debug;
pub mod elf;
// pub mod elf;
pub mod io;
pub mod maths;
pub mod unwind;
// pub mod unwind;
+20 -12
View File
@@ -1,23 +1,31 @@
use core::arch::asm;
use eh_info::ELF;
use alloc::vec::Vec;
use framehop::{
x86_64::{CacheX86_64, UnwinderX86_64},
*,
};
use spin::{Lazy, Mutex};
use unwinder::{RegisterSet, Unwinder};
use crate::arch::x86_64::memory::mapping::KERNEL_ADDRESS_REQUEST;
pub mod eh_info;
pub mod panic;
pub mod unwinder;
/// We should initialise on program start.
pub static UNWINDER: Lazy<Mutex<Unwinder>> = Lazy::new(|| {
// Setup stack traces and proper panic handler. TODO: Handle panics
// differently if not initialised.
let eh_frame_ptr = ELF
.get_section_addr(".eh_frame_hdr")
.expect("Could not get `.eh_frame_hdr` address.");
pub static UNWINDER: Lazy<framehop::x86_64::UnwinderX86_64<Vec<u8>>> =
Lazy::new(|| {
let mut unwinder: UnwinderX86_64<_, MayAllocateDuringUnwind> =
UnwinderX86_64::new();
let eh_info = unsafe { eh_info::EhInfo::from_hdr_ptr(eh_frame_ptr) };
let mut registers = RegisterSet::default();
panic::add_object(
&mut unwinder,
KERNEL_ADDRESS_REQUEST
.get_response()
.unwrap()
.virtual_base(),
);
Mutex::new(Unwinder::new(eh_info, registers))
});
unwinder
});
+156 -39
View File
@@ -1,16 +1,25 @@
//! Defines a simple panic handler which handles stack traces as required.
use core::{arch::asm, panic::PanicInfo};
use core::{arch::asm, ops::Range, panic::PanicInfo};
use alloc::string::ToString;
use fallible_iterator::FallibleIterator;
use gimli::{Register, X86_64};
use alloc::{
borrow::{Cow, ToOwned},
slice,
string::{String, ToString},
vec::Vec,
};
use elf::segment::ProgramHeader;
use framehop::x86_64::{CacheX86_64, UnwinderX86_64};
use framehop::{Unwinder, x86_64::UnwindRegsX86_64};
use super::unwinder::Unwinder;
use crate::{
hcf,
arch::x86_64::memory::mapping::KERNEL_ADDRESS_REQUEST,
prelude::*,
std::unwind::{UNWINDER, eh_info::ELF, unwinder::RegisterSet},
std::{
self,
elf::ElfReader,
unwind::{UNWINDER, eh_info::ELF},
},
};
#[panic_handler]
@@ -18,37 +27,145 @@ use crate::{
pub fn panic_handler(info: &PanicInfo<'_>) -> ! {
debugln!("Kernel panic: {}", info);
let mut rip: u64;
let mut rsp: u64;
unsafe {
asm!("lea {0}, [rip]",
"mov {1}, rsp",
out(reg) rip, out(reg) rsp);
}
let mut unwinder = UNWINDER.lock();
unwinder.regs.set_pc(rip);
unwinder.regs.set_stack_ptr(rsp);
while let Some(call_frame) = unwinder.next().unwrap_or_else(|err| {
// If an unwind error occurred.
debugln!("{:?}", err);
hcf()
}) {
serial_println!("Got frame: {:x?}", call_frame);
let Some((symtab, strtab)) =
ELF.get_symbol_table().unwrap_or_else(|e| {
debugln!("{:?}", e);
hcf()
})
else {
// TODO: Omit symbol names but just print addresses.
debugln!("Didn't find symtab and strtab!");
hcf()
};
// let sym_name = symtab.get(call_frame.pc as usize).unwrap();
}
crate::hcf()
}
use object::{Object, ObjectSection, ObjectSegment};
use framehop::*;
pub fn add_object<U>(unwinder: &mut U, base_avma: u64)
where
U: Unwinder<Module = Module<Vec<u8>>>,
{
let mut buf = ELF.bytes;
let file = object::File::parse(buf).expect("Could not parse object file");
struct Module<'a>(object::File<'a, &'a [u8]>);
impl ModuleSectionInfo<Vec<u8>> for Module<'_> {
fn base_svma(&self) -> u64 {
relative_address_base(&self.0)
}
fn section_svma_range(&mut self, name: &[u8]) -> Option<Range<u64>> {
let section = self.0.section_by_name_bytes(name)?;
Some(section.address()..section.address() + section.size())
}
fn section_data(&mut self, name: &[u8]) -> Option<Vec<u8>> {
match self.0.section_by_name_bytes(name) {
Some(section) => {
section.data().ok().map(|data| data.to_owned())
}
None if name == b".debug_frame" => {
let section =
self.0.section_by_name_bytes(b"__zdebug_frame")?;
get_uncompressed_section_data(&section)
.map(|d| d.into_owned())
}
None => None,
}
}
fn segment_svma_range(&mut self, name: &[u8]) -> Option<Range<u64>> {
let segment = self
.0
.segments()
.find(|s| s.name_bytes() == Ok(Some(name)))?;
Some(segment.address()..segment.address() + segment.size())
}
fn segment_data(&mut self, name: &[u8]) -> Option<Vec<u8>> {
let segment = self
.0
.segments()
.find(|s| s.name_bytes() == Ok(Some(name)))?;
segment.data().ok().map(|data| data.to_owned())
}
}
let module = framehop::Module::new(
"Kernel".to_string(),
base_avma..(base_avma + buf.len() as u64),
base_avma,
Module(file),
);
unwinder.add_module(module);
}
fn get_uncompressed_section_data<'a>(
section: &impl object::ObjectSection<'a>,
) -> Option<Cow<'a, [u8]>> {
// let section_data = section.uncompressed_data().ok()?;
// // Make sure the data is actually decompressed.
// if section.name_bytes().ok()?.starts_with(b"__zdebug_")
// && section_data.starts_with(b"ZLIB\0\0\0\0")
// {
// // Object's built-in compressed section handling didn't detect this
// as a // compressed section. This happens on Go binaries which use
// compressed // sections like __zdebug_ranges, which is generally
// uncommon on macOS, // so object's mach-O parser doesn't handle
// them. // But we want to handle them.
// // Go stopped using zdebug sections for ELF files in https://github.com/golang/go/issues/50796
// // but still uses them for mach-O builds.
// let b = section_data.get(8..12)?;
// let uncompressed_size = u32::from_be_bytes([b[0], b[1], b[2], b[3]]);
// let compressed_bytes = &section_data[12..];
// let mut decompressed = Vec::with_capacity(uncompressed_size as
// usize); let mut decompress = flate2::Decompress::new(true);
// decompress
// .decompress_vec(
// compressed_bytes,
// &mut decompressed,
// flate2::FlushDecompress::Finish,
// )
// .ok()?;
// Some(Cow::Owned(decompressed))
// } else {
// Some(section_data)
// }
todo!()
}
/// Relative addresses are u32 offsets which are relative to some "base
/// address".
///
/// This function computes that base address. It is defined as follows:
///
/// - For Windows binaries, the base address is the "image base address".
/// - For mach-O binaries, the base address is the vmaddr of the __TEXT
/// segment.
/// - For ELF binaries, the base address is zero.
///
/// Stand-alone mach-O dylibs usually have a base address of zero because their
/// __TEXT segment is at address zero.
///
/// In the following cases, the base address is usually non-zero:
///
/// - The "image base address" of Windows binaries is usually non-zero.
/// - mach-O executable files (not dylibs) usually have their __TEXT segment at
/// address 0x100000000.
/// - mach-O libraries in the dyld shared cache have a __TEXT segment at some
/// non-zero address in the cache.
pub fn relative_address_base<'data>(
object_file: &impl object::Object<'data>,
) -> u64 {
if let Some(text_segment) = object_file
.segments()
.find(|s| s.name() == Ok(Some("__TEXT")))
{
// This is a mach-O image. "Relative addresses" are relative to the
// vmaddr of the __TEXT segment.
return text_segment.address();
}
// For PE binaries, relative_address_base() returns the image base address.
// Otherwise it returns zero. This gives regular ELF images a base address
// of zero, which is what we want.
object_file.relative_address_base()
}
+1
View File
@@ -46,6 +46,7 @@ impl FallibleIterator for Unwinder {
let Some(pc) = self.regs.get_pc() else {
return Err(UnwinderError::NoPcRegister);
};
// 0xffffffff8000014b b - 7 = 11 - 7 = 4
if self.is_first {
self.is_first = false;
+1 -1
View File
@@ -32,7 +32,7 @@ else
fi
# Set up test-specific flags
if [ $is_test -eq 1 ]; then
if [$is_test]; then
test_flags="-device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none"
serial_flags="-serial stdio"
else