Skip to content

Crash Engine

include/console/crash.h View source View on GitHub
struct crash_regs (1 field) source
§ union
§ struct
§ uint64_t rip
§ uint64_t rax
§ uint64_t r8
§ uint64_t rsp
§ uint64_t regs[CRASH_REG_COUNT]
struct crash_code (3 fields) source

NULL crashes are the generic, code-less crash

§ const char *name
§ const char *desc
§ const struct crash_facility *facility
struct crash_payload (3 fields) source
§ const struct crash_code *code
§ void *data
§ uintptr_t params[4]
struct crash_facility (5 fields) source
§ const char *name
§ const char *desc
§ const struct crash_code *const *codes

list order

§ size_t nr_codes
§ void (*const dump)(struct crash_payload pl)
struct crash_unwind_node_data (2 fields) source
§ uintptr_t arg
§ union
§ void *ptr
§ size_t rcu_lock_times
§ enum irql irql
§ uintptr_t raw
struct crash_unwind_node (3 fields) source
§ enum crash_unwind_type type
§ struct list_node list
struct crash_unwind_thread_data (3 fields) source
§ struct list_head free_list
§ struct list_head in_use

LIFO

struct crash_thread_data (5 fields) source
§ struct list_head crash_hooks

struct crash_hook

§ bool unwinding
§ bool in_hook
§ jmp_buf env
struct crash_hook (4 fields) source

The idea of crash hooks:

A hook can hook into a specific code, which is checked against a facility to verify if it is hookable.

They are hooked PER-THREAD, so as to not introduce strange non-determinism bugs and the chance that the crash hook registration path itself can crash (would be problematic) due to synchronization violations.

The generic (NULL) code is not hookable

§ char *name
§ struct list_node list
§ const struct crash_code *code
§ enum crash_source source_mask
struct crash_context (10 fields) source
§ struct crash_payload payload
§ enum crash_source source
§ enum crash_format_flags formats
§ const char *stmt
§ const char *file
§ int line
§ const char *func
§ const char *msg
§ const struct crash_regs *regs

NULL = capture caller’s frame via asm

§ void *source_data
enum qemu_exit_codes (3 variants) source
§ QEMU_EXIT_OK = 0
§ QEMU_EXIT_FAIL = 1
§ QEMU_EXIT_PANIC = 2
enum crash_source (8 variants) source
§ CRASH_SOURCE_PANIC = 0

panic() call

§ CRASH_SOURCE_ENFORCE

enforce() failure

§ CRASH_SOURCE_KASAN

ASAN check failure

§ CRASH_SOURCE_UBSAN

UBSAN failure

§ CRASH_SOURCE_NMI_WATCHDOG

Watchdog / Liveness monitor hard stall

§ CRASH_SOURCE_CPU_EXCEPTION

Hardware CPU fault

§ CRASH_SOURCE_NIGHTMARE

Nightmare test harness failure

§ CRASH_SOURCE_LOCK_CHK

Lock validator order / dependency violation

enum crash_format_flags (7 variants) source
§ CRASH_FMT_RAW_SERIAL = 1 << 0

Minimal serial printf

§ CRASH_FMT_VISUAL_PANES = 1 << 1

Dual pane ANSI console report

§ CRASH_FMT_NDJSON = 1 << 2

NDJSON stream

§ CRASH_FMT_DUMP_LOGS = 1 << 3

circular log buffer dump

§ CRASH_FMT_PEER_CPUS = 1 << 4

Quiesce and render peer CPU frames

§ CRASH_FMT_MINIMAL = CRASH_FMT_RAW_SERIAL | CRASH_FMT_NDJSON

Minimal formatting for early boot or nested crashes

enum crash_hook_flags (3 variants) source
§ CRASH_HOOK_DEFAULT = 0
§ CRASH_HOOK_FACILITY = 1 << 0

Facility granularity: match any code whose ->facility is the code’s facility

§ CRASH_HOOK_NO_UNWIND = 1 << 1

