mycelium_kernel/arch/x86_64/
oops.rs1use super::framebuf;
2use core::{
3 panic::PanicInfo,
4 sync::atomic::{AtomicBool, Ordering},
5};
6use embedded_graphics::{
7 mono_font::{ascii, MonoTextStyleBuilder},
8 pixelcolor::{Rgb888, RgbColor as _},
9 prelude::*,
10 text::{Alignment, Text},
11};
12use hal_core::{
13 framebuffer::{Draw, RgbColor},
14 interrupt, Address,
15};
16use hal_x86_64::{control_regs, cpu, interrupt::Registers as X64Registers, serial, vga};
17use mycelium_trace::{embedded_graphics::TextWriterBuilder, writer::MakeWriter};
18use mycelium_util::fmt::{self, Write};
19
20#[derive(Debug)]
21pub struct Oops<'a> {
22 already_panicked: bool,
23 already_faulted: bool,
24 alloc: crate::allocator::State,
25 situation: OopsSituation<'a>,
26}
27
28enum OopsSituation<'a> {
29 Fault {
30 kind: &'static str,
31 fault: Fault<'a>,
32 details: Option<&'a dyn fmt::Display>,
33 },
34 Panic(&'a PanicInfo<'a>),
35 AllocError(alloc::alloc::Layout),
36}
37
38type Fault<'a> = &'a dyn interrupt::ctx::Context<Registers = X64Registers>;
39
40#[cold]
41pub fn oops(oops: Oops<'_>) -> ! {
42 let mut vga = vga::writer();
43
44 unsafe {
49 cpu::intrinsics::cli();
51
52 if let Some(com1) = serial::com1() {
54 com1.force_unlock();
55 }
56
57 vga.force_unlock();
59
60 framebuf::force_unlock();
62 }
63
64 tracing::debug!(target: "oops", message = ?oops);
70
71 let mut framebuf = unsafe { super::framebuf::mk_framebuf() };
72 framebuf.fill(RgbColor::RED);
73
74 let mut target = framebuf.as_draw_target();
75 let uwu = MonoTextStyleBuilder::new()
76 .font(&ascii::FONT_9X15_BOLD)
77 .text_color(Rgb888::RED)
78 .background_color(Rgb888::WHITE)
79 .build();
80
81 let _ = Text::with_alignment(
82 oops.situation.header(),
83 Point::new(5, 15),
84 uwu,
85 Alignment::Left,
86 )
87 .draw(&mut target)
88 .unwrap();
89 drop(framebuf);
90
91 let mk_writer = TextWriterBuilder::default()
92 .starting_point(Point::new(5, 30))
93 .default_color(mycelium_trace::color::Color::BrightWhite)
94 .build(|| unsafe { super::framebuf::mk_framebuf() });
95 writeln!(
96 mk_writer.make_writer(),
97 "uwu mycelium did a widdle fucky-wucky!\n"
98 )
99 .unwrap();
100
101 match oops.situation {
102 OopsSituation::Fault {
103 kind,
104 details: Some(deets),
105 ..
106 } => writeln!(mk_writer.make_writer(), "a {kind} occurred: {deets}\n").unwrap(),
107 OopsSituation::Fault {
108 kind,
109 details: None,
110 ..
111 } => writeln!(mk_writer.make_writer(), "a {kind} occurred!\n").unwrap(),
112 OopsSituation::Panic(panic) => {
113 let mut writer = mk_writer.make_writer();
114 write!(writer, "mycelium panicked: {}", panic.message()).unwrap();
115 if let Some(loc) = panic.location() {
116 writeln!(writer, "at {}:{}:{}", loc.file(), loc.line(), loc.column()).unwrap();
117 }
118 }
119 OopsSituation::AllocError(layout) => {
120 let mut writer = mk_writer.make_writer();
121 writeln!(
122 writer,
123 "out of memory: failed to allocate {}B aligned on a {}B boundary",
124 layout.size(),
125 layout.align()
126 )
127 .unwrap();
128 }
129 }
130
131 if let Some(registers) = oops.situation.registers() {
132 {
133 let mut writer = mk_writer.make_writer();
134 writeln!(
135 writer,
136 "%rip = {:#016x}",
137 registers.instruction_ptr.as_usize()
138 )
139 .unwrap();
140 writeln!(writer, "%rsp = {:#016x}", registers.stack_ptr.as_usize()).unwrap();
141 writeln!(writer, "%rflags = {:#016b}", registers.cpu_flags).unwrap();
142 writeln!(writer, "%cs = {:?}", registers.code_segment).unwrap();
143 writeln!(writer, "%ss = {:?}", registers.stack_segment).unwrap();
144 }
145
146 tracing::debug!(target: "oops", instruction_ptr = ?registers.instruction_ptr);
147 tracing::debug!(target: "oops", stack_ptr = ?registers.stack_ptr);
148 tracing::debug!(target: "oops", code_segment = ?registers.code_segment);
149 tracing::debug!(target: "oops", stack_segment = ?registers.stack_segment);
150 tracing::debug!(target: "oops", cpu_flags = ?fmt::bin(registers.cpu_flags));
151 }
152
153 {
155 let mut writer = mk_writer.make_writer();
156
157 let cr0 = control_regs::Cr0::read();
158 writeln!(&mut writer, "%cr0 = {:#}", cr0.display_set_bits()).unwrap();
159 let cr2 = control_regs::Cr2::read();
160 writeln!(&mut writer, "%cr2 = {cr2:?}").unwrap();
161 let (cr3_page, cr3_flags) = control_regs::cr3::read();
162 writeln!(&mut writer, "%cr3 = {cr3_page:?} ({cr3_flags:?})").unwrap();
163 let cr4 = control_regs::Cr4::read();
164 writeln!(&mut writer, "%cr4 = {:#}", cr4.display_set_bits()).unwrap();
165 }
166
167 if let Some(registers) = oops.situation.registers() {
168 if !oops.already_faulted {
171 let fault_addr = registers.instruction_ptr.as_usize();
172 disassembly(fault_addr, &mk_writer);
173 }
174 }
175
176 if oops.involves_allocator() {
178 let alloc_state = oops.alloc;
179
180 let mut writer = mk_writer.make_writer();
181 if alloc_state.allocating > 0 {
182 writeln!(
183 &mut writer,
184 "...while allocating ({} allocations in progress)!",
185 alloc_state.allocating,
186 )
187 .unwrap();
188 }
189
190 if alloc_state.deallocating > 0 {
191 writeln!(
192 &mut writer,
193 "...while deallocating ({} deallocations in progress)!",
194 alloc_state.deallocating
195 )
196 .unwrap();
197 }
198
199 writer.write_char('\n').unwrap();
200 writeln!(&mut writer, "{alloc_state}").unwrap();
201
202 crate::ALLOC.dump_free_lists();
203 }
204
205 if oops.already_panicked {
206 writeln!(
207 mk_writer.make_writer(),
208 "...while handling a panic! we really screwed up!"
209 )
210 .unwrap();
211 }
212
213 if oops.already_faulted {
214 writeln!(
215 mk_writer.make_writer(),
216 "...while handling a fault! seems real bad lol!"
217 )
218 .unwrap();
219 }
220
221 writeln!(
222 mk_writer.make_writer(),
223 "\nit will NEVER be safe to turn off your computer!"
224 )
225 .unwrap();
226
227 #[cfg(test)]
228 oops.fail_test();
229
230 #[cfg(not(test))]
231 unsafe {
232 cpu::halt()
233 }
234}
235
236static IS_PANICKING: AtomicBool = AtomicBool::new(false);
239static IS_FAULTING: AtomicBool = AtomicBool::new(false);
240
241impl<'a> Oops<'a> {
242 #[inline(always)] pub(super) fn fault(fault: Fault<'a>, kind: &'static str) -> Self {
244 let situation = OopsSituation::Fault {
245 kind,
246 fault,
247 details: None,
248 };
249 Self::mk_fault(situation)
250 }
251
252 #[inline(always)] pub(super) fn fault_with_details(
254 fault: Fault<'a>,
255 kind: &'static str,
256 details: &'a dyn fmt::Display,
257 ) -> Self {
258 let situation = OopsSituation::Fault {
259 kind,
260 fault,
261 details: Some(details),
262 };
263 Self::mk_fault(situation)
264 }
265
266 #[inline(always)]
267 fn mk_fault(situation: OopsSituation<'a>) -> Self {
268 let already_panicked = IS_PANICKING.load(Ordering::Acquire);
269 let already_faulted = IS_FAULTING.swap(true, Ordering::AcqRel);
270 Self {
271 alloc: crate::ALLOC.state(),
272 already_panicked,
273 already_faulted,
274 situation,
275 }
276 }
277
278 pub fn panic(panic: &'a PanicInfo<'a>) -> Self {
279 let already_panicked = IS_PANICKING.swap(true, Ordering::AcqRel);
280 let already_faulted = IS_FAULTING.load(Ordering::Acquire);
281 Self {
282 alloc: crate::ALLOC.state(),
283 already_panicked,
284 already_faulted,
285 situation: OopsSituation::Panic(panic),
286 }
287 }
288
289 pub fn alloc_error(layout: alloc::alloc::Layout) -> Self {
290 let already_panicked = IS_PANICKING.swap(true, Ordering::AcqRel);
291 let already_faulted = IS_FAULTING.load(Ordering::Acquire);
292 Self {
293 alloc: crate::ALLOC.state(),
294 already_panicked,
295 already_faulted,
296 situation: OopsSituation::AllocError(layout),
297 }
298 }
299
300 fn involves_allocator(&self) -> bool {
301 matches!(self.situation, OopsSituation::AllocError(_)) || self.alloc.in_allocator()
302 }
303
304 #[cfg(test)]
305 fn fail_test(&self) -> ! {
306 use super::{qemu_exit, QemuExitCode};
307 let failure = match self.situation {
308 OopsSituation::Panic(_) | OopsSituation::AllocError(_) => mycotest::Failure::Panic,
309 OopsSituation::Fault { .. } => mycotest::Failure::Fault,
310 };
311
312 if let Some(test) = mycotest::runner::current_test() {
313 if let Some(com1) = serial::com1() {
314 let _ = test.write_outcome(Err(failure), com1.lock());
316 }
317 }
318 qemu_exit(QemuExitCode::Failed)
319 }
320}
321
322impl<'a> From<&'a PanicInfo<'a>> for Oops<'a> {
323 #[inline]
324 fn from(panic: &'a PanicInfo<'a>) -> Self {
325 Self::panic(panic)
326 }
327}
328
329impl OopsSituation<'_> {
332 fn header(&self) -> &'static str {
333 match self {
334 Self::Fault { .. } => " OOPSIE-WOOPSIE! ",
335 Self::Panic(_) => " DON'T PANIC! ",
336 Self::AllocError(_) => " ALLOCATOR MACHINE BROKE! ",
337 }
338 }
339
340 fn registers(&self) -> Option<&X64Registers> {
341 match self {
342 Self::Fault { fault, .. } => Some(fault.registers()),
343 _ => None,
344 }
345 }
346}
347
348impl fmt::Debug for OopsSituation<'_> {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 match self {
351 Self::Fault {
352 kind,
353 fault,
354 details,
355 } => {
356 let mut dbg = f.debug_struct("OopsSituation::Fault");
357 dbg.field("kind", kind)
358 .field("registers", fault.registers());
359 if let Some(deets) = details {
360 dbg.field("details", &format_args!("\"{deets}\""));
361 }
362 dbg.finish()
363 }
364 Self::Panic(panic) => f.debug_tuple("OopsSituation::Panic").field(&panic).finish(),
365 Self::AllocError(layout) => f
366 .debug_tuple("OopsSituation::AllocError")
367 .field(&layout)
368 .finish(),
369 }
370 }
371}
372
373#[tracing::instrument(target = "oops", level = "trace", skip(rip, mk_writer), fields(rip = fmt::hex(rip)))]
374#[inline(always)]
375fn disassembly<'a>(rip: usize, mk_writer: &'a impl MakeWriter<'a>) {
376 use yaxpeax_arch::LengthedInstruction;
377 let _ = writeln!(mk_writer.make_writer(), "\nDisassembly:\n");
378 let mut ptr = rip as u64;
379 let decoder = yaxpeax_x86::long_mode::InstDecoder::default();
380 for i in 0..10 {
381 let bytes = unsafe { core::slice::from_raw_parts(ptr as *const u8, 16) };
384 let indent = if i == 0 { "> " } else { " " };
385 let _ = write!(mk_writer.make_writer(), "{indent}{ptr:016x}: ");
386 match decoder.decode_slice(bytes) {
387 Ok(inst) => {
388 let _ = writeln!(mk_writer.make_writer(), "{inst}");
389 tracing::debug!(target: "oops", "{ptr:016x}: {inst}");
390 ptr += inst.len();
391 }
392 Err(e) => {
393 let _ = writeln!(mk_writer.make_writer(), "{e}");
394 tracing::debug!(target: "oops", "{ptr:016x}: {e}");
395 break;
396 }
397 }
398 }
399}