hermit_entry/
lib.rs

1//! # Hermit's loading and entry API.
2//!
3//! This crate parses and loads Hermit applications ([`elf`]).
4//!
5//! Additionally, this crate unifies Hermit's entry API ([`Entry`]) for all loaders and the kernel.
6
7#![no_std]
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![warn(missing_docs)]
10
11pub mod boot_info;
12
13#[cfg(feature = "loader")]
14pub mod elf;
15
16#[cfg(feature = "kernel")]
17mod note;
18
19use core::error::Error;
20use core::fmt;
21use core::str::FromStr;
22
23#[doc(hidden)]
24pub use const_parse::parse_u128 as _parse_u128;
25#[cfg(feature = "kernel")]
26#[doc(hidden)]
27pub use note::{_AbiTag, _Note};
28
29/// GZIP magic number.
30///
31/// For details, see [10.17487/RFC1952](https://doi.org/10.17487/RFC1952).
32#[cfg(feature = "loader")]
33const GZIPMAG: &[u8; 3] = &[0x1f, 0x8b, 0x08];
34
35/// Possible input formats for a Hermit loader.
36#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[cfg(feature = "loader")]
38pub enum Format {
39    /// An ELF file, probably a Hermit kernel.
40    Elf,
41    /// A gzipped tar file, probably containing a config + ELF kernel image, and associated files.
42    Gzip,
43}
44
45/// Attempts to detect the format of an input file (using magic bytes), whether it is an ELF kernel or an image.
46#[cfg(feature = "loader")]
47pub fn detect_format(data: &[u8]) -> Option<Format> {
48    if data.len() < 4 {
49        None
50    } else if data.starts_with(goblin::elf64::header::ELFMAG) {
51        Some(Format::Elf)
52    } else if data.starts_with(GZIPMAG) {
53        Some(Format::Gzip)
54    } else {
55        None
56    }
57}
58
59/// Kernel entry point.
60///
61/// This is the signature of the entry point of the kernel.
62///
63/// `cpu_id` is the number of the CPU core with the boot processor being number 0.
64///
65/// The stack pointer has to be valid for the boot processor only.
66#[cfg(not(target_arch = "riscv64"))]
67pub type Entry =
68    unsafe extern "C" fn(raw_boot_info: &'static boot_info::RawBootInfo, cpu_id: u32) -> !;
69
70/// Kernel entry point.
71///
72/// This is the signature of the entry point of the kernel.
73///
74/// `hart_id` is the number of the hardware thread.
75///
76/// The stack pointer has to be valid for the boot processor only.
77#[cfg(target_arch = "riscv64")]
78pub type Entry =
79    unsafe extern "C" fn(hart_id: usize, raw_boot_info: &'static boot_info::RawBootInfo) -> !;
80
81/// Note type for specifying the hermit entry version.
82///
83/// The note name for this is `HERMIT`.
84///
85/// The `desc` field will be 1 word, which specifies the hermit entry version.
86#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))]
87const NT_HERMIT_ENTRY_VERSION: u32 = 0x5a00;
88
89/// The current hermit entry version.
90#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))]
91const HERMIT_ENTRY_VERSION: u8 = 4;
92
93/// Note type for specifying the Uhyve interface version in an elf header.
94#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))]
95const NT_UHYVE_INTERFACE_VERSION: u32 = 0x5b00;
96
97/// Offsets and values used to interpret the boot params ("zeropage") setup by firecracker
98/// For the full list of values see
99/// <https://github.com/torvalds/linux/blob/b6839ef26e549de68c10359d45163b0cfb031183/arch/x86/include/uapi/asm/bootparam.h#L151-L198>
100#[expect(missing_docs)]
101pub mod fc {
102    pub const LINUX_KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
103    pub const LINUX_KERNEL_HRD_MAGIC: u32 = 0x53726448;
104    pub const LINUX_SETUP_HEADER_OFFSET: usize = 0x1f1;
105    pub const BOOT_FLAG_OFFSET: usize = 13;
106    pub const HDR_MAGIC_OFFSET: usize = 17;
107    pub const E820_ENTRIES_OFFSET: usize = 0x1e8;
108    pub const E820_TABLE_OFFSET: usize = 0x2d0;
109    pub const RAMDISK_IMAGE_OFFSET: usize = 39;
110    pub const RAMDISK_SIZE_OFFSET: usize = 43;
111    pub const CMD_LINE_PTR_OFFSET: usize = 55;
112    pub const CMD_LINE_SIZE_OFFSET: usize = 71;
113}
114
115#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))]
116const NT_GNU_ABI_TAG: u32 = 1;
117#[cfg_attr(not(any(feature = "loader", feature = "kernel")), expect(dead_code))]
118const ELF_NOTE_OS_HERMIT: u32 = 6;
119
120/// A Hermit version.
121#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug)]
122pub struct HermitVersion {
123    /// The major version of Hermit.
124    pub major: u32,
125
126    /// The minor version of Hermit.
127    pub minor: u32,
128
129    /// The patch version of Hermit.
130    pub patch: u32,
131}
132
133impl fmt::Display for HermitVersion {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        let Self {
136            major,
137            minor,
138            patch,
139        } = self;
140        write!(f, "{major}.{minor}.{patch}")
141    }
142}
143
144impl FromStr for HermitVersion {
145    type Err = ParseHermitVersionError;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        let (major, rest) = s.split_once('.').ok_or(ParseHermitVersionError)?;
149        let (minor, patch) = rest.split_once('.').ok_or(ParseHermitVersionError)?;
150
151        let major = major.parse().map_err(|_| ParseHermitVersionError)?;
152        let minor = minor.parse().map_err(|_| ParseHermitVersionError)?;
153        let patch = patch.parse().map_err(|_| ParseHermitVersionError)?;
154
155        Ok(Self {
156            major,
157            minor,
158            patch,
159        })
160    }
161}
162
163/// An error which can be returned when parsing a [`HermitVersion`].
164#[derive(Debug, Clone, PartialEq, Eq)]
165#[non_exhaustive]
166pub struct ParseHermitVersionError;
167
168impl fmt::Display for ParseHermitVersionError {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        f.write_str("provided string could not be parsed as Hermit version")
171    }
172}
173
174impl Error for ParseHermitVersionError {}
175
176/// A Uhyve interface version.
177#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug)]
178pub struct UhyveIfVersion(pub u32);
179
180impl fmt::Display for UhyveIfVersion {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        self.0.fmt(f)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn cmp_hermit_version() {
192        let small = HermitVersion {
193            major: 0,
194            minor: 1,
195            patch: 2,
196        };
197        let big = HermitVersion {
198            major: 2,
199            minor: 1,
200            patch: 0,
201        };
202
203        assert!(small < big);
204        assert!(small == small);
205        assert!(big == big);
206        assert!(big > small);
207    }
208
209    #[test]
210    fn parse_hermit_version() {
211        let version = HermitVersion::from_str("0.1.2").unwrap();
212        assert_eq!(
213            version,
214            HermitVersion {
215                major: 0,
216                minor: 1,
217                patch: 2,
218            }
219        );
220
221        let version = HermitVersion::from_str("2.1.0").unwrap();
222        assert_eq!(
223            version,
224            HermitVersion {
225                major: 2,
226                minor: 1,
227                patch: 0,
228            }
229        );
230    }
231}