By default, the crash handler will make a best-effort attempt to unwind: it’ll try to safely drop locks, exit RCU read-side critical sections, although memory may still leak (this is dependent on the facility implementation).

NO_UNWIND allows this behavior to be skipped. This is because in certain cases, the unwinding cannot happen (e.g. lock checking is disabled), and also because the stale state not unwinding leaves can be used as a postmortem for state verification.

enum crash_unwind_type (7 variants) source
§ CRASH_UNWIND_NONE

should not be reachable

§ CRASH_UNWIND_RCU
§ CRASH_UNWIND_MUTEX
§ CRASH_UNWIND_RWLOCK
§ CRASH_UNWIND_SPINLOCK
§ CRASH_UNWIND_QSPINLOCK
§ CRASH_UNWIND_MAX
void enforce_impl_precond_msg(const char *file, int line, const char *func, const char *prefix, const char *expr, const char *fmt, ...) cc_printf_like(6, 7); cc_noreturn void enforce_impl_code(const char *file, int line, const char *func, const char *prefix, const char *expr, const struct crash_code *code, const char *fmt, ...) cc_printf_like(7, 8); cc_noreturn void enforce_impl_code_only(
    const char *file,
    int line,
    const char *func,
    const char *prefix,
    const char *expr,
    const struct crash_code *code
)
source
enforce_impl_msg() with INVALID_ARGUMENT for enforce_precond()
cc_noreturn void crash_full(const struct crash_context *ctx)
source
bool crash_cpu_is_owner(uint64_t id)
source
void crash_broadcast_nmi(void)
source
size_t crash_code_name(
    const struct crash_code *code,
    char *buf,
    size_t len
)
source
void crash_nmi_handoff(void *p, struct irq_registers *irqc)
source
void debug_print_stack(void)
source
bool crash_code_in_facility(
    const struct crash_code *code,
    const struct crash_facility *f
)
source
void crash_thread_init(struct thread *t)
source
void crash_unwind(void)
source

Enter/exit pairs

void crash_unwind_enter_rcu(void)
source
void crash_unwind_exit_rcu(void)
source
void crash_unwind_enter_mutex(struct mutex *m)
source
void crash_unwind_exit_mutex(struct mutex *m)
source
void crash_unwind_enter_rwlock(struct rwlock *r)
source
void crash_unwind_exit_rwlock(struct rwlock *r)
source
void crash_unwind_enter_spinlock(struct spinlock *s, enum irql old)
source
void crash_unwind_exit_spinlock(struct spinlock *s)
source
void crash_unwind_enter_qspinlock(struct qspinlock *q, enum irql old)
source
void crash_unwind_exit_qspinlock(struct qspinlock *q)
source
void crash_thread_init(struct thread *t)
source
void crash_unwind(void)
source
void crash_unwind_enter_rcu(void)
source
void crash_unwind_exit_rcu(void)
source
void crash_unwind_enter_mutex(struct mutex *m)
source
void crash_unwind_exit_mutex(struct mutex *m)
source
void crash_unwind_enter_rwlock(struct rwlock *r)
source
void crash_unwind_exit_rwlock(struct rwlock *r)
source
void crash_unwind_enter_spinlock(struct spinlock *s, enum irql old)
source
void crash_unwind_exit_spinlock(struct spinlock *s)
source
void crash_unwind_enter_qspinlock(struct qspinlock *q, enum irql old)
source
void crash_unwind_exit_qspinlock(struct qspinlock *q)
source
void qemu_exit(int code)
source
cc_noreturn void enforce_impl_msg(const char *file, int line, const char *func, const char *prefix, const char *expr, const char *fmt, ...) cc_printf_like(6, 7); /* enforce_impl_msg() with INVALID_ARGUMENT for enforce_precond() */ cc_noreturn
source
#define CRASH_REG_COUNT 20
source
#define CRASH_CODES_global
source

Codes that don’t have a facility, like enforce(p, INVALID_ARGUMENT)

