debug: better kernel ring logger
This commit is contained in:
parent
a08fe6ab1b
commit
5edb26a757
@ -18,7 +18,7 @@ pub struct DebugOptions {
|
|||||||
impl Default for DebugOptions {
|
impl Default for DebugOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
serial_level: LogLevel::Debug, // LogLevel::Info,
|
serial_level: LogLevel::Info,
|
||||||
display_level: LogLevel::Info,
|
display_level: LogLevel::Info,
|
||||||
disable_program_trace: false,
|
disable_program_trace: false,
|
||||||
}
|
}
|
||||||
|
@ -1,533 +0,0 @@
|
|||||||
//! Utilities for debug information logging
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
#![allow(missing_docs)]
|
|
||||||
|
|
||||||
use core::{
|
|
||||||
fmt::{self, Arguments},
|
|
||||||
str::FromStr,
|
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use alloc::{
|
|
||||||
format,
|
|
||||||
string::{String, ToString},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use libk_util::{
|
|
||||||
ring::RingBuffer,
|
|
||||||
sync::{
|
|
||||||
spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard},
|
|
||||||
IrqSafeSpinlock,
|
|
||||||
},
|
|
||||||
OneTimeInit, StaticVector,
|
|
||||||
};
|
|
||||||
use yggdrasil_abi::error::Error;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
arch::Cpu,
|
|
||||||
config,
|
|
||||||
fs::sysfs::{
|
|
||||||
self,
|
|
||||||
attribute::{StringAttribute, StringAttributeOps},
|
|
||||||
object::KObject,
|
|
||||||
},
|
|
||||||
task::{process::Process, thread::Thread},
|
|
||||||
time::monotonic_time,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_DEBUG_SINKS: usize = 8;
|
|
||||||
const RING_LOGGER_CAPACITY: usize = 65536;
|
|
||||||
|
|
||||||
static RING_AVAILABLE: AtomicBool = AtomicBool::new(false);
|
|
||||||
static SERIAL_SINK_SET_UP: AtomicBool = AtomicBool::new(false);
|
|
||||||
static DEBUG_LOCK: IrqSafeSpinlock<()> = IrqSafeSpinlock::new(());
|
|
||||||
|
|
||||||
struct KernelLoggerSink;
|
|
||||||
/// Locking log sink for dumping panic info
|
|
||||||
pub struct PanicLoggerSink<'a> {
|
|
||||||
lock: IrqSafeRwLockReadGuard<'a, StaticVector<DebugSinkWrapper, MAX_DEBUG_SINKS>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RingLoggerInner {
|
|
||||||
data: RingBuffer<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Logger sink which collects output to an internal ring buffer
|
|
||||||
pub struct RingLoggerSink {
|
|
||||||
inner: IrqSafeSpinlock<RingLoggerInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines the severity of the message
|
|
||||||
#[derive(Debug, 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: Sync {
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum DebugSinkWrapper {
|
|
||||||
Arc(LogLevel, Arc<dyn DebugSink>),
|
|
||||||
Static(LogLevel, &'static dyn DebugSink),
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for DebugSinkWrapper {}
|
|
||||||
unsafe impl Sync for DebugSinkWrapper {}
|
|
||||||
// #[derive(Clone, Copy)]
|
|
||||||
// struct DebugSinkWrapper {
|
|
||||||
// // inner: &'static dyn DebugSink,
|
|
||||||
// level: LogLevel,
|
|
||||||
// }
|
|
||||||
|
|
||||||
struct SinkWriter<'a> {
|
|
||||||
sink: &'a dyn DebugSink,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Write for SinkWriter<'_> {
|
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
||||||
self.sink.puts(s).map_err(|_| fmt::Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugSinkWrapper {
|
|
||||||
#[inline]
|
|
||||||
pub fn sink(&self) -> &dyn DebugSink {
|
|
||||||
match self {
|
|
||||||
Self::Arc(_, arc) => arc.as_ref(),
|
|
||||||
Self::Static(_, sink) => *sink,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn level(&self) -> LogLevel {
|
|
||||||
match self {
|
|
||||||
Self::Arc(level, _) => *level,
|
|
||||||
Self::Static(level, _) => *level,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_level(&mut self, target: LogLevel) {
|
|
||||||
match self {
|
|
||||||
Self::Arc(level, _) => *level = target,
|
|
||||||
Self::Static(level, _) => *level = target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl log::Log for DebugSinkWrapper {
|
|
||||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
||||||
if LogLevel::from(metadata.level()) < self.level() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if metadata.target() == "program" && config::get().debug.disable_program_trace {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
use core::fmt::Write;
|
|
||||||
|
|
||||||
let level = LogLevel::from(record.level());
|
|
||||||
let sink = self.sink();
|
|
||||||
let cpu = Cpu::try_local().map(|c| c.id());
|
|
||||||
let line = record.line().unwrap_or(0);
|
|
||||||
let args = record.args();
|
|
||||||
|
|
||||||
let (prefix, suffix) = if sink.supports_control_sequences() {
|
|
||||||
(level.log_prefix(), level.log_suffix())
|
|
||||||
} else {
|
|
||||||
("", "")
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut writer = SinkWriter { sink };
|
|
||||||
let now = monotonic_time();
|
|
||||||
let s = now.seconds();
|
|
||||||
|
|
||||||
match record.target() {
|
|
||||||
":program" => {
|
|
||||||
write!(writer, "{prefix}{s:06}:").ok();
|
|
||||||
if let Some(cpu) = cpu {
|
|
||||||
write!(writer, "{cpu}:").ok();
|
|
||||||
} else {
|
|
||||||
write!(writer, "?:").ok();
|
|
||||||
}
|
|
||||||
write!(writer, "ptrace:{args}").ok();
|
|
||||||
}
|
|
||||||
":raw" => {
|
|
||||||
write!(writer, "{prefix}{args}{suffix}").ok();
|
|
||||||
}
|
|
||||||
target => {
|
|
||||||
write!(writer, "{prefix}{s:06}:").ok();
|
|
||||||
if let Some(cpu) = cpu {
|
|
||||||
write!(writer, "{cpu}:").ok();
|
|
||||||
} else {
|
|
||||||
write!(writer, "?:").ok();
|
|
||||||
}
|
|
||||||
writeln!(writer, "{target}:{line}: {args}{suffix}").ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl log::Log for RingLoggerSink {
|
|
||||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
||||||
metadata.target() != "program"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
use fmt::Write;
|
|
||||||
let mut lock = self.inner.lock();
|
|
||||||
let file = record.file().unwrap_or("<???>");
|
|
||||||
let line = record.line().unwrap_or(0);
|
|
||||||
writeln!(lock, "{}:{}: {}", file, line, record.args()).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl log::Log for KernelLoggerSink {
|
|
||||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
||||||
metadata.target() != "io"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
if !self.enabled(record.metadata()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _guard = DEBUG_LOCK.lock();
|
|
||||||
if RING_AVAILABLE.load(Ordering::Acquire) {
|
|
||||||
RING_LOGGER_SINK.log(record);
|
|
||||||
}
|
|
||||||
for sink in DEBUG_SINKS.read().iter() {
|
|
||||||
if sink.enabled(record.metadata()) {
|
|
||||||
sink.log(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 RingLoggerSink {
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: IrqSafeSpinlock::new(RingLoggerInner {
|
|
||||||
data: RingBuffer::with_capacity(RING_LOGGER_CAPACITY),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads data from the sink without blocking and waiting for more to arrive
|
|
||||||
pub fn read(&self, pos: usize, buffer: &mut [u8]) -> usize {
|
|
||||||
unsafe { self.inner.lock().data.read_all_static(pos, buffer) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_buffer(&self) -> Result<(), Error> {
|
|
||||||
self.inner.lock().data.reserve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Write for RingLoggerInner {
|
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
||||||
for ch in s.bytes() {
|
|
||||||
self.data.write(ch);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<log::Level> for LogLevel {
|
|
||||||
fn from(value: log::Level) -> Self {
|
|
||||||
match value {
|
|
||||||
log::Level::Trace => Self::Trace,
|
|
||||||
log::Level::Debug => Self::Debug,
|
|
||||||
log::Level::Info => Self::Info,
|
|
||||||
log::Level::Warn => Self::Warning,
|
|
||||||
log::Level::Error => Self::Error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PanicLoggerSink<'_> {
|
|
||||||
/// Locks the global logger, allowing the panicking CPU to dump its panic info without being
|
|
||||||
/// interrupted
|
|
||||||
pub fn lock() -> Self {
|
|
||||||
Self {
|
|
||||||
lock: DEBUG_SINKS.read(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prints a formatted message to the panic log
|
|
||||||
pub fn log_fmt(&self, args: Arguments) {
|
|
||||||
use log::Log;
|
|
||||||
|
|
||||||
self.log(
|
|
||||||
&log::Record::builder()
|
|
||||||
.level(log::Level::Error)
|
|
||||||
.target(":raw")
|
|
||||||
.args(args)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl log::Log for PanicLoggerSink<'_> {
|
|
||||||
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
for sink in self.lock.iter() {
|
|
||||||
if sink.enabled(record.metadata()) {
|
|
||||||
sink.log(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Logs a formatted message to the panic output
|
|
||||||
pub macro panic_log($sink:expr, $($args:tt)+) {
|
|
||||||
$sink.log_fmt(format_args!($($args)+))
|
|
||||||
}
|
|
||||||
|
|
||||||
static LOGGER: KernelLoggerSink = KernelLoggerSink;
|
|
||||||
|
|
||||||
static DEBUG_SINKS: IrqSafeRwLock<StaticVector<DebugSinkWrapper, MAX_DEBUG_SINKS>> =
|
|
||||||
IrqSafeRwLock::new(StaticVector::new());
|
|
||||||
|
|
||||||
/// See [RingLoggerSink]
|
|
||||||
pub static RING_LOGGER_SINK: RingLoggerSink = RingLoggerSink::new();
|
|
||||||
|
|
||||||
fn make_sysfs_sink_object(index: usize) -> Arc<KObject<usize>> {
|
|
||||||
struct Level;
|
|
||||||
|
|
||||||
impl StringAttributeOps for Level {
|
|
||||||
type Data = usize;
|
|
||||||
const NAME: &'static str = "level";
|
|
||||||
const LIMIT: usize = 16;
|
|
||||||
const WRITEABLE: bool = true;
|
|
||||||
|
|
||||||
fn read(state: &Self::Data) -> Result<String, Error> {
|
|
||||||
let sinks = DEBUG_SINKS.read();
|
|
||||||
let sink = sinks.get(*state).ok_or(Error::InvalidFile)?;
|
|
||||||
Ok(sink.level().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(state: &Self::Data, value: &str) -> Result<(), Error> {
|
|
||||||
let level = LogLevel::from_str(value)?;
|
|
||||||
let mut sinks = DEBUG_SINKS.write();
|
|
||||||
let sink = sinks.get_mut(*state).ok_or(Error::InvalidFile)?;
|
|
||||||
sink.set_level(level);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let object = KObject::new(index);
|
|
||||||
|
|
||||||
object.add_attribute(StringAttribute::from(Level)).ok();
|
|
||||||
|
|
||||||
object
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_sink_inner(sink: DebugSinkWrapper) {
|
|
||||||
let index = {
|
|
||||||
let mut sinks = DEBUG_SINKS.write();
|
|
||||||
let index = sinks.len();
|
|
||||||
sinks.push(sink);
|
|
||||||
index
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(debug) = sysfs::debug() {
|
|
||||||
debug
|
|
||||||
.add_object(format!("{index}"), make_sysfs_sink_object(index))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a debugging output sink
|
|
||||||
pub fn add_sink(sink: Arc<dyn DebugSink>, level: LogLevel) {
|
|
||||||
add_sink_inner(DebugSinkWrapper::Arc(level, sink.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_serial_sink(sink: Arc<dyn DebugSink>, level: LogLevel) {
|
|
||||||
if SERIAL_SINK_SET_UP.swap(true, Ordering::Acquire) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
add_sink(sink, level);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_early_sink(sink: &'static dyn DebugSink, level: LogLevel) {
|
|
||||||
add_sink_inner(DebugSinkWrapper::Static(level, sink));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_early_sinks() {
|
|
||||||
let mut sinks = DEBUG_SINKS.write();
|
|
||||||
// TODO proper sink storage/manipulation
|
|
||||||
for sink in sinks.iter_mut() {
|
|
||||||
if let DebugSinkWrapper::Static(level, _) = sink {
|
|
||||||
*level = LogLevel::Fatal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print a trace message coming from a process
|
|
||||||
pub fn program_trace(process: &Process, _thread: &Thread, message: &str) {
|
|
||||||
log::debug!(
|
|
||||||
target: ":program",
|
|
||||||
"{} ({}) {message}\n",
|
|
||||||
process.name,
|
|
||||||
process.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_logger() {
|
|
||||||
static LOGGER_SET_UP: OneTimeInit<()> = OneTimeInit::new();
|
|
||||||
|
|
||||||
LOGGER_SET_UP.or_init_with(|| {
|
|
||||||
log::set_logger(&LOGGER)
|
|
||||||
.map(|_| log::set_max_level(log::LevelFilter::Trace))
|
|
||||||
.ok();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets the debugging terminal by clearing it
|
|
||||||
pub fn init() {
|
|
||||||
if RING_LOGGER_SINK.init_buffer().is_ok() {
|
|
||||||
RING_AVAILABLE.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
init_logger();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hex_dump(level: log::Level, bytes: &[u8], start_address: u64, with_chars: bool) {
|
|
||||||
if bytes.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line_width = 16;
|
|
||||||
let address_width = (start_address + bytes.len() as u64).ilog10() as usize + 1;
|
|
||||||
|
|
||||||
for i in (0..bytes.len()).step_by(line_width) {
|
|
||||||
log::log!(target: ":raw", level, "{:0width$X}: ", start_address + i as u64, width = address_width);
|
|
||||||
|
|
||||||
for j in 0..line_width {
|
|
||||||
if i + j < bytes.len() {
|
|
||||||
log::log!(target: ":raw", level, "{:02X}", bytes[i + j]);
|
|
||||||
} else {
|
|
||||||
log::log!(target: ":raw", level, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if j % 2 != 0 {
|
|
||||||
log::log!(target: ":raw", level, " ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if with_chars {
|
|
||||||
for j in 0..line_width {
|
|
||||||
if i + j >= bytes.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let ch = if bytes[i + j].is_ascii_graphic() {
|
|
||||||
bytes[i + j]
|
|
||||||
} else {
|
|
||||||
b'.'
|
|
||||||
};
|
|
||||||
log::log!(target: ":raw", level, "{}", ch as char);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::log!(target: ":raw", level, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for LogLevel {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let level = match self {
|
|
||||||
Self::Trace => "trace",
|
|
||||||
Self::Debug => "debug",
|
|
||||||
Self::Info => "info",
|
|
||||||
Self::Warning => "warn",
|
|
||||||
Self::Error => "error",
|
|
||||||
Self::Fatal => "fatal",
|
|
||||||
};
|
|
||||||
|
|
||||||
f.write_str(level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for LogLevel {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"trace" | "t" => Ok(Self::Trace),
|
|
||||||
"debug" | "d" => Ok(Self::Debug),
|
|
||||||
"info" | "i" => Ok(Self::Info),
|
|
||||||
"warn" | "w" => Ok(Self::Warning),
|
|
||||||
"error" | "e" => Ok(Self::Error),
|
|
||||||
"fatal" | "f" => Ok(Self::Fatal),
|
|
||||||
_ => Err(Error::InvalidArgument),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
240
kernel/libk/src/debug/mod.rs
Normal file
240
kernel/libk/src/debug/mod.rs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
//! Utilities for debug information logging
|
||||||
|
|
||||||
|
use alloc::{
|
||||||
|
string::{String, ToString},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use core::{fmt, str::FromStr};
|
||||||
|
use ring::RingLoggerSink;
|
||||||
|
|
||||||
|
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
|
||||||
|
use sink::DEBUG_SINKS;
|
||||||
|
use yggdrasil_abi::error::Error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
fs::sysfs::{
|
||||||
|
attribute::{StringAttribute, StringAttributeOps},
|
||||||
|
object::KObject,
|
||||||
|
},
|
||||||
|
task::{process::Process, thread::Thread},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod panic;
|
||||||
|
mod ring;
|
||||||
|
mod sink;
|
||||||
|
|
||||||
|
pub use panic::{panic_log, PanicLoggerSink};
|
||||||
|
pub use ring::add_kernel_log_file;
|
||||||
|
pub use sink::{add_early_sink, add_serial_sink, add_sink, DebugSink};
|
||||||
|
|
||||||
|
static DEBUG_LOCK: IrqSafeSpinlock<()> = IrqSafeSpinlock::new(());
|
||||||
|
|
||||||
|
struct KernelLoggerSink;
|
||||||
|
|
||||||
|
/// Defines the severity of the message
|
||||||
|
#[derive(Debug, 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 From<log::Level> for LogLevel {
|
||||||
|
fn from(value: log::Level) -> Self {
|
||||||
|
match value {
|
||||||
|
log::Level::Trace => Self::Trace,
|
||||||
|
log::Level::Debug => Self::Debug,
|
||||||
|
log::Level::Info => Self::Info,
|
||||||
|
log::Level::Warn => Self::Warning,
|
||||||
|
log::Level::Error => Self::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for KernelLoggerSink {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
metadata.target() != "io"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if !self.enabled(record.metadata()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RingLoggerSink.log(record);
|
||||||
|
|
||||||
|
let _guard = DEBUG_LOCK.lock();
|
||||||
|
for sink in DEBUG_SINKS.read().iter() {
|
||||||
|
if sink.enabled(record.metadata()) {
|
||||||
|
sink.log(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOGGER: KernelLoggerSink = KernelLoggerSink;
|
||||||
|
|
||||||
|
fn make_sysfs_sink_object(index: usize) -> Arc<KObject<usize>> {
|
||||||
|
struct Level;
|
||||||
|
|
||||||
|
impl StringAttributeOps for Level {
|
||||||
|
type Data = usize;
|
||||||
|
const NAME: &'static str = "level";
|
||||||
|
const LIMIT: usize = 16;
|
||||||
|
const WRITEABLE: bool = true;
|
||||||
|
|
||||||
|
fn read(state: &Self::Data) -> Result<String, Error> {
|
||||||
|
let sinks = DEBUG_SINKS.read();
|
||||||
|
let sink = sinks.get(*state).ok_or(Error::InvalidFile)?;
|
||||||
|
Ok(sink.level().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(state: &Self::Data, value: &str) -> Result<(), Error> {
|
||||||
|
let level = LogLevel::from_str(value)?;
|
||||||
|
let mut sinks = DEBUG_SINKS.write();
|
||||||
|
let sink = sinks.get_mut(*state).ok_or(Error::InvalidFile)?;
|
||||||
|
sink.set_level(level);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = KObject::new(index);
|
||||||
|
|
||||||
|
object.add_attribute(StringAttribute::from(Level)).ok();
|
||||||
|
|
||||||
|
object
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print a trace message coming from a process
|
||||||
|
pub fn program_trace(process: &Process, _thread: &Thread, message: &str) {
|
||||||
|
log::debug!(
|
||||||
|
target: ":program",
|
||||||
|
"{} ({}) {message}\n",
|
||||||
|
process.name,
|
||||||
|
process.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes kernel logging output. Prior to this call, no log entries are recorded.
|
||||||
|
pub fn init_logger() {
|
||||||
|
static LOGGER_SET_UP: OneTimeInit<()> = OneTimeInit::new();
|
||||||
|
|
||||||
|
LOGGER_SET_UP.or_init_with(|| {
|
||||||
|
log::set_logger(&LOGGER)
|
||||||
|
.map(|_| log::set_max_level(log::LevelFilter::Trace))
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the debugging terminal by clearing it
|
||||||
|
pub fn init() {
|
||||||
|
init_logger();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print a hex dump into the kernel log with given level
|
||||||
|
pub fn hex_dump(level: log::Level, bytes: &[u8], start_address: u64, with_chars: bool) {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line_width = 16;
|
||||||
|
let address_width = (start_address + bytes.len() as u64).ilog10() as usize + 1;
|
||||||
|
|
||||||
|
for i in (0..bytes.len()).step_by(line_width) {
|
||||||
|
log::log!(target: ":raw", level, "{:0width$X}: ", start_address + i as u64, width = address_width);
|
||||||
|
|
||||||
|
for j in 0..line_width {
|
||||||
|
if i + j < bytes.len() {
|
||||||
|
log::log!(target: ":raw", level, "{:02X}", bytes[i + j]);
|
||||||
|
} else {
|
||||||
|
log::log!(target: ":raw", level, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if j % 2 != 0 {
|
||||||
|
log::log!(target: ":raw", level, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if with_chars {
|
||||||
|
for j in 0..line_width {
|
||||||
|
if i + j >= bytes.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let ch = if bytes[i + j].is_ascii_graphic() {
|
||||||
|
bytes[i + j]
|
||||||
|
} else {
|
||||||
|
b'.'
|
||||||
|
};
|
||||||
|
log::log!(target: ":raw", level, "{}", ch as char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::log!(target: ":raw", level, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LogLevel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let level = match self {
|
||||||
|
Self::Trace => "trace",
|
||||||
|
Self::Debug => "debug",
|
||||||
|
Self::Info => "info",
|
||||||
|
Self::Warning => "warn",
|
||||||
|
Self::Error => "error",
|
||||||
|
Self::Fatal => "fatal",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for LogLevel {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"trace" | "t" => Ok(Self::Trace),
|
||||||
|
"debug" | "d" => Ok(Self::Debug),
|
||||||
|
"info" | "i" => Ok(Self::Info),
|
||||||
|
"warn" | "w" => Ok(Self::Warning),
|
||||||
|
"error" | "e" => Ok(Self::Error),
|
||||||
|
"fatal" | "f" => Ok(Self::Fatal),
|
||||||
|
_ => Err(Error::InvalidArgument),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
kernel/libk/src/debug/panic.rs
Normal file
54
kernel/libk/src/debug/panic.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use core::fmt::Arguments;
|
||||||
|
|
||||||
|
use libk_util::{sync::spin_rwlock::IrqSafeRwLockReadGuard, StaticVector};
|
||||||
|
|
||||||
|
use super::sink::{DebugSinkWrapper, DEBUG_SINKS, MAX_DEBUG_SINKS};
|
||||||
|
|
||||||
|
/// Locking log sink for dumping panic info
|
||||||
|
pub struct PanicLoggerSink<'a> {
|
||||||
|
lock: IrqSafeRwLockReadGuard<'a, StaticVector<DebugSinkWrapper, MAX_DEBUG_SINKS>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanicLoggerSink<'_> {
|
||||||
|
/// Locks the global logger, allowing the panicking CPU to dump its panic info without being
|
||||||
|
/// interrupted
|
||||||
|
pub fn lock() -> Self {
|
||||||
|
Self {
|
||||||
|
lock: DEBUG_SINKS.read(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a formatted message to the panic log
|
||||||
|
pub fn log_fmt(&self, args: Arguments) {
|
||||||
|
use log::Log;
|
||||||
|
|
||||||
|
self.log(
|
||||||
|
&log::Record::builder()
|
||||||
|
.level(log::Level::Error)
|
||||||
|
.target(":raw")
|
||||||
|
.args(args)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for PanicLoggerSink<'_> {
|
||||||
|
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
for sink in self.lock.iter() {
|
||||||
|
if sink.enabled(record.metadata()) {
|
||||||
|
sink.log(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a formatted message to the panic output
|
||||||
|
pub macro panic_log($sink:expr, $($args:tt)+) {
|
||||||
|
$sink.log_fmt(format_args!($($args)+))
|
||||||
|
}
|
270
kernel/libk/src/debug/ring.rs
Normal file
270
kernel/libk/src/debug/ring.rs
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
use core::{any::Any, fmt, future::poll_fn, task::Poll};
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, waker::QueueWaker};
|
||||||
|
use yggdrasil_abi::{
|
||||||
|
error::Error,
|
||||||
|
io::{FileMode, OpenOptions},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
task::sync::AsyncMutex,
|
||||||
|
vfs::{CommonImpl, Filename, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl},
|
||||||
|
};
|
||||||
|
|
||||||
|
const RECORD_SIZE: usize = 128;
|
||||||
|
const FIXED_LOG_SIZE: usize = 256;
|
||||||
|
|
||||||
|
struct LogFile;
|
||||||
|
pub(super) struct RingLoggerSink;
|
||||||
|
|
||||||
|
struct WriteBuffer {
|
||||||
|
text: [u8; 255],
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Record {
|
||||||
|
len: usize,
|
||||||
|
level: log::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for WriteBuffer {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
let available = self.text.len() - self.len;
|
||||||
|
let amount = available.min(bytes.len());
|
||||||
|
self.text[self.len..self.len + amount].copy_from_slice(&bytes[..amount]);
|
||||||
|
self.len += amount;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut FIXED_LOG_BUFFER: [u8; FIXED_LOG_SIZE * RECORD_SIZE] = [0; FIXED_LOG_SIZE * RECORD_SIZE];
|
||||||
|
static mut FIXED_LOG_RECORDS: [Record; FIXED_LOG_SIZE] = [const {
|
||||||
|
Record {
|
||||||
|
len: 0,
|
||||||
|
level: log::Level::Trace,
|
||||||
|
}
|
||||||
|
}; FIXED_LOG_SIZE];
|
||||||
|
#[allow(static_mut_refs)]
|
||||||
|
static LOG_BUFFER: IrqSafeRwLock<LogBuffer> = IrqSafeRwLock::new(unsafe {
|
||||||
|
LogBuffer::new(LogBufferStorage::Fixed {
|
||||||
|
meta: &mut FIXED_LOG_RECORDS,
|
||||||
|
data: &mut FIXED_LOG_BUFFER,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
static LOG_NOTIFY: QueueWaker = QueueWaker::new();
|
||||||
|
|
||||||
|
enum LogBufferStorage {
|
||||||
|
Fixed {
|
||||||
|
meta: &'static mut [Record; FIXED_LOG_SIZE],
|
||||||
|
data: &'static mut [u8; FIXED_LOG_SIZE * RECORD_SIZE],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogBuffer {
|
||||||
|
storage: LogBufferStorage,
|
||||||
|
head: usize,
|
||||||
|
tail: usize,
|
||||||
|
filled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OpenLogFile {
|
||||||
|
seq: AsyncMutex<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogBufferStorage {
|
||||||
|
fn record_mut(&mut self, index: usize) -> (&mut Record, &mut [u8]) {
|
||||||
|
match self {
|
||||||
|
Self::Fixed { meta, data } => {
|
||||||
|
let index = index % meta.len();
|
||||||
|
(&mut meta[index], &mut data[index * RECORD_SIZE..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record(&self, index: usize) -> (&Record, &[u8]) {
|
||||||
|
match self {
|
||||||
|
Self::Fixed { meta, data } => {
|
||||||
|
let index = index % meta.len();
|
||||||
|
(&meta[index], &data[index * RECORD_SIZE..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Fixed { meta, .. } => meta.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogBuffer {
|
||||||
|
const fn new(storage: LogBufferStorage) -> Self {
|
||||||
|
Self {
|
||||||
|
storage,
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
filled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, level: log::Level, text: &[u8]) {
|
||||||
|
let len = text.len().min(RECORD_SIZE);
|
||||||
|
let (meta, data) = self.storage.record_mut(self.head);
|
||||||
|
meta.len = len;
|
||||||
|
meta.level = level;
|
||||||
|
data[..len].copy_from_slice(&text[..len]);
|
||||||
|
self.head = self.head.wrapping_add(1);
|
||||||
|
if self.filled {
|
||||||
|
self.tail = self.head.wrapping_sub(self.storage.len() - 1);
|
||||||
|
} else if self.head == self.storage.len() {
|
||||||
|
self.filled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommonImpl for LogFile {
|
||||||
|
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegularImpl for LogFile {
|
||||||
|
fn open(
|
||||||
|
&self,
|
||||||
|
_node: &NodeRef,
|
||||||
|
opts: OpenOptions,
|
||||||
|
) -> Result<(u64, Option<InstanceData>), Error> {
|
||||||
|
if opts.contains_any(OpenOptions::WRITE | OpenOptions::APPEND | OpenOptions::TRUNCATE) {
|
||||||
|
return Err(Error::ReadOnly);
|
||||||
|
}
|
||||||
|
let instance = Arc::new(OpenLogFile {
|
||||||
|
seq: AsyncMutex::new(LOG_BUFFER.read().tail),
|
||||||
|
});
|
||||||
|
Ok((0, Some(instance)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(
|
||||||
|
&self,
|
||||||
|
_node: &NodeRef,
|
||||||
|
instance: Option<&InstanceData>,
|
||||||
|
_pos: u64,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
if buf.len() < 4 {
|
||||||
|
return Err(Error::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance: &OpenLogFile = instance
|
||||||
|
.and_then(|t| t.downcast_ref())
|
||||||
|
.ok_or(Error::InvalidFile)?;
|
||||||
|
|
||||||
|
block! {
|
||||||
|
let mut seq = instance.seq.lock().await;
|
||||||
|
let (len, new_seq) = read_log_line(*seq, buf).await;
|
||||||
|
*seq = new_seq;
|
||||||
|
Ok(len)
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&self, _node: &NodeRef, _instance: Option<&InstanceData>) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for RingLoggerSink {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
metadata.level() != log::Level::Trace
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
if !self.enabled(record.metadata()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write = WriteBuffer {
|
||||||
|
text: [0; 255],
|
||||||
|
len: 0,
|
||||||
|
};
|
||||||
|
let result = if record.target() == ":raw" || record.target() == ":program" {
|
||||||
|
write!(write, "{}", record.args())
|
||||||
|
} else {
|
||||||
|
write!(write, "{}\n", record.args())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(()) = result {
|
||||||
|
LOG_BUFFER
|
||||||
|
.write()
|
||||||
|
.write(record.level(), &write.text[..write.len]);
|
||||||
|
LOG_NOTIFY.wake_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a kernel log file to the parent node
|
||||||
|
pub fn add_kernel_log_file(parent: &NodeRef) {
|
||||||
|
let node = Node::regular(
|
||||||
|
LogFile,
|
||||||
|
NodeFlags::IN_MEMORY_SIZE | NodeFlags::IN_MEMORY_PROPS,
|
||||||
|
Some(Metadata::now_root(FileMode::new(0o400))),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let filename = unsafe { Filename::from_str_unchecked("log") };
|
||||||
|
parent.add_child(filename, node).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_log_line(seq: usize, buffer: &mut [u8]) -> (usize, usize) {
|
||||||
|
poll_fn(|cx| {
|
||||||
|
if let Some(out) = try_read_log_line(seq, buffer) {
|
||||||
|
LOG_NOTIFY.remove(cx.waker());
|
||||||
|
Poll::Ready(out)
|
||||||
|
} else {
|
||||||
|
LOG_NOTIFY.register(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_log_line(seq: usize, buffer: &mut [u8]) -> Option<(usize, usize)> {
|
||||||
|
let lock = LOG_BUFFER.read();
|
||||||
|
if seq == lock.head {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let available = lock.head.wrapping_sub(seq);
|
||||||
|
let ridx = if available < lock.storage.len() {
|
||||||
|
seq
|
||||||
|
} else {
|
||||||
|
lock.tail
|
||||||
|
};
|
||||||
|
|
||||||
|
let (meta, data) = lock.storage.record(ridx);
|
||||||
|
let new_seq = ridx.wrapping_add(1);
|
||||||
|
let len = core::cmp::min(meta.len, buffer.len() - 2);
|
||||||
|
|
||||||
|
buffer[0] = match meta.level {
|
||||||
|
log::Level::Trace => b't',
|
||||||
|
log::Level::Debug => b'd',
|
||||||
|
log::Level::Info => b'i',
|
||||||
|
log::Level::Warn => b'w',
|
||||||
|
log::Level::Error => b'e',
|
||||||
|
};
|
||||||
|
buffer[1] = b':';
|
||||||
|
buffer[2..len + 2].copy_from_slice(&data[..len]);
|
||||||
|
|
||||||
|
Some((len + 2, new_seq))
|
||||||
|
}
|
186
kernel/libk/src/debug/sink.rs
Normal file
186
kernel/libk/src/debug/sink.rs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::{format, sync::Arc};
|
||||||
|
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, StaticVector};
|
||||||
|
use yggdrasil_abi::error::Error;
|
||||||
|
|
||||||
|
use crate::{arch::Cpu, config, fs::sysfs, time::monotonic_time};
|
||||||
|
|
||||||
|
use super::{make_sysfs_sink_object, LogLevel};
|
||||||
|
|
||||||
|
/// Generic interface for debug output
|
||||||
|
pub trait DebugSink: Sync {
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum DebugSinkWrapper {
|
||||||
|
Arc(LogLevel, Arc<dyn DebugSink>),
|
||||||
|
Static(LogLevel, &'static dyn DebugSink),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SinkWriter<'a> {
|
||||||
|
sink: &'a dyn DebugSink,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for DebugSinkWrapper {}
|
||||||
|
unsafe impl Sync for DebugSinkWrapper {}
|
||||||
|
|
||||||
|
impl fmt::Write for SinkWriter<'_> {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.sink.puts(s).map_err(|_| fmt::Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugSinkWrapper {
|
||||||
|
#[inline]
|
||||||
|
pub fn sink(&self) -> &dyn DebugSink {
|
||||||
|
match self {
|
||||||
|
Self::Arc(_, arc) => arc.as_ref(),
|
||||||
|
Self::Static(_, sink) => *sink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn level(&self) -> LogLevel {
|
||||||
|
match self {
|
||||||
|
Self::Arc(level, _) => *level,
|
||||||
|
Self::Static(level, _) => *level,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_level(&mut self, target: LogLevel) {
|
||||||
|
match self {
|
||||||
|
Self::Arc(level, _) => *level = target,
|
||||||
|
Self::Static(level, _) => *level = target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for DebugSinkWrapper {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
if LogLevel::from(metadata.level()) < self.level() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if metadata.target() == "program" && config::get().debug.disable_program_trace {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
let level = LogLevel::from(record.level());
|
||||||
|
let sink = self.sink();
|
||||||
|
let cpu = Cpu::try_local().map(|c| c.id());
|
||||||
|
let line = record.line().unwrap_or(0);
|
||||||
|
let args = record.args();
|
||||||
|
|
||||||
|
let (prefix, suffix) = if sink.supports_control_sequences() {
|
||||||
|
(level.log_prefix(), level.log_suffix())
|
||||||
|
} else {
|
||||||
|
("", "")
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut writer = SinkWriter { sink };
|
||||||
|
let now = monotonic_time();
|
||||||
|
let s = now.seconds();
|
||||||
|
|
||||||
|
match record.target() {
|
||||||
|
":program" => {
|
||||||
|
write!(writer, "{prefix}{s:06}:").ok();
|
||||||
|
if let Some(cpu) = cpu {
|
||||||
|
write!(writer, "{cpu}:").ok();
|
||||||
|
} else {
|
||||||
|
write!(writer, "?:").ok();
|
||||||
|
}
|
||||||
|
write!(writer, "ptrace:{args}").ok();
|
||||||
|
}
|
||||||
|
":raw" => {
|
||||||
|
write!(writer, "{prefix}{args}{suffix}").ok();
|
||||||
|
}
|
||||||
|
target => {
|
||||||
|
write!(writer, "{prefix}{s:06}:").ok();
|
||||||
|
if let Some(cpu) = cpu {
|
||||||
|
write!(writer, "{cpu}:").ok();
|
||||||
|
} else {
|
||||||
|
write!(writer, "?:").ok();
|
||||||
|
}
|
||||||
|
writeln!(writer, "{target}:{line}: {args}{suffix}").ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) const MAX_DEBUG_SINKS: usize = 8;
|
||||||
|
|
||||||
|
pub(super) static DEBUG_SINKS: IrqSafeRwLock<StaticVector<DebugSinkWrapper, MAX_DEBUG_SINKS>> =
|
||||||
|
IrqSafeRwLock::new(StaticVector::new());
|
||||||
|
|
||||||
|
static SERIAL_SINK_SET_UP: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Adds a debugging output sink
|
||||||
|
pub fn add_sink(sink: Arc<dyn DebugSink>, level: LogLevel) {
|
||||||
|
add_sink_inner(DebugSinkWrapper::Arc(level, sink.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a serial debug output sink. Only one will be added, rest are ignored.
|
||||||
|
pub fn add_serial_sink(sink: Arc<dyn DebugSink>, level: LogLevel) {
|
||||||
|
if SERIAL_SINK_SET_UP.swap(true, Ordering::Acquire) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_sink(sink, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an early output sink, which is disabled by a call to [disable_early_sinks].
|
||||||
|
pub fn add_early_sink(sink: &'static dyn DebugSink, level: LogLevel) {
|
||||||
|
add_sink_inner(DebugSinkWrapper::Static(level, sink));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables eraly output sinks
|
||||||
|
pub fn disable_early_sinks() {
|
||||||
|
let mut sinks = DEBUG_SINKS.write();
|
||||||
|
// TODO proper sink storage/manipulation
|
||||||
|
for sink in sinks.iter_mut() {
|
||||||
|
if let DebugSinkWrapper::Static(level, _) = sink {
|
||||||
|
*level = LogLevel::Fatal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_sink_inner(sink: DebugSinkWrapper) {
|
||||||
|
let index = {
|
||||||
|
let mut sinks = DEBUG_SINKS.write();
|
||||||
|
let index = sinks.len();
|
||||||
|
sinks.push(sink);
|
||||||
|
index
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(debug) = sysfs::debug() {
|
||||||
|
debug
|
||||||
|
.add_object(format!("{index}"), make_sysfs_sink_object(index))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ use alloc::sync::Arc;
|
|||||||
use libk_util::OneTimeInit;
|
use libk_util::OneTimeInit;
|
||||||
use yggdrasil_abi::{error::Error, io::FileMode};
|
use yggdrasil_abi::{error::Error, io::FileMode};
|
||||||
|
|
||||||
use crate::vfs::{impls::MemoryDirectory, path::OwnedFilename, Metadata, Node, NodeFlags};
|
use crate::vfs::{impls::MemoryDirectory, path::OwnedFilename, Metadata, Node, NodeFlags, NodeRef};
|
||||||
|
|
||||||
use super::attribute::Attribute;
|
use super::attribute::Attribute;
|
||||||
|
|
||||||
@ -39,6 +39,10 @@ impl<D> KObject<D> {
|
|||||||
self.node.add_child(filename, child.node.clone())
|
self.node.add_child(filename, child.node.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn node(&self) -> &NodeRef {
|
||||||
|
&self.node
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn data(&self) -> &D {
|
pub(super) fn data(&self) -> &D {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ use git_version::git_version;
|
|||||||
use kernel_arch::{Architecture, ArchitectureImpl};
|
use kernel_arch::{Architecture, ArchitectureImpl};
|
||||||
use libk::{
|
use libk::{
|
||||||
arch::Cpu,
|
arch::Cpu,
|
||||||
|
debug,
|
||||||
fs::{
|
fs::{
|
||||||
devfs,
|
devfs,
|
||||||
sysfs::{
|
sysfs::{
|
||||||
@ -115,9 +116,11 @@ fn register_sysfs_attributes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let kernel = sysfs::kernel().unwrap();
|
let kernel = sysfs::kernel().unwrap();
|
||||||
|
let kernel_node = kernel.node();
|
||||||
|
|
||||||
kernel.add_attribute(StringAttribute::from(Version)).ok();
|
kernel.add_attribute(StringAttribute::from(Version)).ok();
|
||||||
kernel.add_attribute(StringAttribute::from(Arch)).ok();
|
kernel.add_attribute(StringAttribute::from(Arch)).ok();
|
||||||
|
debug::add_kernel_log_file(kernel_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Common kernel main function. Must be called for BSP processor only.
|
/// Common kernel main function. Must be called for BSP processor only.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user