From 264a9616efcdcce9c73d365979ea445e3b3f79db Mon Sep 17 00:00:00 2001
From: Seiya Nuta <nuta@seiya.me>
Date: Sun, 19 Dec 2021 15:50:32 +0900
Subject: [PATCH] Support saving kernel crash logs using boot2dump (#139)

---
 Cargo.lock           |  7 +++++++
 kernel/Cargo.toml    |  1 +
 kernel/lang_items.rs | 46 +++++++++++++++++++++++++++++++++++++++++++-
 kernel/logger.rs     | 15 +++++++++++----
 4 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 94e226e2..115b4793 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -61,6 +61,12 @@ dependencies = [
  "wyz",
 ]
 
+[[package]]
+name = "boot2dump"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225c5460e6cda737adafc046c6106ca1769963bce43261b8a38a18dfcc2efa50"
+
 [[package]]
 name = "buddy_system_allocator"
 version = "0.8.0"
@@ -180,6 +186,7 @@ dependencies = [
  "arrayvec 0.7.2",
  "atomic_refcell",
  "bitflags",
+ "boot2dump",
  "crossbeam",
  "goblin",
  "hashbrown",
diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml
index 121bc9d1..e6682f49 100644
--- a/kernel/Cargo.toml
+++ b/kernel/Cargo.toml
@@ -13,6 +13,7 @@ log = "0.4"
 spin = "0.9.2"
 goblin = { version = "0.4", default-features = false, features = ["elf64"] }
 smoltcp = { version = "0.7.5", default-features = false, features = ["alloc", "proto-ipv4", "socket", "socket-raw", "socket-udp", "socket-tcp", "proto-dhcpv4", "ethernet"] }
+boot2dump = { version = "0" }
 
 # Data structues.
 bitflags = "1.3.2"
diff --git a/kernel/lang_items.rs b/kernel/lang_items.rs
index a5d91dab..fae6da4a 100644
--- a/kernel/lang_items.rs
+++ b/kernel/lang_items.rs
@@ -1,6 +1,27 @@
 use core::sync::atomic::AtomicBool;
 
 pub static PANICKED: AtomicBool = AtomicBool::new(false);
+static mut KERNEL_DUMP_BUF: KernelDump = KernelDump::empty();
+
+#[repr(C, packed)]
+struct KernelDump {
+    /// `0xdeadbeee`
+    magic: u32,
+    /// The length of the kernel log.
+    len: u32,
+    /// The kernel log (including the panic message).
+    log: [u8; 4096],
+}
+
+impl KernelDump {
+    const fn empty() -> KernelDump {
+        KernelDump {
+            magic: 0,
+            len: 0,
+            log: [0; 4096],
+        }
+    }
+}
 
 #[alloc_error_handler]
 fn alloc_error_handler(layout: core::alloc::Layout) -> ! {
@@ -11,6 +32,7 @@ fn alloc_error_handler(layout: core::alloc::Layout) -> ! {
 #[panic_handler]
 #[cfg(not(test))]
 fn panic(info: &core::panic::PanicInfo) -> ! {
+    use crate::logger::KERNEL_LOG_BUF;
     use core::sync::atomic::Ordering;
 
     if PANICKED.load(Ordering::SeqCst) {
@@ -21,5 +43,27 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
     PANICKED.store(true, Ordering::SeqCst);
     error!("{}", info);
     kerla_runtime::backtrace::backtrace();
-    kerla_runtime::arch::halt();
+
+    unsafe {
+        warn!("preparing a crash dump...");
+        KERNEL_LOG_BUF.force_unlock();
+        let mut off = 0;
+        let mut log_buffer = KERNEL_LOG_BUF.lock();
+        while let Some(slice) = log_buffer.pop_slice(KERNEL_DUMP_BUF.log.len().saturating_sub(off))
+        {
+            KERNEL_DUMP_BUF.log[off..(off + slice.len())].copy_from_slice(slice);
+            off += slice.len();
+        }
+
+        KERNEL_DUMP_BUF.magic = 0xdeadbeee;
+        KERNEL_DUMP_BUF.len = off as u32;
+
+        warn!("prepared crash dump: log_len={}", off);
+        warn!("booting boot2dump...");
+        let dump_as_bytes = core::slice::from_raw_parts(
+            &KERNEL_DUMP_BUF as *const _ as *const u8,
+            core::mem::size_of::<KernelDump>(),
+        );
+        boot2dump::save_to_file_and_reboot("kerla.dump", dump_as_bytes);
+    }
 }
diff --git a/kernel/logger.rs b/kernel/logger.rs
index ef55af48..379a59b9 100644
--- a/kernel/logger.rs
+++ b/kernel/logger.rs
@@ -25,11 +25,18 @@ impl Printer for LoggedPrinter {
     fn print_bytes(&self, s: &[u8]) {
         self.inner.print_bytes(s);
 
-        // Don't write into the kernel log buffer as it may call a printk function
-        // due to an assertion.
-        if !PANICKED.load(Ordering::SeqCst) {
-            KERNEL_LOG_BUF.lock().push_slice(s);
+        if PANICKED.load(Ordering::SeqCst) {
+            // If kernel panics, it's uncertain whether KERNEL_LOG_BUF is in
+            // a dead lock.
+            //
+            // Since only single CPU can continue handling a panic, we can
+            // ensure it's safe to unlock it.
+            unsafe {
+                KERNEL_LOG_BUF.force_unlock();
+            }
         }
+
+        KERNEL_LOG_BUF.lock().push_slice(s);
     }
 }