Expansion
#define CRASH_CODES_global \
    (INVALID_ARGUMENT, "Argument was NULL or out of range"), \
        (INVALID_STATE, "Object was in a state this operation forbids"), \
        (INVALID_CONTEXT, \
         "Operation ran at a forbidden IRQL, IRQ or boot stage"), \
        (OUT_OF_MEMORY, "Allocation that must not fail did fail"), \
        (UNSUPPORTED, "Hardware or firmware lacks a required feature"), \
        (CORRUPTION, "Data structure or allocator invariant was broken"), \
        (CAPACITY_EXCEEDED, "A fixed-size limit was exceeded"), \
        (UNREACHABLE, "Control reached a path that must not execute"), \
        (DUPLICATE, "Object was registered, inserted, or defined twice"), \
        (OVERFLOW, "Counter or arithmetic result exceeded its range"), \
        (UNDERFLOW, "Counter was decremented below zero"), \
        (STALE_REFERENCE, "Reference to an object that is dying or dead"), \
        (BAD_FREE, "Freed memory was already free or never allocated"), \
        (UNINITIALIZED, "Object was used before initialization"), \
        (INIT_FAILED, "A required initialization step failed"), \
        (NOT_FOUND, "Lookup that must succeed found nothing")
#define CRASH_UNWIND_NODES 128
source
#define CRASH_WAIT_US MS_TO_US(500)
source

Quiesce timeout per peer CPU

#define CRASH_SPIN_ONE_US 5
source
#define CRASH_MAX_DEPTH 2
source

Max recursive fault depth

#define CRASH_MSG_MAX 256
source
#define CRASH_PAYLOAD(c, d)
source
Expansion
#define CRASH_PAYLOAD(c, d) ((struct crash_payload) {.code = c, .data = d})
#define CRASH_PARAMS(c, p0, p1, p2, p3)
source
Expansion
#define CRASH_PARAMS(c, p0, p1, p2, p3) \
    ((struct crash_payload) {.code = (c), \
                             .params = {(uintptr_t) (p0), (uintptr_t) (p1), \
                                        (uintptr_t) (p2), (uintptr_t) (p3)}})
#define CRASH_CODE_TO_PAYLOAD(c)
source
Expansion
#define CRASH_CODE_TO_PAYLOAD(c) ((struct crash_payload) {.code = c})
#define CRASH_FACILITY(f)
source

Use like this:   #define CRASH_CODES_asan
      (USE_AFTER_FREE, “Use after free”),
      (PARTIAL_GRANULE)

