Skip to content

Commit 52a0a67

Browse files
authored
Merge pull request #335 from rust-osdev/kernel-stack-fixes
Create kernel stack with correct size and set up a guard page
2 parents ca98936 + aee51ad commit 52a0a67

File tree

8 files changed

+148
-33
lines changed

8 files changed

+148
-33
lines changed

Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ members = [
2525
"tests/test_kernels/pie",
2626
"tests/test_kernels/lto",
2727
"tests/test_kernels/ramdisk",
28+
"tests/test_kernels/min_stack",
2829
]
2930
exclude = ["examples/basic", "examples/test_framework"]
3031

@@ -65,6 +66,7 @@ test_kernel_map_phys_mem = { path = "tests/test_kernels/map_phys_mem", artifact
6566
test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = "x86_64-unknown-none" }
6667
test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" }
6768
test_kernel_config_file = { path = "tests/test_kernels/config_file", artifact = "bin", target = "x86_64-unknown-none" }
69+
test_kernel_min_stack = { path = "tests/test_kernels/min_stack", artifact = "bin", target = "x86_64-unknown-none" }
6870

6971
[profile.dev]
7072
panic = "abort"
@@ -118,6 +120,9 @@ rustflags = [
118120
"code-model=large",
119121
]
120122

123+
[profile.test.package.test_kernel_min_stack]
124+
opt-level = 2
125+
121126
[build-dependencies]
122127
llvm-tools = "0.1.1"
123128
async-process = "1.6.0"

