diff --git a/Cargo.toml b/Cargo.toml index bdf7b9a..bf37ebd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] align-address = "0.3" +const_parse = "1" goblin = { version = "0.9", default-features = false, features = ["elf64"], optional = true } log = { version = "0.4", optional = true } plain = { version = "0.2", optional = true } diff --git a/src/elf.rs b/src/elf.rs index 1d14ddf..155d5e9 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -18,6 +18,7 @@ use log::{info, warn}; use plain::Plain; use crate::boot_info::{LoadInfo, TlsInfo}; +use crate::HermitVersion; // See https://refspecs.linuxbase.org/elf/x86_64-abi-0.98.pdf #[cfg(target_arch = "x86_64")] @@ -67,8 +68,12 @@ pub struct KernelObject<'a> { /// Symbol table for relocations dynsyms: &'a [Sym], + + /// The kernel's Hermit version if any. + hermit_version: Option, } +#[derive(Clone)] struct NoteIterator<'a> { bytes: &'a [u8], align: usize, @@ -104,6 +109,36 @@ fn iter_notes(bytes: &[u8], align: usize) -> NoteIterator<'_> { NoteIterator { bytes, align } } +#[derive(Debug)] +struct ParseHermitVersionError; + +impl TryFrom> for HermitVersion { + type Error = ParseHermitVersionError; + + fn try_from(value: Note<'_>) -> Result { + if value.name != "GNU" { + return Err(ParseHermitVersionError); + } + + if value.ty != crate::NT_GNU_ABI_TAG { + return Err(ParseHermitVersionError); + } + + let data = <[u8; 16]>::try_from(value.desc).map_err(|_| ParseHermitVersionError)?; + let data = unsafe { mem::transmute::<[u8; 16], [u32; 4]>(data) }; + + if data[0] != crate::ELF_NOTE_OS_HERMIT { + return Err(ParseHermitVersionError); + } + + Ok(Self { + major: data[1], + minor: data[2], + patch: data[3], + }) + } +} + /// An error returned when parsing a kernel ELF fails. #[derive(Debug)] pub struct ParseKernelError(&'static str); @@ -138,6 +173,22 @@ impl KernelObject<'_> { SectionHeader::slice_from_bytes_len(&elf[start..], len).unwrap() }; + let note_section = phs + .iter() + .find(|ph| ph.p_type == program_header::PT_NOTE) + .ok_or(ParseKernelError("Kernel does not have note section"))?; + let mut note_iter = iter_notes( + &elf[note_section.p_offset as usize..][..note_section.p_filesz as usize], + note_section.p_align as usize, + ); + + let hermit_version = note_iter + .clone() + .find_map(|note| HermitVersion::try_from(note).ok()); + if let Some(hermit_version) = hermit_version { + info!("Found Hermit version {hermit_version}"); + } + // General compatibility checks { let class = header.e_ident[header::EI_CLASS]; @@ -153,14 +204,6 @@ impl KernelObject<'_> { warn!("Kernel is not a hermit application"); } - let note_section = phs - .iter() - .find(|ph| ph.p_type == program_header::PT_NOTE) - .ok_or(ParseKernelError("Kernel does not have note section"))?; - let mut note_iter = iter_notes( - &elf[note_section.p_offset as usize..][..note_section.p_filesz as usize], - note_section.p_align as usize, - ); let note = note_iter .find(|note| note.name == "HERMIT" && note.ty == crate::NT_HERMIT_ENTRY_VERSION) .ok_or(ParseKernelError( @@ -222,9 +265,15 @@ impl KernelObject<'_> { phs, relas, dynsyms, + hermit_version, }) } + /// Returns the Hermit version of this kernel if present. + pub fn hermit_version(&self) -> Option { + self.hermit_version + } + /// Required memory size for loading. pub fn mem_size(&self) -> usize { let first_ph = self diff --git a/src/lib.rs b/src/lib.rs index e4a0e1c..eecc6df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,13 @@ pub mod elf; #[cfg(feature = "kernel")] mod note; +use core::fmt; + +#[doc(hidden)] +pub use const_parse::parse_u128 as _parse_u128; #[cfg(feature = "kernel")] #[doc(hidden)] -pub use note::_Note; +pub use note::{_AbiTag, _Note}; /// Kernel entry point. /// @@ -71,3 +75,32 @@ pub mod fc { pub const CMD_LINE_PTR_OFFSET: usize = 55; pub const CMD_LINE_SIZE_OFFSET: usize = 71; } + +#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))] +const NT_GNU_ABI_TAG: u32 = 1; +#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))] +const ELF_NOTE_OS_HERMIT: u32 = 6; + +/// A Hermit version. +#[derive(Clone, Copy, Debug)] +pub struct HermitVersion { + /// The major version of Hermit. + pub major: u32, + + /// The minor version of Hermit. + pub minor: u32, + + /// The patch version of Hermit. + pub patch: u32, +} + +impl fmt::Display for HermitVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + major, + minor, + patch, + } = self; + write!(f, "{major}.{minor}.{patch}") + } +} diff --git a/src/note.rs b/src/note.rs index 3bc09b7..01784aa 100644 --- a/src/note.rs +++ b/src/note.rs @@ -1,3 +1,7 @@ +use core::mem; + +use crate::HermitVersion; + /// Defines the hermit entry version in the note section. /// /// This macro must be used in a module that is guaranteed to be linked. @@ -39,3 +43,48 @@ struct Nhdr32 { n_descsz: u32, n_type: u32, } + +/// Defines the current Hermit kernel version in the note section. +/// +/// The version is saved in `.note.ABI-tag` in accordance with [LSB]. +/// +/// [LSB]: https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/noteabitag.html +#[macro_export] +macro_rules! define_abi_tag { + () => { + #[used] + #[link_section = ".note.ABI-tag"] + static ABI_TAG: $crate::_AbiTag = $crate::_AbiTag::new($crate::HermitVersion { + major: $crate::_parse_u128(::core::env!("CARGO_PKG_VERSION_MAJOR")) as u32, + minor: $crate::_parse_u128(::core::env!("CARGO_PKG_VERSION_MINOR")) as u32, + patch: $crate::_parse_u128(::core::env!("CARGO_PKG_VERSION_PATCH")) as u32, + }); + }; +} + +#[repr(C)] +#[doc(hidden)] +pub struct _AbiTag { + header: Nhdr32, + name: [u8; 4], + data: [u32; 4], +} + +impl _AbiTag { + pub const fn new(version: HermitVersion) -> Self { + Self { + header: Nhdr32 { + n_namesz: mem::size_of::<[u8; 4]>() as u32, + n_descsz: mem::size_of::<[u32; 4]>() as u32, + n_type: crate::NT_GNU_ABI_TAG, + }, + name: *b"GNU\0", + data: [ + crate::ELF_NOTE_OS_HERMIT, + version.major, + version.minor, + version.patch, + ], + } + } +}