Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration test uefi #225

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "limine-bootloader"]
path = integration-test/limine-bootloader
url = https://github.com/limine-bootloader/limine.git
branch = v7.x-binary
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

## Developer Guide

This is a pretty normal Cargo workspace with two crates. For the integration
test, please read the
corresponding [instructions](./integration-test/README.md).
1 change: 1 addition & 0 deletions integration-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
serial.txt
20 changes: 14 additions & 6 deletions integration-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@
This directory contains integration tests for the `multiboot2` and the
`multiboot2-header` crate. The integration tests start a QEMU VM and do certain
checks at runtime. If something fails, they instruct QEMU to exit with an error
code. All output of the VM is printed to the screen. If
code. All output of the VM is printed to the screen.

The `bins` directory contains binaries that **are** the tests. The `tests`
The `bins` directory contains Rust binaries that **are** the tests. The `tests`
directory contains test definitions, run scripts, and other relevant files. The
main entry to run all tests is `./run.sh` in this directory.

## TL;DR:
- `$ nix-shell --run ./run.sh` to execute the integration tests with Nix (recommended)
- `$ ./run.sh` to execute the integration tests (you have to install dependencies manually)
## TL;DR

- `$ nix-shell --run ./run.sh` to execute the integration tests with Nix
(recommended)
- `$ nix-shell --run "integration-test/run.sh"` to run the test from the
project root
- `$ ./run.sh` to execute the integration tests (you have to get the
dependencies manually)

## Prerequisites