api/src/config.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ pub struct BootloaderConfig {
2222
/// The size of the stack that the bootloader should allocate for the kernel (in bytes).
2323
///
2424
/// The bootloader starts the kernel with a valid stack pointer. This setting defines
25-
/// the stack size that the bootloader should allocate and map. The stack is created
26-
/// with a guard page, so a stack overflow will lead to a page fault.
25+
/// the stack size that the bootloader should allocate and map.
26+
///
27+
/// The stack is created with a additional guard page, so a stack overflow will lead to
28+
/// a page fault.
2729
pub kernel_stack_size: u64,
2830

2931
/// Configuration for the frame buffer that can be used by the kernel to display pixels
@@ -360,6 +362,14 @@ impl Default for ApiVersion {
360362
#[non_exhaustive]
361363
pub struct Mappings {
362364
/// Configures how the kernel stack should be mapped.
365+
///
366+
/// If a fixed address is set, it must be page aligned.
367+
///
368+
/// Note that the first page of the kernel stack is intentionally left unmapped
369+
/// to act as a guard page. This ensures that a page fault occurs on a stack
370+
/// overflow. For example, setting the kernel stack address to
371+
/// `FixedAddress(0xf_0000_0000)` will result in a guard page at address
372+
/// `0xf_0000_0000` and the kernel stack starting at address `0xf_0000_1000`.
363373
pub kernel_stack: Mapping,
364374
/// Specifies where the [`crate::BootInfo`] struct should be placed in virtual memory.
365375
pub boot_info: Mapping,

common/src/lib.rs

+49-31
Original file line numberDiff line numberDiff line change
@@ -207,17 +207,20 @@ where
207207
.expect("no entry point");
208208
log::info!("Entry point at: {:#x}", entry_point.as_u64());
209209
// create a stack
210-
let stack_start_addr = mapping_addr(
211-
config.mappings.kernel_stack,
212-
config.kernel_stack_size,
213-
16,
214-
&mut used_entries,
215-
);
216-
let stack_start: Page = Page::containing_address(stack_start_addr);
217-
let stack_end = {
218-
let end_addr = stack_start_addr + config.kernel_stack_size;
219-
Page::containing_address(end_addr - 1u64)
210+
let stack_start = {
211+
// we need page-alignment because we want a guard page directly below the stack
212+
let guard_page = mapping_addr_page_aligned(
213+
config.mappings.kernel_stack,
214+
// allocate an additional page as a guard page
215+
Size4KiB::SIZE + config.kernel_stack_size,
216+
&mut used_entries,
217+
"kernel stack start",
218+
);
219+
guard_page + 1
220220
};
221+
let stack_end_addr = stack_start.start_address() + config.kernel_stack_size;
222+
223+
let stack_end = Page::containing_address(stack_end_addr - 1u64);
221224
for page in Page::range_inclusive(stack_start, stack_end) {
222225
let frame = frame_allocator
223226
.allocate_frame()
@@ -265,13 +268,12 @@ where
265268
let framebuffer_start_frame: PhysFrame = PhysFrame::containing_address(framebuffer.addr);
266269
let framebuffer_end_frame =
267270
PhysFrame::containing_address(framebuffer.addr + framebuffer.info.byte_len - 1u64);
268-
let start_page = Page::from_start_address(mapping_addr(
271+
let start_page = mapping_addr_page_aligned(
269272
config.mappings.framebuffer,
270273
u64::from_usize(framebuffer.info.byte_len),
271-
Size4KiB::SIZE,
272274
&mut used_entries,
273-
))
274-
.expect("the framebuffer address must be page aligned");
275+
"framebuffer",
276+
);
275277
for (i, frame) in
276278
PhysFrame::range_inclusive(framebuffer_start_frame, framebuffer_end_frame).enumerate()
277279
{
@@ -292,19 +294,17 @@ where
292294
};
293295
let ramdisk_slice_len = system_info.ramdisk_len;
294296
let ramdisk_slice_start = if let Some(ramdisk_address) = system_info.ramdisk_addr {
295-
let ramdisk_address_start = mapping_addr(
297+
let start_page = mapping_addr_page_aligned(
296298
config.mappings.ramdisk_memory,
297299
system_info.ramdisk_len,
298-
Size4KiB::SIZE,
299300
&mut used_entries,
301+
"ramdisk start",
300302
);
301303
let physical_address = PhysAddr::new(ramdisk_address);
302304
let ramdisk_physical_start_page: PhysFrame<Size4KiB> =
303305
PhysFrame::containing_address(physical_address);
304306
let ramdisk_page_count = (system_info.ramdisk_len - 1) / Size4KiB::SIZE;
305307
let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count;
306-
let start_page = Page::from_start_address(ramdisk_address_start)
307-
.expect("the ramdisk start address must be page aligned");
308308

309309
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
310310
for (i, frame) in
@@ -320,7 +320,7 @@ where
320320
),
321321
};
322322
}
323-
Some(ramdisk_address_start)
323+
Some(start_page.start_address())
324324
} else {
325325
None
326326
};
@@ -334,7 +334,8 @@ where
334334

335335
let size = max_phys.as_u64();
336336
let alignment = Size2MiB::SIZE;
337-
let offset = mapping_addr(mapping, size, alignment, &mut used_entries);
337+
let offset = mapping_addr(mapping, size, alignment, &mut used_entries)
338+
.expect("start address for physical memory mapping must be 2MiB-page-aligned");
338339

339340
for frame in PhysFrame::range_inclusive(start_frame, end_frame) {
340341
let page = Page::containing_address(offset + frame.start_address().as_u64());
@@ -390,7 +391,10 @@ where
390391
Mappings {
391392
framebuffer: framebuffer_virt_addr,
392393
entry_point,
393-
stack_end,
394+
// Use the configured stack size, even if it's not page-aligned. However, we
395+
// need to align it down to the next 16-byte boundary because the System V
396+
// ABI requires a 16-byte stack alignment.
397+
stack_top: stack_end_addr.align_down(16u8),
394398
used_entries,
395399
physical_memory_offset,
396400
recursive_index,
@@ -407,8 +411,8 @@ where
407411
pub struct Mappings {
408412
/// The entry point address of the kernel.
409413
pub entry_point: VirtAddr,
410-
/// The stack end page of the kernel.
411-
pub stack_end: Page,
414+
/// The (exclusive) end address of the kernel stack.
415+
pub stack_top: VirtAddr,
412416
/// Keeps track of used entries in the level 4 page table, useful for finding a free
413417
/// virtual memory when needed.
414418
pub used_entries: UsedLevel4Entries,
@@ -462,11 +466,8 @@ where
462466
u64::from_usize(combined.size()),
463467
u64::from_usize(combined.align()),
464468
&mut mappings.used_entries,
465-
);
466-
assert!(
467-
boot_info_addr.is_aligned(u64::from_usize(combined.align())),
468-
"boot info addr is not properly aligned"
469-
);
469+
)
470+
.expect("boot info addr is not properly aligned");
470471

471472
let memory_map_regions_addr = boot_info_addr + memory_regions_offset;
472473
let memory_map_regions_end = boot_info_addr + combined.size();
@@ -561,7 +562,7 @@ pub fn switch_to_kernel(
561562
} = page_tables;
562563
let addresses = Addresses {
563564
page_table: kernel_level_4_frame,
564-
stack_top: mappings.stack_end.start_address(),
565+
stack_top: mappings.stack_top,
565566
entry_point: mappings.entry_point,
566567
boot_info,
567568
};
@@ -612,15 +613,32 @@ struct Addresses {
612613
boot_info: &'static mut BootInfo,
613614
}
614615

616+
fn mapping_addr_page_aligned(
617+
mapping: Mapping,
618+
size: u64,
619+
used_entries: &mut UsedLevel4Entries,
620+
kind: &str,
621+
) -> Page {
622+
match mapping_addr(mapping, size, Size4KiB::SIZE, used_entries) {
623+
Ok(addr) => Page::from_start_address(addr).unwrap(),
624+
Err(addr) => panic!("{kind} address must be page-aligned (is `{addr:?})`"),
625+
}
626+
}
627+
615628
fn mapping_addr(
616629
mapping: Mapping,
617630
size: u64,
618631
alignment: u64,
619632
used_entries: &mut UsedLevel4Entries,
620-
) -> VirtAddr {
621-
match mapping {
633+
) -> Result<VirtAddr, VirtAddr> {
634+
let addr = match mapping {
622635
Mapping::FixedAddress(addr) => VirtAddr::new(addr),
623636
Mapping::Dynamic => used_entries.get_free_address(size, alignment),
637+
};
638+
if addr.is_aligned(alignment) {
639+
Ok(addr)
640+
} else {
641+
Err(addr)
624642
}
625643
}
626644

tests/min_stack.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use bootloader_test_runner::run_test_kernel;
2+
3+
#[test]
4+
fn basic_boot() {
5+
run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_MIN_STACK_basic_boot"));
6+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "test_kernel_min_stack"
3+
version = "0.1.0"
4+
authors = ["Philipp Oppermann <[email protected]>"]
5+
edition = "2021"
6+
7+
[dependencies]
8+
bootloader_api = { path = "../../../api" }
9+
x86_64 = { version = "0.14.7", default-features = false, features = [
10+
"instructions",
11+
"inline_asm",
12+
] }
13+
uart_16550 = "0.2.10"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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, BootloaderConfig};
5+
use core::fmt::Write;
6+
use test_kernel_min_stack::{exit_qemu, serial, QemuExitCode};
7+
8+
const BOOTLOADER_CONFIG: BootloaderConfig = {
9+
let mut config = BootloaderConfig::new_default();
10+
config.kernel_stack_size = 3000;
11+
config
12+
};
13+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
14+
15+
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
16+
writeln!(serial(), "Entered kernel with boot info: {boot_info:?}").unwrap();
17+
exit_qemu(QemuExitCode::Success);
18+
}
19+
20+
/// This function is called on panic.
21+
#[panic_handler]
22+
#[cfg(not(test))]
23+
fn panic(info: &core::panic::PanicInfo) -> ! {
24+
let _ = writeln!(serial(), "PANIC: {info}");
25+
exit_qemu(QemuExitCode::Failed);
26+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![no_std]
2+
3+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4+
#[repr(u32)]
5+
pub enum QemuExitCode {
6+
Success = 0x10,
7+
Failed = 0x11,
8+
}
9+
10+
pub fn exit_qemu(exit_code: QemuExitCode) -> ! {
11+
use x86_64::instructions::{nop, port::Port};
12+
13+
unsafe {
14+
let mut port = Port::new(0xf4);
15+
port.write(exit_code as u32);
16+
}
17+
18+
loop {
19+
nop();
20+
}
21+
}
22+
23+
pub fn serial() -> uart_16550::SerialPort {
24+
let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
25+
port.init();
26+
port
27+
}

0 commit comments

Comments
 (0)