blob: 15acc2206f05c2ad4cef581d5c1bb3017b545996 [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
use core::ffi::{CStr, c_void};
use core::fmt;
use core::ptr::without_provenance;
use libc::stdio::{FILE, fprintf, stdout};
use object_constants_rs as object_constants;
use crate::top::boot_constants::kernel_debug_ident;
mod backtrace_ffi;
#[cfg(ktest)]
mod tests;
const FORMAT: &CStr = c"{{{bt:%zu:%p:%s}}}\n";
const RA: &CStr = c"ra";
const PC: &CStr = c"pc";
/// Contains an array of return address values representing a thread's backtrace.
///
/// This type is not thread-safe.
#[repr(C)]
#[derive(Clone)]
pub struct Backtrace {
addr: [usize; Backtrace::MAX_SIZE],
size: usize,
first_frame_type: FrameType,
}
// The layout is shared with the C++ `Backtrace` in <lib/backtrace.h>.
zr::static_assert!(core::mem::size_of::<FrameType>() == 4);
zr::static_assert!(core::mem::align_of::<FrameType>() == 4);
zr::static_assert!(core::mem::size_of::<Backtrace>() == object_constants::kBacktraceSize);
zr::static_assert!(core::mem::align_of::<Backtrace>() == object_constants::kBacktraceAlign);
zr::static_assert!(core::mem::offset_of!(Backtrace, addr) == 0);
zr::static_assert!(core::mem::offset_of!(Backtrace, size) == 256);
zr::static_assert!(core::mem::offset_of!(Backtrace, first_frame_type) == 264);
/// With the possible exception of the first frame, each address in the backtrace is a return
/// address. The first frame may be either a return address or a precise location (think PC).
/// See the "Presentation elements" section of `//docs/reference/kernel/symbolizer_markup.md` for
/// details. Unless specified, the first frame is assumed to be a return address.
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FrameType {
#[default]
ReturnAddress = 0,
PreciseLocation = 1,
}
impl Backtrace {
/// The maximum number of elements in a backtrace. The value should be large enough to
/// capture the full call stack for most kernel crashes, but also small enough that it's
/// reasonable to allocate a `Backtrace` on the stack.
pub const MAX_SIZE: usize = 32;
/// Creates an empty `Backtrace`.
#[inline]
pub const fn new() -> Self {
Self { addr: [0; Self::MAX_SIZE], size: 0, first_frame_type: FrameType::ReturnAddress }
}
/// Returns the number of elements in this backtrace.
#[inline]
pub fn size(&self) -> usize {
self.size
}
/// Returns the underlying elements.
#[inline]
pub fn data(&self) -> &[usize] {
&self.addr[..self.size.min(Self::MAX_SIZE)]
}
/// Resets the size to 0.
#[inline]
pub fn reset(&mut self) {
self.size = 0;
}
/// Adds one element to the array. See also [`Self::set_first_frame_type`].
#[inline]
pub fn push_back(&mut self, addr: usize) {
if self.size < Self::MAX_SIZE {
self.addr[self.size] = addr;
self.size += 1;
}
}
/// Sets the type of the first frame; see [`FrameType`].
#[inline]
pub fn set_first_frame_type(&mut self, ty: FrameType) {
self.first_frame_type = ty;
}
/// Pretty-prints this backtrace to `stdout`.
#[inline]
pub fn print(&self) {
// SAFETY: `stdout()` is non-null and aligned, pointing to `gStdout`, which is initialized
// by global constructors during early boot (`lk_main`) with a thread-safe write callback
// that does not mutate `gStdout`, and is not mutated thereafter during normal kernel
// operation (like C++ `printf`, `print()` must not be called before global constructors
// run; note that `console_rust` ktests temporarily overwrite `gStdout` assuming quiescent
// output).
unsafe { self.print_to_file(stdout()) };
}
/// Pretty-prints this backtrace to `file`.
///
/// # Safety
///
/// `file` must be non-null, properly aligned, and point to an initialized `FILE` with a
/// non-null write callback that is valid to call for the duration of this method, without
/// `*file` being mutated.
pub unsafe fn print_to_file(&self, file: *mut FILE) {
// SAFETY: The caller guarantees that `file` is non-null, aligned, and points to an
// initialized `FILE` with a valid non-null write callback and that `*file` is not mutated
// for the duration of the call.
unsafe {
FILE::write_raw(file, kernel_debug_ident());
self.print_without_version(file);
}
}
/// Pretty-prints this backtrace to `file`, without the `kernel_debug_ident` preamble that
/// [`Self::print_to_file`] writes first.
///
/// # Safety
///
/// `file` must be non-null, properly aligned, and point to an initialized `FILE` with a
/// non-null write callback that is valid to call for the duration of this method, without
/// `*file` being mutated.
pub unsafe fn print_without_version(&self, file: *mut FILE) {
for (i, &addr) in self.data().iter().enumerate() {
let mut ty = RA;
if self.first_frame_type == FrameType::PreciseLocation && i == 0 {
ty = PC;
}
// SAFETY: `FORMAT` is NUL-terminated and its conversions match the arguments:
// `%zu` <- `usize`, `%p` <- `*const c_void` (via `without_provenance(addr)`), and
// `%s` <- the NUL-terminated `ty`. The caller guarantees that `file` is non-null,
// aligned, and points to an initialized `FILE` with a valid non-null write callback
// and that `*file` is not mutated for the duration of the call.
unsafe {
fprintf(file, FORMAT.as_ptr(), i, without_provenance::<c_void>(addr), ty.as_ptr())
};
}
}
}
impl fmt::Debug for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Backtrace")
.field("addr", &self.data())
.field("first_frame_type", &self.first_frame_type)
.finish()
}
}
impl Default for Backtrace {
#[inline]
fn default() -> Self {
Self::new()
}
}