The tests are executed best when using [`nix`](https://nixos.org/)/`nix-shell`
to get the relevant tools. Otherwise, please make sure the following packages
are available:
- grub helper tools

- grub helper tools (grub-file)
- rustup
- OVMF
- QEMU
- xorriso
16 changes: 0 additions & 16 deletions integration-test/bins/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions integration-test/bins/multiboot2_chainloader/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[package]
name = "multiboot2_chainloader"
description = "Integrationtest: Multiboot2 chainloader"
description = """
Integrationtest: Multiboot2 chainloader. The loader itself loads via Multiboot1,
but the payload is loaded via Multiboot2 by the loader.
"""
version = "0.1.0"
edition = "2021"
publish = false
Expand All @@ -9,7 +12,6 @@ publish = false
anyhow.workspace = true
log.workspace = true
good_memory_allocator.workspace = true
multiboot = "0.8"
multiboot2.workspace = true
multiboot2-header.workspace = true
util.workspace = true
Expand Down
4 changes: 2 additions & 2 deletions integration-test/bins/multiboot2_chainloader/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
fn main() {
let linker_script = "multiboot2_chainloader/link.ld";
let linker_script = "link.ld";
println!("cargo:rerun-if-changed={linker_script}");
println!("cargo:rustc-link-arg=-T{linker_script}");
println!("cargo:rustc-link-arg=-Tmultiboot2_chainloader/{linker_script}");
}
4 changes: 2 additions & 2 deletions integration-test/bins/multiboot2_chainloader/link.ld
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ PHDRS

SECTIONS {
/* Chainloader linked at 8M, payload at 16M */
.text 8M : AT(8M) ALIGN(4K)
.text 12M : AT(12M) ALIGN(4K)
{
KEEP(*(.multiboot_header));
KEEP(*(.multiboot2_header));
*(.text .text.*)
} : kernel_rx

Expand Down
64 changes: 52 additions & 12 deletions integration-test/bins/multiboot2_chainloader/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,58 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType};
use log::{debug, info};
use multiboot2::{
BootLoaderNameTag, CommandLineTag, MaybeDynSized, MemoryArea, MemoryAreaType, MemoryMapTag,
ModuleTag, SmbiosTag,
BootLoaderNameTag, CommandLineTag, EFIMemoryAreaType, MaybeDynSized, MemoryArea,
MemoryAreaType, MemoryMapTag, ModuleTag, SmbiosTag,
};

fn get_free_mmap_areas(
mbi: &multiboot2::BootInformation,
) -> Vec<(u64 /* start */, u64 /* size */)> {
match (mbi.memory_map_tag(), mbi.efi_memory_map_tag()) {
(Some(mmt), None) => mmt
.memory_areas()
.iter()
.filter(|ma| ma.typ() == MemoryAreaType::Available)
.map(|ma| (ma.start_address(), ma.size()))
.collect::<alloc::vec::Vec<_>>(),
(_, Some(mmt)) => mmt
.memory_areas()
.filter(|ma| ma.ty == EFIMemoryAreaType::CONVENTIONAL)
.map(|ma| (ma.phys_start, ma.page_count * 4096))
.collect::<alloc::vec::Vec<_>>(),
_ => panic!("No usable memory map"),
}
}

fn assert_load_segment_fits_into_memory(
start: u64,
size: u64,
free_areas: &[(u64 /* start */, u64 /* size */)],
) {
let end = start + size;
let range = free_areas
.iter()
.find(|(a_start, a_size)| start >= *a_start && end <= a_start + a_size);
if let Some(range) = range {
debug!("Can load load segment (0x{start:x?}, {size:x?}) into free memory area {range:#x?}");
} else {
panic!("Can't load load segment (0x{start:x?}, {size:x?}) into any area!");
}
}

/// Loads the first module into memory. Assumes that the module is a ELF file.
/// The handoff is performed according to the Multiboot2 spec.
pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
pub fn load_module(mbi: &multiboot2::BootInformation) -> ! {
let mut modules = mbi.module_tags();

// Load the ELF from the Multiboot1 boot module.
let elf_mod = modules.next().expect("Should have payload");
let elf_bytes = unsafe {
core::slice::from_raw_parts(
elf_mod.start as *const u64 as *const u8,
(elf_mod.end - elf_mod.start) as usize,
elf_mod.start_address() as *const u64 as *const u8,
elf_mod.module_size() as usize,
)
};
let elf = elf_rs::Elf32::from_bytes(elf_bytes).expect("Should be valid ELF");
Expand All @@ -28,10 +67,11 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
log::info!("Multiboot2 header:\n{hdr:#?}");
}

// Map the load segments into memory (at their corresponding link).
// Load the load segments into memory (at their corresponding link address).
{
let elf = elf_rs::Elf32::from_bytes(elf_bytes).expect("Should be valid ELF");
let free_areas = get_free_mmap_areas(mbi);
elf.program_header_iter()
.inspect(|ph| assert_load_segment_fits_into_memory(ph.vaddr(), ph.memsz(), &free_areas))
.filter(|ph| ph.ph_type() == ProgramType::LOAD)
.for_each(|ph| {
map_memory(ph);
Expand All @@ -53,9 +93,9 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
MemoryAreaType::Reserved,
)]))
.add_module(ModuleTag::new(
elf_mod.start as u32,
elf_mod.end as u32,
elf_mod.string.unwrap(),
elf_mod.start_address(),
elf_mod.end_address(),
elf_mod.cmdline().unwrap(),
))
// Test that we can add SmbiosTag multiple times.
.add_smbios(SmbiosTag::new(1, 1, &[1, 2, 3]))
Expand All @@ -66,7 +106,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {

log::info!(
"Handing over to ELF: {}",
elf_mod.string.unwrap_or("<unknown>")
elf_mod.cmdline().unwrap_or("<unknown>")
);

// handoff
Expand All @@ -84,7 +124,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
/// address space. The loader assumes that the addresses to not clash with the
/// loader (or anything else).
fn map_memory(ph: ProgramHeaderEntry) {
log::debug!("Mapping LOAD segment {ph:#?}");
debug!("Mapping LOAD segment {ph:#?}");
let dest_ptr = ph.vaddr() as *mut u8;
let content = ph.content().expect("Should have content");
unsafe { core::ptr::copy(content.as_ptr(), dest_ptr, content.len()) };
Expand Down
24 changes: 18 additions & 6 deletions integration-test/bins/multiboot2_chainloader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#![feature(error_in_core)]

mod loader;
mod multiboot;

extern crate alloc;

Expand All @@ -12,17 +11,30 @@ extern crate util;

use util::init_environment;

core::arch::global_asm!(include_str!("multiboot2_header.S"), options(att_syntax));
core::arch::global_asm!(include_str!("start.S"), options(att_syntax));

/// Entry into the Rust code from assembly using the x86 SystemV calling
/// convention.
#[no_mangle]
fn rust_entry(multiboot_magic: u32, multiboot_hdr: *const u32) -> ! {
init_environment();
let x = 0.12 + 0.56;
log::debug!("{x}");
log::debug!("multiboot_hdr={multiboot_hdr:x?}, multiboot_magic=0x{multiboot_magic:x?}");
let mbi = multiboot::get_mbi(multiboot_magic, multiboot_hdr as u32).unwrap();
let module_iter = mbi.modules().expect("Should provide modules");
loader::load_module(module_iter);
assert_eq!(multiboot_magic, multiboot2::MAGIC);
let mbi = unsafe { multiboot2::BootInformation::load(multiboot_hdr.cast()) }.unwrap();

if let Some(mmap) = mbi.efi_memory_map_tag() {
log::debug!("efi memory map:",);
for desc in mmap.memory_areas() {
log::warn!(
" start=0x{:016x?} size={:016x?} type={:?}, attr={:?}",
desc.phys_start,
desc.page_count * 4096,
desc.ty,
desc.att
);
}
}

loader::load_module(&mbi);
}
41 changes: 0 additions & 41 deletions integration-test/bins/multiboot2_chainloader/src/multiboot.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# The assembly code uses the GNU Assembly (GAS) flavor with Intel noprefix
# syntax.

# Symbol from main.rs
.EXTERN start

.code32
.align 8
.section .multiboot2_header

mb2_header_start:
.long 0xe85250d6 # magic number
.long 0 # architecture 0 (protected mode i386)
.long mb2_header_end - mb2_header_start # header length
# checksum
.long 0x100000000 - (0xe85250d6 + 0 + (mb2_header_end - mb2_header_start))

# REQUIRED END TAG
.align 8
.Lmb2_header_tag_end_start:
.word 0 # type (16bit)
.word 0 # flags (16bit)
.long .Lmb2_header_tag_end_end - .Lmb2_header_tag_end_start # size (32bit)
.Lmb2_header_tag_end_end:
mb2_header_end:
Loading
Loading