yggdrasil/src/debug.rs

280 lines
7.3 KiB
Rust

//! Utilities for debug information logging
use core::fmt::{self, Arguments};
use abi::error::Error;
use alloc::sync::Arc;
use futures_util::Future;
use kernel_util::{
sync::IrqSafeSpinlock,
util::{OneTimeInit, StaticVector},
};
use crate::{
task::{process::Process, runtime::QueueWaker},
util::ring::RingBuffer,
};
const MAX_DEBUG_SINKS: usize = 4;
const RING_LOGGER_CAPACITY: usize = 65536;
struct SimpleLogger;
struct RingLoggerInner {
data: RingBuffer<u8, RING_LOGGER_CAPACITY>,
}
pub struct RingLoggerSink {
inner: IrqSafeSpinlock<RingLoggerInner>,
waker: QueueWaker,
}
/// Defines the severity of the message
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
/// Very verbose low-level debugging information
Trace,
/// Debugging and verbose information
Debug,
/// General information about transitions in the system state
Info,
/// Non-critical abnormalities or notices
Warning,
/// Failures of non-essential components
Error,
/// Irrecoverable errors which result in kernel panic
Fatal,
}
/// Generic interface for debug output
pub trait DebugSink {
/// Sends a single byte to the output
fn putc(&self, c: u8) -> Result<(), Error>;
/// Sends a string of bytes to the output
fn puts(&self, s: &str) -> Result<(), Error> {
for &byte in s.as_bytes() {
self.putc(byte)?;
}
Ok(())
}
/// Returns `true` if the device supports vt100-like control sequences
fn supports_control_sequences(&self) -> bool {
false
}
}
macro_rules! log_print_raw {
($level:expr, $($args:tt)+) => {
$crate::debug::debug_internal(format_args!($($args)+), $level)
};
}
macro_rules! log_print {
($level:expr, $($args:tt)+) => {
log_print_raw!($level, "cpu{}:{}:{}: {}", /* $crate::task::Cpu::local_id() */ 0, file!(), line!(), format_args!($($args)+))
};
}
macro_rules! debug_tpl {
($d:tt $name:ident, $nameln:ident, $level:ident) => {
#[allow(unused_macros)]
/// Prints the message to the log
macro_rules! $name {
($d($d args:tt)+) => (log_print!($crate::debug::LogLevel::$level, $d($d args)+));
}
/// Prints the message to the log, terminated by a newline character
#[allow(unused_macros)]
macro_rules! $nameln {
() => {
$name!("\n")
};
($d($d args:tt)+) => ($name!("{}\n", format_args!($d($d args)+)));
}
};
}
debug_tpl!($ debug, debugln, Debug);
debug_tpl!($ info, infoln, Info);
debug_tpl!($ warn, warnln, Warning);
debug_tpl!($ error, errorln, Error);
debug_tpl!($ fatal, fatalln, Fatal);
#[derive(Clone, Copy)]
struct DebugSinkWrapper {
inner: &'static dyn DebugSink,
level: LogLevel,
}
impl log::Log for SimpleLogger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
let file = record.file().unwrap_or("<???>");
let line = record.line().unwrap_or(0);
match record.level() {
log::Level::Error => {
log_print_raw!(LogLevel::Error, "{}:{}: {}\n", file, line, record.args())
}
log::Level::Warn => {
log_print_raw!(LogLevel::Warning, "{}:{}: {}\n", file, line, record.args())
} // warnln!("{}", record.args()),
log::Level::Info => {
log_print_raw!(LogLevel::Info, "{}:{}: {}\n", file, line, record.args())
}
log::Level::Debug => {
log_print_raw!(LogLevel::Debug, "{}:{}: {}\n", file, line, record.args())
}
log::Level::Trace => {
log_print_raw!(LogLevel::Trace, "{}:{}: {}\n", file, line, record.args())
}
}
}
fn flush(&self) {}
}
impl LogLevel {
fn log_prefix(self) -> &'static str {
match self {
LogLevel::Trace => "",
LogLevel::Debug => "",
LogLevel::Info => "\x1b[36m\x1b[1m",
LogLevel::Warning => "\x1b[33m\x1b[1m",
LogLevel::Error => "\x1b[31m\x1b[1m",
LogLevel::Fatal => "\x1b[38;2;255;0;0m\x1b[1m",
}
}
fn log_suffix(self) -> &'static str {
match self {
LogLevel::Trace => "",
LogLevel::Debug => "",
LogLevel::Info => "\x1b[0m",
LogLevel::Warning => "\x1b[0m",
LogLevel::Error => "\x1b[0m",
LogLevel::Fatal => "\x1b[0m",
}
}
}
impl fmt::Write for DebugSinkWrapper {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.inner.puts(s).ok();
Ok(())
}
}
impl RingLoggerSink {
pub const fn new() -> Self {
Self {
inner: IrqSafeSpinlock::new(RingLoggerInner {
data: RingBuffer::new(0),
}),
waker: QueueWaker::new(),
}
}
pub fn read(&self, pos: usize, buffer: &mut [u8]) -> usize {
unsafe { self.inner.lock().data.read_all_static(pos, buffer) }
}
fn write_fmt(&self, args: fmt::Arguments<'_>) -> fmt::Result {
use fmt::Write;
self.inner.lock().write_fmt(args)
}
}
impl fmt::Write for RingLoggerInner {
fn write_str(&mut self, s: &str) -> fmt::Result {
for ch in s.bytes() {
unsafe {
self.data.write_unchecked(ch);
}
}
Ok(())
}
}
static LOGGER: SimpleLogger = SimpleLogger;
static DEBUG_SINKS: IrqSafeSpinlock<StaticVector<DebugSinkWrapper, MAX_DEBUG_SINKS>> =
IrqSafeSpinlock::new(StaticVector::new());
pub static RING_LOGGER_SINK: RingLoggerSink = RingLoggerSink::new();
/// Prints a hex-dump of a slice, appending a virtual address offset to the output
pub fn hex_dump(level: LogLevel, addr_offset: usize, data: &[u8]) {
for (i, b) in data.iter().enumerate() {
if i % 16 == 0 {
log_print_raw!(level, "{:X}: ", addr_offset + i)
}
log_print_raw!(level, "{:02X}", b);
if i % 16 == 15 {
log_print_raw!(level, "\n");
} else if i % 2 == 1 {
log_print_raw!(level, " ");
}
}
if data.len() % 16 != 0 {
log_print_raw!(level, "\n");
}
}
/// Adds a debugging output sink
pub fn add_sink(sink: &'static dyn DebugSink, level: LogLevel) {
DEBUG_SINKS
.lock()
.push(DebugSinkWrapper { inner: sink, level });
}
/// Print a trace message coming from a process
pub fn program_trace(process: &Process, message: &str) {
log_print_raw!(
LogLevel::Trace,
"[trace {} {:?}] {}\n",
process.id(),
process.name(),
message
);
}
/// Resets the debugging terminal by clearing it
pub fn init() {
log::set_logger(&LOGGER)
.map(|_| log::set_max_level(log::LevelFilter::Trace))
.ok();
log_print_raw!(LogLevel::Info, "\x1b[0m");
}
#[doc(hidden)]
pub fn debug_internal(args: Arguments, level: LogLevel) {
use fmt::Write;
RING_LOGGER_SINK.write_fmt(args).ok();
for sink in DEBUG_SINKS.lock().iter_mut() {
if level < sink.level {
continue;
}
if sink.inner.supports_control_sequences() {
sink.write_str(level.log_prefix()).ok();
}
sink.write_fmt(args).ok();
if sink.inner.supports_control_sequences() {
sink.write_str(level.log_suffix()).ok();
}
}
}