Skip to content

Introduction to hermit‐entry

Panagiotis "Ivory" Vasilopoulos edited this page Jun 13, 2025 · 1 revision

This document describes hermit-entry from the perspective of a Uhyve developer. It mostly includes technical details that are not directly related to Uhyve itself on a superficial level.

hermit-entry is the loader of Hermit. While QEMU requires using a prebuilt copy of the Hermit loader as its "kernel", Uhyve - being a special-purpose hypervisor/VMM that is only meant to run Hermit images - directly integrates hermit-entry as part of its core functionality.

hermit-entry parses the ELF sections Hermit images "on behalf" of Uhyve, and takes care of some lifting, such as parsing the following:

  • the version of a Hermit kernel
  • the minimum memory required to load the kernel
  • the start address that is predefined by the kernel

A start address is predefined when the file's headers do not "say" that the file is relocatable. In that case, e.g. Program Header Offset defined in the ELF file is used. Otherwise, hermit-entry performs relocations if the file is relocatable.

The kernel's entry point is defined from the ELF file's e_entry. It is essentially a virtual address that is to be used after the kernel is loaded onto the memory. If the image is relocatable, the value read from e_entry is added to start_addr. In the context of Uhyve, start_addr is an address randomly generated by Uhyve if ASLR is enabled. Otherwise a default value of 0x40000 (KERNEL_OFFSET) is used.

Example Program

The following program depicts an example of how hermit-entry is used. This is essentially a very simplified version of code that was copy-pasted from the kernel.

The backstory is that I attempted to experiment with hermit-entry to create a debugging tool that depicts some basic information on ELF files; it uses all interfaces exposed by hermit-entry. However, this quick project didn't go very far, as hermit-entry's functionality that actually parses images is, understandably, private. Perhaps it could be "recycled" into some useful test cases in the future.

Cargo.toml:

[package]
name = "hermit-entry-demo"
version = "0.1.0"
edition = "2024"

[dependencies]
clap = { version = "4.5", features = ["derive", "env"] }
hermit-entry = { version = "0.10.3", features = ["loader"] }
thiserror = "2.0.12"
uhyve-interface = "0.1.3"

src/main.rs:

use std::io;
use std::{fs, path::PathBuf};

use clap::Parser;
use hermit_entry::elf::{KernelObject, ParseKernelError};
use thiserror::Error;

#[derive(Parser, Debug)]
#[clap(version, author)]
struct Args {
    #[clap(short = 'i', long, value_name = "KERNEL")]
    kernel_path: PathBuf,
}

#[derive(Error, Debug)]
pub enum LoadKernelError {
    #[error(transparent)]
    Io(#[from] io::Error),
    #[error("{0}")]
    ParseKernelError(ParseKernelError),
    #[error("guest memory size is not large enough")]
    InsufficientMemory,
}

fn main() {
    let args = Args::parse();
    let kernel_path = args.kernel_path;

    let elf = fs::read(&kernel_path).unwrap();
    let object: KernelObject<'_> = KernelObject::parse(&elf)
        .map_err(LoadKernelError::ParseKernelError)
        .unwrap();

    println!("Version: {:?}", object.hermit_version());
    println!("Memory Size: {:#x?}", object.mem_size());
    println!("Start Address: {:#x?}", object.start_addr().unwrap());
}

Clone this wiki locally