mirror of
https://github.com/justinian/jsix.git
synced 2025-12-09 16:04:32 -08:00
[panic] Have panics stop all cores
Kernel panics previously only stopped the calling core. This commit re-implements the panic system to allow us to stop all cores on a panic. Changes include: - panic now sends an NMI to all cores. This means we can't control the contents of their registers, so panic information has been moved to a global struct, and the panicking cpu sets the pointer to that data in its cpu_data. - the panic_handler is now set up with mutexes to print appropriately and only initialize objects once. - copying _current_gsbase into the panic handler, and #including the cpprt.cpp file (so that we can define NDEBUG and not have it try to link the assert code back in) - making the symbol data pointer in kargs an actual pointer again, not an address - and carrying that through to the panic handler - the number of cpus is now saved globally in the kernel as g_num_cpus
This commit is contained in:
@@ -114,7 +114,7 @@ load_resources(bootproto::args *args, video::screen *screen, uefi::handle image,
|
||||
continue;
|
||||
|
||||
util::buffer symbol_table = loader::load_file(disk, d);
|
||||
args->symbol_table = reinterpret_cast<uintptr_t>(symbol_table.pointer);
|
||||
args->symbol_table = symbol_table.pointer;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,11 +139,11 @@ init_ap_trampoline:
|
||||
ret
|
||||
|
||||
extern long_ap_startup
|
||||
global ap_idle
|
||||
global ap_idle:function (ap_idle.end - ap_idle)
|
||||
ap_idle:
|
||||
call long_ap_startup
|
||||
sti
|
||||
.hang:
|
||||
hlt
|
||||
jmp .hang
|
||||
|
||||
.end:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace panic {
|
||||
|
||||
uint32_t *apic_icr = reinterpret_cast<uint32_t*>(0xffffc000fee00300);
|
||||
uintptr_t symbol_table = 0;
|
||||
void const *symbol_table = nullptr;
|
||||
|
||||
} // namespace panic
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cpu.h"
|
||||
|
||||
namespace panic {
|
||||
|
||||
constexpr uint32_t send_nmi_command =
|
||||
(4 << 8) | // Delivery mode NMI
|
||||
(1 << 14) | // assert level high
|
||||
(1 << 18); // destination self
|
||||
(2 << 18); // destination all
|
||||
|
||||
extern uint32_t *apic_icr;
|
||||
extern uintptr_t symbol_table;
|
||||
extern void const *symbol_table;
|
||||
|
||||
__attribute__ ((always_inline))
|
||||
inline void panic(
|
||||
@@ -19,19 +20,23 @@ inline void panic(
|
||||
const char *file = __builtin_FILE(),
|
||||
uint64_t line = __builtin_LINE())
|
||||
{
|
||||
register uintptr_t syms asm("rdi");
|
||||
register const char *m asm("rsi");
|
||||
register const char *fn asm("rdx");
|
||||
register const char *fi asm("rcx");
|
||||
register uint64_t l asm("r8");
|
||||
cpu_data &cpu = current_cpu();
|
||||
|
||||
asm volatile ("mov %1, %0" : "=r"(syms) : "r"(symbol_table));
|
||||
asm volatile ("mov %1, %0" : "=r"(m) : "r"(message));
|
||||
asm volatile ("mov %1, %0" : "=r"(fn) : "r"(function));
|
||||
asm volatile ("mov %1, %0" : "=r"(fi) : "r"(file));
|
||||
asm volatile ("mov %1, %0" : "=r"(l) : "r"(line));
|
||||
// Grab the global panic block for ourselves
|
||||
cpu.panic = __atomic_exchange_n(&g_panic_data_p, nullptr, __ATOMIC_ACQ_REL);
|
||||
|
||||
// If we aren't the first CPU to panic, cpu.panic will be null
|
||||
if (cpu.panic) {
|
||||
cpu.panic->symbol_data = symbol_table;
|
||||
cpu.panic->message = message;
|
||||
cpu.panic->function = function;
|
||||
cpu.panic->file = file;
|
||||
cpu.panic->line = line;
|
||||
cpu.panic->cpus = g_num_cpus;
|
||||
|
||||
*apic_icr = send_nmi_command;
|
||||
}
|
||||
|
||||
*apic_icr = send_nmi_command;
|
||||
while (1) asm ("hlt");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "assert.h"
|
||||
#include "cpu.h"
|
||||
@@ -12,6 +13,11 @@
|
||||
#include "syscall.h"
|
||||
#include "tss.h"
|
||||
|
||||
unsigned g_num_cpus = 1;
|
||||
|
||||
panic_data g_panic_data;
|
||||
panic_data *g_panic_data_p = &g_panic_data;
|
||||
|
||||
cpu_data g_bsp_cpu_data;
|
||||
|
||||
void
|
||||
@@ -37,9 +43,18 @@ cpu_validate()
|
||||
#undef CPU_FEATURE_REQ
|
||||
}
|
||||
|
||||
void
|
||||
global_cpu_init()
|
||||
{
|
||||
memset(&g_panic_data, 0, sizeof(g_panic_data));
|
||||
}
|
||||
|
||||
void
|
||||
cpu_early_init(cpu_data *cpu)
|
||||
{
|
||||
if (cpu->index == 0)
|
||||
global_cpu_init();
|
||||
|
||||
cpu->idt->install();
|
||||
cpu->gdt->install();
|
||||
|
||||
|
||||
@@ -18,6 +18,20 @@ struct cpu_state
|
||||
uint64_t rip, cs, rflags, rsp, ss;
|
||||
};
|
||||
|
||||
/// Kernel-wide panic information
|
||||
struct panic_data
|
||||
{
|
||||
void const * symbol_data;
|
||||
char const * message;
|
||||
char const * function;
|
||||
char const * file;
|
||||
uint32_t line;
|
||||
uint16_t cpus;
|
||||
};
|
||||
|
||||
extern unsigned g_num_cpus;
|
||||
extern panic_data *g_panic_data_p;
|
||||
|
||||
/// Per-cpu state data. If you change this, remember to update the assembly
|
||||
/// version in 'tasking.inc'
|
||||
struct cpu_data
|
||||
@@ -39,6 +53,7 @@ struct cpu_data
|
||||
// Members beyond this point do not appear in
|
||||
// the assembly version
|
||||
lapic *apic;
|
||||
panic_data *panic;
|
||||
};
|
||||
|
||||
extern "C" cpu_data * _current_gsbase();
|
||||
|
||||
@@ -82,7 +82,7 @@ kernel_main(bootproto::args *args)
|
||||
{
|
||||
if (args->panic) {
|
||||
IDT::set_nmi_handler(args->panic->entrypoint);
|
||||
panic::symbol_table = args->symbol_table | mem::linear_offset;
|
||||
panic::symbol_table = util::offset_pointer(args->symbol_table, mem::linear_offset);
|
||||
}
|
||||
|
||||
init_console();
|
||||
@@ -149,7 +149,7 @@ kernel_main(bootproto::args *args)
|
||||
apic->calibrate_timer();
|
||||
|
||||
const auto &apic_ids = devices.get_apic_ids();
|
||||
unsigned num_cpus = start_aps(*apic, apic_ids, args->pml4);
|
||||
g_num_cpus = start_aps(*apic, apic_ids, args->pml4);
|
||||
|
||||
interrupts_enable();
|
||||
g_com1.handle_interrupt();
|
||||
@@ -178,7 +178,7 @@ kernel_main(bootproto::args *args)
|
||||
}
|
||||
*/
|
||||
|
||||
scheduler *sched = new scheduler {num_cpus};
|
||||
scheduler *sched = new scheduler {g_num_cpus};
|
||||
scheduler_ready = true;
|
||||
|
||||
// Load the init server
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
namespace panicking {
|
||||
|
||||
const char *clear = "\e[0m\n";
|
||||
|
||||
void
|
||||
print_header(
|
||||
serial_port &out,
|
||||
@@ -15,7 +17,8 @@ print_header(
|
||||
const char *file,
|
||||
uint64_t line)
|
||||
{
|
||||
out.write("\n\n\e[5;31m PANIC:\e[0;1;31m ");
|
||||
out.write(clear);
|
||||
out.write("\n\e[5;31m PANIC:\e[0;1;31m ");
|
||||
if (message) {
|
||||
out.write(message);
|
||||
out.write("\n ");
|
||||
@@ -28,8 +31,16 @@ print_header(
|
||||
char linestr[6];
|
||||
snprintf(linestr, sizeof(linestr), "%ld", line);
|
||||
out.write(linestr);
|
||||
}
|
||||
|
||||
out.write("\n \e[0;31m===================================================================================\n");
|
||||
void
|
||||
print_cpu(serial_port &out, cpu_data &cpu)
|
||||
{
|
||||
out.write("\n \e[0;31m==[ CPU: ");
|
||||
char cpuid[7];
|
||||
snprintf(cpuid, sizeof(cpuid), "%4x", cpu.id);
|
||||
out.write(cpuid);
|
||||
out.write(" ]====================================================================\n");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -64,7 +75,7 @@ print_reg(serial_port &out, const char *name, uint64_t val, const char *color)
|
||||
void
|
||||
print_cpu_state(serial_port &out, const cpu_state ®s)
|
||||
{
|
||||
out.write("\e[0m\n");
|
||||
out.write(clear);
|
||||
|
||||
// Row 1
|
||||
print_reg(out, "rsp", regs.rsp, "1;34");
|
||||
@@ -99,7 +110,7 @@ print_cpu_state(serial_port &out, const cpu_state ®s)
|
||||
print_reg(out, "ss", regs.ss, "1;33");
|
||||
print_reg(out, "cs", regs.cs, "1;33");
|
||||
print_reg(out, "flg", regs.rflags, "0;37");
|
||||
out.write("\e[0m\n");
|
||||
out.write(clear);
|
||||
}
|
||||
|
||||
} // namespace panicking
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
struct cpu_state;
|
||||
struct cpu_data;
|
||||
|
||||
namespace panicking {
|
||||
|
||||
@@ -24,6 +25,7 @@ void print_header(
|
||||
const char *file,
|
||||
uint64_t line);
|
||||
|
||||
void print_cpu(serial_port &out, cpu_data &cpu);
|
||||
void print_callstack(serial_port &out, symbol_table &syms, frame const *fp);
|
||||
void print_cpu_state(serial_port &out, const cpu_state ®s);
|
||||
|
||||
|
||||
@@ -10,9 +10,17 @@ _panic_entry:
|
||||
push 0 ; NMI doesn't push an error code
|
||||
push 2 ; NMI is int 2
|
||||
push_all
|
||||
check_swap_gs
|
||||
|
||||
mov r9, rsp
|
||||
|
||||
mov rax, [rsp + REGS.rip]
|
||||
push rax
|
||||
|
||||
jmp panic_handler
|
||||
|
||||
|
||||
global _current_gsbase
|
||||
_current_gsbase:
|
||||
mov rax, [gs:0]
|
||||
ret
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
#include "kutil/spinlock.h"
|
||||
#include <new>
|
||||
#include <util/no_construct.h>
|
||||
|
||||
#include "assert.h"
|
||||
#include "cpu.h"
|
||||
#include "display.h"
|
||||
#include "io.h"
|
||||
#include "serial.h"
|
||||
#include "symbol_table.h"
|
||||
|
||||
struct cpu_state;
|
||||
|
||||
bool main_cpu_done = false;
|
||||
bool asserting_locked = false;
|
||||
unsigned remaining = 0;
|
||||
|
||||
util::no_construct<panicking::serial_port> __com1_storage;
|
||||
panicking::serial_port &com1 = __com1_storage.value;
|
||||
|
||||
util::no_construct<panicking::symbol_table> __syms_storage;
|
||||
panicking::symbol_table &syms = __syms_storage.value;
|
||||
|
||||
constexpr int order = __ATOMIC_ACQ_REL;
|
||||
|
||||
extern "C"
|
||||
void panic_handler(
|
||||
const void *symbol_data,
|
||||
@@ -16,15 +30,49 @@ void panic_handler(
|
||||
uint64_t line,
|
||||
const cpu_state *regs)
|
||||
{
|
||||
panicking::serial_port com1(panicking::COM1);
|
||||
panicking::symbol_table syms(symbol_data);
|
||||
cpu_data &cpu = current_cpu();
|
||||
panic_data *panic = cpu.panic;
|
||||
|
||||
// If we're not running on the CPU that panicked, wait
|
||||
// for it to finish
|
||||
if (!panic) {
|
||||
while (!main_cpu_done);
|
||||
} else {
|
||||
new (&com1) panicking::serial_port {panicking::COM1};
|
||||
new (&syms) panicking::symbol_table {panic->symbol_data};
|
||||
remaining = panic->cpus;
|
||||
}
|
||||
|
||||
while (__atomic_test_and_set(&asserting_locked, order))
|
||||
asm ("pause");
|
||||
|
||||
panicking::frame const *fp = nullptr;
|
||||
asm ( "mov %%rbp, %0" : "=r" (fp) );
|
||||
|
||||
print_header(com1, message, function, file, line);
|
||||
if (panic)
|
||||
print_header(com1, panic->message, panic->function,
|
||||
panic->file, panic->line);
|
||||
|
||||
print_cpu(com1, cpu);
|
||||
print_callstack(com1, syms, fp);
|
||||
print_cpu_state(com1, *regs);
|
||||
|
||||
__atomic_clear(&asserting_locked, order);
|
||||
|
||||
// If we're running on the CPU that panicked, tell the
|
||||
// others we have finished
|
||||
if (panic)
|
||||
main_cpu_done = true;
|
||||
|
||||
if (__atomic_sub_fetch(&remaining, 1, order) == 0) {
|
||||
// No remaining CPUs, if we're running on QEMU,
|
||||
// tell it to exit
|
||||
constexpr uint32_t exit_code = 0;
|
||||
asm ( "out %0, %1" :: "a"(exit_code), "Nd"(0xf4) );
|
||||
}
|
||||
|
||||
while (1) asm ("hlt");
|
||||
}
|
||||
|
||||
#define NDEBUG
|
||||
#include "../cpprt.cpp"
|
||||
|
||||
@@ -133,7 +133,7 @@ struct args
|
||||
program *panic;
|
||||
allocation_register *allocations;
|
||||
uintptr_t modules;
|
||||
uintptr_t symbol_table;
|
||||
void const *symbol_table;
|
||||
|
||||
void *runtime_services;
|
||||
void *acpi_table;
|
||||
|
||||
Reference in New Issue
Block a user