[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:
Justin C. Miller
2022-01-08 01:00:43 -08:00
parent a3fff889d1
commit eeef23c2b7
12 changed files with 134 additions and 30 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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

View File

@@ -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 &regs)
{
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 &regs)
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

View File

@@ -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 &regs);

View File

@@ -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

View File

@@ -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"