blob: 205dec0d9083b8033dc47b6a5d384bb09814cd6a [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::c_char;
use boot_options::BootOptions;
use debug::ltracef;
use syscalls_macro::syscall;
use zx_status::{ErrorStatus, Status};
use zx_types::{ZX_RSRC_SYSTEM_DEBUG_BASE, ZX_RSRC_SYSTEM_TRACING_BASE, zx_status_t};
use crate::object::{HandleValue, validate_system_resource};
use crate::user_copy::{UserInOutPtr, UserInPtr, UserOutPtr};
const LOCAL_TRACE: u32 = 0;
/// Maximum number of bytes that can be written in a single `zx_debug_write`
/// or `zx_debug_send_command` syscall.
pub const MAX_DEBUG_WRITE_SIZE: usize = 256;
unsafe extern "C" {
fn cpp_platform_dgetc(c: *mut c_char, wait: bool) -> i32;
fn cpp_persistent_dlog_write(ptr: *const c_char, len: usize);
fn cpp_dlog_serial_write(ptr: *const c_char, len: usize);
fn cpp_console_run_script(str: *const c_char) -> zx_status_t;
fn cpp_ktrace_read_user(
ptr: UserOutPtr<u8>,
offset: u32,
len: usize,
out_actual: *mut usize,
) -> zx_status_t;
fn cpp_ktrace_control(action: u32, options: u32) -> zx_status_t;
}
#[syscall]
pub fn sys_debug_read(
handle: HandleValue,
ptr: UserOutPtr<u8>,
max_len: usize,
len: UserOutPtr<usize>,
) -> Result<(), ErrorStatus> {
ltracef!("ptr {:p}\n", ptr.as_ptr());
if BootOptions::get().enable_serial_syscalls != boot_options::SerialDebugSyscalls::Enabled {
return Err(Status::NOT_SUPPORTED.into());
}
validate_system_resource(handle, ZX_RSRC_SYSTEM_DEBUG_BASE)?;
let mut idx = 0;
while idx < max_len {
let mut c: c_char = 0;
// Wait only on the first character.
// The API for this function can return any number of characters up to the supplied buffer
// length, however there is no notification mechanism for when there are bytes to read.
// Hence, we need to read at least one character or applications will be forced to spin poll.
// We avoid reading all the characters so that interactive applications can stay responsive
// without losing efficiency by being forced to read one character at a time.
let wait = idx == 0;
// SAFETY: `&mut c` is a valid pointer to stack storage.
let err = unsafe { cpp_platform_dgetc(&mut c, wait) };
if err < 0 {
Status::ok(err)?;
} else if err == 0 {
break;
}
let mut byte = c as u8;
if byte == b'\r' {
byte = b'\n';
}
ptr.element_offset(idx).copy_to_user(&byte)?;
idx += 1;
}
len.copy_to_user(&idx)?;
Ok(())
}
#[syscall]
pub fn sys_debug_write(ptr: UserInPtr<u8>, mut len: usize) -> Result<(), ErrorStatus> {
ltracef!("ptr {:p}, len {}\n", ptr.as_ptr(), len);
let enable_serial = BootOptions::get().enable_serial_syscalls;
if enable_serial != boot_options::SerialDebugSyscalls::Enabled
&& enable_serial != boot_options::SerialDebugSyscalls::OutputOnly
{
return Err(Status::NOT_SUPPORTED.into());
}
if len > MAX_DEBUG_WRITE_SIZE {
len = MAX_DEBUG_WRITE_SIZE;
}
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); MAX_DEBUG_WRITE_SIZE];
let slice = ptr.copy_slice_from_user(&mut buf[..len]).map_err(|_| Status::INVALID_ARGS)?;
// Dump what we can into the persistent dlog, if we have one.
// SAFETY: `slice.as_ptr()` points to `len` initialized bytes in kernel stack memory.
unsafe {
cpp_persistent_dlog_write(slice.as_ptr().cast::<c_char>(), len);
}
// This path to serial out arbitrates with the debug log
// drainer and/or kernel ll debug path to minimize interleaving
// of serial output between various sources.
// SAFETY: `slice.as_ptr()` points to `len` initialized bytes in kernel stack memory.
unsafe {
cpp_dlog_serial_write(slice.as_ptr().cast::<c_char>(), len);
}
Ok(())
}
#[syscall]
pub fn sys_debug_send_command(
resource: HandleValue,
ptr: UserInPtr<u8>,
len: usize,
) -> Result<(), ErrorStatus> {
ltracef!("ptr {:p}, len {}\n", ptr.as_ptr(), len);
if !BootOptions::get().enable_debugging_syscalls {
return Err(Status::NOT_SUPPORTED.into());
}
validate_system_resource(resource, ZX_RSRC_SYSTEM_DEBUG_BASE)?;
if len > MAX_DEBUG_WRITE_SIZE {
return Err(Status::INVALID_ARGS.into());
}
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); MAX_DEBUG_WRITE_SIZE + 2];
ptr.copy_slice_from_user(&mut buf[..len]).map_err(|_| Status::INVALID_ARGS)?;
buf[len].write(b'\n');
buf[len + 1].write(0);
// SAFETY: `buf[..len + 2]` has been fully initialized with the user command followed by
// a newline and null terminator, making `buf.as_ptr()` a valid null-terminated C string.
let status = unsafe { cpp_console_run_script(buf.as_ptr().cast::<c_char>()) };
Status::ok(status)?;
Ok(())
}
#[syscall]
pub fn sys_ktrace_read(
handle: HandleValue,
data: UserOutPtr<u8>,
offset: u32,
len: usize,
out_actual: UserOutPtr<usize>,
) -> Result<(), ErrorStatus> {
validate_system_resource(handle, ZX_RSRC_SYSTEM_TRACING_BASE)?;
let mut actual: usize = 0;
// SAFETY: Calling C++ helper to read ktrace data into the user buffer and output actual count.
let status = unsafe { cpp_ktrace_read_user(data, offset, len, &mut actual) };
Status::ok(status)?;
out_actual.copy_to_user(&actual)?;
Ok(())
}
#[syscall]
pub fn sys_ktrace_control(
handle: HandleValue,
action: u32,
options: u32,
_ptr: UserInOutPtr<u8>,
) -> Result<(), ErrorStatus> {
validate_system_resource(handle, ZX_RSRC_SYSTEM_TRACING_BASE)?;
// SAFETY: Calling C++ helper to perform ktrace control operation.
let status = unsafe { cpp_ktrace_control(action, options) };
Status::ok(status)?;
Ok(())
}