Skip to content

Commit

Permalink
integration-test: boot multiboot2_chainloader itself also via Multiboot2
Browse files Browse the repository at this point in the history
Since I don't use QEMU Multiboot1-direct boot anymore, we can use Multiboot2
here as well.

This also fights some problems with Limine which doesn't want to load the
Multiboot1 kernel in an UEFI environment.
  • Loading branch information
phip1611 committed Aug 24, 2024
1 parent 517e470 commit 7240a4f
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 80 deletions.
1 change: 0 additions & 1 deletion integration-test/bins/multiboot2_chainloader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,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/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
53 changes: 46 additions & 7 deletions integration-test/bins/multiboot2_chainloader/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
use alloc::boxed::Box;
use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType};
use log::{debug, info};
use multiboot2::{
BootLoaderNameTag, CommandLineTag, 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<_>>(),
(None, 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!("Invalid 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 +66,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 Down Expand Up @@ -66,7 +105,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 +123,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
22 changes: 18 additions & 4 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,6 +11,7 @@ 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
Expand All @@ -20,7 +20,21 @@ core::arch::global_asm!(include_str!("start.S"), options(att_syntax));
fn rust_entry(multiboot_magic: u32, multiboot_hdr: *const u32) -> ! {
init_environment();
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:
13 changes: 0 additions & 13 deletions integration-test/bins/multiboot2_chainloader/src/start.S
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,6 @@

.code32

.section .multiboot_header, "a", @progbits

/*
* Multiboot v1 Header.
* Required so that we can be booted by QEMU via the "-kernel" parameter.
*/
.align 8
.global multiboot_header
multiboot_header:
.long 0x1badb002
.long 0x0
.long -0x1badb002

.section .text

.global start
Expand Down
4 changes: 2 additions & 2 deletions integration-test/bins/multiboot2_payload/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 16M : AT(16M) ALIGN(4K)
.text 24M : AT(24M) ALIGN(4K)
{
*(.multiboot2_header)
KEEP(*(.multiboot2_header));
*(.text .text.*)
} : kernel_rx

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Multiboot2 Header definition.
# The assembly code uses the GNU Assembly (GAS) flavor with Intel noprefix
# syntax.

Expand Down
8 changes: 4 additions & 4 deletions integration-test/bins/util/src/allocator.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use good_memory_allocator::SpinLockedAllocator;

#[repr(align(0x4000))]
struct Align16K<T>(T);
#[repr(align(0x1000))]
struct PageAlign<T>(T);

/// 16 KiB naturally aligned backing storage for heap.
static mut HEAP: Align16K<[u8; 0x4000]> = Align16K([0; 0x4000]);
/// 16 KiB page-aligned backing storage for heap.
static mut HEAP: PageAlign<[u8; 0x4000]> = PageAlign([0; 0x4000]);

#[global_allocator]
static ALLOCATOR: SpinLockedAllocator = SpinLockedAllocator::empty();
Expand Down
7 changes: 4 additions & 3 deletions integration-test/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ function fn_build_rust_bins() {
cargo build --release --verbose
cd -

echo "Verifying multiboot2_chainloader ..."
test -f $BINS_DIR/multiboot2_chainloader
file --brief $BINS_DIR/multiboot2_chainloader | grep -q "ELF 32-bit LSB executable"
# For simplicity, the chainloader itself boots via Multiboot 1. Sufficient.
grub-file --is-x86-multiboot $BINS_DIR/multiboot2_chainloader
grub-file --is-x86-multiboot2 $BINS_DIR/multiboot2_chainloader

echo "Verifying multiboot2_payload ..."
test -f $BINS_DIR/multiboot2_payload
file --brief $BINS_DIR/multiboot2_payload | grep -q "ELF 32-bit LSB executable"
grub-file --is-x86-multiboot2 $BINS_DIR/multiboot2_payload
Expand Down Expand Up @@ -155,7 +156,7 @@ function fn_test_loader() {
fn_build_limine_iso

fn_run_test_bios $TEST_DIR/image.iso
#fn_run_test_uefi $TEST_DIR/image.iso
fn_run_test_uefi $TEST_DIR/image.iso
}

fn_main
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ VERBOSE=yes
INTERFACE_BRANDING=integration-test

:integration-test
# For simplicity reasons, the loader itself boots via Multiboot 1. Sufficient.
PROTOCOL=multiboot
PROTOCOL=multiboot2
KERNEL_PATH=boot:///kernel
KERNEL_CMDLINE=some kernel cmdline
MODULE_PATH=boot:///payload
Expand Down

0 comments on commit 7240a4f

Please sign in to comment.