Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit fa4be39

Browse files
committedFeb 18, 2025·
Specify kernel-base virt addr in BootloaderConfig
1 parent 170320b commit fa4be39

18 files changed

+313
-47
lines changed
 

‎Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ test_kernel_config_file = { path = "tests/test_kernels/config_file", artifact =
6060
test_kernel_min_stack = { path = "tests/test_kernels/min_stack", artifact = "bin", target = "x86_64-unknown-none" }
6161
test_kernel_lower_memory_free = { path = "tests/test_kernels/lower_memory_free", artifact = "bin", target = "x86_64-unknown-none" }
6262
test_kernel_write_usable_memory = { path = "tests/test_kernels/write_usable_memory", artifact = "bin", target = "x86_64-unknown-none" }
63+
test_kernel_fixed_kernel_address = { path = "tests/test_kernels/fixed_kernel_address", artifact = "bin", target = "x86_64-unknown-none" }
6364

6465
[profile.dev]
6566
panic = "abort"

‎Changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Unreleased
22

3+
## Features
4+
5+
* add `kernel_base` mapping to the BootloaderConfig to specify the virtual address
6+
at which position-independent kernels are loaded.
7+
8+
39
# 0.11.10 – 2025-02-10
410

511
* [Remove "UEFI boot" log message](https://github.com/rust-osdev/bootloader/pull/476)

‎api/build.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ fn main() {
1515
(31, 9),
1616
(40, 9),
1717
(49, 9),
18-
(58, 10),
19-
(68, 10),
20-
(78, 1),
21-
(79, 9),
18+
(58, 9),
19+
(67, 10),
20+
(77, 10),
21+
(87, 1),
2222
(88, 9),
2323
(97, 9),
2424
(106, 9),
2525
(115, 9),
26+
(124, 9),
2627
];
2728

2829
let mut code = String::new();

‎api/src/config.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl BootloaderConfig {
4343
0x3D,
4444
];
4545
#[doc(hidden)]
46-
pub const SERIALIZED_LEN: usize = 124;
46+
pub const SERIALIZED_LEN: usize = 133;
4747

4848
/// Creates a new default configuration with the following values:
4949
///
@@ -77,6 +77,7 @@ impl BootloaderConfig {
7777
} = version;
7878
let Mappings {
7979
kernel_stack,
80+
kernel_base,
8081
boot_info,
8182
framebuffer,
8283
physical_memory,
@@ -97,53 +98,56 @@ impl BootloaderConfig {
9798
concat_4_3(one, two)
9899
};
99100
let buf = concat_16_7(Self::UUID, version);
101+
100102
let buf = concat_23_8(buf, kernel_stack_size.to_le_bytes());
101103

102104
let buf = concat_31_9(buf, kernel_stack.serialize());
103-
let buf = concat_40_9(buf, boot_info.serialize());
104-
let buf = concat_49_9(buf, framebuffer.serialize());
105+
let buf = concat_40_9(buf, kernel_base.serialize());
106+
107+
let buf = concat_49_9(buf, boot_info.serialize());
108+
let buf = concat_58_9(buf, framebuffer.serialize());
105109

106-
let buf = concat_58_10(
110+
let buf = concat_67_10(
107111
buf,
108112
match physical_memory {
109113
Option::None => [0; 10],
110114
Option::Some(m) => concat_1_9([1], m.serialize()),
111115
},
112116
);
113-
let buf = concat_68_10(
117+
let buf = concat_77_10(
114118
buf,
115119
match page_table_recursive {
116120
Option::None => [0; 10],
117121
Option::Some(m) => concat_1_9([1], m.serialize()),
118122
},
119123
);
120-
let buf = concat_78_1(buf, [(*aslr) as u8]);
121-
let buf = concat_79_9(
124+
let buf = concat_87_1(buf, [(*aslr) as u8]);
125+
let buf = concat_88_9(
122126
buf,
123127
match dynamic_range_start {
124128
Option::None => [0; 9],
125129
Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
126130
},
127131
);
128-
let buf = concat_88_9(
132+
let buf = concat_97_9(
129133
buf,
130134
match dynamic_range_end {
131135
Option::None => [0; 9],
132136
Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
133137
},
134138
);
135139

136-
let buf = concat_97_9(buf, ramdisk_memory.serialize());
140+
let buf = concat_106_9(buf, ramdisk_memory.serialize());
137141

138-
let buf = concat_106_9(
142+
let buf = concat_115_9(
139143
buf,
140144
match minimum_framebuffer_height {
141145
Option::None => [0; 9],
142146
Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
143147
},
144148
);
145149

146-
concat_115_9(
150+
concat_124_9(
147151
buf,
148152
match minimum_framebuffer_width {
149153
Option::None => [0; 9],
@@ -196,6 +200,7 @@ impl BootloaderConfig {
196200

197201
let (mappings, s) = {
198202
let (&kernel_stack, s) = split_array_ref(s);
203+
let (&kernel_base, s) = split_array_ref(s);
199204
let (&boot_info, s) = split_array_ref(s);
200205
let (&framebuffer, s) = split_array_ref(s);
201206
let (&physical_memory_some, s) = split_array_ref(s);
@@ -211,6 +216,7 @@ impl BootloaderConfig {
211216

212217
let mappings = Mappings {
213218
kernel_stack: Mapping::deserialize(&kernel_stack)?,
219+
kernel_base: Mapping::deserialize(&kernel_base)?,
214220
boot_info: Mapping::deserialize(&boot_info)?,
215221
framebuffer: Mapping::deserialize(&framebuffer)?,
216222
physical_memory: match physical_memory_some {
@@ -371,6 +377,11 @@ pub struct Mappings {
371377
/// `FixedAddress(0xf_0000_0000)` will result in a guard page at address
372378
/// `0xf_0000_0000` and the kernel stack starting at address `0xf_0000_1000`.
373379
pub kernel_stack: Mapping,
380+
/// Configures the base address of the kernel.
381+
///
382+
/// If a fixed address is set, it must be paged aligned and the kernel must be
383+
/// a position-independent exectuable.
384+
pub kernel_base: Mapping,
374385
/// Specifies where the [`crate::BootInfo`] struct should be placed in virtual memory.
375386
pub boot_info: Mapping,
376387
/// Specifies the mapping of the frame buffer memory region.
@@ -413,6 +424,7 @@ impl Mappings {
413424
pub const fn new_default() -> Self {
414425
Self {
415426
kernel_stack: Mapping::new_default(),
427+
kernel_base: Mapping::new_default(),
416428
boot_info: Mapping::new_default(),
417429
framebuffer: Mapping::new_default(),
418430
physical_memory: Option::None,
@@ -430,6 +442,7 @@ impl Mappings {
430442
let recursive = rand::random();
431443
Self {
432444
kernel_stack: Mapping::random(),
445+
kernel_base: Mapping::random(),
433446
boot_info: Mapping::random(),
434447
framebuffer: Mapping::random(),
435448
physical_memory: if phys {

‎common/src/legacy_memory_region.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
use bootloader_api::info::{MemoryRegion, MemoryRegionKind};
2-
use core::{
3-
cmp,
4-
iter::{empty, Empty},
5-
mem::MaybeUninit,
6-
};
2+
use core::{cmp, mem::MaybeUninit};
73
use x86_64::{
84
align_down, align_up,
95
structures::paging::{FrameAllocator, PhysFrame, Size4KiB},

‎common/src/level_4_entries.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use crate::{entropy, load_kernel::VirtualAddressOffset, BootInfo, RawFrameBufferInfo};
1+
use crate::{
2+
entropy,
3+
load_kernel::{calc_elf_memory_requirements, ElfMemoryRequirements, VirtualAddressOffset},
4+
BootInfo, RawFrameBufferInfo,
5+
};
26
use bootloader_api::{config, info::MemoryRegion, BootloaderConfig};
37
use core::{alloc::Layout, iter::Step};
48
use rand::{
@@ -11,7 +15,7 @@ use x86_64::{
1115
structures::paging::{Page, PageTableIndex, Size4KiB},
1216
PhysAddr, VirtAddr,
1317
};
14-
use xmas_elf::program::ProgramHeader;
18+
use xmas_elf::{header, program::ProgramHeader, ElfFile};
1519

1620
/// Keeps track of used entries in a level 4 page table.
1721
///
@@ -33,7 +37,8 @@ impl UsedLevel4Entries {
3337
regions_len: usize,
3438
framebuffer: Option<&RawFrameBufferInfo>,
3539
config: &BootloaderConfig,
36-
) -> Self {
40+
kernel_elf: &ElfFile<'_>,
41+
) -> Result<Self, &'static str> {
3742
let mut used = UsedLevel4Entries {
3843
entry_state: [false; 512],
3944
rng: config.mappings.aslr.then(entropy::build_rng),
@@ -73,6 +78,23 @@ impl UsedLevel4Entries {
7378
used.mark_range_as_used(kernel_stack_address, config.kernel_stack_size);
7479
}
7580

81+
if let config::Mapping::FixedAddress(kernel_base) = config.mappings.kernel_base {
82+
let ElfMemoryRequirements { size, align, .. } =
83+
calc_elf_memory_requirements(kernel_elf);
84+
85+
if !VirtAddr::new(kernel_base).is_aligned(align) {
86+
return Err("kernel_code mapping alignment does not match elf file");
87+
}
88+
89+
used.mark_range_as_used(kernel_base, size);
90+
}
91+
if kernel_elf.header.pt2.type_().as_type() == header::Type::Executable {
92+
let ElfMemoryRequirements { size, min_addr, .. } =
93+
calc_elf_memory_requirements(kernel_elf);
94+
95+
used.mark_range_as_used(min_addr, size);
96+
}
97+
7698
if let config::Mapping::FixedAddress(boot_info_address) = config.mappings.boot_info {
7799
let boot_info_layout = Layout::new::<BootInfo>();
78100
let regions = regions_len + 1; // one region might be split into used/unused
@@ -110,7 +132,7 @@ impl UsedLevel4Entries {
110132
}
111133
}
112134

113-
used
135+
Ok(used)
114136
}
115137

116138
/// Marks all p4 entries in the range `[address..address+size)` as used.

‎common/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ where
187187
frame_allocator.len(),
188188
framebuffer,
189189
config,
190-
);
190+
&kernel.elf,
191+
)
192+
.expect("Failed to mark level 4 entries as used");
191193

192194
// Enable support for the no-execute bit in page tables.
193195
enable_nxe_bit();

‎common/src/load_kernel.rs

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{level_4_entries::UsedLevel4Entries, PAGE_SIZE};
2-
use bootloader_api::info::TlsTemplate;
2+
use bootloader_api::{config::Mapping, info::TlsTemplate};
33
use core::{cmp, iter::Step, mem::size_of, ops::Add};
44

55
use x86_64::{
@@ -59,27 +59,30 @@ where
5959
let virtual_address_offset = match elf_file.header.pt2.type_().as_type() {
6060
header::Type::None => unimplemented!(),
6161
header::Type::Relocatable => unimplemented!(),
62-
header::Type::Executable => VirtualAddressOffset::zero(),
62+
header::Type::Executable => match kernel.config.mappings.kernel_base {
63+
Mapping::Dynamic => VirtualAddressOffset::zero(),
64+
_ => {
65+
return Err(concat!(
66+
"Invalid kernel_code mapping. ",
67+
"Executable can only be mapped at virtual_address_offset 0."
68+
))
69+
}
70+
},
6371
header::Type::SharedObject => {
64-
// Find the highest virtual memory address and the biggest alignment.
65-
let load_program_headers = elf_file
66-
.program_iter()
67-
.filter(|h| matches!(h.get_type(), Ok(Type::Load)));
68-
let max_addr = load_program_headers
69-
.clone()
70-
.map(|h| h.virtual_addr() + h.mem_size())
71-
.max()
72-
.unwrap_or(0);
73-
let min_addr = load_program_headers
74-
.clone()
75-
.map(|h| h.virtual_addr())
76-
.min()
77-
.unwrap_or(0);
78-
let size = max_addr - min_addr;
79-
let align = load_program_headers.map(|h| h.align()).max().unwrap_or(1);
80-
81-
let offset = used_entries.get_free_address(size, align).as_u64();
82-
VirtualAddressOffset::new(i128::from(offset) - i128::from(min_addr))
72+
let ElfMemoryRequirements {
73+
size,
74+
align,
75+
min_addr,
76+
} = calc_elf_memory_requirements(&elf_file);
77+
match kernel.config.mappings.kernel_base {
78+
Mapping::Dynamic => {
79+
let offset = used_entries.get_free_address(size, align).as_u64();
80+
VirtualAddressOffset::new(i128::from(offset) - i128::from(min_addr))
81+
}
82+
Mapping::FixedAddress(address) => {
83+
VirtualAddressOffset::new(i128::from(address))
84+
}
85+
}
8386
}
8487
header::Type::Core => unimplemented!(),
8588
header::Type::ProcessorSpecific(_) => unimplemented!(),
@@ -750,6 +753,41 @@ pub fn load_kernel(
750753
))
751754
}
752755

756+
/// Basic information about the memory segments of an elf file.
757+
pub struct ElfMemoryRequirements {
758+
/// total size needed for all segments
759+
pub size: u64,
760+
/// memory alignment for the elf file
761+
pub align: u64,
762+
/// the smallest virtual address used by the elf file
763+
pub min_addr: u64,
764+
}
765+
766+
/// Calculates basic requirements needed to allocate memory for an elf file.
767+
pub fn calc_elf_memory_requirements(elf_file: &ElfFile) -> ElfMemoryRequirements {
768+
// Find the highest virtual memory address and the biggest alignment.
769+
let load_program_headers = elf_file
770+
.program_iter()
771+
.filter(|h| matches!(h.get_type(), Ok(Type::Load)));
772+
let max_addr = load_program_headers
773+
.clone()
774+
.map(|h| h.virtual_addr() + h.mem_size())
775+
.max()
776+
.unwrap_or(0);
777+
let min_addr = load_program_headers
778+
.clone()
779+
.map(|h| h.virtual_addr())
780+
.min()
781+
.unwrap_or(0);
782+
let size = max_addr - min_addr;
783+
let align = load_program_headers.map(|h| h.align()).max().unwrap_or(1);
784+
ElfMemoryRequirements {
785+
size,
786+
align,
787+
min_addr,
788+
}
789+
}
790+
753791
/// A helper type used to offset virtual addresses for position independent
754792
/// executables.
755793
#[derive(Clone, Copy)]

‎tests/fixed_kernel_address.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use bootloader_test_runner::run_test_kernel;
2+
3+
#[test]
4+
fn basic_boot() {
5+
run_test_kernel(env!(
6+
"CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_basic_boot"
7+
));
8+
}
9+
10+
#[test]
11+
fn should_panic() {
12+
run_test_kernel(env!(
13+
"CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_should_panic"
14+
));
15+
}
16+
17+
#[test]
18+
fn check_boot_info() {
19+
run_test_kernel(env!(
20+
"CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_check_boot_info"
21+
));
22+
}
23+
24+
#[test]
25+
fn verify_kernel_address() {
26+
run_test_kernel(env!(
27+
"CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_verify_kernel_address"
28+
));
29+
}

‎tests/test_kernels/Cargo.lock

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/test_kernels/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
"min_stack",
1313
"lower_memory_free",
1414
"write_usable_memory",
15+
"fixed_kernel_address",
1516
]
1617

1718
[profile.release]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "test_kernel_fixed_kernel_address"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[target.'cfg(target_arch = "x86_64")'.dependencies]
7+
bootloader_api = { path = "../../../api" }
8+
x86_64 = { version = "0.15.2", default-features = false, features = [
9+
"instructions",
10+
] }
11+
uart_16550 = "0.2.10"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![no_std] // don't link the Rust standard library
2+
#![no_main] // disable all Rust-level entry points
3+
4+
use bootloader_api::{entry_point, BootInfo};
5+
use test_kernel_fixed_kernel_address::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG};
6+
7+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
8+
9+
fn kernel_main(_boot_info: &'static mut BootInfo) -> ! {
10+
exit_qemu(QemuExitCode::Success);
11+
}
12+
13+
/// This function is called on panic.
14+
#[cfg(not(test))]
15+
#[panic_handler]
16+
fn panic(_info: &core::panic::PanicInfo) -> ! {
17+
exit_qemu(QemuExitCode::Failed);
18+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![no_std] // don't link the Rust standard library
2+
#![no_main] // disable all Rust-level entry points
3+
4+
use bootloader_api::{entry_point, BootInfo};
5+
use test_kernel_fixed_kernel_address::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG, KERNEL_ADDR};
6+
7+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
8+
9+
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
10+
assert_eq!(boot_info.kernel_image_offset, KERNEL_ADDR);
11+
12+
exit_qemu(QemuExitCode::Success);
13+
}
14+
15+
/// This function is called on panic.
16+
#[cfg(not(test))]
17+
#[panic_handler]
18+
fn panic(info: &core::panic::PanicInfo) -> ! {
19+
use core::fmt::Write;
20+
21+
let _ = writeln!(test_kernel_fixed_kernel_address::serial(), "PANIC: {info}");
22+
exit_qemu(QemuExitCode::Failed);
23+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![no_std] // don't link the Rust standard library
2+
#![no_main] // disable all Rust-level entry points
3+
4+
use bootloader_api::{entry_point, BootInfo};
5+
use test_kernel_fixed_kernel_address::BOOTLOADER_CONFIG;
6+
7+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
8+
9+
fn kernel_main(_boot_info: &'static mut BootInfo) -> ! {
10+
panic!();
11+
}
12+
13+
/// This function is called on panic.
14+
#[cfg(not(test))]
15+
#[panic_handler]
16+
fn panic(_info: &core::panic::PanicInfo) -> ! {
17+
use test_kernel_fixed_kernel_address::{exit_qemu, QemuExitCode};
18+
19+
exit_qemu(QemuExitCode::Success);
20+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![no_std] // don't link the Rust standard library
2+
#![no_main] // disable all Rust-level entry points
3+
4+
use bootloader_api::{entry_point, BootInfo};
5+
use test_kernel_fixed_kernel_address::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG, KERNEL_ADDR};
6+
7+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
8+
9+
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
10+
// verify that kernel is loaded at the specified base address.
11+
let rip = x86_64::registers::read_rip().as_u64();
12+
let kernel_start = KERNEL_ADDR;
13+
let kernel_end = kernel_start + boot_info.kernel_len;
14+
let kernel_range = kernel_start..kernel_end;
15+
16+
assert!(kernel_range.contains(&rip));
17+
18+
exit_qemu(QemuExitCode::Success);
19+
}
20+
21+
/// This function is called on panic.
22+
#[cfg(not(test))]
23+
#[panic_handler]
24+
fn panic(info: &core::panic::PanicInfo) -> ! {
25+
use core::fmt::Write;
26+
27+
let _ = writeln!(test_kernel_fixed_kernel_address::serial(), "PANIC: {info}");
28+
exit_qemu(QemuExitCode::Failed);
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![no_std]
2+
3+
use bootloader_api::{config::Mapping, BootloaderConfig};
4+
5+
pub const KERNEL_ADDR: u64 = 0x1987_6543_0000;
6+
7+
pub const BOOTLOADER_CONFIG: BootloaderConfig = {
8+
let mut config = BootloaderConfig::new_default();
9+
config.mappings.kernel_base = Mapping::FixedAddress(KERNEL_ADDR);
10+
config
11+
};
12+
13+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14+
#[repr(u32)]
15+
pub enum QemuExitCode {
16+
Success = 0x10,
17+
Failed = 0x11,
18+
}
19+
20+
pub fn exit_qemu(exit_code: QemuExitCode) -> ! {
21+
use x86_64::instructions::{nop, port::Port};
22+
23+
unsafe {
24+
let mut port = Port::new(0xf4);
25+
port.write(exit_code as u32);
26+
}
27+
28+
loop {
29+
nop();
30+
}
31+
}
32+
33+
pub fn serial() -> uart_16550::SerialPort {
34+
let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
35+
port.init();
36+
port
37+
}

0 commit comments

Comments
 (0)
Please sign in to comment.