Skip to content

Commit 7240a4f

Browse files
committed
integration-test: boot multiboot2_chainloader itself also via Multiboot2
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.
1 parent 517e470 commit 7240a4f

File tree

12 files changed

+102
-80
lines changed

12 files changed

+102
-80
lines changed

integration-test/bins/multiboot2_chainloader/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ publish = false
1212
anyhow.workspace = true
1313
log.workspace = true
1414
good_memory_allocator.workspace = true
15-
multiboot = "0.8"
1615
multiboot2.workspace = true
1716
multiboot2-header.workspace = true
1817
util.workspace = true

integration-test/bins/multiboot2_chainloader/link.ld

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ PHDRS
1111

1212
SECTIONS {
1313
/* Chainloader linked at 8M, payload at 16M */
14-
.text 8M : AT(8M) ALIGN(4K)
14+
.text 12M : AT(12M) ALIGN(4K)
1515
{
16-
KEEP(*(.multiboot_header));
16+
KEEP(*(.multiboot2_header));
1717
*(.text .text.*)
1818
} : kernel_rx
1919

integration-test/bins/multiboot2_chainloader/src/loader.rs

+46-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,57 @@
11
use alloc::boxed::Box;
22
use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType};
3+
use log::{debug, info};
34
use multiboot2::{
45
BootLoaderNameTag, CommandLineTag, MaybeDynSized, MemoryArea, MemoryAreaType, MemoryMapTag,
56
ModuleTag, SmbiosTag,
67
};
78

9+
fn get_free_mmap_areas(
10+
mbi: &multiboot2::BootInformation,
11+
) -> Vec<(u64 /* start */, u64 /* size */)> {
12+
match (mbi.memory_map_tag(), mbi.efi_memory_map_tag()) {
13+
(Some(mmt), None) => mmt
14+
.memory_areas()
15+
.iter()
16+
.filter(|ma| ma.typ() == MemoryAreaType::Available)
17+
.map(|ma| (ma.start_address(), ma.size()))
18+
.collect::<alloc::vec::Vec<_>>(),
19+
(None, Some(mmt)) => mmt
20+
.memory_areas()
21+
.filter(|ma| ma.ty == EFIMemoryAreaType::CONVENTIONAL)
22+
.map(|ma| (ma.phys_start, ma.page_count * 4096))
23+
.collect::<alloc::vec::Vec<_>>(),
24+
_ => panic!("Invalid memory map"),
25+
}
26+
}
27+
28+
fn assert_load_segment_fits_into_memory(
29+
start: u64,
30+
size: u64,
31+
free_areas: &[(u64 /* start */, u64 /* size */)],
32+
) {
33+
let end = start + size;
34+
let range = free_areas
35+
.iter()
36+
.find(|(a_start, a_size)| start >= *a_start && end <= a_start + a_size);
37+
if let Some(range) = range {
38+
debug!("Can load load segment (0x{start:x?}, {size:x?}) into free memory area {range:#x?}");
39+
} else {
40+
panic!("Can't load load segment (0x{start:x?}, {size:x?}) into any area!");
41+
}
42+
}
43+
844
/// Loads the first module into memory. Assumes that the module is a ELF file.
945
/// The handoff is performed according to the Multiboot2 spec.
10-
pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
46+
pub fn load_module(mbi: &multiboot2::BootInformation) -> ! {
47+
let mut modules = mbi.module_tags();
48+
1149
// Load the ELF from the Multiboot1 boot module.
1250
let elf_mod = modules.next().expect("Should have payload");
1351
let elf_bytes = unsafe {
1452
core::slice::from_raw_parts(
15-
elf_mod.start as *const u64 as *const u8,
16-
(elf_mod.end - elf_mod.start) as usize,
53+
elf_mod.start_address() as *const u64 as *const u8,
54+
elf_mod.module_size() as usize,
1755
)
1856
};
1957
let elf = elf_rs::Elf32::from_bytes(elf_bytes).expect("Should be valid ELF");
@@ -28,10 +66,11 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
2866
log::info!("Multiboot2 header:\n{hdr:#?}");
2967
}
3068

