keos/lang/
panicking.rs

1//! KEOS panic handler.
2use crate::thread::STACK_SIZE;
3use abyss::{
4    dev::x86_64::apic::{IPIDest, Mode, send_ipi},
5    interrupt::{InterruptGuard, NMI_EXPECTED_PANICKING},
6    unwind::{DwarfReader, Peeker, StackFrame, UnwindBacktrace},
7    x86_64::{intrinsics::cpuid, kernel_gs, pio::Pio},
8};
9use addr2line::{Context, Frame};
10use alloc::{borrow::Cow, sync::Arc};
11use core::mem::ManuallyDrop;
12use core::sync::atomic::Ordering;
13
14#[derive(Clone)]
15struct EhFrameReader;
16
17impl EhFrameReader {
18    fn start() -> usize {
19        unsafe extern "C" {
20            static __eh_frame_hdr_start: u8;
21        }
22        unsafe { &__eh_frame_hdr_start as *const _ as usize }
23    }
24
25    fn end() -> usize {
26        unsafe extern "C" {
27            static __eh_frame_end: u8;
28        }
29        unsafe { &__eh_frame_end as *const _ as usize }
30    }
31}
32
33impl Peeker for EhFrameReader {
34    fn read<T>(&self, ofs: usize) -> Option<T>
35    where
36        T: Copy,
37    {
38        let (start, end) = (Self::start(), Self::end());
39        if ofs >= start && ofs + core::mem::size_of::<T>() < end {
40            unsafe { (ofs as *const T).as_ref().cloned() }
41        } else {
42            None
43        }
44    }
45}
46
47struct BackTracePrinter<'a>(
48    Frame<'a, gimli::EndianArcSlice<gimli::LittleEndian>>,
49    Option<u64>,
50);
51
52impl core::fmt::Display for BackTracePrinter<'_> {
53    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        if let Some(pc) = self.1 {
55            if let Some(Ok(name)) = self.0.function.as_ref().map(|n| n.demangle()) {
56                writeln!(formatter, "{name} [0x{pc:016x}]")?;
57            } else {
58                writeln!(formatter, "0x{pc:016x}")?;
59            }
60        }
61        if let Some(file) = self.0.location.as_ref().and_then(|n| n.file) {
62            write!(formatter, "                         at {file}:")?;
63        } else {
64            write!(formatter, "                         at ?:")?;
65        }
66        if let Some(line) = self.0.location.as_ref().and_then(|n| n.line) {
67            write!(formatter, "{line}:")?;
68        } else {
69            write!(formatter, "?:")?;
70        }
71        if let Some(col) = self.0.location.as_ref().and_then(|n| n.column) {
72            write!(formatter, "{col}")
73        } else {
74            write!(formatter, "?")
75        }
76    }
77}
78
79fn do_backtrace(state: &mut (isize, bool), frame: &StackFrame) {
80    let (depth, has_non_kernel_addr) = state;
81    let pc = frame.pc() as u64;
82    if pc >> 48 != 0xffff {
83        *has_non_kernel_addr = true;
84    }
85    *depth += 1;
86    if *depth == -1 {
87        return; /* skip `abyss::unwind::x86_64::StackFrame::current` */
88    }
89    if let Some(ctxt) = unsafe { DEBUG_CONTEXT.as_ref() }
90        && let Ok(mut frames) = ctxt.find_frames(pc)
91        && let Ok(Some(frame)) = frames.next()
92    {
93        println!(
94            "  {:2}: {}",
95            depth,
96            BackTracePrinter(
97                frame,
98                Some(
99                    pc + 1, /* For a normal call frame we need to back up so we point within the
100                            call itself; this is important because a) the call might be the
101                            very last instruction of the function and the edge of the FDE,
102                            and b) so that run_cfi_program() runs locations up to the call
103                            but not more. */
104                )
105            ),
106        );
107        while let Ok(Some(frame)) = frames.next() {
108            println!("{}", BackTracePrinter(frame, None));
109        }
110        return;
111    }
112    println!("  {:2}: 0x{:016x}  - ?", depth, pc);
113}
114
115static mut DEBUG_CONTEXT: Option<Context<gimli::EndianArcSlice<gimli::LittleEndian>>> = None;
116
117#[allow(dead_code)]
118#[allow(unreachable_code)]
119#[allow(clippy::empty_loop)]
120#[inline(never)]
121#[panic_handler]
122fn panic(info: &core::panic::PanicInfo) -> ! {
123    // Disabling preempt before we go on.
124    let _cli = ManuallyDrop::new(InterruptGuard::new());
125
126    match crate::PANIC_DEPTH.fetch_add(1, Ordering::SeqCst) {
127        0 => {}
128        1 => {
129            unsafe {
130                abyss::kprint::force_unlock_serial();
131            }
132            println!("*** PANIC recursed: Forcing the shutdown ***");
133            println!("{}", info);
134            unsafe {
135                abyss::x86_64::power_control::power_off();
136            }
137        }
138        _ => unsafe {
139            abyss::x86_64::power_control::power_off();
140        },
141    }
142    // Asserting expected NMI for every CPU except the faulting CPU.
143    //
144    // # Safety
145    // NEVER use print!() or println!() before force serial unlock
146    NMI_EXPECTED_PANICKING.store(true, Ordering::SeqCst);
147    for (cpuid, is_online) in abyss::boot::ONLINE_CPU
148        .iter()
149        .enumerate()
150        .filter(|(id, o)| o.load(Ordering::SeqCst) && *id != cpuid())
151    {
152        unsafe {
153            send_ipi(IPIDest::Cpu(cpuid), Mode::Nmi);
154        }
155        is_online.store(false, Ordering::SeqCst);
156    }
157    // Forcefully remove kprint serial lock to prevent deadlock.
158    unsafe {
159        abyss::kprint::force_unlock_serial();
160    }
161    fn panic_internal_poweroff(has_non_kernel_addr: bool) -> ! {
162        if has_non_kernel_addr {
163            println!("\nWARNING: Non-kernel address detected in the backtrace.\n");
164            println!(
165                "It indicates that some of addresses above are either user program's or even completely wrong."
166            );
167            println!(
168                "For details, please refer to `Debugging a User Process` chapter of the KeOS documentation."
169            );
170        }
171        println!("\nShutting down the system in few moment...");
172        for _ in 0..50 {
173            fn init_pit(val: u16) {
174                // Calibrate through the PIT
175                // Set the Gate high, disable speaker
176                let chan2_gate = Pio::new(0x61);
177                chan2_gate.write_u8((chan2_gate.read_u8() & !0x2) | 1);
178                // Counter 2, mode 0 (one-shot), binary count
179                Pio::new(0x43).write_u8(0xb0);
180                Pio::new(0x42).write_u8(val as u8); // low byte
181                Pio::new(0x42).write_u8((val >> 8) as u8); // high byte
182            }
183            init_pit(0xFFFF);
184
185            loop {
186                fn read_pit() -> u16 {
187                    let low = Pio::new(0x42).read_u8() as u16;
188                    let high = Pio::new(0x42).read_u8() as u16;
189                    low | high << 8
190                }
191                if read_pit() == 0 {
192                    break;
193                }
194            }
195        }
196        unsafe {
197            abyss::x86_64::power_control::power_off();
198        }
199    }
200
201    let frame = StackFrame::current();
202
203    if unsafe { ((frame.sp() & !(STACK_SIZE - 1)) as *mut crate::thread::ThreadStack).as_mut() }
204        .map(|stack| stack.magic == crate::thread::THREAD_MAGIC)
205        .unwrap_or(false)
206    {
207        crate::thread::with_current(|th| {
208            println!(
209                "\n\nKeOS thread '{}' [core #{}, tid #{}] {}\n",
210                th.name,
211                abyss::x86_64::intrinsics::cpuid(),
212                th.tid,
213                info
214            );
215        });
216    } else {
217        println!(
218            "\n\nKeOS thread '<unknown>' [core #{}, tid #?] {}\n",
219            abyss::x86_64::intrinsics::cpuid(),
220            info
221        );
222    }
223
224    println!("Stack Backtrace: ");
225    let mut state = (-2, false);
226    let sp_hi = frame.sp() & !(STACK_SIZE - 1);
227    if let Err(e) = unsafe {
228        UnwindBacktrace::new(
229            frame,
230            sp_hi..sp_hi + STACK_SIZE,
231            DwarfReader::from_peeker(EhFrameReader::start(), EhFrameReader),
232        )
233        .run(&mut state, |state, this, _| {
234            do_backtrace(state, &this.frame)
235        })
236    } {
237        println!("** Backtrace Failed: {:?}", e);
238    }
239    if let Some(trap_frame) = unsafe { kernel_gs::current().interrupt_frame.as_ref() } {
240        println!("Triggered by event at: ");
241        let frame = trap_frame.to_stack_frame();
242        let sp_hi = frame.sp() & !(STACK_SIZE - 1);
243        if let Err(e) = unsafe {
244            UnwindBacktrace::new(
245                frame,
246                sp_hi..sp_hi + STACK_SIZE,
247                DwarfReader::from_peeker(EhFrameReader::start(), EhFrameReader),
248            )
249            .run(&mut state, |depth, this, _| {
250                do_backtrace(depth, &this.frame)
251            })
252        } {
253            println!("** Backtrace Failed: {:?}", e);
254        }
255    }
256    panic_internal_poweroff(state.1)
257}
258
259/// Load debugging symbols from kernel image
260#[allow(clippy::result_unit_err)]
261pub(crate) fn load_debug_infos() -> bool {
262    use object::{Object, ObjectSection};
263    let Some(kernel_disk) = abyss::dev::get_bdev(0) else {
264        return false;
265    };
266    let image_size = kernel_disk.block_cnt() * kernel_disk.block_size();
267    let mut kernel_image = alloc::vec![0u8; image_size].into_boxed_slice();
268    if kernel_disk
269        .read_bios(&mut Some((0, kernel_image.as_mut())).into_iter())
270        .is_err()
271    {
272        return false;
273    }
274    let Ok(kernel) = object::File::parse(kernel_image.as_ref()) else {
275        return false;
276    };
277    let Ok(dwarf): Result<_, ()> = gimli::Dwarf::load(|id| {
278        let data = kernel
279            .section_by_name(id.name())
280            .and_then(|section| section.uncompressed_data().ok())
281            .unwrap_or(Cow::Borrowed(&[]));
282        let data: Arc<[u8]> = Arc::from(data.as_ref());
283        Ok(gimli::EndianArcSlice::new(data, gimli::LittleEndian))
284    }) else {
285        return false;
286    };
287    unsafe {
288        DEBUG_CONTEXT = Context::from_dwarf(dwarf).ok();
289        DEBUG_CONTEXT.is_some()
290    }
291}