//! 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.
    pub base_addrs: BaseAddresses,
    /// The parsed `.eh_frame_hdr` section.
    pub 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].
    pub hdr_table: EhHdrTable<'static, EndianSlice<'static, LittleEndian>>,
    /// The parsed `.eh_frame` containing the CFIs (call frame information).
    pub 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,
        }
    }
}