CRASH_CODE(asan, USE_AFTER_FREE)
Expansion
#define CRASH_FACILITY(f) __crash_facility_##f
#define CRASH_CODE(f, n)
source
Expansion
#define CRASH_CODE(f, n) _crash_code_sym(f, n)
#define CRASH_CODE_NTH(f, i)
source
Expansion
#define CRASH_CODE_NTH(f, i) __crash_codes_##f[(i)]
#define CRASH_CODES_ENUM(f, prefix)
source
Expansion
#define CRASH_CODES_ENUM(f, prefix) \
    PP_FOREACH_CTX(_crash_code_enumerator, prefix, CRASH_CODES_##f)
#define crash_code_in(c, f)
source
Expansion
#define crash_code_in(c, f) crash_code_in_facility((c), &CRASH_FACILITY(f))
#define CRASH_CODES_DECLARE(f)
source
Expansion
#define CRASH_CODES_DECLARE(f) \
    extern const struct crash_facility CRASH_FACILITY(f); \
    extern const struct crash_code *const __crash_codes_##f[]; \
    PP_FOREACH_CTX(_crash_code_extern, f, CRASH_CODES_##f)
#define CRASH_FACILITY_DEFINE(f, d, ...)
source
Expansion
#define CRASH_FACILITY_DEFINE(f, d, ...) \
    CRASH_CODES_DECLARE(f); \
    PP_FOREACH_CTX(_crash_code_def, f, CRASH_CODES_##f) \
    const struct crash_code *const __crash_codes_##f[] = { \
        PP_FOREACH_CTX(_crash_code_ptr, f, CRASH_CODES_##f)}; \
    const struct crash_facility CRASH_FACILITY(f) = { \
        .name = #f, \
        .desc = d, \
        .codes = __crash_codes_##f, \
        .nr_codes = ct_array_size(__crash_codes_##f), \
        __VA_ARGS__}
#define _crash_code_sym(f, n)
source
Expansion
#define _crash_code_sym(f, n) __crash_code_##f##_##n
#define _crash_code_name(t)
source
Expansion
#define _crash_code_name(t) PP_GET_1 t
#define _crash_code_desc(t)
source
Expansion
#define _crash_code_desc(t) _crash_code_desc_(PP_UNPAREN(t), NULL, ~)
#define _crash_code_desc_(...)
source
Expansion
#define _crash_code_desc_(...) _crash_code_desc_pick(__VA_ARGS__)
#define _crash_code_desc_pick(n, d, ...)
source
Expansion
#define _crash_code_desc_pick(n, d, ...) d
#define _crash_code_extern(f, t)
source
#define _crash_code_extern_(f, n)
source
Expansion
#define _crash_code_extern_(f, n) \
    extern const struct crash_code _crash_code_sym(f, n)[1];
#define _crash_code_def(f, t)
source
#define _crash_code_def_(f, n, d)
source
Expansion
#define _crash_code_def_(f, n, d) \
    const struct crash_code _crash_code_sym(f, n)[1] = { \
        {PP_STRINGIZE(n), d, &CRASH_FACILITY(f)}};
#define _crash_code_ptr(f, t)
source
Expansion
#define _crash_code_ptr(f, t) _crash_code_ptr_(f, _crash_code_name(t))
#define _crash_code_ptr_(f, n)
source
Expansion
#define _crash_code_ptr_(f, n) _crash_code_sym(f, n),
#define _crash_code_enumerator(p, t)
source
Expansion
#define _crash_code_enumerator(p, t) PP_CONCAT(p, _crash_code_name(t)),
#define CRASH_CODE_ARG(a)
source
Expansion
#define CRASH_CODE_ARG(a) PP_IIF(PP_IS_PAREN(a))(_crash_tuple_code(a), a)
#define _crash_tuple_code(t)
source
Expansion
#define _crash_tuple_code(t) \
    PP_CONCAT(_crash_tuple_code_, _crash_narg(PP_UNPAREN(t))) t
#define _crash_narg(...)
source
Expansion
#define _crash_narg(...) PP_NARG(__VA_ARGS__)
#define _crash_tuple_code_1(n)
source
Expansion
#define _crash_tuple_code_1(n) CRASH_CODE(global, n)
#define _crash_tuple_code_2(f, n)
source
Expansion
#define _crash_tuple_code_2(f, n) CRASH_CODE(f, n)
#define CRASH_CODE_SCOPED(a, ...)
source

Wraps stmt in the bare-name scope unless code was a tuple

Expansion
#define CRASH_CODE_SCOPED(a, ...) \
    PP_CONCAT(_crash_code_scoped_, PP_IS_PAREN(a))(__VA_ARGS__)
#define _crash_code_scoped_1(...)
source
Expansion
#define _crash_code_scoped_1(...) __VA_ARGS__
#define _crash_code_scoped_0(...)
source
Expansion
#define _crash_code_scoped_0(...) \
    ({ \
        _CRASH_CODE_SCOPE \
        __VA_ARGS__; \
    })
#define _crash_code_alias(f, t)
source
Expansion
#define _crash_code_alias(f, t) _crash_code_alias_(f, _crash_code_name(t))
#define _crash_code_alias_(f, n)
source
Expansion
#define _crash_code_alias_(f, n) \
    cc_unused extern const struct crash_code n[1] asm(PP_STRINGIZE(__USER_LABEL_PREFIX__) PP_STRINGIZE(_crash_code_sym(f, n)));
#define _CRASH_CODE_SCOPE
source