Changeset View
Changeset View
Standalone View
Standalone View
sys/kern/subr_witness.c
Show First 20 Lines • Show All 332 Lines • ▼ Show 20 Lines | |||||
static void witness_ddb_display(int(*)(const char *fmt, ...)); | static void witness_ddb_display(int(*)(const char *fmt, ...)); | ||||
static void witness_ddb_display_descendants(int(*)(const char *fmt, ...), | static void witness_ddb_display_descendants(int(*)(const char *fmt, ...), | ||||
struct witness *, int indent); | struct witness *, int indent); | ||||
static void witness_ddb_display_list(int(*prnt)(const char *fmt, ...), | static void witness_ddb_display_list(int(*prnt)(const char *fmt, ...), | ||||
struct witness_list *list); | struct witness_list *list); | ||||
static void witness_ddb_level_descendants(struct witness *parent, int l); | static void witness_ddb_level_descendants(struct witness *parent, int l); | ||||
static void witness_ddb_list(struct thread *td); | static void witness_ddb_list(struct thread *td); | ||||
#endif | #endif | ||||
static void witness_enter_debugger(const char *msg); | |||||
static void witness_debugger(int cond, const char *msg); | static void witness_debugger(int cond, const char *msg); | ||||
static void witness_free(struct witness *m); | static void witness_free(struct witness *m); | ||||
static struct witness *witness_get(void); | static struct witness *witness_get(void); | ||||
static uint32_t witness_hash_djb2(const uint8_t *key, uint32_t size); | static uint32_t witness_hash_djb2(const uint8_t *key, uint32_t size); | ||||
static struct witness *witness_hash_get(const char *key); | static struct witness *witness_hash_get(const char *key); | ||||
static void witness_hash_put(struct witness *w); | static void witness_hash_put(struct witness *w); | ||||
static void witness_init_hash_tables(void); | static void witness_init_hash_tables(void); | ||||
static void witness_increment_graph_generation(void); | static void witness_increment_graph_generation(void); | ||||
static void witness_lock_list_free(struct lock_list_entry *lle); | static void witness_lock_list_free(struct lock_list_entry *lle); | ||||
static struct lock_list_entry *witness_lock_list_get(void); | static struct lock_list_entry *witness_lock_list_get(void); | ||||
static int witness_lock_order_add(struct witness *parent, | static int witness_lock_order_add(struct witness *parent, | ||||
struct witness *child); | struct witness *child); | ||||
static int witness_lock_order_check(struct witness *parent, | static int witness_lock_order_check(struct witness *parent, | ||||
struct witness *child); | struct witness *child); | ||||
static struct witness_lock_order_data *witness_lock_order_get( | static struct witness_lock_order_data *witness_lock_order_get( | ||||
struct witness *parent, | struct witness *parent, | ||||
struct witness *child); | struct witness *child); | ||||
static void witness_list_lock(struct lock_instance *instance, | static void witness_list_lock(struct lock_instance *instance, | ||||
int (*prnt)(const char *fmt, ...)); | int (*prnt)(const char *fmt, ...)); | ||||
static int witness_output(const char *fmt, ...) __printflike(1, 2); | static int witness_output(const char *fmt, ...) __printflike(1, 2); | ||||
static int witness_output_drain(void *arg __unused, const char *data, | |||||
int len); | |||||
static int witness_voutput(const char *fmt, va_list ap) __printflike(1, 0); | static int witness_voutput(const char *fmt, va_list ap) __printflike(1, 0); | ||||
static void witness_setflag(struct lock_object *lock, int flag, int set); | static void witness_setflag(struct lock_object *lock, int flag, int set); | ||||
FEATURE(witness, "kernel has witness(9) support"); | FEATURE(witness, "kernel has witness(9) support"); | ||||
static SYSCTL_NODE(_debug, OID_AUTO, witness, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, | static SYSCTL_NODE(_debug, OID_AUTO, witness, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, | ||||
"Witness Locking"); | "Witness Locking"); | ||||
▲ Show 20 Lines • Show All 907 Lines • ▼ Show 20 Lines | witness_checkorder(struct lock_object *lock, int flags, const char *file, | ||||
* the lock we most recently acquired in the lock order tree, | * the lock we most recently acquired in the lock order tree, | ||||
* then there is no need for any further checks. | * then there is no need for any further checks. | ||||
*/ | */ | ||||
if (isitmychild(w1, w)) | if (isitmychild(w1, w)) | ||||
goto out; | goto out; | ||||
for (j = 0, lle = lock_list; lle != NULL; lle = lle->ll_next) { | for (j = 0, lle = lock_list; lle != NULL; lle = lle->ll_next) { | ||||
for (i = lle->ll_count - 1; i >= 0; i--, j++) { | for (i = lle->ll_count - 1; i >= 0; i--, j++) { | ||||
struct stack pstack; | |||||
bool pstackv, trace; | |||||
MPASS(j < LOCK_CHILDCOUNT * LOCK_NCHILDREN); | MPASS(j < LOCK_CHILDCOUNT * LOCK_NCHILDREN); | ||||
lock1 = &lle->ll_children[i]; | lock1 = &lle->ll_children[i]; | ||||
/* | /* | ||||
* Ignore the interlock. | * Ignore the interlock. | ||||
*/ | */ | ||||
if (interlock == lock1->li_lock) | if (interlock == lock1->li_lock) | ||||
▲ Show 20 Lines • Show All 70 Lines • ▼ Show 20 Lines | reversal: | ||||
/* | /* | ||||
* If the lock order is blessed, bail before logging | * If the lock order is blessed, bail before logging | ||||
* anything. We don't look for other lock order | * anything. We don't look for other lock order | ||||
* violations though, which may be a bug. | * violations though, which may be a bug. | ||||
*/ | */ | ||||
if (blessed(w, w1)) | if (blessed(w, w1)) | ||||
goto out; | goto out; | ||||
trace = witness_trace; | |||||
mjg: flipping the flag at the wrong time can cause pstackv to be unititalized. best would be to… | |||||
Done Inline ActionsSure, that's a good suggestion. I'm not sure we need atomic_load, though. I thought FreeBSD atomic model widely assumed word-sized loads don't tear, and non-atomic access to the mostly-RO sysctl values are extremely common in subr_witness. cem: Sure, that's a good suggestion. I'm not sure we need atomic_load, though. I thought FreeBSD… | |||||
Done Inline Actionsthe point is to prevent the compiler from reloading the var. it may be there are other factors here which de facto accomplish this, but there is no need to rely on them. mjg: the point is to prevent the compiler from reloading the var. it may be there are other factors… | |||||
if (trace) { | |||||
struct witness_lock_order_data *data; | |||||
pstackv = false; | |||||
data = witness_lock_order_get(w, w1); | |||||
if (data != NULL) { | |||||
stack_copy(&data->wlod_stack, | |||||
&pstack); | |||||
pstackv = true; | |||||
} | |||||
} | |||||
mtx_unlock_spin(&w_mtx); | mtx_unlock_spin(&w_mtx); | ||||
#ifdef WITNESS_NO_VNODE | #ifdef WITNESS_NO_VNODE | ||||
/* | /* | ||||
* There are known LORs between VNODE locks. They are | * There are known LORs between VNODE locks. They are | ||||
* not an indication of a bug. VNODE locks are flagged | * not an indication of a bug. VNODE locks are flagged | ||||
* as such (LO_IS_VNODE) and we don't yell if the LOR | * as such (LO_IS_VNODE) and we don't yell if the LOR | ||||
* is between 2 VNODE locks. | * is between 2 VNODE locks. | ||||
Show All 30 Lines | "lock order reversal: (Giant after non-sleepable)\n"); | ||||
if (i == 0 && lle->ll_next != NULL) { | if (i == 0 && lle->ll_next != NULL) { | ||||
lle = lle->ll_next; | lle = lle->ll_next; | ||||
i = lle->ll_count - 1; | i = lle->ll_count - 1; | ||||
MPASS(i >= 0 && i < LOCK_NCHILDREN); | MPASS(i >= 0 && i < LOCK_NCHILDREN); | ||||
} else | } else | ||||
i--; | i--; | ||||
} while (i >= 0); | } while (i >= 0); | ||||
if (i < 0) { | if (i < 0) { | ||||
witness_output(" 1st %p %s (%s) @ %s:%d\n", | witness_output(" 1st %p %s (%s, %s) @ %s:%d\n", | ||||
lock1->li_lock, lock1->li_lock->lo_name, | lock1->li_lock, lock1->li_lock->lo_name, | ||||
w1->w_name, fixup_filename(lock1->li_file), | w1->w_name, w1->w_class->lc_name, | ||||
fixup_filename(lock1->li_file), | |||||
lock1->li_line); | lock1->li_line); | ||||
witness_output(" 2nd %p %s (%s) @ %s:%d\n", lock, | witness_output(" 2nd %p %s (%s, %s) @ %s:%d\n", | ||||
lock->lo_name, w->w_name, | lock, lock->lo_name, w->w_name, | ||||
fixup_filename(file), line); | w->w_class->lc_name, fixup_filename(file), | ||||
line); | |||||
} else { | } else { | ||||
witness_output(" 1st %p %s (%s) @ %s:%d\n", | struct witness *w2 = lock2->li_lock->lo_witness; | ||||
witness_output(" 1st %p %s (%s, %s) @ %s:%d\n", | |||||
lock2->li_lock, lock2->li_lock->lo_name, | lock2->li_lock, lock2->li_lock->lo_name, | ||||
lock2->li_lock->lo_witness->w_name, | w2->w_name, w2->w_class->lc_name, | ||||
fixup_filename(lock2->li_file), | fixup_filename(lock2->li_file), | ||||
lock2->li_line); | lock2->li_line); | ||||
witness_output(" 2nd %p %s (%s) @ %s:%d\n", | witness_output(" 2nd %p %s (%s, %s) @ %s:%d\n", | ||||
lock1->li_lock, lock1->li_lock->lo_name, | lock1->li_lock, lock1->li_lock->lo_name, | ||||
w1->w_name, fixup_filename(lock1->li_file), | w1->w_name, w1->w_class->lc_name, | ||||
fixup_filename(lock1->li_file), | |||||
lock1->li_line); | lock1->li_line); | ||||
witness_output(" 3rd %p %s (%s) @ %s:%d\n", lock, | witness_output(" 3rd %p %s (%s, %s) @ %s:%d\n", lock, | ||||
lock->lo_name, w->w_name, | lock->lo_name, w->w_name, | ||||
fixup_filename(file), line); | w->w_class->lc_name, fixup_filename(file), | ||||
line); | |||||
} | } | ||||
witness_debugger(1, __func__); | if (trace) { | ||||
char buf[64]; | |||||
struct sbuf sb; | |||||
sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); | |||||
sbuf_set_drain(&sb, witness_output_drain, | |||||
NULL); | |||||
if (pstackv) { | |||||
sbuf_printf(&sb, | |||||
"Lock order %s -> %s established at:\n", | |||||
w->w_name, w1->w_name); | |||||
stack_sbuf_print_flags(&sb, &pstack, | |||||
M_NOWAIT, STACK_SBUF_FMT_LONG); | |||||
} | |||||
sbuf_printf(&sb, | |||||
"Lock order %s -> %s attempted at:\n", | |||||
w1->w_name, w->w_name); | |||||
stack_save(&pstack); | |||||
stack_sbuf_print_flags(&sb, &pstack, M_NOWAIT, | |||||
STACK_SBUF_FMT_LONG); | |||||
sbuf_finish(&sb); | |||||
sbuf_delete(&sb); | |||||
} | |||||
witness_enter_debugger(__func__); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* If requested, build a new lock order. However, don't build a new | * If requested, build a new lock order. However, don't build a new | ||||
* relationship between a sleepable lock and Giant if it is in the | * relationship between a sleepable lock and Giant if it is in the | ||||
* wrong direction. The correct lock order is that sleepable locks | * wrong direction. The correct lock order is that sleepable locks | ||||
▲ Show 20 Lines • Show All 1,634 Lines • ▼ Show 20 Lines | if (witness_trace) { | ||||
stack_zero(&st); | stack_zero(&st); | ||||
stack_save(&st); | stack_save(&st); | ||||
witness_output("stack backtrace:\n"); | witness_output("stack backtrace:\n"); | ||||
stack_sbuf_print_ddb(&sb, &st); | stack_sbuf_print_ddb(&sb, &st); | ||||
sbuf_finish(&sb); | sbuf_finish(&sb); | ||||
} | } | ||||
witness_enter_debugger(msg); | |||||
} | |||||
static void | |||||
witness_enter_debugger(const char *msg) | |||||
{ | |||||
#ifdef KDB | #ifdef KDB | ||||
if (witness_kdb) | if (witness_kdb) | ||||
kdb_enter(KDB_WHY_WITNESS, msg); | kdb_enter(KDB_WHY_WITNESS, msg); | ||||
#endif | #endif | ||||
} | } |
flipping the flag at the wrong time can cause pstackv to be unititalized. best would be to atomic_load int the flag before any of this