-
-
Notifications
You must be signed in to change notification settings - Fork 169
/
Copy pathlogger.rs
257 lines (231 loc) · 8.08 KB
/
logger.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// SPDX-License-Identifier: MIT OR Apache-2.0
//! This optional feature adds support for the `log` crate, providing
//! a custom logger implementation which writes to a UEFI text output protocol.
//!
//! The main export of this module is the `Logger` structure,
//! which implements the `log` crate's trait `Log`.
//!
//! # Implementation details
//!
//! The implementation is not the most efficient, since there is no buffering done,
//! and the messages have to be converted from UTF-8 to UEFI's UCS-2.
//!
//! The last part also means that some Unicode characters might not be
//! supported by the UEFI console. Don't expect emoji output support.
use crate::proto::console::text::Output;
use crate::system;
use core::fmt::{self, Write};
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
/// Global logger object
static LOGGER: Logger = Logger::new();
/// Set up logging
///
/// This is unsafe because you must arrange for the logger to be reset with
/// disable() on exit from UEFI boot services.
pub unsafe fn init() {
// Connect the logger to stdout.
system::with_stdout(|stdout| unsafe {
LOGGER.set_output(stdout);
});
// Set the logger.
log::set_logger(&LOGGER).unwrap(); // Can only fail if already initialized.
// Set logger max level to level specified by log features
log::set_max_level(log::STATIC_MAX_LEVEL);
}
pub fn disable() {
LOGGER.disable();
}
/// Writer to the QEMU debugcon device and the debug-console of
/// cloud-hypervisor.
///
/// More info: <https://phip1611.de/blog/how-to-use-qemus-debugcon-feature/>
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "log-debugcon"
))]
#[derive(Copy, Clone, Debug)]
struct DebugconWriter;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "log-debugcon"
))]
impl DebugconWriter {
const IO_PORT: u16 = 0xe9;
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "log-debugcon"
))]
impl core::fmt::Write for DebugconWriter {
fn write_str(&mut self, s: &str) -> fmt::Result {
for &byte in s.as_bytes() {
unsafe {
core::arch::asm!("outb %al, %dx", in("al") byte, in("dx") Self::IO_PORT, options(att_syntax))
};
}
Ok(())
}
}
/// Logging implementation which writes to a UEFI output stream.
///
/// If this logger is used as a global logger, you must disable it using the
/// `disable` method before exiting UEFI boot services in order to prevent
/// undefined behaviour from inadvertent logging.
#[derive(Debug)]
pub struct Logger {
writer: AtomicPtr<Output>,
}
impl Logger {
/// Creates a new logger.
///
/// The logger is initially disabled. Call [`set_output`] to enable it.
///
/// [`set_output`]: Self::set_output
#[must_use]
pub const fn new() -> Self {
Self {
writer: AtomicPtr::new(ptr::null_mut()),
}
}
/// Get the output pointer (may be null).
#[must_use]
fn output(&self) -> *mut Output {
self.writer.load(Ordering::Acquire)
}
/// Set the [`Output`] to which the logger will write.
///
/// If a null pointer is passed for `output`, this method is equivalent to
/// calling [`disable`].
///
/// # Safety
///
/// The `output` pointer must either be null or point to a valid [`Output`]
/// object. That object must remain valid until the logger is either
/// disabled, or `set_output` is called with a different `output`.
///
/// You must arrange for the [`disable`] method to be called or for this
/// logger to be otherwise discarded before boot services are exited.
///
/// [`disable`]: Self::disable
pub unsafe fn set_output(&self, output: *mut Output) {
self.writer.store(output, Ordering::Release);
}
/// Disable the logger.
pub fn disable(&self) {
unsafe { self.set_output(ptr::null_mut()) }
}
}
impl log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
// We decide in `log` already if something is printed. We do not
// need micro optimizations here.
true
}
fn log(&self, record: &log::Record) {
if let Some(writer) = unsafe { self.output().as_mut() } {
// Ignore all errors. Since we're in the logger implementation we
// can't log the error. We also don't want to panic, since logging
// is generally not critical functionality.
let _ = DecoratedLog::write(
writer,
record.level(),
record.args(),
record.file().unwrap_or("<unknown file>"),
record.line().unwrap_or(0),
);
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "log-debugcon"
))]
{
// Ignore all errors. Since we're in the logger implementation we
// can't log the error. We also don't want to panic, since logging
// is generally not critical functionality.
let _ = DecoratedLog::write(
&mut DebugconWriter,
record.level(),
record.args(),
record.file().unwrap_or("<unknown file>"),
record.line().unwrap_or(0),
);
}
}
fn flush(&self) {
// This simple logger does not buffer output.
}
}
// The logger is not thread-safe, but the UEFI boot environment only uses one processor.
unsafe impl Sync for Logger {}
unsafe impl Send for Logger {}
/// Writer wrapper which prints a log level in front of every line of text
///
/// This is less easy than it sounds because...
///
/// 1. The fmt::Arguments is a rather opaque type, the ~only thing you can do
/// with it is to hand it to an fmt::Write implementation.
/// 2. Without using memory allocation, the easy cop-out of writing everything
/// to a String then post-processing is not available.
///
/// Therefore, we need to inject ourselves in the middle of the fmt::Write
/// machinery and intercept the strings that it sends to the Writer.
struct DecoratedLog<'writer, 'a, W: fmt::Write> {
writer: &'writer mut W,
log_level: log::Level,
at_line_start: bool,
file: &'a str,
line: u32,
}
impl<'writer, 'a, W: fmt::Write> DecoratedLog<'writer, 'a, W> {
// Call this method to print a level-annotated log
fn write(
writer: &'writer mut W,
log_level: log::Level,
args: &fmt::Arguments,
file: &'a str,
line: u32,
) -> fmt::Result {
let mut decorated_writer = Self {
writer,
log_level,
at_line_start: true,
file,
line,
};
writeln!(decorated_writer, "{}", *args)
}
}
impl<W: fmt::Write> fmt::Write for DecoratedLog<'_, '_, W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
// Split the input string into lines
let mut lines = s.lines();
// The beginning of the input string may actually fall in the middle of
// a line of output. We only print the log level if it truly is at the
// beginning of a line of output.
let first = lines.next().unwrap_or("");
if self.at_line_start {
write!(
self.writer,
"[{:>5}]: {:>12}@{:03}: ",
self.log_level, self.file, self.line
)?;
self.at_line_start = false;
}
write!(self.writer, "{first}")?;
// For the remainder of the line iterator (if any), we know that we are
// truly at the beginning of lines of output.
for line in lines {
let level = self.log_level;
write!(self.writer, "\n{level}: {line}")?;
}
// If the string ends with a newline character, we must 1/propagate it
// to the output (it was swallowed by the iteration) and 2/prepare to
// write the log level of the beginning of the next line (if any).
if let Some('\n') = s.chars().next_back() {
writeln!(self.writer)?;
self.at_line_start = true;
}
Ok(())
}
}