diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index b32f5d1e..ee4687cf 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -165,6 +165,12 @@ set(KLIBC_SOURCES ../userspace/libraries/LibC/arch/${BANAN_ARCH}/string.S ) +set(LIBDEFLATE_SOURCE + ../userspace/libraries/LibDEFLATE/Compressor.cpp + ../userspace/libraries/LibDEFLATE/Decompressor.cpp + ../userspace/libraries/LibDEFLATE/HuffmanTree.cpp +) + set(LIBFONT_SOURCES ../userspace/libraries/LibFont/Font.cpp ../userspace/libraries/LibFont/PSF.cpp @@ -175,18 +181,25 @@ set(LIBINPUT_SOURCE ../userspace/libraries/LibInput/KeyEvent.cpp ) +set(LIBQR_SOURCE + ../userspace/libraries/LibQR/QRCode.cpp +) + set(KERNEL_SOURCES ${KERNEL_SOURCES} ${BAN_SOURCES} ${KLIBC_SOURCES} + ${LIBDEFLATE_SOURCE} ${LIBFONT_SOURCES} ${LIBINPUT_SOURCE} + ${LIBQR_SOURCE} ) add_executable(kernel ${KERNEL_SOURCES}) target_compile_definitions(kernel PRIVATE __is_kernel) target_compile_definitions(kernel PRIVATE __arch=${BANAN_ARCH}) +target_compile_definitions(kernel PRIVATE LIBDEFLATE_AVOID_STACK=1) target_compile_options(kernel PRIVATE -O2 -g @@ -240,9 +253,11 @@ add_custom_command( banan_include_headers(kernel ban) banan_include_headers(kernel libc) -banan_include_headers(kernel libfont) +banan_include_headers(kernel libdeflate) banan_include_headers(kernel libelf) +banan_include_headers(kernel libfont) banan_include_headers(kernel libinput) +banan_include_headers(kernel libqr) banan_install_headers(kernel) set_target_properties(kernel PROPERTIES OUTPUT_NAME banan-os.kernel) diff --git a/kernel/include/kernel/Debug.h b/kernel/include/kernel/Debug.h index d36e9b3b..b872980a 100644 --- a/kernel/include/kernel/Debug.h +++ b/kernel/include/kernel/Debug.h @@ -74,6 +74,8 @@ namespace Debug { void dump_stack_trace(); + void dump_qr_code(); + void putchar(char); void print_prefix(const char*, int); diff --git a/kernel/include/kernel/Panic.h b/kernel/include/kernel/Panic.h index 45a05d49..e02de6f2 100644 --- a/kernel/include/kernel/Panic.h +++ b/kernel/include/kernel/Panic.h @@ -19,15 +19,26 @@ namespace Kernel asm volatile("cli"); const bool had_debug_lock = Debug::s_debug_lock.current_processor_has_lock(); - derrorln("Kernel panic at {}", location); - if (had_debug_lock) - derrorln(" while having debug lock..."); - derrorln(message, BAN::forward(args)...); - if (!g_paniced) + + bool first_panic = false; + { - g_paniced = true; - Debug::dump_stack_trace(); + SpinLockGuard _(Debug::s_debug_lock); + derrorln("Kernel panic at {}", location); + if (had_debug_lock) + derrorln(" while having debug lock..."); + derrorln(message, BAN::forward(args)...); + if (!g_paniced) + { + Debug::dump_stack_trace(); + g_paniced = true; + first_panic = true; + } } + + if (first_panic) + Debug::dump_qr_code(); + asm volatile("ud2"); __builtin_unreachable(); } diff --git a/kernel/kernel/Debug.cpp b/kernel/kernel/Debug.cpp index c9034df2..f2bc233b 100644 --- a/kernel/kernel/Debug.cpp +++ b/kernel/kernel/Debug.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,6 +7,9 @@ #include #include +#include +#include + #include bool g_disable_debug = false; @@ -15,6 +19,15 @@ namespace Debug Kernel::RecursiveSpinLock s_debug_lock; + static constexpr char s_panic_url_prefix[] = "https://bananymous.com/panic#"; + static constexpr size_t s_qr_code_max_capacity { 2953 }; + static bool s_qr_code_shown { false }; + + static char s_debug_buffer[16 * 1024] {}; + static size_t s_debug_buffer_tail { 0 }; + static size_t s_debug_buffer_size { 0 }; + static uint8_t s_debug_ansi_state { 0 }; + void dump_stack_trace() { using namespace Kernel; @@ -72,17 +85,203 @@ namespace Debug BAN::Formatter::print(Debug::putchar, "\e[m"); } + static void queue_debug_buffer(char ch) + { + switch (s_debug_ansi_state) + { + case 1: + if (ch == '[') + { + s_debug_ansi_state = 2; + break; + } + s_debug_ansi_state = 0; + [[fallthrough]]; + case 0: + if (ch == '\e') + { + s_debug_ansi_state = 1; + break; + } + if (!isprint(ch) && ch != '\n') + break; + s_debug_buffer[(s_debug_buffer_tail + s_debug_buffer_size) % sizeof(s_debug_buffer)] = ch; + if (s_debug_buffer_size < sizeof(s_debug_buffer)) + s_debug_buffer_size++; + else + s_debug_buffer_tail = (s_debug_buffer_tail + 1) % sizeof(s_debug_buffer); + break; + case 2: + if (isalpha(ch)) + s_debug_ansi_state = 0; + break; + } + } + + static void reverse(char* first, char* last) + { + const size_t len = last - first; + for (size_t i = 0; i < len / 2; i++) + BAN::swap(first[i], first[len - i - 1]); + } + + static void rotate(char* first, char* middle, char* last) + { + reverse(first, middle); + reverse(middle, last); + reverse(first, last); + } + + static BAN::ErrorOr> compress_kernel_logs() + { + constexpr size_t max_size = ((s_qr_code_max_capacity + 3) / 4 * 3) - sizeof(s_panic_url_prefix); + + BAN::Vector result; + + size_t l = 0, r = s_debug_buffer_size; + while (l + 50 < r) + { + const size_t middle = (l + r) / 2; + const uint8_t* base = reinterpret_cast(s_debug_buffer) + s_debug_buffer_size - middle; + + auto compressed = TRY(LibDEFLATE::Compressor({ base, middle }, LibDEFLATE::StreamType::Zlib).compress()); + if (compressed.size() > max_size) + r = middle; + else + { + l = middle; + result = BAN::move(compressed); + } + } + + return result; + } + + void dump_qr_code() + { + ASSERT(Kernel::g_paniced); + + auto boot_framebuffer = Kernel::FramebufferDevice::boot_framebuffer(); + if (!boot_framebuffer) + { + derrorln("No boot framebuffer, not generating QR code"); + return; + } + if (boot_framebuffer->width() < 177 + 8 || boot_framebuffer->height() < 177 + 8) + { + derrorln("Boot framebuffer is too small for a qr code"); + return; + } + + // rotate logs to start from index 0 and be contiguous + rotate(s_debug_buffer, s_debug_buffer + s_debug_buffer_tail, s_debug_buffer + sizeof(s_debug_buffer)); + + auto compressed_or_error = compress_kernel_logs(); + if (compressed_or_error.is_error()) + { + // TODO: send uncompressed logs? + derrorln("Failed to compress kernel logs: {}", compressed_or_error.error()); + return; + } + + auto compressed = compressed_or_error.release_value(); + + static uint8_t qr_code_data[s_qr_code_max_capacity]; + size_t qr_code_data_len = 0; + + for (size_t i = 0; s_panic_url_prefix[i]; i++) + qr_code_data[qr_code_data_len++] = s_panic_url_prefix[i]; + + constexpr char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + for (size_t i = 0; i < compressed.size() / 3; i++) + { + const uint32_t bits = + ((compressed[3 * i + 0]) << 16) | + ((compressed[3 * i + 1]) << 8) | + ((compressed[3 * i + 2]) << 0); + + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 18) & 0x3F]; + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 12) & 0x3F]; + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 6) & 0x3F]; + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 0) & 0x3F]; + } + + switch (compressed.size() % 3) + { + case 0: + break; + case 1: + { + const uint16_t bits = + (compressed[compressed.size() - 1] << 4); + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 6) & 0x3F]; + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 0) & 0x3F]; + break; + } + case 2: + { + const uint32_t bits = + (compressed[compressed.size() - 2] << 10) | + (compressed[compressed.size() - 1] << 2); + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 12) & 0x3F]; + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 6) & 0x3F]; + qr_code_data[qr_code_data_len++] = alphabet[(bits >> 0) & 0x3F]; + break; + } + } + + auto qr_or_error = LibQR::QRCode::generate({ qr_code_data, qr_code_data_len }); + if (qr_or_error.is_error()) + { + derrorln("Failed to generate QR code"); + return; + } + + auto qr_code = qr_or_error.release_value(); + + // after this point no more logs are printed to framebuffer + s_qr_code_shown = true; + + const size_t min_framebuffer_dimension = BAN::Math::min(boot_framebuffer->width(), boot_framebuffer->height()); + const size_t module_size = min_framebuffer_dimension / (qr_code.size() + 8); + + for (size_t y = 0; y < (qr_code.size() + 8) * module_size; y++) + for (size_t x = 0; x < (qr_code.size() + 8) * module_size; x++) + boot_framebuffer->set_pixel(x, y, 0xFFFFFF); + + for (size_t y = 0; y < qr_code.size(); y++) + { + for (size_t x = 0; x < qr_code.size(); x++) + { + if (!qr_code.get(x, y)) + continue; + for (size_t i = 0; i < module_size; i++) + for (size_t j = 0; j < module_size; j++) + boot_framebuffer->set_pixel((x + 4) * module_size + j, (y + 4) * module_size + i, 0x000000); + } + } + + boot_framebuffer->sync_pixels_rectangle(0, 0, (qr_code.size() + 8) * module_size, (qr_code.size() + 8) * module_size); + } + void putchar(char ch) { using namespace Kernel; + if (!g_paniced) + queue_debug_buffer(ch); + if (g_disable_debug) return; if (Kernel::Serial::has_devices()) return Kernel::Serial::putchar_any(ch); if (Kernel::TTY::is_initialized()) + { + if (s_qr_code_shown) + return; return Kernel::TTY::putchar_current(ch); + } if (g_terminal_driver) {