feat(renaser): pánico y OOM dejan testimonio por COM1

En pantalla solo cabe la franja roja: un grito breve, sin matiz. Para
diagnosticar colapsos en máquinas distintas a la del autor —donde no
es posible reproducirlos a mano—, los manejadores de fallo escriben
ahora una pista por COM1, además de pintar la franja.

- `baliza`: sumidero `Serie` que formatea sin tocar el heap, escribe a
  0x3F8 con espera acotada (antes mudo que colgar el sistema).
- El panic-handler vuelca el mensaje y la ubicación del `panic!`.
- El alloc-error-handler vuelca el `Layout` que reventó el techo.

QEMU con `-serial stdio` enruta COM1 a la terminal de `cargo run` — la
pista llega a quien lanzó el kernel, aunque la pantalla esté en negro.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 23:51:40 +00:00
parent 8787b0566a
commit d85bb3819e
+69 -5
View File
@@ -7,12 +7,54 @@
// una franja de advertencia incluso cuando el resto del kernel ya no es fiable. // una franja de advertencia incluso cuando el resto del kernel ya no es fiable.
// ============================================================================= // =============================================================================
use core::fmt::Write;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use core::ptr; use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering}; use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
use crate::grafico::{escribir_pixel_volatil, Pantalla}; use crate::grafico::{escribir_pixel_volatil, Pantalla};
// =============================================================================
// TESTIMONIO POR EL PUERTO SERIE — para diagnosticar colapsos sin pantalla
// -----------------------------------------------------------------------------
// En pantalla solo cabe la franja roja: un grito breve, sin matiz. Pero los
// manejadores de fallo escriben TAMBIEN al puerto serie COM1 —que QEMU enruta
// a la terminal de `cargo run` con `-serial stdio`—. Asi, cuando el kernel
// colapsa fuera del Proxmox del autor, deja una pista legible de la causa.
// =============================================================================
/// Puerto de datos de COM1.
const SERIE_DATOS: u16 = 0x3F8;
/// Registro de estado de linea de COM1 — bit 5: el transmisor esta libre.
const SERIE_LSR: u16 = 0x3FD;
/// Envia un byte por COM1, con una espera acotada por si el firmware no nos lo
/// dejo configurado. Si se agota la paciencia, calla — antes mudo que cuelgue.
fn serie_escribir(byte: u8) {
for _ in 0..1_000_000 {
// SEGURIDAD: 0x3FD es el registro de estado de linea de COM1, fijo en
// la arquitectura PC; leerlo es inocuo.
let lsr = unsafe { x86_64::instructions::port::Port::<u8>::new(SERIE_LSR).read() };
if lsr & 0x20 != 0 {
// SEGURIDAD: 0x3F8 es el puerto de datos de COM1.
unsafe { x86_64::instructions::port::Port::<u8>::new(SERIE_DATOS).write(byte) };
return;
}
}
}
/// Sumidero de impresion al puerto serie — formatea sin tocar el heap.
struct Serie;
impl Write for Serie {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for &b in s.as_bytes() {
serie_escribir(b);
}
Ok(())
}
}
/// Datos del framebuffer expuestos a los manejadores de fallo. Todo son /// Datos del framebuffer expuestos a los manejadores de fallo. Todo son
/// atomicos: la baliza es intrinsecamente `Sync`, sin cerrojos. /// atomicos: la baliza es intrinsecamente `Sync`, sin cerrojos.
pub(crate) struct BalizaPanico { pub(crate) struct BalizaPanico {
@@ -102,27 +144,49 @@ pub(crate) static BALIZA_PANICO: BalizaPanico = BalizaPanico::apagada();
// MANEJADORES DE FALLO — cuando el sistema colapsa, lo DIBUJA // MANEJADORES DE FALLO — cuando el sistema colapsa, lo DIBUJA
// ============================================================================= // =============================================================================
/// Si renaser colapsa, tatuamos una franja ROJA en lo alto del framebuffer. /// Si renaser colapsa, tatuamos una franja ROJA en lo alto del framebuffer Y
/// dejamos por COM1 un testimonio del panico: su mensaje, su lugar — la pista
/// que en pantalla no cabe.
#[panic_handler] #[panic_handler]
fn al_colapsar(_info: &PanicInfo) -> ! { fn al_colapsar(info: &PanicInfo) -> ! {
x86_64::instructions::interrupts::disable(); x86_64::instructions::interrupts::disable();
BALIZA_PANICO.pintar_banda( BALIZA_PANICO.pintar_banda(
0, 0,
BALIZA_PANICO.franja(), BALIZA_PANICO.franja(),
BALIZA_PANICO.pixel_alerta.load(Ordering::Relaxed), BALIZA_PANICO.pixel_alerta.load(Ordering::Relaxed),
); );
let _ = writeln!(Serie);
let _ = writeln!(Serie, "*** renaser :: panico ***");
if let Some(lugar) = info.location() {
let _ = writeln!(
Serie,
" en {}:{}:{}",
lugar.file(),
lugar.line(),
lugar.column()
);
}
let _ = writeln!(Serie, " {}", info.message());
crate::detener() crate::detener()
} }
/// Si el heap se agota, tatuamos una franja NARANJA: un fallo distinto al /// Si el heap se agota, tatuamos una franja NARANJA y dejamos en el serie la
/// colapso, y distinguible de un vistazo. /// disposicion que reviento el techo: tamaño y alineamiento.
#[alloc_error_handler] #[alloc_error_handler]
fn al_agotar_memoria(_disposicion: core::alloc::Layout) -> ! { fn al_agotar_memoria(disposicion: core::alloc::Layout) -> ! {
x86_64::instructions::interrupts::disable(); x86_64::instructions::interrupts::disable();
BALIZA_PANICO.pintar_banda( BALIZA_PANICO.pintar_banda(
0, 0,
BALIZA_PANICO.franja(), BALIZA_PANICO.franja(),
BALIZA_PANICO.pixel_oom.load(Ordering::Relaxed), BALIZA_PANICO.pixel_oom.load(Ordering::Relaxed),
); );
let _ = writeln!(Serie);
let _ = writeln!(Serie, "*** renaser :: agotamiento de memoria ***");
let _ = writeln!(
Serie,
" layout: tamaño={} alineamiento={}",
disposicion.size(),
disposicion.align()
);
crate::detener() crate::detener()
} }