1/* @title: Crash Engine */
2#pragma once
3#include <asm.h>
4#include <compiler.h>
5#include <linker/symbols.h>
6#include <sch/irql.h>
7#include <setjmp.h>
8#include <stdatomic.h>
9#include <stdbool.h>
10#include <stddef.h>
11#include <stdint.h>
12#include <structures/list.h>
13#include <time/time.h>
14#include <types/types.h>
15
16struct spinlock;
17struct qspinlock;
18struct rwlock;
19struct mutex;
20struct thread;
21struct irq_context;
22
23#define CRASH_REG_COUNT 20
24
25enum qemu_exit_codes {
26 QEMU_EXIT_OK = 0,
27 QEMU_EXIT_FAIL = 1,
28 QEMU_EXIT_PANIC = 2,
29};
30
31struct crash_regs {
32 union {
33 struct {
34 uint64_t rip, rflags, cr2, cr3;
35 uint64_t rax, rbx, rcx, rdx, rbp, rdi, rsi;
36 uint64_t r8, r9, r10, r11, r12, r13, r14, r15;
37 uint64_t rsp;
38 };
39
40 uint64_t regs[CRASH_REG_COUNT];
41 };
42};
43
44static_assert_struct_size_eq(crash_regs, CRASH_REG_COUNT * 8);
45
46enum crash_code {
47 CRASH_CODE_GENERIC,
48};
49
50enum crash_source {
51 CRASH_SOURCE_PANIC = 0, /* panic() call */
52 CRASH_SOURCE_ASSERT, /* assertion failure */
53 CRASH_SOURCE_KASAN, /* ASAN check failure */
54 CRASH_SOURCE_UBSAN, /* UBSAN failure */
55 CRASH_SOURCE_NMI_WATCHDOG, /* Watchdog / Liveness monitor hard stall */
56 CRASH_SOURCE_CPU_EXCEPTION, /* Hardware CPU fault */
57 CRASH_SOURCE_NIGHTMARE, /* Nightmare test harness failure */
58 CRASH_SOURCE_LOCK_CHK, /* Lock validator order / dependency violation */
59};
60
61enum crash_format_flags {
62 CRASH_FMT_RAW_SERIAL = 1 << 0, /* Minimal serial printf */
63 CRASH_FMT_VISUAL_PANES = 1 << 1, /* Dual pane ANSI console report */
64 CRASH_FMT_NDJSON = 1 << 2, /* NDJSON stream */
65 CRASH_FMT_DUMP_LOGS = 1 << 3, /* circular log buffer dump */
66 CRASH_FMT_PEER_CPUS = 1 << 4, /* Quiesce and render peer CPU frames */
67
68 /* Default formatting */
69 CRASH_FMT_DEFAULT = CRASH_FMT_RAW_SERIAL | CRASH_FMT_VISUAL_PANES |
70 CRASH_FMT_NDJSON | CRASH_FMT_DUMP_LOGS |
71 CRASH_FMT_PEER_CPUS,
72
73 /* Minimal formatting for early boot or nested crashes */
74 CRASH_FMT_MINIMAL = CRASH_FMT_RAW_SERIAL | CRASH_FMT_NDJSON,
75};
76
77enum crash_hook_flags {
78 CRASH_HOOK_DEFAULT = 0,
79 CRASH_HOOK_FACILITY = 1 << 0, /* Facility granularity.
80 * This means that the crash_code the
81 * crash_hook contains is only checked for the
82 * upper word facility
83 */
84
85 CRASH_HOOK_NO_UNWIND = 1 << 1, /* By default, the crash handler will make
86 * a best-effort attempt to unwind: it'll
87 * try to safely drop locks, exit RCU
88 * read-side critical sections, although
89 * memory may still leak (this is dependent
90 * on the facility implementation).
91 *
92 * NO_UNWIND allows this behavior to be
93 * skipped. This is because in certain cases,
94 * the unwinding cannot happen (e.g. lock
95 * checking is disabled), and also because
96 * the stale state not unwinding
97 * leaves can be used as a postmortem
98 * for state verification.
99 */
100
101};
102
103enum crash_unwind_type {
104 CRASH_UNWIND_NONE, /* should not be reachable */
105 CRASH_UNWIND_RCU,
106 CRASH_UNWIND_MUTEX,
107 CRASH_UNWIND_RWLOCK,
108 CRASH_UNWIND_SPINLOCK,
109 CRASH_UNWIND_QSPINLOCK,
110
111 CRASH_UNWIND_MAX,
112};
113
114struct crash_payload {
115 enum crash_code code;
116 void *data;
117 uintptr_t params[4];
118};
119
120struct report_target;
121struct crash_facility {
122 uint16_t prefix;
123 const char *name;
124 const char *desc;
125 uint16_t hookable_threshold; /* We use this to state that if the delta
126 * >= this, we treat it as hookable. So,
127 * if it's left unset, it'll be 0 and
128 * all codes are hookable. We do this
129 * because setting some high bit can cause
130 * the enum to count funny as it would
131 * bump up from there
132 *
133 * this is basically "First hookable code"
134 */
135 const char *(*const to_str)(uint16_t delta);
136 void (*const dump)(uint16_t delta, struct crash_payload pl);
137
138 void (*const emit_ndjson)(uint16_t delta, struct crash_payload pl);
139};
140
141/* The idea here:
142 *
143 * Each thread has a fixed pool of unwind nodes, with a list that stores
144 * the currently active nodes, which are unwound in LIFO order.
145 *
146 * The reason why it's a pool and not a stack is because you can have code
147 * such as
148 *
149 * mutex_lock(&lock);
150 * rcu_read_lock();
151 *
152 * mutex_unlock(&lock);
153 * rcu_read_unlock();
154 *
155 * and with a naive stack, the semantics break here/become tricky because
156 * the thread would have to reorganize the stack, whereas a list_head
157 * can just be yanked out of the middle.
158 *
159 * Thus, the unwind node enqueue path is:
160 *
161 * (1) try to reserve one
162 * (2) list_add_tail it
163 *
164 * And dequeue becomes:
165 *
166 * (1) find the pointer to the current thing in question
167 * (2) dequeue it
168 *
169 * RCU is even simpler: the first rcu_read_lock gets a node,
170 * and every subsequent lock bumps a counter, dec'ing the counter
171 * upon unlock, and the crash path simply rcu_read_unlock's it
172 * that many times.
173 */
174
175struct crash_unwind_node_data {
176 uintptr_t arg;
177 union {
178 void *ptr;
179 size_t rcu_lock_times;
180 enum irql irql;
181 uintptr_t raw;
182 };
183};
184
185struct crash_unwind_node {
186 enum crash_unwind_type type;
187 struct list_head list;
188 struct crash_unwind_node_data data;
189};
190
191#define CRASH_UNWIND_NODES 128
192struct crash_unwind_perthread {
193 struct list_head free_list;
194 struct list_head in_use; /* LIFO */
195 struct crash_unwind_node nodes[CRASH_UNWIND_NODES];
196};
197
198struct crash_perthread {
199 struct crash_unwind_perthread unwind;
200 struct list_head crash_hooks; /* struct crash_hook */
201 bool unwinding;
202 bool in_hook;
203 jmp_buf env;
204};
205
206/*
207 * The idea of crash hooks:
208 *
209 * A hook can hook into a specific code, which is checked
210 * against a facility to verify if it is hookable.
211 *
212 * They are hooked PER-THREAD, so as to not introduce strange
213 * non-determinism bugs and the chance that the crash hook
214 * registration path itself can crash (would be problematic)
215 * due to synchronization violations.
216 *
217 * CRASH_CODE_GENERIC is not hookable
218 */
219struct crash_hook {
220 char *name;
221 struct list_head list;
222 enum crash_code code;
223 enum crash_source source_mask;
224};
225
226struct crash_context {
227 struct crash_payload payload;
228 enum crash_source source;
229 enum crash_format_flags formats;
230 const char *file;
231 int line;
232 const char *func;
233 const char *msg;
234 const struct crash_regs *regs; /* NULL = capture caller's frame via asm */
235 void *source_data;
236};
237
238#define CRASH_WAIT_US MS_TO_US(500) /* Quiesce timeout per peer CPU */
239#define CRASH_SPIN_ONE_US 5
240#define CRASH_MAX_DEPTH 2 /* Max recursive fault depth */
241#define CRASH_MSG_MAX 256
242
243#define CRASH_PAYLOAD(c, d) ((struct crash_payload) {.code = c, .data = d})
244#define CRASH_PARAMS(c, p0, p1, p2, p3) \
245 ((struct crash_payload) {.code = (c), \
246 .params = {(uintptr_t) (p0), (uintptr_t) (p1), \
247 (uintptr_t) (p2), (uintptr_t) (p3)}})
248
249#define CRASH_CODE_TO_PAYLOAD(c) ((struct crash_payload) {.code = c})
250#define CRASH_CODE_CREATE(pre, del) \
251 ({ ((((int) (pre)) << 16) | (((int) (del)) & 0xFFFF)); })
252
253#define CRASH_CODE_GET_FACILITY(c) ({ (((c)) >> 16) & 0xFFFF; })
254
255#define CRASH_CODE_GET_DELTA(c) ({ (((c)) & 0xFFFF); })
256
257#define CRASH_CODE_PREFIX(n) ((__crash_facility_##n).prefix)
258#define CRASH_CODE_DELTA_START (1)
259#define CRASH_CODE(n, d) CRASH_CODE_CREATE(CRASH_CODE_PREFIX(n), d)
260
261#define CRASH_FACILITY(n) __crash_facility_##n
262#define CRASH_FACILITY_EXTERN(n) \
263 extern struct crash_facility __crash_facility_##n
264#define CRASH_FACILITY_DECLARE(n, ...) \
265 LINKER_SECTION_OBJECT(struct crash_facility, crash_facilities) \
266 __crash_facility_##n = {.name = #n, __VA_ARGS__}
267
268LINKER_SECTION_DEFINE(struct crash_facility, crash_facilities);
269
270__noreturn void assert_impl_default(struct crash_payload payload,
271 const char *file, int line,
272 const char *func, const char *fmt, ...);
273
274__noreturn void assert_impl_assertion(struct crash_payload payload,
275 const char *file, int line,
276 const char *func, const char *prefix,
277 const char *assertion, const char *fmt,
278 ...);
279__noreturn void crash_full(const struct crash_context *ctx);
280
281bool crash_cpu_is_owner(uint64_t id);
282void crash_broadcast_nmi(void);
283void crash_facilities_init(void);
284const char *crash_code_from_facility_to_str(enum crash_code code);
285__noreturn void crash_nmi_handoff(void *p, struct irq_context *ctx);
286void debug_print_stack(void);
287void crash_facility_printf(const char *fmt, ...);
288void crash_perthread_init(struct thread *t);
289void crash_unwind(void);
290
291/* Enter/exit pairs */
292void crash_unwind_enter_rcu(void);
293void crash_unwind_exit_rcu(void);
294void crash_unwind_enter_mutex(struct mutex *m);
295void crash_unwind_exit_mutex(struct mutex *m);
296void crash_unwind_enter_rwlock(struct rwlock *r);
297void crash_unwind_exit_rwlock(struct rwlock *r);
298void crash_unwind_enter_spinlock(struct spinlock *s, enum irql old);
299void crash_unwind_exit_spinlock(struct spinlock *s);
300void crash_unwind_enter_qspinlock(struct qspinlock *q, enum irql old);
301void crash_unwind_exit_qspinlock(struct qspinlock *q);
302
303static inline void qemu_exit(int code) {
304 outb(port: 0xf4, value: (uint8_t) code);
305}
306
307static inline const char *crash_code_to_str(enum crash_code code) {
308 switch (code) {
309 case CRASH_CODE_GENERIC: return "Generic";
310 default: return crash_code_from_facility_to_str(code);
311 }
312}
313