// Copyright 2018 The Fuchsia Authors // // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Always assert to catch changes when lockdep is not enabled. static_assert(sizeof(lockdep_state_t) == sizeof(lockdep::ThreadLockState), "lockdep_state_t and lockdep::ThreadLockState out of sync!"); #if WITH_LOCK_DEP namespace { // Event to wake up the loop detector thread when a new edge is added to the // lock dependency graph. event_t graph_edge_event = EVENT_INITIAL_VALUE(graph_edge_event, false, EVENT_FLAG_AUTOUNSIGNAL); // Loop detection thread. Traverses the lock dependency graph to find circular // lock dependencies. int LockDepThread(void* /*arg*/) { while (true) { __UNUSED zx_status_t error = event_wait(&graph_edge_event); // Add some hysteresis to avoid re-triggering the loop detector on // close successive updates to the graph and to give the inline // validation reports a chance to print out first. thread_sleep_relative(ZX_SEC(2)); lockdep::LoopDetectionPass(); } return 0; } void LockDepInit(unsigned /*level*/) { thread_t* t = thread_create("lockdep", &LockDepThread, NULL, LOW_PRIORITY); thread_detach_and_resume(t); } // Dumps the state of the lock dependency graph. void DumpLockClassState() { printf("Lock class states:\n"); for (auto& state : lockdep::LockClassState::Iter()) { printf(" %s {\n", state.name()); for (lockdep::LockClassId id : state.dependency_set()) { printf(" %s\n", lockdep::LockClassState::GetName(id)); } printf(" }\n"); } printf("\nConnected sets:\n"); for (auto& state : lockdep::LockClassState::Iter()) { // Only handle root nodes in the outer loop. The nested loop will pick // up all of the child nodes under each parent node. if (state.connected_set() == &state) { printf("{\n"); for (auto& other_state : lockdep::LockClassState::Iter()) { if (other_state.connected_set() == &state) printf(" %s\n", other_state.name()); } printf("}\n"); } } } // Top-level lockdep command. int CommandLockDep(int argc, const cmd_args* argv, uint32_t flags) { if (argc < 2) { printf("Not enough arguments:\n"); usage: printf("%s dump : dump lock classes\n", argv[0].str); printf("%s loop : trigger loop detection pass\n", argv[0].str); return -1; } if (strcmp(argv[1].str, "dump") == 0) { DumpLockClassState(); } else if (strcmp(argv[1].str, "loop") == 0) { printf("Triggering loop detection pass:\n"); lockdep::SystemTriggerLoopDetection(); } else { printf("Unrecognized subcommand: '%s'\n", argv[1].str); goto usage; } return 0; } // Utility to cast from lockdep_state_t* to ThreadLockState*. inline lockdep::ThreadLockState* ToThreadLockState(lockdep_state_t* state) { return reinterpret_cast(state); } } // anonymous namespace STATIC_COMMAND_START STATIC_COMMAND("lockdep", "kernel lock diagnostics", &CommandLockDep) STATIC_COMMAND_END(lockdep); LK_INIT_HOOK(lockdep, LockDepInit, LK_INIT_LEVEL_THREADING); namespace lockdep { // Prints a kernel oops when a normal lock order violation is detected. void SystemLockValidationError(AcquiredLockEntry* bad_entry, AcquiredLockEntry* conflicting_entry, ThreadLockState* state, void* caller_address, void* caller_frame, LockResult result) { thread_t* const current_thread = get_current_thread(); char owner_name[THREAD_NAME_LENGTH]; thread_owner_name(current_thread, owner_name); const uint64_t user_pid = current_thread->user_pid; const uint64_t user_tid = current_thread->user_tid; printf("\nZIRCON KERNEL PANIC\n"); printf("Lock validation failed for thread %p pid %" PRIu64 " tid %" PRIu64 " (%s:%s):\n", current_thread, user_pid, user_tid, owner_name, current_thread->name); printf("Reason: %s\n", ToString(result)); printf("Bad lock: name=%s order=%" PRIu64 "\n", LockClassState::GetName(bad_entry->id()), bad_entry->order()); printf("Conflict: name=%s order=%" PRIu64 "\n", LockClassState::GetName(conflicting_entry->id()), conflicting_entry->order()); printf("caller=%p frame=%p\n", caller_address, caller_frame); printf("BUILDID %s\n", version.buildid); // Log the ELF build ID in the format the symbolizer scripts understand. if (version.elf_build_id[0] != '\0') { printf("dso: id=%s base=%#lx name=zircon.elf\n", version.elf_build_id, (uintptr_t)__code_start); } thread_print_current_backtrace_at_frame(caller_frame); printf("\n"); } // Issues a kernel panic when a fatal lock order violation is detected. void SystemLockValidationFatal(AcquiredLockEntry* lock_entry, ThreadLockState* state, void* caller_address, void* caller_frame, LockResult result) { _panic(caller_address, caller_frame, "Fatal lock violation detected! name=%s reason=%s\n", LockClassState::GetName(lock_entry->id()), ToString(result)); } // Prints a kernel oops when a circular lock dependency is detected. void SystemCircularLockDependencyDetected(LockClassState* connected_set_root) { printf("\nZIRCON KERNEL OOPS\n"); printf("Circular lock dependency detected:\n"); for (auto& node : lockdep::LockClassState::Iter()) { if (node.connected_set() == connected_set_root) printf(" %s\n", node.name()); } printf("\n"); } // Returns a pointer to the ThreadLockState instance for the current thread when // thread context or for the current CPU when in irq context. ThreadLockState* SystemGetThreadLockState() { ThreadLockState* state; if (arch_blocking_disallowed()) state = ToThreadLockState(&get_local_percpu()->lock_state); else state = ToThreadLockState(&get_current_thread()->lock_state); return state; } // Initializes an instance of ThreadLockState. void SystemInitThreadLockState(ThreadLockState* state) { new (state) ThreadLockState(); } // Wakes up the loop detector thread to re-evaluate the dependency graph. void SystemTriggerLoopDetection() { event_signal(&graph_edge_event, /*reschedule=*/false); } } // namespace lockdep #endif