Begin setting up stack unwinding/tracing.
This actually worked so I am chuffed, but it doesn't read the necessary DWARF structures just yet. Still a good step forwards.
This commit is contained in:
Generated
+14
@@ -99,6 +99,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "elf"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -111,7 +117,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
|
"elf",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"gimli",
|
||||||
"libm",
|
"libm",
|
||||||
"limine",
|
"limine",
|
||||||
"linked_list_allocator",
|
"linked_list_allocator",
|
||||||
@@ -145,6 +153,12 @@ dependencies = [
|
|||||||
"pin-utils",
|
"pin-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.31.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|||||||
+2
-7
@@ -19,13 +19,8 @@ futures-util = { version = "0.3.31", default-features = false, features = [
|
|||||||
"alloc",
|
"alloc",
|
||||||
] }
|
] }
|
||||||
linked_list_allocator = { version = "0.10.5", features = ["use_spin"] }
|
linked_list_allocator = { version = "0.10.5", features = ["use_spin"] }
|
||||||
# unwinding = { version = "0.2.5", default-features = false, features = [
|
gimli = { version = "0.31.1", default-features = false, features = ["read"] }
|
||||||
# "unwinder",
|
elf = { version = "0.7.4", default-features = false, features = ["nightly"] }
|
||||||
# "fde-static",
|
|
||||||
# "personality",
|
|
||||||
# "panic",
|
|
||||||
# "hide-trace",
|
|
||||||
# ] }
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.2.14"
|
cc = "1.2.14"
|
||||||
|
|||||||
@@ -46,12 +46,6 @@ SECTIONS
|
|||||||
*(.rodata .rodata.*)
|
*(.rodata .rodata.*)
|
||||||
} :rodata
|
} :rodata
|
||||||
|
|
||||||
/* Adds support for stack unwinding using the unwinding crate. */
|
|
||||||
. = ALIGN(8);
|
|
||||||
PROVIDE(__eh_frame = .);
|
|
||||||
.eh_frame : { KEEP (*(.eh_frame)) *(.eh_frame.*) }
|
|
||||||
|
|
||||||
|
|
||||||
/* Move to the next memory page for .data */
|
/* Move to the next memory page for .data */
|
||||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ static HIGHER_HALF_DIRECT_MAP_REQUEST: HhdmRequest = HhdmRequest::new();
|
|||||||
|
|
||||||
#[used]
|
#[used]
|
||||||
#[unsafe(link_section = ".requests")]
|
#[unsafe(link_section = ".requests")]
|
||||||
static KERNEL_ADDRESS_REQUEST: KernelAddressRequest =
|
pub static KERNEL_ADDRESS_REQUEST: KernelAddressRequest =
|
||||||
KernelAddressRequest::new();
|
KernelAddressRequest::new();
|
||||||
|
|
||||||
/// ```rs
|
/// ```rs
|
||||||
|
|||||||
+14
-1
@@ -19,6 +19,8 @@ use arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator;
|
|||||||
use arch::x86_64::memory::mapping;
|
use arch::x86_64::memory::mapping;
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
use limine::BaseRevision;
|
use limine::BaseRevision;
|
||||||
|
use std::unwind;
|
||||||
|
use std::unwind::eh_info::ELF;
|
||||||
use x86_64::VirtAddr;
|
use x86_64::VirtAddr;
|
||||||
|
|
||||||
pub mod arch;
|
pub mod arch;
|
||||||
@@ -64,7 +66,7 @@ pub fn hcf() -> ! {
|
|||||||
|
|
||||||
pub fn boot() -> Result<(), &'static str> {
|
pub fn boot() -> Result<(), &'static str> {
|
||||||
if !BASE_REVISION.is_supported() {
|
if !BASE_REVISION.is_supported() {
|
||||||
return Err("base revision not supported");
|
return Err("Base revision not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
use arch::x86_64::{gdt, interrupts};
|
use arch::x86_64::{gdt, interrupts};
|
||||||
@@ -118,5 +120,16 @@ pub fn boot() -> Result<(), &'static str> {
|
|||||||
x86_64::instructions::interrupts::enable();
|
x86_64::instructions::interrupts::enable();
|
||||||
println_log!("[Success]");
|
println_log!("[Success]");
|
||||||
|
|
||||||
|
print_log!(" Setting up stack unwinder, panic handler... ");
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
let _eh_info =
|
||||||
|
unsafe { unwind::eh_info::EhInfo::from_hdr_ptr(eh_frame_ptr) };
|
||||||
|
println_log!("[Success]");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
//! Basic ELF parsing functionality using the `elf` crate.
|
||||||
|
//!
|
||||||
|
//! This may be extended in the future to support loading programs, however we
|
||||||
|
//! currently use this for getting the sizes of sections in our kernel ELF at
|
||||||
|
//! runtime.
|
||||||
|
//!
|
||||||
|
//! This is used for implementing stacktraces in std::unwind.
|
||||||
|
//!
|
||||||
|
//! # TODO
|
||||||
|
//!
|
||||||
|
//! * Add support for loading binary programs (this should probably be written
|
||||||
|
//! in a different module)
|
||||||
|
|
||||||
|
use alloc::format;
|
||||||
|
use elf::{
|
||||||
|
ElfBytes, ParseError,
|
||||||
|
endian::LittleEndian,
|
||||||
|
parse::{ParseAt, ParsingTable},
|
||||||
|
section::{SectionHeader, SectionHeaderTable},
|
||||||
|
string_table::StringTable,
|
||||||
|
};
|
||||||
|
use limine::request::KernelFileRequest;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
/// The length of the ELF header in bytes.
|
||||||
|
pub const ELF_HEADER_LEN: usize = 64;
|
||||||
|
|
||||||
|
/// Information about our own ELF file to make ELF parsing easier, such as the
|
||||||
|
/// length of the file and a pointer to the contents.
|
||||||
|
#[used]
|
||||||
|
pub static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new();
|
||||||
|
|
||||||
|
/// A list of errors that may occur when parsing ELF files.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ElfError {
|
||||||
|
/// Returned if a section did not exist in [ElfReader::get_section_size].
|
||||||
|
SectionNotExists,
|
||||||
|
/// Parse errors returned by the `elf` crate.
|
||||||
|
OtherParseError(elf::ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<elf::ParseError> for ElfError {
|
||||||
|
fn from(err: elf::ParseError) -> Self {
|
||||||
|
Self::OtherParseError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ElfReader {
|
||||||
|
/// Structure returned by the `elf` crate having parsed the ELF header.
|
||||||
|
elf: ElfBytes<'static, LittleEndian>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElfReader {
|
||||||
|
/// Parses the ELF file for the kernel, this uses data from Limine's Kernel
|
||||||
|
/// File Request to get a slice over the whole executable file.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Assumes a properly formed ELF file, and that Limine returns a correct
|
||||||
|
/// pointer to the start of the file as well as a valid length in bytes.
|
||||||
|
///
|
||||||
|
/// 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) };
|
||||||
|
|
||||||
|
let elf: ElfBytes<'static, LittleEndian> =
|
||||||
|
elf::ElfBytes::minimal_parse(elf_hdr_slice)?;
|
||||||
|
|
||||||
|
Ok(Self { elf })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the section size of `section_name` in bytes.
|
||||||
|
pub fn get_section_size(
|
||||||
|
&self,
|
||||||
|
section_name: &'static str,
|
||||||
|
) -> Result<u64, ElfError> {
|
||||||
|
Ok(self.get_section_header(section_name)?.sh_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the start address of the section `section_name` in memory.
|
||||||
|
pub fn get_section_addr(
|
||||||
|
&self,
|
||||||
|
section_name: &'static str,
|
||||||
|
) -> Result<*const u8, ElfError> {
|
||||||
|
Ok(self.get_section_header(section_name)?.sh_addr as *const u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the section header of `section_name`.
|
||||||
|
pub fn get_section_header(
|
||||||
|
&self,
|
||||||
|
section_name: &'static str,
|
||||||
|
) -> Result<SectionHeader, ElfError> {
|
||||||
|
let section_hdr = self
|
||||||
|
.elf
|
||||||
|
.section_header_by_name(section_name)
|
||||||
|
.map_err(|_e| ElfError::SectionNotExists)?;
|
||||||
|
|
||||||
|
section_hdr.ok_or(ElfError::SectionNotExists)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
pub mod application;
|
pub mod application;
|
||||||
pub mod ascii;
|
pub mod ascii;
|
||||||
|
pub mod elf;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod maths;
|
pub mod maths;
|
||||||
|
pub mod unwind;
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
//! Contains a [EhInfo] struct that contains the parsed DWARF exception header
|
||||||
|
//! data from the ELF .eh_frame and .eh_frame_hdr sections.
|
||||||
|
|
||||||
|
use alloc::{boxed::Box, slice};
|
||||||
|
use gimli::{
|
||||||
|
BaseAddresses, EhFrame, EhFrameHdr, EhHdrTable, EndianSlice, LittleEndian,
|
||||||
|
ParsedEhFrameHdr,
|
||||||
|
};
|
||||||
|
use spin::Lazy;
|
||||||
|
|
||||||
|
use crate::{println_log, std::elf::ElfReader};
|
||||||
|
|
||||||
|
/// Contains useful data parsed from the ELF file in question. In the kernel
|
||||||
|
/// this will be our own process.
|
||||||
|
///
|
||||||
|
/// We use this to implement stack traces and potential unwinding.
|
||||||
|
///
|
||||||
|
/// # Sources
|
||||||
|
///
|
||||||
|
/// This code is reproduced from [lesenchal.fr](https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way#h5.1-parsing-eh_frame-and-eh_frame_hdr-with-gimli)
|
||||||
|
/// and will later be extended as required.
|
||||||
|
pub struct EhInfo {
|
||||||
|
/// A set of base addresses used for relative addressing.
|
||||||
|
base_addrs: BaseAddresses,
|
||||||
|
/// The parsed `.eh_frame_hdr` section.
|
||||||
|
hdr: &'static ParsedEhFrameHdr<EndianSlice<'static, LittleEndian>>,
|
||||||
|
/// The lookup table in the parsed `.eh_frame_hdr` section.
|
||||||
|
/// This is a binary search table, it is optional but should be present as
|
||||||
|
/// we are linking with LLD(?) \[needs citation].
|
||||||
|
hdr_table: EhHdrTable<'static, EndianSlice<'static, LittleEndian>>,
|
||||||
|
/// The parsed `.eh_frame` containing the CFIs (call frame information).
|
||||||
|
eh_frame: EhFrame<EndianSlice<'static, LittleEndian>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores the [ElfReader] struct for this ELF file.
|
||||||
|
pub static ELF: Lazy<ElfReader> =
|
||||||
|
Lazy::new(|| unsafe { ElfReader::new().unwrap() });
|
||||||
|
|
||||||
|
impl EhInfo {
|
||||||
|
/// Gets the `.eh_frame_hdr` size in bytes.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If we can't get the size of `.eh_frame_hdr`.
|
||||||
|
pub fn eh_frame_hdr_size() -> usize {
|
||||||
|
ELF.get_section_size(".eh_frame_hdr")
|
||||||
|
.expect("Cannot get size of `.eh_frame_hdr`.") as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the `.eh_frame` size in bytes.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If we can't get the size of `.eh_frame`.
|
||||||
|
pub fn eh_frame_size() -> usize {
|
||||||
|
ELF.get_section_size(".eh_frame")
|
||||||
|
.expect("Cannot get size of `.eh_frame`.") as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a [EhInfo] from the base address. This is defined for a
|
||||||
|
/// symbol in the linker script so we can initialise stack traces and -- in
|
||||||
|
/// the future -- unwinding.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Assumes the `.eh_frame_hdr` pointer to be valid, as well as the sizes of
|
||||||
|
/// the containing sections. These sizes are computed using [ElfReader].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function panics if Gimli throws parsing errors -- for example due
|
||||||
|
/// to a malformed or corrupted binary, or because this is called in
|
||||||
|
/// release-mode, or on a stripped binary.
|
||||||
|
///
|
||||||
|
/// # TODOs
|
||||||
|
///
|
||||||
|
/// * Support external System.map files which list symbols and contain
|
||||||
|
/// debugging information.
|
||||||
|
pub unsafe fn from_hdr_ptr(eh_frame_hdr: *const u8) -> Self {
|
||||||
|
let mut base_addrs = BaseAddresses::default();
|
||||||
|
// We add the `.eh_frame_hdr` pointer to the set of base addresses which
|
||||||
|
// are used by Gimli for later parsing. This may be used to compute a
|
||||||
|
// pointer to `.eh_frame`.
|
||||||
|
base_addrs = base_addrs.set_eh_frame_hdr(eh_frame_hdr as u64);
|
||||||
|
|
||||||
|
// Leaking the Box gives us a reference with `'static` lifetime to use
|
||||||
|
// in Self.
|
||||||
|
let hdr = Box::leak(Box::new(
|
||||||
|
// We need to construct a slice as input for `EhFrameHdr::new`.
|
||||||
|
// This is sound if data pointer and length are known to be
|
||||||
|
// correct.
|
||||||
|
EhFrameHdr::new(
|
||||||
|
unsafe {
|
||||||
|
core::slice::from_raw_parts(
|
||||||
|
eh_frame_hdr,
|
||||||
|
Self::eh_frame_hdr_size(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
LittleEndian,
|
||||||
|
) // Parse the header using the base address we provided (virtual
|
||||||
|
// memory). Address size is how many bytes make an
|
||||||
|
// address (64 bits).
|
||||||
|
.parse(&base_addrs, 8)
|
||||||
|
.expect(
|
||||||
|
"Could not parse `.eh_frame_hdr`. The ELF must be malformed.",
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Create a pointer to the `.eh_frame` ready to parse it.
|
||||||
|
let eh_frame = match hdr.eh_frame_ptr() {
|
||||||
|
gimli::Pointer::Direct(addr) => addr as *mut u8,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the `.eh_frame` address for addresses relative to this section.
|
||||||
|
base_addrs = base_addrs.set_eh_frame(eh_frame as u64);
|
||||||
|
|
||||||
|
// Finally parse the `.eh_frame` section of our ELF.
|
||||||
|
let eh_frame = EhFrame::new(
|
||||||
|
unsafe {
|
||||||
|
core::slice::from_raw_parts(eh_frame, Self::eh_frame_size())
|
||||||
|
},
|
||||||
|
LittleEndian,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
base_addrs,
|
||||||
|
hdr,
|
||||||
|
hdr_table: hdr.table().expect(
|
||||||
|
"The CFI binary search table was not present in this binary, oh dear.",
|
||||||
|
),
|
||||||
|
eh_frame,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod eh_info;
|
||||||
Reference in New Issue
Block a user