31-
// Map the load segments into memory (at their corresponding link).
69+
// Load the load segments into memory (at their corresponding link address).
3270
{
33-
let elf = elf_rs::Elf32::from_bytes(elf_bytes).expect("Should be valid ELF");
71+
let free_areas = get_free_mmap_areas(mbi);
3472
elf.program_header_iter()
73+
.inspect(|ph| assert_load_segment_fits_into_memory(ph.vaddr(), ph.memsz(), &free_areas))
3574
.filter(|ph| ph.ph_type() == ProgramType::LOAD)
3675
.for_each(|ph| {
3776
map_memory(ph);
@@ -66,7 +105,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
66105

67106
log::info!(
68107
"Handing over to ELF: {}",
69-
elf_mod.string.unwrap_or("<unknown>")
108+
elf_mod.cmdline().unwrap_or("<unknown>")
70109
);
71110

72111
// handoff
@@ -84,7 +123,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
84123
/// address space. The loader assumes that the addresses to not clash with the
85124
/// loader (or anything else).
86125
fn map_memory(ph: ProgramHeaderEntry) {
87-
log::debug!("Mapping LOAD segment {ph:#?}");
126+
debug!("Mapping LOAD segment {ph:#?}");
88127
let dest_ptr = ph.vaddr() as *mut u8;
89128
let content = ph.content().expect("Should have content");
90129
unsafe { core::ptr::copy(content.as_ptr(), dest_ptr, content.len()) };

integration-test/bins/multiboot2_chainloader/src/main.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#![feature(error_in_core)]
44

55
mod loader;
6-
mod multiboot;
76

87
extern crate alloc;
98

@@ -12,6 +11,7 @@ extern crate util;
1211

1312
use util::init_environment;
1413

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

1717
/// Entry into the Rust code from assembly using the x86 SystemV calling
@@ -20,7 +20,21 @@ core::arch::global_asm!(include_str!("start.S"), options(att_syntax));
2020
fn rust_entry(multiboot_magic: u32, multiboot_hdr: *const u32) -> ! {
2121
init_environment();
2222
log::debug!("multiboot_hdr={multiboot_hdr:x?}, multiboot_magic=0x{multiboot_magic:x?}");
23-
let mbi = multiboot::get_mbi(multiboot_magic, multiboot_hdr as u32).unwrap();
24-
let module_iter = mbi.modules().expect("Should provide modules");
25-
loader::load_module(module_iter);
23+
assert_eq!(multiboot_magic, multiboot2::MAGIC);
24+
let mbi = unsafe { multiboot2::BootInformation::load(multiboot_hdr.cast()) }.unwrap();
25+
26+
if let Some(mmap) = mbi.efi_memory_map_tag() {
27+
log::debug!("efi memory map:",);
28+
for desc in mmap.memory_areas() {
29+
log::warn!(
30+
" start=0x{:016x?} size={:016x?} type={:?}, attr={:?}",
31+
desc.phys_start,
32+
desc.page_count * 4096,
33+
desc.ty,
34+
desc.att
35+
);
36+
}
37+
}
38+
39+
loader::load_module(&mbi);
2640
}

integration-test/bins/multiboot2_chainloader/src/multiboot.rs

-41
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# The assembly code uses the GNU Assembly (GAS) flavor with Intel noprefix
2+
# syntax.
3+
4+
# Symbol from main.rs
5+
.EXTERN start
6+
7+
.code32
8+
.align 8
9+
.section .multiboot2_header
10+
11+
mb2_header_start:
12+
.long 0xe85250d6 # magic number
13+
.long 0 # architecture 0 (protected mode i386)
14+
.long mb2_header_end - mb2_header_start # header length
15+
# checksum
16+
.long 0x100000000 - (0xe85250d6 + 0 + (mb2_header_end - mb2_header_start))
17+
18+
# REQUIRED END TAG
19+
.align 8
20+
.Lmb2_header_tag_end_start:
21+
.word 0 # type (16bit)
22+
.word 0 # flags (16bit)
23+
.long .Lmb2_header_tag_end_end - .Lmb2_header_tag_end_start # size (32bit)
24+
.Lmb2_header_tag_end_end:
25+
mb2_header_end:

integration-test/bins/multiboot2_chainloader/src/start.S

-13
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,6 @@
33

44
.code32
55

6-
.section .multiboot_header, "a", @progbits
7-
8-
/*
9-
* Multiboot v1 Header.
10-
* Required so that we can be booted by QEMU via the "-kernel" parameter.
11-
*/
12-
.align 8
13-
.global multiboot_header
14-
multiboot_header:
15-
.long 0x1badb002
16-
.long 0x0
17-
.long -0x1badb002
18-
196
.section .text
207

218
.global start

integration-test/bins/multiboot2_payload/link.ld

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ PHDRS
1111

1212
SECTIONS {
1313
/* Chainloader linked at 8M, payload at 16M */
14-
.text 16M : AT(16M) ALIGN(4K)
14+
.text 24M : AT(24M) ALIGN(4K)
1515
{
16-
*(.multiboot2_header)
16+
KEEP(*(.multiboot2_header));
1717
*(.text .text.*)
1818
} : kernel_rx
1919

integration-test/bins/multiboot2_payload/src/multiboot2_header.S

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# Multiboot2 Header definition.
21
# The assembly code uses the GNU Assembly (GAS) flavor with Intel noprefix
32
# syntax.
43

integration-test/bins/util/src/allocator.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use good_memory_allocator::SpinLockedAllocator;
22

3-
#[repr(align(0x4000))]
4-
struct Align16K<T>(T);
3+
#[repr(align(0x1000))]
4+
struct PageAlign<T>(T);
55

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

99
#[global_allocator]
1010
static ALLOCATOR: SpinLockedAllocator = SpinLockedAllocator::empty();

integration-test/run.sh

+4-3
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ function fn_build_rust_bins() {
4444
cargo build --release --verbose
4545
cd -
4646

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

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

157158
fn_run_test_bios $TEST_DIR/image.iso
158-
#fn_run_test_uefi $TEST_DIR/image.iso
159+
fn_run_test_uefi $TEST_DIR/image.iso
159160
}
160161

161162
fn_main

integration-test/tests/02-boot-loader-and-chainload/limine.cfg

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ VERBOSE=yes
44
INTERFACE_BRANDING=integration-test
55

66
:integration-test
7-
# For simplicity reasons, the loader itself boots via Multiboot 1. Sufficient.
8-
PROTOCOL=multiboot
7+
PROTOCOL=multiboot2
98
KERNEL_PATH=boot:///kernel
109
KERNEL_CMDLINE=some kernel cmdline
1110
MODULE_PATH=boot:///payload

0 commit comments

Comments
 (0)