| 1 | #ifdef DEBUG_LOCK_CHK |
| 2 | |
| 3 | #include <console/crash.h> |
| 4 | #include <console/printf.h> |
| 5 | #include <ndjson.h> |
| 6 | #include <nightmare/nightmare.h> |
| 7 | #include <stdatomic.h> |
| 8 | #include <string.h> |
| 9 | |
| 10 | #include "lock_chk_internal.h" |
| 11 | |
| 12 | NDJSON_DECLARE(lock_chk_finding, NDJSON_SECTION_LOCK_CHK, NDJSON_KIND_FINDING, |
| 13 | 1, NDJSON_STR(kind), NDJSON_STR(sig), NDJSON_STR(file), |
| 14 | NDJSON_U64(line), NDJSON_STR(class), NDJSON_STR(msg), |
| 15 | NDJSON_STR(mode), NDJSON_U64(cycle_len), |
| 16 | NDJSON_STR(capacity_pool), NDJSON_U64(capacity_used), |
| 17 | NDJSON_U64(capacity_limit)); |
| 18 | |
| 19 | static const char *lock_chk_fail_kind_name(enum lock_chk_failure_kind kind) { |
| 20 | switch (kind) { |
| 21 | case LOCK_CHK_FAIL_CYCLE: return "cycle" ; |
| 22 | case LOCK_CHK_FAIL_RECURSION: return "recursion" ; |
| 23 | case LOCK_CHK_FAIL_RELEASE: return "release" ; |
| 24 | case LOCK_CHK_FAIL_CONTEXT: return "context" ; |
| 25 | case LOCK_CHK_FAIL_THREAD_EXIT: return "thread_exit" ; |
| 26 | case LOCK_CHK_FAIL_SPIN_ORDER: return "spin_order" ; |
| 27 | case LOCK_CHK_FAIL_CAPACITY: return "capacity" ; |
| 28 | case LOCK_CHK_FAIL_UNINITIALIZED: return "uninitialized" ; |
| 29 | case LOCK_CHK_FAIL_NOT_HELD: return "not_held" ; |
| 30 | case LOCK_CHK_FAIL_UNEXPECTED_HELD: return "unexpected_held" ; |
| 31 | } |
| 32 | return "unknown" ; |
| 33 | } |
| 34 | |
| 35 | static const char *lock_chk_mode_name(enum lock_chk_mode mode) { |
| 36 | if (mode == LOCK_CHK_MODE_EXCLUSIVE) |
| 37 | return "exclusive" ; |
| 38 | if (mode == LOCK_CHK_MODE_SHARED) |
| 39 | return "shared" ; |
| 40 | return "" ; |
| 41 | } |
| 42 | |
| 43 | static uint64_t |
| 44 | lock_chk_calc_generic_sig(const struct lock_chk_failure *failure) { |
| 45 | uint64_t signature = HASH_FNV1A_64_OFFSET_BASIS; |
| 46 | signature = |
| 47 | lock_chk_hash_string(signature, lock_chk_fail_kind_name(failure->kind)); |
| 48 | if (failure->class != NULL) { |
| 49 | signature = lock_chk_hash_string(signature, failure->class->name); |
| 50 | signature = lock_chk_hash_string(signature, failure->class->file); |
| 51 | signature = lock_chk_hash_bytes(signature, &failure->class->line, |
| 52 | sizeof(failure->class->line)); |
| 53 | } |
| 54 | if (failure->site != NULL) { |
| 55 | signature = lock_chk_hash_string(signature, failure->site->file); |
| 56 | signature = lock_chk_hash_bytes(signature, &failure->site->line, |
| 57 | sizeof(failure->site->line)); |
| 58 | } |
| 59 | signature = lock_chk_hash_bytes(signature, &failure->subclass, |
| 60 | sizeof(failure->subclass)); |
| 61 | return lock_chk_hash_bytes(signature, &failure->mode, |
| 62 | sizeof(failure->mode)); |
| 63 | } |
| 64 | |
| 65 | static uint64_t |
| 66 | lock_chk_failure_signature(const struct lock_chk_failure *failure) { |
| 67 | if (failure->signature != 0) |
| 68 | return failure->signature; |
| 69 | if (failure->kind == LOCK_CHK_FAIL_CYCLE && failure->cycle_len != 0) |
| 70 | return lock_chk_calc_canonical_cycle_sig(failure->cycle_hops, |
| 71 | failure->cycle_len); |
| 72 | return lock_chk_calc_generic_sig(failure); |
| 73 | } |
| 74 | |
| 75 | static void lock_chk_emit_finding(const struct lock_chk_failure *failure, |
| 76 | const char *kind, const char *signature) { |
| 77 | ndjson_emit(lock_chk_finding, .kind = kind, .sig = signature, |
| 78 | .file = failure->site ? failure->site->file : "" , |
| 79 | .line = failure->site ? failure->site->line : 0, |
| 80 | .class = failure->class ? failure->class->name : "" , |
| 81 | .msg = failure->msg, .mode = lock_chk_mode_name(failure->mode), |
| 82 | .cycle_len = failure->cycle_len, |
| 83 | .capacity_pool = |
| 84 | failure->capacity_pool ? failure->capacity_pool : "" , |
| 85 | .capacity_used = failure->capacity_used, |
| 86 | .capacity_limit = failure->capacity_limit); |
| 87 | } |
| 88 | |
| 89 | static void lock_chk_report_degradation(const struct lock_chk_failure *failure, |
| 90 | uint64_t signature, |
| 91 | const char *signature_text) { |
| 92 | static _Atomic bool emitted = false; |
| 93 | if (atomic_exchange_explicit(&emitted, true, memory_order_relaxed)) |
| 94 | return; |
| 95 | |
| 96 | printf_unlocked("\n*** LOCK_CHK WARNING: Capacity exhausted for pool '%s' " |
| 97 | "(%u/%u) - entering DEGRADED mode ***\n\n" , |
| 98 | failure->capacity_pool ? failure->capacity_pool |
| 99 | : "<unknown>" , |
| 100 | failure->capacity_used, failure->capacity_limit); |
| 101 | lock_chk_emit_finding(failure, "capacity_degraded" , signature_text); |
| 102 | nightmare_request_external_fail("lock_chk_degraded" , signature, "%s" , |
| 103 | failure->msg); |
| 104 | } |
| 105 | |
| 106 | static void lock_chk_print_cycle(const struct lock_chk_failure *failure) { |
| 107 | if (failure->kind != LOCK_CHK_FAIL_CYCLE || failure->cycle_len == 0) |
| 108 | return; |
| 109 | |
| 110 | printf_unlocked("Cycle path (%u hops%s):\n" , failure->cycle_len, |
| 111 | failure->cycle_truncated ? ", truncated" : "" ); |
| 112 | for (uint16_t i = 0; i < failure->cycle_len; i++) { |
| 113 | const struct lock_chk_cycle_hop *hop = &failure->cycle_hops[i]; |
| 114 | printf_unlocked( |
| 115 | " [%u] '%s' (%s:%u sc %u) [%s]\n" , i, |
| 116 | hop->from_class ? hop->from_class->name : "<unknown>" , |
| 117 | hop->from_class ? hop->from_class->file : "?" , |
| 118 | hop->from_class ? hop->from_class->line : 0, hop->from_subclass, |
| 119 | hop->from_mode == LOCK_CHK_MODE_EXCLUSIVE ? "EXCLUSIVE" : "SHARED" ); |
| 120 | printf_unlocked( |
| 121 | " -> '%s' (%s:%u sc %u) [%s] at %s:%u\n" , |
| 122 | hop->to_class ? hop->to_class->name : "<unknown>" , |
| 123 | hop->to_class ? hop->to_class->file : "?" , |
| 124 | hop->to_class ? hop->to_class->line : 0, hop->to_subclass, |
| 125 | hop->to_mode == LOCK_CHK_MODE_EXCLUSIVE ? "EXCLUSIVE" : "SHARED" , |
| 126 | hop->site ? hop->site->file : "?" , hop->site ? hop->site->line : 0); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | static void lock_chk_print_failure(const struct lock_chk_failure *failure, |
| 131 | const char *signature) { |
| 132 | printf_unlocked("\n========================================================" |
| 133 | "========================\n" ); |
| 134 | printf_unlocked("LOCK_CHK VIOLATION: %s\n" , failure->msg); |
| 135 | printf_unlocked("Signature: 0x%s\n" , signature); |
| 136 | if (failure->site != NULL) |
| 137 | printf_unlocked("Location: %s:%u\n" , failure->site->file, |
| 138 | failure->site->line); |
| 139 | if (failure->class != NULL) |
| 140 | printf_unlocked("Lock Class: '%s' (%s:%u subclass %u)\n" , |
| 141 | failure->class->name, failure->class->file, |
| 142 | failure->class->line, failure->subclass); |
| 143 | |
| 144 | lock_chk_print_cycle(failure); |
| 145 | if (failure->kind == LOCK_CHK_FAIL_CAPACITY) |
| 146 | printf_unlocked("Capacity pool '%s': used %u / limit %u\n" , |
| 147 | failure->capacity_pool ? failure->capacity_pool |
| 148 | : "<unknown>" , |
| 149 | failure->capacity_used, failure->capacity_limit); |
| 150 | printf_unlocked("==========================================================" |
| 151 | "======================\n\n" ); |
| 152 | } |
| 153 | |
| 154 | void lock_chk_report_failure(const struct lock_chk_failure *failure) { |
| 155 | uint64_t signature = lock_chk_failure_signature(failure); |
| 156 | char signature_text[24]; |
| 157 | snprintf(signature_text, sizeof(signature_text), "%016lx" , signature); |
| 158 | |
| 159 | if (failure->kind == LOCK_CHK_FAIL_CAPACITY && |
| 160 | !lock_chk_capacity_should_panic()) { |
| 161 | lock_chk_report_degradation(failure, signature, signature_text); |
| 162 | return; |
| 163 | } |
| 164 | |
| 165 | lock_chk_print_failure(failure, signature_text); |
| 166 | lock_chk_emit_finding(failure, lock_chk_fail_kind_name(failure->kind), |
| 167 | signature_text); |
| 168 | nightmare_request_external_fail("lock_chk" , signature, "%s" , failure->msg); |
| 169 | |
| 170 | crash_full(&(struct crash_context){ |
| 171 | .source = CRASH_SOURCE_LOCK_CHK, |
| 172 | .formats = CRASH_FMT_DEFAULT, |
| 173 | .file = failure->site ? failure->site->file : __RELFILE__, |
| 174 | .line = failure->site ? failure->site->line : __LINE__, |
| 175 | .func = "lock_chk" , |
| 176 | .msg = failure->msg, |
| 177 | .regs = NULL, |
| 178 | }); |
| 179 | } |
| 180 | |
| 181 | #endif /* DEBUG_LOCK_CHK */ |
| 182 | |