1#ifdef DEBUG_LOCK_CHK
2
3#include <atomic.h>
4#include <console/crash.h>
5#include <console/printf.h>
6#include <console/term.h>
7#include <enforce.h>
8#include <ndjson.h>
9#include <nightmare/nightmare.h>
10#include <string.h>
11
12#include "internal.h"
13
14NDJSON_DEFINE(lock_chk_finding, NDJSON_SECTION_LOCK_CHK, NDJSON_KIND_FINDING, 1,
15 NDJSON_STR(kind), NDJSON_STR(sig), NDJSON_STR(file),
16 NDJSON_U64(line), NDJSON_STR(class), NDJSON_STR(msg),
17 NDJSON_STR(mode), NDJSON_U64(cycle_len),
18 NDJSON_STR(capacity_pool), NDJSON_U64(capacity_used),
19 NDJSON_U64(capacity_limit));
20
21static const char *lock_chk_fail_kind_to_str(enum lock_chk_failure_kind kind) {
22 return CRASH_CODE_NTH(lock_chk, kind)->name;
23}
24
25static const char *lock_chk_mode_name(enum lock_chk_mode mode) {
26 if (mode == LOCK_CHK_MODE_EXCLUSIVE)
27 return "exclusive";
28 if (mode == LOCK_CHK_MODE_SHARED)
29 return "shared";
30 return "";
31}
32
33static const char *lock_chk_fault_msg(const struct lock_chk_fault *fault) {
34 if (fault->report == NULL || fault->report->msg[0] == '\0')
35 return "lock_chk failure (report buffer busy)";
36
37 return fault->report->msg;
38}
39
40static uint16_t lock_chk_fault_cycle_len(const struct lock_chk_fault *fault) {
41 return fault->report != NULL ? fault->report->cycle_len : 0;
42}
43
44static uint64_t lock_chk_calc_generic_sig(const struct lock_chk_fault *fault) {
45 uint64_t signature = HASH_FNV1A_64_OFFSET_BASIS;
46 signature =
47 lock_chk_hash_str(signature, lock_chk_fail_kind_to_str(fault->kind));
48 const struct lock_chk_class *class = fault->class;
49 if (class != NULL) {
50 signature = lock_chk_hash_str(signature, class->name);
51 signature = lock_chk_hash_str(signature, class->file);
52 signature =
53 lock_chk_hash_bytes(signature, &class->line, sizeof(class->line));
54 }
55 if (fault->site != NULL) {
56 signature = lock_chk_hash_str(signature, fault->site->file);
57 signature = lock_chk_hash_bytes(signature, &fault->site->line,
58 sizeof(fault->site->line));
59 }
60 signature = lock_chk_hash_bytes(signature, &fault->subclass,
61 sizeof(fault->subclass));
62 return lock_chk_hash_bytes(signature, &fault->mode, sizeof(fault->mode));
63}
64
65static uint64_t lock_chk_fail_sig(const struct lock_chk_fault *fault) {
66 const struct lock_chk_report *report = fault->report;
67 if (report != NULL) {
68 if (report->signature != 0)
69 return report->signature;
70 if (fault->kind == LOCK_CHK_FAIL_CYCLE && report->cycle_len != 0)
71 return lock_chk_calc_sig(report->cycle_hops, report->cycle_len);
72 }
73
74 return lock_chk_calc_generic_sig(fault);
75}
76
77static void lock_chk_emit_finding(const struct lock_chk_fault *fault,
78 const char *kind, const char *signature) {
79 ndjson_emit(lock_chk_finding, .kind = kind, .sig = signature,
80 .file = fault->site ? fault->site->file : "",
81 .line = fault->site ? fault->site->line : 0,
82 .class = fault->class ? fault->class->name : "",
83 .msg = lock_chk_fault_msg(fault),
84 .mode = lock_chk_mode_name(fault->mode),
85 .cycle_len = lock_chk_fault_cycle_len(fault),
86 .capacity_pool =
87 fault->capacity_pool ? fault->capacity_pool : "",
88 .capacity_used = fault->capacity_used,
89 .capacity_limit = fault->capacity_limit);
90}
91
92static void maybe_report_degradation(const struct lock_chk_fault *fault,
93 uint64_t signature,
94 const char *signature_text) {
95 static atomic_bool emitted = false;
96 if (atomic_xchg_rlx(&emitted, true))
97 return;
98
99 printf_unlocked("\n*** LOCK_CHK WARNING: Capacity exhausted for pool '%s' "
100 "(%u/%u) because DEGRADED***\n\n",
101 fault->capacity_pool ? fault->capacity_pool : "<unknown>",
102 fault->capacity_used, fault->capacity_limit);
103 lock_chk_emit_finding(fault, "CAPACITY_DEGRADED", signature_text);
104 nightmare_request_external_fail("lock_chk_degraded", signature, "%s",
105 lock_chk_fault_msg(fault));
106}
107
108static void lock_chk_crash_dump(struct crash_payload pl);
109
110CRASH_FACILITY_DEFINE(lock_chk, "Lock dependency validator",
111 .dump = lock_chk_crash_dump);
112
113#define lock_chk_dump_field(label, fmt, ...) \
114 crash_facility_line("%s%-9s%s " fmt, term_style(TERM_SEV_LABEL), label, \
115 term_style_reset(), ##__VA_ARGS__)
116
117static const char *lock_chk_class_name(const struct lock_chk_class *class) {
118 return class ? class->name : "<unknown>";
119}
120
121static const char *lock_chk_basename(const char *path) {
122 const char *slash = path ? strrchr(path, '/') : NULL;
123 return slash ? slash + 1 : (path ? path : "?");
124}
125
126static void dump_cycle(const struct lock_chk_report *report) {
127 crash_facility_line("");
128 crash_facility_line(
129 "%scycle%s %s(%u hops%s)%s", term_style(TERM_SEV_LABEL),
130 term_style_reset(), term_style(TERM_SEV_DIM), report->cycle_len,
131 report->cycle_truncated ? ", truncated" : "", term_style_reset());
132
133 for (uint16_t i = 0; i < report->cycle_len; i++) {
134 const struct lock_chk_cycle_hop *hop = &report->cycle_hops[i];
135
136 crash_facility_line(
137 "%s[%u]%s '%s'/%u %s%s%s", term_style(TERM_SEV_DIM), i,
138 term_style_reset(), lock_chk_class_name(hop->from_class),
139 hop->from_subclass, term_style(TERM_SEV_DIM),
140 lock_chk_mode_name(hop->from_mode), term_style_reset());
141 crash_facility_line(
142 " %s->%s '%s'/%u %s%s%s", term_style(TERM_SEV_WARN),
143 term_style_reset(), lock_chk_class_name(hop->to_class),
144 hop->to_subclass, term_style(TERM_SEV_DIM),
145 lock_chk_mode_name(hop->to_mode), term_style_reset());
146 crash_facility_line(
147 " %sat %s:%u%s", term_style(TERM_SEV_DIM),
148 lock_chk_basename(hop->site ? hop->site->file : NULL),
149 hop->site ? hop->site->line : 0, term_style_reset());
150 }
151}
152
153static void lock_chk_crash_dump(struct crash_payload pl) {
154 const struct lock_chk_fault *fault = pl.data;
155 uint64_t signature = pl.params[0];
156
157 if (!fault)
158 return;
159
160 lock_chk_dump_field("signature", "%016lx", signature);
161
162 const struct lock_chk_class *class = fault->class;
163 if (class) {
164 lock_chk_dump_field("class", "'%s'/%u", class->name, fault->subclass);
165 lock_chk_dump_field(
166 "", "%sdeclared at %s:%u%s", term_style(TERM_SEV_DIM),
167 lock_chk_basename(class->file), class->line, term_style_reset());
168 }
169
170 if (fault->instance)
171 lock_chk_dump_field("instance", "%s %p",
172 lock_chk_type_to_str(fault->type), fault->instance);
173
174 if (fault->mode == LOCK_CHK_MODE_EXCLUSIVE ||
175 fault->mode == LOCK_CHK_MODE_SHARED)
176 lock_chk_dump_field("mode", "%s", lock_chk_mode_name(fault->mode));
177
178 if (fault->kind == LOCK_CHK_FAIL_CAPACITY)
179 lock_chk_dump_field("capacity", "'%s' used %u / limit %u",
180 fault->capacity_pool ? fault->capacity_pool
181 : "<unknown>",
182 fault->capacity_used, fault->capacity_limit);
183
184 const struct lock_chk_report *report = fault->report;
185 if (fault->kind == LOCK_CHK_FAIL_CYCLE && report && report->cycle_len)
186 dump_cycle(report);
187}
188
189void lock_chk_report_fail(const struct lock_chk_fault *fault) {
190 uint64_t signature = lock_chk_fail_sig(fault);
191 char signature_text[24];
192 snprintf(signature_text, sizeof(signature_text), "%016lx", signature);
193
194 if (fault->kind == LOCK_CHK_FAIL_CAPACITY &&
195 !lock_chk_global.panic_on_exhaustion) {
196 maybe_report_degradation(fault, signature, signature_text);
197 lock_chk_report_release();
198 return;
199 }
200
201 lock_chk_emit_finding(fault, lock_chk_fail_kind_to_str(fault->kind),
202 signature_text);
203 nightmare_request_external_fail("lock_chk", signature, "%s",
204 lock_chk_fault_msg(fault));
205
206 crash_full(&(struct crash_context){
207 .payload = {.code = CRASH_CODE_NTH(lock_chk, fault->kind),
208 .data = (void *) fault,
209 .params = {signature}},
210 .source = CRASH_SOURCE_LOCK_CHK,
211 .formats = CRASH_FMT_DEFAULT,
212 .file = fault->site ? fault->site->file : __RELFILE__,
213 .line = fault->site ? fault->site->line : __LINE__,
214 .func = "lock_chk",
215 .msg = lock_chk_fault_msg(fault),
216 .regs = NULL,
217 });
218}
219
220#endif /* DEBUG_LOCK_CHK */
221