280 lines
7.3 KiB
Rust
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();
|
|
}
|
|
}
|
|
}
|