fix: stacks at top of user VA in own 1 MiB band (guard, heap kept below), 8/16 pages default; kernel fault msg names pid, kind, flags stack overflow; flaky OVERFLOW + spawntest/test-qemu check it
This commit is contained in:
@@ -182,17 +182,29 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
.is_some_and(|ctx| fp::first_use(frame, ctx)) => {}
|
||||
_ => {
|
||||
let uart = crate::hw::uart::Uart::new();
|
||||
uart.write_str("FAULT: scause=");
|
||||
uart.write_hex(scause);
|
||||
let stval: usize;
|
||||
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
||||
uart.write_str(" stval=");
|
||||
let kern = kern();
|
||||
uart.write_str("FAULT pid=");
|
||||
let pid = kern.current_thread().and_then(|t| kern.thread_process(t));
|
||||
uart.write_hex(pid.map_or(usize::MAX, |p| p.raw() as usize));
|
||||
uart.write_str(": ");
|
||||
uart.write_str(match scause {
|
||||
2 => "illegal instruction",
|
||||
5 | 13 => "load fault",
|
||||
7 | 15 => "store fault",
|
||||
12 => "instruction fetch fault",
|
||||
_ => "fault",
|
||||
});
|
||||
uart.write_str(" at ");
|
||||
uart.write_hex(stval);
|
||||
uart.write_str(" sepc=");
|
||||
if matches!(scause, 5 | 7 | 13 | 15) && types::layout::is_stack_overflow(stval) {
|
||||
uart.write_str(" (stack overflow: give it more stack)");
|
||||
}
|
||||
uart.write_str(", pc ");
|
||||
uart.write_hex(frame.sepc);
|
||||
uart.put_char(b'\n');
|
||||
|
||||
let kern = kern();
|
||||
if let Some(current_tid) = kern.current_thread() {
|
||||
let fault = match scause {
|
||||
2 => domain::fault::Fault::IllegalInstruction,
|
||||
|
||||
@@ -16,6 +16,16 @@ pub mod op {
|
||||
pub const CRASH: usize = 2;
|
||||
/// Blocks for good inside the handler.
|
||||
pub const HANG: usize = 3;
|
||||
/// Recurses until its stack runs out: the kernel says it overflowed.
|
||||
pub const OVERFLOW: usize = 4;
|
||||
}
|
||||
|
||||
/// Ever deeper, a KiB of stack a call, until there is none.
|
||||
#[allow(unconditional_recursion, reason = "running off the stack is what it is for")]
|
||||
fn deeper(n: usize) -> usize {
|
||||
let frame = core::hint::black_box([n as u8; 1024]);
|
||||
// Used after the call, so the call is not a jump.
|
||||
deeper(n + 1) + frame[n % 1024] as usize
|
||||
}
|
||||
|
||||
/// Everything goes through `handle_call`.
|
||||
@@ -49,6 +59,9 @@ impl Server for Flaky {
|
||||
match words[0] {
|
||||
op::WHO => {}
|
||||
op::CRASH => panic!("asked to crash"),
|
||||
op::OVERFLOW => {
|
||||
core::hint::black_box(deeper(0));
|
||||
}
|
||||
op::HANG => {
|
||||
// A notification nobody signals.
|
||||
if let Ok(never) = syscall::create_notification() {
|
||||
|
||||
@@ -267,6 +267,16 @@ fn restarted_after_hanging() -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A server that runs off its stack faults on the guard below it, and is
|
||||
/// restarted like any other that died.
|
||||
fn restarted_after_overflowing() -> Result<(), &'static str> {
|
||||
let _ = syscall::request(FLAKY, [flaky::op::OVERFLOW, 0, 0, 0]);
|
||||
if flaky()? != 1 {
|
||||
return Err("the overflowed server was not restarted");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check() -> Result<(), &'static str> {
|
||||
let mut fs = Storage::attach(STORAGE, STORAGE_BUF_VA).map_err(|_| "cannot attach to storage")?;
|
||||
write_a_file(&mut fs)?;
|
||||
@@ -305,6 +315,9 @@ fn check() -> Result<(), &'static str> {
|
||||
|
||||
restarted_after_hanging()?;
|
||||
libkos::println!("spawntest: a hung server is noticed, stopped and restarted: ok");
|
||||
|
||||
restarted_after_overflowing()?;
|
||||
libkos::println!("spawntest: a server that overflows its stack is caught and restarted: ok");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::manifest::{Device, Program, Scope};
|
||||
use crate::spawn::Spawner;
|
||||
use crate::supervise::{MAX_SUPERVISED, Supervised};
|
||||
|
||||
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(0x10000);
|
||||
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(types::layout::STACK_TOP);
|
||||
const MAX_SERVED: usize = 8;
|
||||
|
||||
fn perms(list: &[Permission]) -> PermissionSet {
|
||||
|
||||
@@ -7,14 +7,12 @@ use types::bounded::{CapSlot, PageCount};
|
||||
use types::budget::Limits;
|
||||
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
const USER_CODE_BASE: usize = 0x10000;
|
||||
/// A program's stack, below its image, unless its manifest says more.
|
||||
pub(crate) const STACK_PAGES: usize = 4;
|
||||
/// A program's stack (see `types::layout`), unless init's table says more.
|
||||
pub(crate) const STACK_PAGES: usize = 8;
|
||||
/// An app's: drawing (tiny-skia, fontdue) takes more than 16 KiB.
|
||||
pub(crate) const APP_STACK_PAGES: usize = 8;
|
||||
/// The most there is room for under the image, above the arguments page
|
||||
/// (page 0 stays unmapped).
|
||||
const MAX_STACK_PAGES: usize = (USER_CODE_BASE - types::start_arg::ARGS_VA) / PAGE_SIZE - 1;
|
||||
pub(crate) const APP_STACK_PAGES: usize = 16;
|
||||
/// The most the stack's band holds, leaving a guard page at its bottom.
|
||||
const MAX_STACK_PAGES: usize = types::layout::STACK_BAND / PAGE_SIZE - 1;
|
||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
||||
/// Where a program file is read through on its way into a process, a
|
||||
/// chunk at a time, so a program may be any size.
|
||||
@@ -102,7 +100,7 @@ fn load_from(file: &mut Stream, limits: Limits, stack_pages: usize) -> Option<Lo
|
||||
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
||||
let filled = elf_info.segments().all(|segment| load_segment(file, process_cap, segment).is_some())
|
||||
&& (0..stack_pages).all(|i| {
|
||||
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE);
|
||||
let dst = VirtualAddress::from_raw(types::layout::STACK_TOP - (stack_pages - i) * PAGE_SIZE);
|
||||
PageCount::new(1).and_then(syscall::request_pages).is_some_and(|va| {
|
||||
syscall::map_page(process_cap, va, dst, false);
|
||||
true
|
||||
|
||||
@@ -72,7 +72,14 @@ impl<P: Platform> Kernel<P> {
|
||||
|
||||
fn first_fit_from(&self, pid: ProcessId, from: usize, pages: usize) -> Option<VirtualAddress> {
|
||||
let mut candidate = from;
|
||||
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages) {
|
||||
// Never into the stack's band (`types::layout`).
|
||||
let below_stacks = |c: usize| {
|
||||
c.checked_add(pages * PAGE_SIZE)
|
||||
.is_some_and(|end| end <= types::layout::HEAP_END)
|
||||
};
|
||||
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages)
|
||||
&& below_stacks(candidate)
|
||||
{
|
||||
let va = VirtualAddress::from_raw(candidate);
|
||||
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
||||
None => return Some(va),
|
||||
|
||||
18
crates/types/src/layout.rs
Normal file
18
crates/types/src/layout.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! Where things sit in every program's address space, below 1 GiB (the
|
||||
//! RISC-V adapter's user window): the arguments page at `start_arg::
|
||||
//! ARGS_VA`, the image from 0x10000, the heap above it (wherever the
|
||||
//! kernel finds room, below `HEAP_END`), and the stack at the very top,
|
||||
//! in a band of its own. The band's pages below the stack are never
|
||||
//! mapped, so running off the stack faults there, and the kernel says so.
|
||||
|
||||
/// The first thread's stack ends here (the top of the user window).
|
||||
pub const STACK_TOP: usize = 0x4000_0000;
|
||||
/// The band the stack grows down into: its unmapped rest is the guard.
|
||||
pub const STACK_BAND: usize = 0x10_0000;
|
||||
/// The heap stays below the stack's band.
|
||||
pub const HEAP_END: usize = STACK_TOP - STACK_BAND;
|
||||
|
||||
/// Whether a fault at `va` is a stack running off its end.
|
||||
pub const fn is_stack_overflow(va: usize) -> bool {
|
||||
va >= HEAP_END && va < STACK_TOP
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub mod gatekeeper;
|
||||
pub mod generation;
|
||||
pub mod id;
|
||||
pub mod input;
|
||||
pub mod layout;
|
||||
pub mod log;
|
||||
pub mod macros;
|
||||
pub mod mapping;
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
use crate::budget::Limits;
|
||||
|
||||
pub const SPAWN: usize = 0;
|
||||
/// Where init puts the child's stack top (its stack ends at the image).
|
||||
pub const STACK_TOP: usize = 0x10000;
|
||||
/// Where init puts the child's stack top (see `layout`).
|
||||
pub const STACK_TOP: usize = crate::layout::STACK_TOP;
|
||||
/// A slot word meaning "none".
|
||||
pub const NONE: usize = usize::MAX;
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ const PERMISSIONS_SHIFT: u32 = 48;
|
||||
const SESSION_SHIFT: u32 = 56;
|
||||
|
||||
/// Where the arguments page sits: the program's arguments, NUL separated,
|
||||
/// the first its name. Below the stack, which ends at the image.
|
||||
/// the first its name. Above the unmapped page 0, below the image (see
|
||||
/// `layout`).
|
||||
pub const ARGS_VA: usize = 0x1000;
|
||||
pub const ARGS_SIZE: usize = 4096;
|
||||
|
||||
|
||||
@@ -79,9 +79,12 @@ the slots of the console, log and storage capabilities it was handed, and
|
||||
whether its parent put an arguments page at `ARGS_VA` (NUL-separated, the
|
||||
program's name first; the parent fills a page and `MapPage`s it over, so
|
||||
the program owns it and it goes when the program does). It sits at
|
||||
`0x1000`, above the unmapped page 0; the stack runs from just below the
|
||||
image (`0x10000`) down toward it: 4 pages for system programs unless init's
|
||||
table says more, 8 for apps.
|
||||
`0x1000`, above the unmapped page 0, below the image (`0x10000`). The
|
||||
stack is at the top of the user window (`types::layout`): it ends at
|
||||
`STACK_TOP` (1 GiB) in a 1 MiB band of its own, 8 pages for system
|
||||
programs unless init's table says more, 16 for apps; the rest of the band
|
||||
is never mapped (the heap stays below it), so running off the stack
|
||||
faults there and the kernel prints `stack overflow` with the process id.
|
||||
|
||||
Boot loads only init and storage, both packed into the kernel image (they
|
||||
are what reads the disk, so they cannot come from it). The disk is one ext4
|
||||
|
||||
@@ -57,6 +57,11 @@ echo "test-qemu: second boot: wrong passcode refused, right one signs in, sealed
|
||||
rm -f "${KEYS}" "${MON}"
|
||||
|
||||
grep -a "smptest:\|spawntest:\|Hello\|SMPTEST" "${LOG}"
|
||||
# The kernel names a stack overflow (flaky's, asked for) as one.
|
||||
overflow_ok=1
|
||||
if ! grep -aq "(stack overflow" "${LOG}"; then
|
||||
echo "test-qemu: a stack overflow was not reported as one"; overflow_ok=0
|
||||
fi
|
||||
# What k-os wrote must be ext4 that the real tools accept.
|
||||
disk_ok=1
|
||||
if command -v e2fsck >/dev/null && command -v debugfs >/dev/null; then
|
||||
@@ -81,7 +86,7 @@ if command -v e2fsck >/dev/null && command -v debugfs >/dev/null; then
|
||||
fi
|
||||
rm -f "${LOG}.fsck"
|
||||
fi
|
||||
if [ "${disk_ok}" -eq 1 ] && [ ${first_ok} -eq 1 ] && [ ${second_ok} -eq 1 ] && grep -aq "SMPTEST PASS" "${LOG}"; then
|
||||
if [ "${disk_ok}" -eq 1 ] && [ ${overflow_ok} -eq 1 ] && [ ${first_ok} -eq 1 ] && [ ${second_ok} -eq 1 ] && grep -aq "SMPTEST PASS" "${LOG}"; then
|
||||
echo "test-qemu: pass (${SMP} harts)"
|
||||
rm -f "${LOG}"
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user