blob: e11ac43a8fedd357eeea3233546da362d8301b50 [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! Kernel thread stack ABI: the sizes and guard regions of the machine stack and, when enabled,
//! the unsafe stack and shadow call stack.
use handoff::ZirconAbiSpec_Stack;
/// Stack size building blocks shared by the stack specifications below.
pub mod internal {
/// A common, convenience value for machine and unsafe stack sizes. Not intended
/// to be a part of the public API.
// The kernel build sets CUSTOM_DEFAULT_STACK_SIZE in the environment from the same GN value
// that it passes to C++ as a define.
pub const DEFAULT_STACK_SIZE: u32 = match option_env!("CUSTOM_DEFAULT_STACK_SIZE") {
Some(size) => {
let size = zr::parse_usize(size).expect("CUSTOM_DEFAULT_STACK_SIZE invalid");
assert!(size <= u32::MAX as usize, "CUSTOM_DEFAULT_STACK_SIZE too large");
size as u32
}
None => 2 * page::SIZE as u32,
};
/// The would-be unsafe stack size, if enabled.
pub const UNSAFE_STACK_SIZE: u32 = DEFAULT_STACK_SIZE;
/// The size of the machine stack.
pub const MACHINE_STACK_SIZE: u32 =
// If there is no unsafe stack on x86, extend the machine stack by the
// would-be unsafe stack size for consistency.
DEFAULT_STACK_SIZE
+ if cfg!(all(target_arch = "x86_64", not(sanitize = "safestack"))) {
UNSAFE_STACK_SIZE
} else {
0
};
// Physboot requires page-aligned stack sizes (`ZirconAbiSpec::Stack::AssertValid`), so a bad
// CUSTOM_DEFAULT_STACK_SIZE fails the build here rather than at boot.
zr::static_assert!(page::is_aligned(UNSAFE_STACK_SIZE as usize));
zr::static_assert!(page::is_aligned(MACHINE_STACK_SIZE as usize));
/// The would-be unsafe shadow call stack size, if enabled.
pub const SHADOW_CALL_STACK_SIZE: u32 = page::SIZE as u32;
/// The size of the unmapped 'guard' region to ensure lies above or below the
/// mapped stack.
pub const STACK_GUARD_REGION_SIZE: u32 = page::SIZE as u32;
}
/// The machine stack specification.
pub const MACHINE_STACK: ZirconAbiSpec_Stack = ZirconAbiSpec_Stack {
size_bytes: internal::MACHINE_STACK_SIZE,
lower_guard_size_bytes: internal::STACK_GUARD_REGION_SIZE,
upper_guard_size_bytes: internal::STACK_GUARD_REGION_SIZE,
};
/// The unsafe stack specification.
#[cfg(sanitize = "safestack")]
pub const UNSAFE_STACK: ZirconAbiSpec_Stack = ZirconAbiSpec_Stack {
size_bytes: internal::UNSAFE_STACK_SIZE,
lower_guard_size_bytes: internal::STACK_GUARD_REGION_SIZE,
upper_guard_size_bytes: internal::STACK_GUARD_REGION_SIZE,
};
/// The shadow call stack specification.
#[cfg(sanitize = "shadow-call-stack")]
pub const SHADOW_CALL_STACK: ZirconAbiSpec_Stack = ZirconAbiSpec_Stack {
size_bytes: internal::SHADOW_CALL_STACK_SIZE,
lower_guard_size_bytes: internal::STACK_GUARD_REGION_SIZE,
upper_guard_size_bytes: internal::STACK_GUARD_REGION_SIZE,
};
// `top/handoff.cc` initializes the kernel's `kZirconAbiSpec` from the C++
// `<lib/thread-stack/abi.h>` constants, and physboot uses it to set up the boot stacks.
/// Checks the `thread_stack` constants against the kernel's `kZirconAbiSpec`.
#[cfg(ktest)]
#[unittest::suite(name = "thread_stack")]
mod tests {
use handoff::{ZirconAbiSpec, ZirconAbiSpec_Stack};
use unittest::expect_eq;
// SAFETY: Declaring this `safe` is sound because reads of it can never be UB:
// - `ZirconAbiSpec` is the bindgen type for the C++ object, and its layout is pinned by the
// handoff-rs `layout_tests`.
// - `kZirconAbiSpec` is `extern "C" constexpr` in `top/handoff.cc`, so it lives in
// RODATA/RELRO, is relocated by physboot before the kernel is entered, and is never written
// afterwards.
// - Every field holds a valid value for its Rust type (`bool` is 0 or 1, the `entry` function
// pointer is non-null) because the C++ initializer sets them.
unsafe extern "C" {
/// The kernel's ABI specification for physboot.
#[link_name = "kZirconAbiSpec"]
safe static ZIRCON_ABI_SPEC: ZirconAbiSpec;
}
/// The value that `kZirconAbiSpec` has for a stack that is not enabled.
const NO_STACK: ZirconAbiSpec_Stack =
ZirconAbiSpec_Stack { size_bytes: 0, lower_guard_size_bytes: 0, upper_guard_size_bytes: 0 };
/// Expects `$rust` to match `$cpp` field by field. This is a macro rather than a function
/// because `expect_eq!` records failures in the enclosing `#[test]` function.
macro_rules! expect_stack_eq {
($rust:expr, $cpp:expr) => {{
let rust: &ZirconAbiSpec_Stack = $rust;
let cpp: &ZirconAbiSpec_Stack = $cpp;
expect_eq!(rust.size_bytes, cpp.size_bytes);
expect_eq!(rust.lower_guard_size_bytes, cpp.lower_guard_size_bytes);
expect_eq!(rust.upper_guard_size_bytes, cpp.upper_guard_size_bytes);
}};
}
/// Tests that `MACHINE_STACK` matches the C++ machine stack.
#[test]
fn machine_stack() {
expect_stack_eq!(&super::MACHINE_STACK, &ZIRCON_ABI_SPEC.machine_stack);
}
/// Tests that `UNSAFE_STACK` exists exactly when C++ has one and that it matches.
#[test]
fn unsafe_stack() {
#[cfg(sanitize = "safestack")]
expect_stack_eq!(&super::UNSAFE_STACK, &ZIRCON_ABI_SPEC.unsafe_stack);
#[cfg(not(sanitize = "safestack"))]
expect_stack_eq!(&NO_STACK, &ZIRCON_ABI_SPEC.unsafe_stack);
}
/// Tests that `SHADOW_CALL_STACK` exists exactly when C++ has one and that it matches.
#[test]
fn shadow_call_stack() {
#[cfg(sanitize = "shadow-call-stack")]
expect_stack_eq!(&super::SHADOW_CALL_STACK, &ZIRCON_ABI_SPEC.shadow_call_stack);
#[cfg(not(sanitize = "shadow-call-stack"))]
expect_stack_eq!(&NO_STACK, &ZIRCON_ABI_SPEC.shadow_call_stack);
}
}