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)) => {}
|
.is_some_and(|ctx| fp::first_use(frame, ctx)) => {}
|
||||||
_ => {
|
_ => {
|
||||||
let uart = crate::hw::uart::Uart::new();
|
let uart = crate::hw::uart::Uart::new();
|
||||||
uart.write_str("FAULT: scause=");
|
|
||||||
uart.write_hex(scause);
|
|
||||||
let stval: usize;
|
let stval: usize;
|
||||||
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
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_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.write_hex(frame.sepc);
|
||||||
uart.put_char(b'\n');
|
uart.put_char(b'\n');
|
||||||
|
|
||||||
let kern = kern();
|
|
||||||
if let Some(current_tid) = kern.current_thread() {
|
if let Some(current_tid) = kern.current_thread() {
|
||||||
let fault = match scause {
|
let fault = match scause {
|
||||||
2 => domain::fault::Fault::IllegalInstruction,
|
2 => domain::fault::Fault::IllegalInstruction,
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ pub mod op {
|
|||||||
pub const CRASH: usize = 2;
|
pub const CRASH: usize = 2;
|
||||||
/// Blocks for good inside the handler.
|
/// Blocks for good inside the handler.
|
||||||
pub const HANG: usize = 3;
|
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`.
|
/// Everything goes through `handle_call`.
|
||||||
@@ -49,6 +59,9 @@ impl Server for Flaky {
|
|||||||
match words[0] {
|
match words[0] {
|
||||||
op::WHO => {}
|
op::WHO => {}
|
||||||
op::CRASH => panic!("asked to crash"),
|
op::CRASH => panic!("asked to crash"),
|
||||||
|
op::OVERFLOW => {
|
||||||
|
core::hint::black_box(deeper(0));
|
||||||
|
}
|
||||||
op::HANG => {
|
op::HANG => {
|
||||||
// A notification nobody signals.
|
// A notification nobody signals.
|
||||||
if let Ok(never) = syscall::create_notification() {
|
if let Ok(never) = syscall::create_notification() {
|
||||||
|
|||||||
@@ -267,6 +267,16 @@ fn restarted_after_hanging() -> Result<(), &'static str> {
|
|||||||
Ok(())
|
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> {
|
fn check() -> Result<(), &'static str> {
|
||||||
let mut fs = Storage::attach(STORAGE, STORAGE_BUF_VA).map_err(|_| "cannot attach to storage")?;
|
let mut fs = Storage::attach(STORAGE, STORAGE_BUF_VA).map_err(|_| "cannot attach to storage")?;
|
||||||
write_a_file(&mut fs)?;
|
write_a_file(&mut fs)?;
|
||||||
@@ -305,6 +315,9 @@ fn check() -> Result<(), &'static str> {
|
|||||||
|
|
||||||
restarted_after_hanging()?;
|
restarted_after_hanging()?;
|
||||||
libkos::println!("spawntest: a hung server is noticed, stopped and restarted: ok");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::manifest::{Device, Program, Scope};
|
|||||||
use crate::spawn::Spawner;
|
use crate::spawn::Spawner;
|
||||||
use crate::supervise::{MAX_SUPERVISED, Supervised};
|
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;
|
const MAX_SERVED: usize = 8;
|
||||||
|
|
||||||
fn perms(list: &[Permission]) -> PermissionSet {
|
fn perms(list: &[Permission]) -> PermissionSet {
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ use types::bounded::{CapSlot, PageCount};
|
|||||||
use types::budget::Limits;
|
use types::budget::Limits;
|
||||||
|
|
||||||
const PAGE_SIZE: usize = 4096;
|
const PAGE_SIZE: usize = 4096;
|
||||||
const USER_CODE_BASE: usize = 0x10000;
|
/// A program's stack (see `types::layout`), unless init's table says more.
|
||||||
/// A program's stack, below its image, unless its manifest says more.
|
pub(crate) const STACK_PAGES: usize = 8;
|
||||||
pub(crate) const STACK_PAGES: usize = 4;
|
|
||||||
/// An app's: drawing (tiny-skia, fontdue) takes more than 16 KiB.
|
/// An app's: drawing (tiny-skia, fontdue) takes more than 16 KiB.
|
||||||
pub(crate) const APP_STACK_PAGES: usize = 8;
|
pub(crate) const APP_STACK_PAGES: usize = 16;
|
||||||
/// The most there is room for under the image, above the arguments page
|
/// The most the stack's band holds, leaving a guard page at its bottom.
|
||||||
/// (page 0 stays unmapped).
|
const MAX_STACK_PAGES: usize = types::layout::STACK_BAND / PAGE_SIZE - 1;
|
||||||
const MAX_STACK_PAGES: usize = (USER_CODE_BASE - types::start_arg::ARGS_VA) / PAGE_SIZE - 1;
|
|
||||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
||||||
/// Where a program file is read through on its way into a process, a
|
/// 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.
|
/// 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 process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
||||||
let filled = elf_info.segments().all(|segment| load_segment(file, process_cap, segment).is_some())
|
let filled = elf_info.segments().all(|segment| load_segment(file, process_cap, segment).is_some())
|
||||||
&& (0..stack_pages).all(|i| {
|
&& (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| {
|
PageCount::new(1).and_then(syscall::request_pages).is_some_and(|va| {
|
||||||
syscall::map_page(process_cap, va, dst, false);
|
syscall::map_page(process_cap, va, dst, false);
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -72,7 +72,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
fn first_fit_from(&self, pid: ProcessId, from: usize, pages: usize) -> Option<VirtualAddress> {
|
fn first_fit_from(&self, pid: ProcessId, from: usize, pages: usize) -> Option<VirtualAddress> {
|
||||||
let mut candidate = from;
|
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);
|
let va = VirtualAddress::from_raw(candidate);
|
||||||
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
||||||
None => return Some(va),
|
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 generation;
|
||||||
pub mod id;
|
pub mod id;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod layout;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod mapping;
|
pub mod mapping;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
use crate::budget::Limits;
|
use crate::budget::Limits;
|
||||||
|
|
||||||
pub const SPAWN: usize = 0;
|
pub const SPAWN: usize = 0;
|
||||||
/// Where init puts the child's stack top (its stack ends at the image).
|
/// Where init puts the child's stack top (see `layout`).
|
||||||
pub const STACK_TOP: usize = 0x10000;
|
pub const STACK_TOP: usize = crate::layout::STACK_TOP;
|
||||||
/// A slot word meaning "none".
|
/// A slot word meaning "none".
|
||||||
pub const NONE: usize = usize::MAX;
|
pub const NONE: usize = usize::MAX;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ const PERMISSIONS_SHIFT: u32 = 48;
|
|||||||
const SESSION_SHIFT: u32 = 56;
|
const SESSION_SHIFT: u32 = 56;
|
||||||
|
|
||||||
/// Where the arguments page sits: the program's arguments, NUL separated,
|
/// 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_VA: usize = 0x1000;
|
||||||
pub const ARGS_SIZE: usize = 4096;
|
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
|
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
|
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
|
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
|
`0x1000`, above the unmapped page 0, below the image (`0x10000`). The
|
||||||
image (`0x10000`) down toward it: 4 pages for system programs unless init's
|
stack is at the top of the user window (`types::layout`): it ends at
|
||||||
table says more, 8 for apps.
|
`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
|
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
|
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}"
|
rm -f "${KEYS}" "${MON}"
|
||||||
|
|
||||||
grep -a "smptest:\|spawntest:\|Hello\|SMPTEST" "${LOG}"
|
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.
|
# What k-os wrote must be ext4 that the real tools accept.
|
||||||
disk_ok=1
|
disk_ok=1
|
||||||
if command -v e2fsck >/dev/null && command -v debugfs >/dev/null; then
|
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
|
fi
|
||||||
rm -f "${LOG}.fsck"
|
rm -f "${LOG}.fsck"
|
||||||
fi
|
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)"
|
echo "test-qemu: pass (${SMP} harts)"
|
||||||
rm -f "${LOG}"
|
rm -f "${LOG}"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user