Kernel: Show QR code with panic logs on kernel panic
This makes debugging on real hardware easier!
This commit is contained in:
parent
f519cb2cc0
commit
5f61581e1d
|
|
@ -165,6 +165,12 @@ set(KLIBC_SOURCES
|
||||||
../userspace/libraries/LibC/arch/${BANAN_ARCH}/string.S
|
../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
|
set(LIBFONT_SOURCES
|
||||||
../userspace/libraries/LibFont/Font.cpp
|
../userspace/libraries/LibFont/Font.cpp
|
||||||
../userspace/libraries/LibFont/PSF.cpp
|
../userspace/libraries/LibFont/PSF.cpp
|
||||||
|
|
@ -175,18 +181,25 @@ set(LIBINPUT_SOURCE
|
||||||
../userspace/libraries/LibInput/KeyEvent.cpp
|
../userspace/libraries/LibInput/KeyEvent.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(LIBQR_SOURCE
|
||||||
|
../userspace/libraries/LibQR/QRCode.cpp
|
||||||
|
)
|
||||||
|
|
||||||
set(KERNEL_SOURCES
|
set(KERNEL_SOURCES
|
||||||
${KERNEL_SOURCES}
|
${KERNEL_SOURCES}
|
||||||
${BAN_SOURCES}
|
${BAN_SOURCES}
|
||||||
${KLIBC_SOURCES}
|
${KLIBC_SOURCES}
|
||||||
|
${LIBDEFLATE_SOURCE}
|
||||||
${LIBFONT_SOURCES}
|
${LIBFONT_SOURCES}
|
||||||
${LIBINPUT_SOURCE}
|
${LIBINPUT_SOURCE}
|
||||||
|
${LIBQR_SOURCE}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(kernel ${KERNEL_SOURCES})
|
add_executable(kernel ${KERNEL_SOURCES})
|
||||||
|
|
||||||
target_compile_definitions(kernel PRIVATE __is_kernel)
|
target_compile_definitions(kernel PRIVATE __is_kernel)
|
||||||
target_compile_definitions(kernel PRIVATE __arch=${BANAN_ARCH})
|
target_compile_definitions(kernel PRIVATE __arch=${BANAN_ARCH})
|
||||||
|
target_compile_definitions(kernel PRIVATE LIBDEFLATE_AVOID_STACK=1)
|
||||||
|
|
||||||
target_compile_options(kernel PRIVATE
|
target_compile_options(kernel PRIVATE
|
||||||
-O2 -g
|
-O2 -g
|
||||||
|
|
@ -240,9 +253,11 @@ add_custom_command(
|
||||||
|
|
||||||
banan_include_headers(kernel ban)
|
banan_include_headers(kernel ban)
|
||||||
banan_include_headers(kernel libc)
|
banan_include_headers(kernel libc)
|
||||||
banan_include_headers(kernel libfont)
|
banan_include_headers(kernel libdeflate)
|
||||||
banan_include_headers(kernel libelf)
|
banan_include_headers(kernel libelf)
|
||||||
|
banan_include_headers(kernel libfont)
|
||||||
banan_include_headers(kernel libinput)
|
banan_include_headers(kernel libinput)
|
||||||
|
banan_include_headers(kernel libqr)
|
||||||
|
|
||||||
banan_install_headers(kernel)
|
banan_install_headers(kernel)
|
||||||
set_target_properties(kernel PROPERTIES OUTPUT_NAME banan-os.kernel)
|
set_target_properties(kernel PROPERTIES OUTPUT_NAME banan-os.kernel)
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,8 @@
|
||||||
namespace Debug
|
namespace Debug
|
||||||
{
|
{
|
||||||
void dump_stack_trace();
|
void dump_stack_trace();
|
||||||
|
void dump_qr_code();
|
||||||
|
|
||||||
void putchar(char);
|
void putchar(char);
|
||||||
void print_prefix(const char*, int);
|
void print_prefix(const char*, int);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,26 @@ namespace Kernel
|
||||||
asm volatile("cli");
|
asm volatile("cli");
|
||||||
|
|
||||||
const bool had_debug_lock = Debug::s_debug_lock.current_processor_has_lock();
|
const bool had_debug_lock = Debug::s_debug_lock.current_processor_has_lock();
|
||||||
derrorln("Kernel panic at {}", location);
|
|
||||||
if (had_debug_lock)
|
bool first_panic = false;
|
||||||
derrorln(" while having debug lock...");
|
|
||||||
derrorln(message, BAN::forward<Args>(args)...);
|
|
||||||
if (!g_paniced)
|
|
||||||
{
|
{
|
||||||
g_paniced = true;
|
SpinLockGuard _(Debug::s_debug_lock);
|
||||||
Debug::dump_stack_trace();
|
derrorln("Kernel panic at {}", location);
|
||||||
|
if (had_debug_lock)
|
||||||
|
derrorln(" while having debug lock...");
|
||||||
|
derrorln(message, BAN::forward<Args>(args)...);
|
||||||
|
if (!g_paniced)
|
||||||
|
{
|
||||||
|
Debug::dump_stack_trace();
|
||||||
|
g_paniced = true;
|
||||||
|
first_panic = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (first_panic)
|
||||||
|
Debug::dump_qr_code();
|
||||||
|
|
||||||
asm volatile("ud2");
|
asm volatile("ud2");
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include <kernel/Debug.h>
|
#include <kernel/Debug.h>
|
||||||
|
#include <kernel/Device/FramebufferDevice.h>
|
||||||
#include <kernel/InterruptController.h>
|
#include <kernel/InterruptController.h>
|
||||||
#include <kernel/Lock/SpinLock.h>
|
#include <kernel/Lock/SpinLock.h>
|
||||||
#include <kernel/Memory/PageTable.h>
|
#include <kernel/Memory/PageTable.h>
|
||||||
|
|
@ -6,6 +7,9 @@
|
||||||
#include <kernel/Terminal/TTY.h>
|
#include <kernel/Terminal/TTY.h>
|
||||||
#include <kernel/Timer/Timer.h>
|
#include <kernel/Timer/Timer.h>
|
||||||
|
|
||||||
|
#include <LibDEFLATE/Compressor.h>
|
||||||
|
#include <LibQR/QRCode.h>
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
bool g_disable_debug = false;
|
bool g_disable_debug = false;
|
||||||
|
|
@ -15,6 +19,15 @@ namespace Debug
|
||||||
|
|
||||||
Kernel::RecursiveSpinLock s_debug_lock;
|
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()
|
void dump_stack_trace()
|
||||||
{
|
{
|
||||||
using namespace Kernel;
|
using namespace Kernel;
|
||||||
|
|
@ -72,17 +85,203 @@ namespace Debug
|
||||||
BAN::Formatter::print(Debug::putchar, "\e[m");
|
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<BAN::Vector<uint8_t>> compress_kernel_logs()
|
||||||
|
{
|
||||||
|
constexpr size_t max_size = ((s_qr_code_max_capacity + 3) / 4 * 3) - sizeof(s_panic_url_prefix);
|
||||||
|
|
||||||
|
BAN::Vector<uint8_t> 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<const uint8_t*>(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)
|
void putchar(char ch)
|
||||||
{
|
{
|
||||||
using namespace Kernel;
|
using namespace Kernel;
|
||||||
|
|
||||||
|
if (!g_paniced)
|
||||||
|
queue_debug_buffer(ch);
|
||||||
|
|
||||||
if (g_disable_debug)
|
if (g_disable_debug)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Kernel::Serial::has_devices())
|
if (Kernel::Serial::has_devices())
|
||||||
return Kernel::Serial::putchar_any(ch);
|
return Kernel::Serial::putchar_any(ch);
|
||||||
if (Kernel::TTY::is_initialized())
|
if (Kernel::TTY::is_initialized())
|
||||||
|
{
|
||||||
|
if (s_qr_code_shown)
|
||||||
|
return;
|
||||||
return Kernel::TTY::putchar_current(ch);
|
return Kernel::TTY::putchar_current(ch);
|
||||||
|
}
|
||||||
|
|
||||||
if (g_terminal_driver)
|
if (g_terminal_driver)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue