blob: d88957d80dd0d1b331ccfcced37f27d839d487f5 [file]
// Copyright 2021 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
//! Jump trace (`jtrace`) ring-buffer recorder, hooks, and console command.
mod jtrace_ffi;
mod jtrace_internal;
#[cfg(ktest)]
mod jtrace_tests;
use core::cell::UnsafeCell;
use core::ffi::{CStr, c_char, c_void};
use core::mem::{align_of, size_of};
pub use jtrace_bindings::{
Field32, Field64, FileFuncLineInfo, IsPersistent, LargeEntry, SmallEntry, TraceBufferType,
UseLargeEntries, UseMonoTimestamps,
};
use kprint::kformat;
use libc::stdio::FILE;
use zx_types::{zx_duration_t, zx_koid_t, zx_ticks_t, zx_time_t};
use crate::arch_rs::vm::is_kernel_address;
use crate::kernel::types::cpu_num_t;
use crate::platform_rs::timer::{zx_msec, zx_sec, zx_usec};
use crate::vm::vm_aspace::VmAspace;
use self::jtrace_internal::{AlignedBuffer, Config, Header, JTrace, TraceHooks, UNKNOWN_TID};
unsafe extern "C" {
// Defined in //zircon/kernel/lib/io.
#[allow(non_upper_case_globals)]
static gStdoutUnbuffered: FILE;
}
const PERSISTENT_RAM_ALLOCATION_GRANULARITY: usize =
zr::parse_usize(env!("PERSISTENT_RAM_ALLOCATION_GRANULARITY"))
.expect("PERSISTENT_RAM_ALLOCATION_GRANULARITY invalid");
/// Target buffer size in bytes (`JTRACE_TARGET_BUFFER_SIZE`).
pub const TARGET_BUFFER_SIZE: usize =
zr::parse_usize(env!("JTRACE_TARGET_BUFFER_SIZE")).expect("JTRACE_TARGET_BUFFER_SIZE invalid");
/// Per-CPU last-entry storage count (`JTRACE_LAST_ENTRY_STORAGE`).
pub const LAST_ENTRY_STORAGE: usize =
zr::parse_usize(env!("JTRACE_LAST_ENTRY_STORAGE")).expect("JTRACE_LAST_ENTRY_STORAGE invalid");
/// Whether large entries are enabled (`JTRACE_USE_LARGE_ENTRIES`).
pub const USE_LARGE_ENTRIES: bool = cfg!(jtrace_use_large_entries);
/// Whether persistent trace buffers are enabled (`JTRACE_IS_PERSISTENT`).
pub const IS_PERSISTENT: bool = cfg!(jtrace_persistent);
/// Whether monotonic timestamps are used (`JTRACE_USE_MONO_TIMESTAMPS`).
pub const USE_MONO_TIMESTAMPS: bool = cfg!(jtrace_use_mono_timestamps);
/// Target persistent trace buffer size in bytes (`TARGET_BUFFER_SIZE` when `IS_PERSISTENT`, `0`
/// otherwise).
pub const TARGET_PERSISTENT_BUFFER_SIZE: usize = if IS_PERSISTENT { TARGET_BUFFER_SIZE } else { 0 };
zr::static_assert!(
!IS_PERSISTENT || TARGET_BUFFER_SIZE.is_multiple_of(PERSISTENT_RAM_ALLOCATION_GRANULARITY)
);
const RECOVERY_BUFFER_SIZE: usize = TARGET_PERSISTENT_BUFFER_SIZE;
const NON_PERSISTENT_BUFFER_SIZE: usize = if IS_PERSISTENT { 0 } else { TARGET_BUFFER_SIZE };
zr::static_assert!(size_of::<TraceBufferType>() == 4);
zr::static_assert!(align_of::<TraceBufferType>() == 4);
zr::static_assert!(size_of::<FileFuncLineInfo>() == 24);
zr::static_assert!(align_of::<FileFuncLineInfo>() == 8);
/// Read-only view of the fields of a [`TraceEntry`].
#[derive(Clone, Copy)]
struct TraceEntryFields {
/// Timestamp in ticks.
pub ts_ticks: zx_ticks_t,
/// Null-terminated tag string pointer.
pub tag: *const c_char,
/// Pointer to static file/function/line info (`null` for small entries).
pub ffl_info: *const FileFuncLineInfo,
/// Thread koid (`UNKNOWN_TID` for small entries).
pub tid: zx_koid_t,
/// CPU ID on which the entry was logged.
pub cpu_id: cpu_num_t,
/// First 32-bit payload field.
pub a: u32,
/// Second 32-bit payload field (`0` for small entries).
pub b: u32,
/// Third 32-bit payload field (`0` for small entries).
pub c: u32,
/// Fourth 32-bit payload field (`0` for small entries).
pub d: u32,
/// First 64-bit payload field (`0` for small entries).
pub e: u64,
/// Second 64-bit payload field (`0` for small entries).
pub f: u64,
}
/// Mutable view of the header-populated fields of a [`TraceEntry`].
struct TraceEntryFieldsMut<'a> {
/// Mutable reference to the entry's timestamp in ticks.
pub ts_ticks: &'a mut zx_ticks_t,
/// Mutable reference to the entry's CPU ID.
pub cpu_id: &'a mut cpu_num_t,
/// Optional mutable reference to the entry's thread koid (`None` for small entries).
pub tid: Option<&'a mut zx_koid_t>,
}
impl TraceEntryFieldsMut<'_> {
/// Sets the entry's thread koid if present.
#[inline]
pub fn set_tid(&mut self, tid: zx_koid_t) {
if let Some(slot) = &mut self.tid {
**slot = tid;
}
}
}
/// Common trait implemented by [`SmallEntry`] and [`LargeEntry`].
trait TraceEntry: Copy + Default + 'static {
/// Whether this entry type is a large entry.
const USE_LARGE_ENTRIES: UseLargeEntries;
/// Returns a read-only view of all entry fields.
fn fields(&self) -> TraceEntryFields;
/// Returns mutable references to the runtime-populated entry fields.
fn fields_mut(&mut self) -> TraceEntryFieldsMut<'_>;
/// Dispatches this entry to the appropriate `TraceHooks::print_*_trace_entry` method.
fn print_trace_entry<H: TraceHooks + ?Sized>(
&self,
hooks: &H,
buf_type: TraceBufferType,
ts: zx_time_t,
delta: zx_duration_t,
);
/// Constructs a test entry with sequential field values starting at `val`.
#[cfg(ktest)]
fn new_test_entry(tag: &'static CStr, ffl_info: &'static FileFuncLineInfo, val: u32) -> Self;
}
zr::static_assert!(size_of::<SmallEntry>() == 24);
zr::static_assert!(align_of::<SmallEntry>() == 8);
impl TraceEntry for SmallEntry {
const USE_LARGE_ENTRIES: UseLargeEntries = UseLargeEntries::No;
#[inline]
fn fields(&self) -> TraceEntryFields {
TraceEntryFields {
ts_ticks: self.ts_ticks,
tag: self.tag,
ffl_info: core::ptr::null(),
tid: UNKNOWN_TID,
cpu_id: self.cpu_id,
a: self.a,
b: 0,
c: 0,
d: 0,
e: 0,
f: 0,
}
}
#[inline]
fn fields_mut(&mut self) -> TraceEntryFieldsMut<'_> {
TraceEntryFieldsMut { ts_ticks: &mut self.ts_ticks, cpu_id: &mut self.cpu_id, tid: None }
}
#[inline]
fn print_trace_entry<H: TraceHooks + ?Sized>(
&self,
hooks: &H,
buf_type: TraceBufferType,
ts: zx_time_t,
delta: zx_duration_t,
) {
hooks.print_small_trace_entry(self, buf_type, ts, delta);
}
#[cfg(ktest)]
#[inline]
fn new_test_entry(tag: &'static CStr, ffl_info: &'static FileFuncLineInfo, val: u32) -> Self {
Self::new(tag, ffl_info, val)
}
}
zr::static_assert!(size_of::<LargeEntry>() == 72);
zr::static_assert!(align_of::<LargeEntry>() == 8);
impl TraceEntry for LargeEntry {
const USE_LARGE_ENTRIES: UseLargeEntries = UseLargeEntries::Yes;
#[inline]
fn fields(&self) -> TraceEntryFields {
TraceEntryFields {
ts_ticks: self.ts_ticks,
tag: self.tag,
ffl_info: self.ffl_info,
tid: self.tid,
cpu_id: self.cpu_id,
a: self.a,
b: self.b,
c: self.c,
d: self.d,
e: self.e,
f: self.f,
}
}
#[inline]
fn fields_mut(&mut self) -> TraceEntryFieldsMut<'_> {
TraceEntryFieldsMut {
ts_ticks: &mut self.ts_ticks,
cpu_id: &mut self.cpu_id,
tid: Some(&mut self.tid),
}
}
#[inline]
fn print_trace_entry<H: TraceHooks + ?Sized>(
&self,
hooks: &H,
buf_type: TraceBufferType,
ts: zx_time_t,
delta: zx_duration_t,
) {
hooks.print_large_trace_entry(self, buf_type, ts, delta);
}
#[cfg(ktest)]
#[inline]
fn new_test_entry(tag: &'static CStr, ffl_info: &'static FileFuncLineInfo, val: u32) -> Self {
Self::new(
tag,
ffl_info,
val,
val + 1,
val + 2,
val + 3,
u64::from(val + 4),
u64::from(val + 5),
)
}
}
/// Make an attempt to validate a virtual address as being a valid kernel virtual
/// address. Do not allow this to be called when blocking is not allowed
/// (holding a spinlock, hard IRQ time, etc) as blocking mutexes in the VM
/// subsystem will need to be acquired in order to perform the validation.
fn validate_vaddr(addr: usize) -> bool {
if !is_kernel_address(addr) {
return false;
}
let aspace = VmAspace::kernel_aspace();
let err = aspace.arch_aspace().query(addr);
err.is_ok()
}
/// SafeString is a small helper class which does its best to validate that
/// string literal pointers recovered from a persistent trace buffer are valid
/// before attempting to render them. Persistent trace buffers are stored in
/// "persistent" RAM passed to the kernel by the bootloader, and _could_ have
/// suffered from corruption during a spontaneous reboot, so it is important to
/// ensure that they represent a valid kernel virtual address before attempting
/// to render them.
enum SafeString {
// Safety invariant: Points to a valid null-terminated C string for the lifetime of `Self`.
Valid(*const c_char),
Replaced([u8; 32]),
}
impl SafeString {
/// Creates a `SafeString` from `str_ptr`, replacing null or unmapped recovered addresses.
///
/// # Safety
///
/// The caller must ensure that if `str_ptr` is non-null (and mapped in the kernel address
/// space when `buf_type == TraceBufferType::Recovered`), `str_ptr` points to a valid
/// null-terminated C string that remains valid for reads for the lifetime of the returned
/// `SafeString` and any pointer obtained from [`SafeString::get`].
unsafe fn new(str_ptr: *const c_char, buf_type: TraceBufferType) -> Self {
// Only attempt to validate the string's virtual address if we are
// attempting to print a recovered log. If we are in the process of dumping
// the current log, there is a very good chance that we are in the middle of
// a panic and unable to validate virtual addresses due to the VM locking
// requirements.
//
// Also check for nullptr while we are at it. It is possible for a dumped
// jtrace to have nullptr entries for last-per-cpu records in the case that
// there were never any records for a given CPU.
if str_ptr.is_null()
|| (buf_type == TraceBufferType::Recovered && !validate_vaddr(str_ptr.addr()))
{
let mut replacement_buf = [0u8; 32];
let _ = kformat!(&mut replacement_buf, "<Invalid {:p}>", str_ptr);
Self::Replaced(replacement_buf)
} else {
Self::Valid(str_ptr)
}
}
#[inline]
fn get(&self) -> *const c_char {
match self {
Self::Valid(ptr) => *ptr,
Self::Replaced(buf) => buf.as_ptr().cast::<c_char>(),
}
}
}
/// Production [`TraceHooks`] implementation writing directly to unbuffered stdout.
struct ProductionTraceHooks;
impl ProductionTraceHooks {
/// Renders a single trace entry into a 256-byte buffer and writes it directly
/// to unbuffered stdout (`gStdoutUnbuffered.write`) instead of rendering into
/// the current thread's linebuffer, which is too short to hold an entire
/// large entry on a single line (and expanding every thread's linebuffer just
/// for `jtrace` would waste memory across all threads).
///
/// `jtrace` buffer dump operations deliberately do not acquire any internal
/// lock to serialize concurrent dumps, by design, for four reasons:
///
/// 1) Kernel stacks are small (8 KB by default), so we avoid keeping multiple
/// large formatting buffers live on the stack at once: header and footer
/// messages in `JTrace::dump_buf` use short-lived scoped buffers that are
/// dropped before `print_trace_entry` is called, ensuring only this single
/// 256-byte `linebuf` is on the stack during entry formatting.
/// 2) Dumping a `jtrace` buffer usually happens during a kernel panic, where
/// acquiring any lock must be avoided (for example, panicking during a
/// trace dump while holding a dump-serialization lock would deadlock the
/// panic dump).
/// 3) Aside from a panic, the only other place a trace buffer is dumped in
/// production is from the kernel console (`cmd_jtrace`), where commands
/// are already serialized by the singleton console `CommandLock`.
/// 4) Even if concurrent dumps ever occurred, `kformat!` formats into the
/// caller's `linebuf` and returns an explicit length-bounded `&[u8]` slice
/// passed directly to `FILE::write` without relying on NUL termination, so
/// at worst output lines could interleave without ever reading past the
/// end of `linebuf`.
fn internal_print_trace_entry<E: TraceEntry>(
&self,
entry: &E,
buf_type: TraceBufferType,
ts: zx_time_t,
delta: zx_duration_t,
) {
let e = entry.fields();
let ts_sec = ts / zx_sec(1);
let ts_nsec = ts % zx_sec(1);
// SAFETY: `e.tag` is either validated via `validate_vaddr` (when `Recovered`) or a
// static `.rodata` string literal recorded during normal execution.
let tag = unsafe { SafeString::new(e.tag, buf_type) };
let delta_usec = delta / zx_usec(1);
let delta_nsec = delta % zx_usec(1);
let mut linebuf = [0u8; 256];
if E::USE_LARGE_ENTRIES == UseLargeEntries::No {
let formatted = kformat!(
&mut linebuf,
"[{:4d}.{:09d}][cpu {:u}] : {:08x} : ({:5d}.{:03d} uSec) : ({:cs})\n",
ts_sec,
ts_nsec,
e.cpu_id,
e.a,
delta_usec,
delta_nsec,
tag.get()
);
self.print_info(formatted);
} else {
static FALLBACK: FileFuncLineInfo =
FileFuncLineInfo::new(c"<bad FFL pointer>", c"<bad FFL pointer>", 0);
let mut ffl_info = e.ffl_info;
let ffl_addr = ffl_info.addr();
let ffl_end = ffl_addr.wrapping_add(size_of::<FileFuncLineInfo>() - 1);
if ffl_info.is_null()
|| !ffl_info.is_aligned()
|| (buf_type == TraceBufferType::Recovered
&& (!validate_vaddr(ffl_addr) || !validate_vaddr(ffl_end)))
{
ffl_info = &raw const FALLBACK;
}
// SAFETY: `ffl_info` is non-null, aligned, and either `&FALLBACK` or a validated
// kernel `.rodata` `FileFuncLineInfo` pointer.
let ffl = unsafe { &*ffl_info };
// SAFETY: `ffl.file` and `ffl.func` are validated when `Recovered` or point to
// static `.rodata` string literals.
let file = unsafe { SafeString::new(ffl.file, buf_type) };
// SAFETY: See above.
let func = unsafe { SafeString::new(ffl.func, buf_type) };
// SAFETY: `file.get()` returns a valid null-terminated C string.
let trimmed_file = unsafe { Self::trim_filename(file.get()) };
let mut tid_buffer;
let tid_str: &[u8] = if e.tid != UNKNOWN_TID {
tid_buffer = [0u8; 16];
kformat!(&mut tid_buffer, "{:8u}", e.tid)
} else {
b" Unknown"
};
let formatted = kformat!(
&mut linebuf,
concat!(
"[{:4d}.{:09d}][cpu {:u} tid {:s}] : {:08x} {:08x} {:08x} {:08x} {:016x} ",
"{:016x} : ({:8d}.{:03d} uSec) : {:cs}:{:cs}:{:d} ({:cs})\n",
),
ts_sec,
ts_nsec,
e.cpu_id,
tid_str,
e.a,
e.b,
e.c,
e.d,
e.e,
e.f,
delta_usec,
delta_nsec,
trimmed_file,
func.get(),
ffl.line,
tag.get()
);
self.print_info(formatted);
}
}
/// Returns a pointer to the filename component after the last `'/'` in `fname`.
///
/// # Safety
///
/// The caller must ensure that `fname` points to a valid null-terminated C string for the
/// duration of this call.
unsafe fn trim_filename(fname: *const c_char) -> *const c_char {
// SAFETY: Caller guarantees `fname` is a valid null-terminated C string.
let bytes = unsafe { CStr::from_ptr(fname) }.to_bytes();
bytes.iter().rposition(|&b| b == b'/').map_or(fname, |pos| {
// SAFETY: `pos + 1 <= bytes.len()`, which lies within the same C string allocation.
unsafe { fname.add(pos + 1) }
})
}
}
impl TraceHooks for ProductionTraceHooks {
fn print_warning(&self, msg: &[u8]) {
// SAFETY: `gStdoutUnbuffered` is a C++ global `FILE` that is never reassigned.
unsafe {
gStdoutUnbuffered.write(msg);
}
}
fn print_info(&self, msg: &[u8]) {
// SAFETY: `gStdoutUnbuffered` is a C++ global `FILE` that is never reassigned.
unsafe {
gStdoutUnbuffered.write(msg);
}
}
#[sanitize(address = "off")]
fn hexdump(&self, data: &[u8]) {
let _ = pretty::hexdump8_very_ex_rs(
&mut debug::dprintf::KernelConsoleWriter,
data,
data.as_ptr().addr() as u64,
);
}
fn print_large_trace_entry(
&self,
e: &LargeEntry,
buf_type: TraceBufferType,
ts: zx_time_t,
delta: zx_duration_t,
) {
self.internal_print_trace_entry(e, buf_type, ts, delta);
}
fn print_small_trace_entry(
&self,
e: &SmallEntry,
buf_type: TraceBufferType,
ts: zx_time_t,
delta: zx_duration_t,
) {
self.internal_print_trace_entry(e, buf_type, ts, delta);
}
}
/// Active trace entry type (`LargeEntry` or `SmallEntry`) selected by `jtrace_use_large_entries`.
#[cfg(jtrace_use_large_entries)]
pub type ProdEntry = LargeEntry;
/// Active trace entry type (`LargeEntry` or `SmallEntry`) selected by `jtrace_use_large_entries`.
#[cfg(not(jtrace_use_large_entries))]
pub type ProdEntry = SmallEntry;
const PROD_IS_PERSISTENT: i32 =
if IS_PERSISTENT { IsPersistent::Yes as i32 } else { IsPersistent::No as i32 };
const PROD_USE_LARGE_ENTRIES: i32 =
if USE_LARGE_ENTRIES { UseLargeEntries::Yes as i32 } else { UseLargeEntries::No as i32 };
const PROD_USE_MONO_TIMESTAMPS: i32 =
if USE_MONO_TIMESTAMPS { UseMonoTimestamps::Yes as i32 } else { UseMonoTimestamps::No as i32 };
struct NonPersistentBuffer {
data: UnsafeCell<AlignedBuffer<NON_PERSISTENT_BUFFER_SIZE>>,
}
// SAFETY: `NonPersistentBuffer` is handed to `G_TRACE.set_location` once during single-core
// early boot and thereafter managed via atomic operations on `Header` in `JTrace`.
unsafe impl Sync for NonPersistentBuffer {}
static NON_PERSISTENT_BUFFER: NonPersistentBuffer =
NonPersistentBuffer { data: UnsafeCell::new(AlignedBuffer([0u8; NON_PERSISTENT_BUFFER_SIZE])) };
impl NonPersistentBuffer {
fn get() -> (*mut u8, usize) {
(NON_PERSISTENT_BUFFER.data.get().cast::<u8>(), NON_PERSISTENT_BUFFER_SIZE)
}
}
type JTraceConfig = Config<
ProdEntry,
TARGET_BUFFER_SIZE,
LAST_ENTRY_STORAGE,
RECOVERY_BUFFER_SIZE,
PROD_IS_PERSISTENT,
PROD_USE_LARGE_ENTRIES,
PROD_USE_MONO_TIMESTAMPS,
>;
type ProdJTrace = JTrace<JTraceConfig, ProductionTraceHooks>;
zr::static_assert!(ProdJTrace::REQUIRED_BUFFER_ALIGNMENT == align_of::<Header<JTraceConfig>>());
static G_TRACE: ProdJTrace =
JTrace::new(ProductionTraceHooks, AlignedBuffer([0u8; RECOVERY_BUFFER_SIZE]));
// Thunks
//
/// Initializes the global `jtrace` subsystem during early boot.
pub fn jtrace_init() {
if TARGET_BUFFER_SIZE == 0 {
return;
}
// Note: jtrace_init is called very early on in boot, before global
// constructors have been called. Do not add any behavior which depends on
// global ctors at this point in the code.
if !IS_PERSISTENT {
// SAFETY: `jtrace_init` is called once during single-core early boot, and
// `NonPersistentBuffer::get()` returns a static 8-byte aligned zero-initialized live
// buffer.
unsafe {
G_TRACE.set_location(NonPersistentBuffer::get());
}
}
}
/// Notifies `jtrace` that `thread_init_early` has completed.
///
/// # Safety
///
/// The caller must ensure that early thread initialization is complete so that
/// [`ThreadPtr::current()`](crate::kernel::thread::ThreadPtr::current) is valid on the calling
/// CPU.
pub unsafe fn jtrace_set_after_thread_init_early() {
if TARGET_BUFFER_SIZE == 0 {
return;
}
// SAFETY: Caller guarantees early thread initialization is complete.
unsafe {
G_TRACE.set_after_thread_init_early();
}
}
/// Sets the memory location of the global `jtrace` buffer.
///
/// # Safety
///
/// The caller must ensure that this function is not called concurrently with itself or
/// `jtrace_dump(TraceBufferType::Recovered)`. If `ptr` is non-null, properly aligned, and `len`
/// is large enough to hold at least one entry, the caller must ensure that `ptr` refers to a live
/// (not freed) object/allocation of `len` initialized (e.g. zero-initialized or previously
/// initialized persistent RAM) readable and writable bytes that remains valid for the lifetime of
/// the kernel.
pub unsafe fn jtrace_set_location(ptr: *mut c_void, len: usize) {
if TARGET_BUFFER_SIZE == 0 {
return;
}
// SAFETY: Caller upholds the buffer validity, initialization, and lifetime preconditions.
unsafe {
G_TRACE.set_location((ptr.cast::<u8>(), len));
}
}
/// Invalidates the global `jtrace` header magic.
pub fn jtrace_invalidate() {
if TARGET_BUFFER_SIZE == 0 {
return;
}
G_TRACE.invalidate();
}
/// Records `e` in the global `jtrace` buffer.
///
/// # Safety
///
/// The caller must ensure that `e`'s pointer fields (`tag`, and for [`LargeEntry`] `ffl_info` and
/// its `file`/`func` pointers) are either null or point to valid `'static` null-terminated C
/// strings / [`FileFuncLineInfo`].
pub unsafe fn jtrace_log(e: &mut ProdEntry) {
if TARGET_BUFFER_SIZE == 0 {
return;
}
// SAFETY: Caller guarantees `e`'s pointer fields are null or valid `'static` C strings /
// `FileFuncLineInfo`.
unsafe {
G_TRACE.log(e);
}
}
/// Dumps the specified `jtrace` buffer.
pub fn jtrace_dump(which: TraceBufferType) {
if TARGET_BUFFER_SIZE == 0 {
return;
}
if which == TraceBufferType::Recovered {
G_TRACE.dump_recovered();
} else {
G_TRACE.dump(zx_msec(50));
}
}
#[cfg(console_enabled)]
mod console {
use core::ffi::c_int;
use kprint::kprintln;
use super::{
G_TRACE, IS_PERSISTENT, LAST_ENTRY_STORAGE, TARGET_BUFFER_SIZE, TraceBufferType,
USE_LARGE_ENTRIES, jtrace_dump,
};
use crate::arch_rs::max_num_cpus;
use crate::console::{CMD_AVAIL_NORMAL, CmdArgs, static_command};
/// CLI command for `jtrace`.
///
/// # Safety
///
/// The caller must ensure that `argc >= 1` and `argv` points to a valid array of at least
/// `argc` [`CmdArgs`] elements, where each element's `arg_str` is a valid null-terminated C
/// string for the duration of this call.
unsafe extern "C" fn cmd_jtrace(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int {
if argc <= 0 || argv.is_null() {
return -1;
}
// SAFETY: `argc > 0` and `argv` is non-null; caller guarantees `argv` is valid for `argc`
// elements.
let argv = unsafe { core::slice::from_raw_parts(argv, argc as usize) };
let program = argv[0].as_str();
let usage = || -> c_int {
kprintln!("usage: {:s} [-r|-i]", program);
kprintln!(
" -r : dump the recovered trace buffer instead of the current trace buffer."
);
kprintln!(" -i : dump information about the current JTRACE configuration.");
-1
};
match argc {
1 => {
jtrace_dump(TraceBufferType::Current);
}
2 => {
if argv[1].as_str() == "-r" {
jtrace_dump(TraceBufferType::Recovered);
} else if argv[1].as_str() == "-i" {
if TARGET_BUFFER_SIZE == 0 {
kprintln!("Debug tracing is not enabled in this build.");
} else {
let (loc_data, loc_size) = G_TRACE.get_location();
kprintln!("JTRACE configuration");
kprintln!("--------------------");
kprintln!("Requested Buffer Size : {}", TARGET_BUFFER_SIZE);
kprintln!("Allocated Buffer Size : {}", loc_size);
kprintln!("Allocated Buffer Loc : {:p}", loc_data);
kprintln!("Per-CPU last entry cnt : {}", LAST_ENTRY_STORAGE);
kprintln!(
"Large entries : {:s}",
if USE_LARGE_ENTRIES { "yes" } else { "no" }
);
kprintln!(
"Persistent : {:s}",
if IS_PERSISTENT { "yes" } else { "no" }
);
if (LAST_ENTRY_STORAGE != 0)
&& (LAST_ENTRY_STORAGE != max_num_cpus() as usize)
{
kprintln!(
concat!(
"\nWarning! Configured per-cpu last entry count ({}) ",
"does not match target's number of CPUs ({})",
),
LAST_ENTRY_STORAGE,
max_num_cpus()
);
}
}
} else {
return usage();
}
}
_ => {
return usage();
}
}
0
}
static_command!(
CMD_JTRACE,
c"jtrace".as_ptr(),
c"dump the current or recovered jtrace".as_ptr(),
cmd_jtrace,
CMD_AVAIL_NORMAL
);
}