Kernel panic is a special case, and there is no signal or exception for that so we need to rely on special workaround called `dumptid`. FreeBSDKernel plugin is supposed to find this thread and set it manually through `SetStopInfo()` in `CalculateStopInfo()` like Mach core plugin does. Before (We had to find and select crashed thread list otherwise thread 1 was selected by default): ``` ➜ sudo lldb /boot/panic/kernel -c /var/crash/vmcore.last (lldb) target create "/boot/panic/kernel" --core "/var/crash/vmcore.last" Core file '/var/crash/vmcore.last' (x86_64) was loaded. (lldb) bt * thread #1, name = '(pid 12991) dtrace' * frame #0: 0xffffffff80bf9322 kernel`sched_switch(td=0xfffff8015882f780, flags=259) at sched_ule.c:2448:26 frame #1: 0xffffffff80bd38d2 kernel`mi_switch(flags=259) at kern_synch.c:530:2 frame #2: 0xffffffff80c29799 kernel`sleepq_switch(wchan=0xfffff8014edff300, pri=0) at subr_sleepqueue.c:608:2 frame #3: 0xffffffff80c29b76 kernel`sleepq_catch_signals(wchan=0xfffff8014edff300, pri=0) at subr_sleepqueue.c:523:3 frame #4: 0xffffffff80c29d32 kernel`sleepq_timedwait_sig(wchan=<unavailable>, pri=<unavailable>) at subr_sleepqueue.c:704:11 frame #5: 0xffffffff80bd2e2d kernel`_sleep(ident=0xfffff8014edff300, lock=0xffffffff81df2880, priority=768, wmesg="uwait", sbt=2573804118162, pr=0, flags=512) at kern_synch.c:215:10 frame #6: 0xffffffff80be8622 kernel`umtxq_sleep(uq=0xfffff8014edff300, wmesg="uwait", timo=0xfffffe0279cb3d20) at kern_umtx.c:843:11 frame #7: 0xffffffff80bef87a kernel`do_wait(td=0xfffff8015882f780, addr=<unavailable>, id=0, timeout=0xfffffe0279cb3d90, compat32=1, is_private=1) at kern_umtx.c:1316:12 frame #8: 0xffffffff80bed264 kernel`__umtx_op_wait_uint_private(td=0xfffff8015882f780, uap=0xfffffe0279cb3dd8, ops=<unavailable>) at kern_umtx.c:3990:10 frame #9: 0xffffffff80beaabe kernel`sys__umtx_op [inlined] kern__umtx_op(td=<unavailable>, obj=<unavailable>, op=<unavailable>, val=<unavailable>, uaddr1=<unavailable>, uaddr2=<unavailable>, ops=<unavailable>) at kern_umtx.c:4999:10 frame #10: 0xffffffff80beaa89 kernel`sys__umtx_op(td=<unavailable>, uap=<unavailable>) at kern_umtx.c:5024:10 frame #11: 0xffffffff81122cd1 kernel`amd64_syscall [inlined] syscallenter(td=0xfffff8015882f780) at subr_syscall.c:165:11 frame #12: 0xffffffff81122c19 kernel`amd64_syscall(td=0xfffff8015882f780, traced=0) at trap.c:1208:2 frame #13: 0xffffffff810f1dbb kernel`fast_syscall_common at exception.S:570 ``` After: ``` ➜ sudo ./build/bin/lldb /boot/panic/kernel -c /var/crash/vmcore.last (lldb) target create "/boot/panic/kernel" --core "/var/crash/vmcore.last" Core file '/var/crash/vmcore.last' (x86_64) was loaded. (lldb) bt * thread #18, name = '(pid 5409) powerd (crashed)', stop reason = kernel panic * frame #0: 0xffffffff80bc6c91 kernel`__curthread at pcpu_aux.h:57:2 [inlined] frame #1: 0xffffffff80bc6c91 kernel`doadump(textdump=0) at kern_shutdown.c:399:2 frame #2: 0xffffffff804b3b7a kernel`db_dump(dummy=<unavailable>, dummy2=<unavailable>, dummy3=<unavailable>, dummy4=<unavailable>) at db_command.c:596:10 frame #3: 0xffffffff804b396d kernel`db_command(last_cmdp=<unavailable>, cmd_table=<unavailable>, dopager=true) at db_command.c:508:3 frame #4: 0xffffffff804b362d kernel`db_command_loop at db_command.c:555:3 frame #5: 0xffffffff804b7026 kernel`db_trap(type=<unavailable>, code=<unavailable>) at db_main.c:267:3 frame #6: 0xffffffff80c16aaf kernel`kdb_trap(type=3, code=0, tf=0xfffffe01b605b930) at subr_kdb.c:790:13 frame #7: 0xffffffff8112154e kernel`trap(frame=<unavailable>) at trap.c:614:8 frame #8: 0xffffffff810f14c8 kernel`calltrap at exception.S:285 frame #9: 0xffffffff81da2290 kernel`cn_devtab + 64 frame #10: 0xfffffe01b605b8b0 frame #11: 0xffffffff84001c43 dtrace.ko`dtrace_panic(format=<unavailable>) at dtrace.c:652:2 frame #12: 0xffffffff84005524 dtrace.ko`dtrace_action_panic(ecb=0xfffff80539cad580) at dtrace.c:7022:2 [inlined] frame #13: 0xffffffff840054de dtrace.ko`dtrace_probe(id=88998, arg0=14343377283488, arg1=<unavailable>, arg2=<unavailable>, arg3=<unavailable>, arg4=<unavailable>) at dtrace.c:7665:6 frame #14: 0xffffffff83e5213d systrace.ko`systrace_probe(sa=<unavailable>, type=<unavailable>, retval=<unavailable>) at systrace.c:226:2 frame #15: 0xffffffff8112318d kernel`syscallenter(td=0xfffff801318d5780) at subr_syscall.c:160:4 [inlined] frame #16: 0xffffffff81123112 kernel`amd64_syscall(td=0xfffff801318d5780, traced=0) at trap.c:1208:2 frame #17: 0xffffffff810f1dbb kernel`fast_syscall_common at exception.S:570 ```
337 lines
12 KiB
C++
337 lines
12 KiB
C++
//===-- ProcessFreeBSDKernel.cpp ------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Target/DynamicLoader.h"
|
|
|
|
#include "Plugins/DynamicLoader/FreeBSD-Kernel/DynamicLoaderFreeBSDKernel.h"
|
|
#include "ProcessFreeBSDKernel.h"
|
|
#include "ThreadFreeBSDKernel.h"
|
|
|
|
#if LLDB_ENABLE_FBSDVMCORE
|
|
#include <fvc.h>
|
|
#endif
|
|
#if defined(__FreeBSD__)
|
|
#include <kvm.h>
|
|
#endif
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
LLDB_PLUGIN_DEFINE(ProcessFreeBSDKernel)
|
|
|
|
namespace {
|
|
|
|
#if LLDB_ENABLE_FBSDVMCORE
|
|
class ProcessFreeBSDKernelFVC : public ProcessFreeBSDKernel {
|
|
public:
|
|
ProcessFreeBSDKernelFVC(lldb::TargetSP target_sp, lldb::ListenerSP listener,
|
|
fvc_t *fvc, const FileSpec &core_file);
|
|
|
|
~ProcessFreeBSDKernelFVC();
|
|
|
|
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
|
lldb_private::Status &error) override;
|
|
|
|
private:
|
|
fvc_t *m_fvc;
|
|
|
|
const char *GetError();
|
|
};
|
|
#endif // LLDB_ENABLE_FBSDVMCORE
|
|
|
|
#if defined(__FreeBSD__)
|
|
class ProcessFreeBSDKernelKVM : public ProcessFreeBSDKernel {
|
|
public:
|
|
ProcessFreeBSDKernelKVM(lldb::TargetSP target_sp, lldb::ListenerSP listener,
|
|
kvm_t *fvc, const FileSpec &core_file);
|
|
|
|
~ProcessFreeBSDKernelKVM();
|
|
|
|
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
|
lldb_private::Status &error) override;
|
|
|
|
private:
|
|
kvm_t *m_kvm;
|
|
|
|
const char *GetError();
|
|
};
|
|
#endif // defined(__FreeBSD__)
|
|
|
|
} // namespace
|
|
|
|
ProcessFreeBSDKernel::ProcessFreeBSDKernel(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp,
|
|
const FileSpec &core_file)
|
|
: PostMortemProcess(target_sp, listener_sp, core_file) {}
|
|
|
|
lldb::ProcessSP ProcessFreeBSDKernel::CreateInstance(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp,
|
|
const FileSpec *crash_file,
|
|
bool can_connect) {
|
|
ModuleSP executable = target_sp->GetExecutableModule();
|
|
if (crash_file && !can_connect && executable) {
|
|
#if LLDB_ENABLE_FBSDVMCORE
|
|
fvc_t *fvc =
|
|
fvc_open(executable->GetFileSpec().GetPath().c_str(),
|
|
crash_file->GetPath().c_str(), nullptr, nullptr, nullptr);
|
|
if (fvc)
|
|
return std::make_shared<ProcessFreeBSDKernelFVC>(target_sp, listener_sp,
|
|
fvc, *crash_file);
|
|
#endif
|
|
|
|
#if defined(__FreeBSD__)
|
|
kvm_t *kvm =
|
|
kvm_open2(executable->GetFileSpec().GetPath().c_str(),
|
|
crash_file->GetPath().c_str(), O_RDONLY, nullptr, nullptr);
|
|
if (kvm)
|
|
return std::make_shared<ProcessFreeBSDKernelKVM>(target_sp, listener_sp,
|
|
kvm, *crash_file);
|
|
#endif
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::Initialize() {
|
|
static llvm::once_flag g_once_flag;
|
|
|
|
llvm::call_once(g_once_flag, []() {
|
|
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
|
GetPluginDescriptionStatic(), CreateInstance);
|
|
});
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::Terminate() {
|
|
PluginManager::UnregisterPlugin(ProcessFreeBSDKernel::CreateInstance);
|
|
}
|
|
|
|
Status ProcessFreeBSDKernel::DoDestroy() { return Status(); }
|
|
|
|
bool ProcessFreeBSDKernel::CanDebug(lldb::TargetSP target_sp,
|
|
bool plugin_specified_by_name) {
|
|
return true;
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::RefreshStateAfterStop() {}
|
|
|
|
bool ProcessFreeBSDKernel::DoUpdateThreadList(ThreadList &old_thread_list,
|
|
ThreadList &new_thread_list) {
|
|
if (old_thread_list.GetSize(false) == 0) {
|
|
// Make up the thread the first time this is called so we can set our one
|
|
// and only core thread state up.
|
|
|
|
// We cannot construct a thread without a register context as that crashes
|
|
// LLDB but we can construct a process without threads to provide minimal
|
|
// memory reading support.
|
|
switch (GetTarget().GetArchitecture().GetMachine()) {
|
|
case llvm::Triple::aarch64:
|
|
case llvm::Triple::x86:
|
|
case llvm::Triple::x86_64:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
Status error;
|
|
|
|
// struct field offsets are written as symbols so that we don't have
|
|
// to figure them out ourselves
|
|
int32_t offset_p_list = ReadSignedIntegerFromMemory(
|
|
FindSymbol("proc_off_p_list"), 4, -1, error);
|
|
int32_t offset_p_pid =
|
|
ReadSignedIntegerFromMemory(FindSymbol("proc_off_p_pid"), 4, -1, error);
|
|
int32_t offset_p_threads = ReadSignedIntegerFromMemory(
|
|
FindSymbol("proc_off_p_threads"), 4, -1, error);
|
|
int32_t offset_p_comm = ReadSignedIntegerFromMemory(
|
|
FindSymbol("proc_off_p_comm"), 4, -1, error);
|
|
|
|
int32_t offset_td_tid = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_tid"), 4, -1, error);
|
|
int32_t offset_td_plist = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_plist"), 4, -1, error);
|
|
int32_t offset_td_pcb = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_pcb"), 4, -1, error);
|
|
int32_t offset_td_oncpu = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_oncpu"), 4, -1, error);
|
|
int32_t offset_td_name = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_name"), 4, -1, error);
|
|
|
|
// fail if we were not able to read any of the offsets
|
|
if (offset_p_list == -1 || offset_p_pid == -1 || offset_p_threads == -1 ||
|
|
offset_p_comm == -1 || offset_td_tid == -1 || offset_td_plist == -1 ||
|
|
offset_td_pcb == -1 || offset_td_oncpu == -1 || offset_td_name == -1)
|
|
return false;
|
|
|
|
// dumptid contains the thread-id of the crashing thread
|
|
// dumppcb contains its PCB
|
|
int32_t dumptid =
|
|
ReadSignedIntegerFromMemory(FindSymbol("dumptid"), 4, -1, error);
|
|
lldb::addr_t dumppcb = FindSymbol("dumppcb");
|
|
|
|
// stoppcbs is an array of PCBs on all CPUs
|
|
// each element is of size pcb_size
|
|
int32_t pcbsize =
|
|
ReadSignedIntegerFromMemory(FindSymbol("pcb_size"), 4, -1, error);
|
|
lldb::addr_t stoppcbs = FindSymbol("stoppcbs");
|
|
|
|
// from FreeBSD sys/param.h
|
|
constexpr size_t fbsd_maxcomlen = 19;
|
|
|
|
// iterate through a linked list of all processes
|
|
// allproc is a pointer to the first list element, p_list field
|
|
// (found at offset_p_list) specifies the next element
|
|
for (lldb::addr_t proc =
|
|
ReadPointerFromMemory(FindSymbol("allproc"), error);
|
|
proc != 0 && proc != LLDB_INVALID_ADDRESS;
|
|
proc = ReadPointerFromMemory(proc + offset_p_list, error)) {
|
|
int32_t pid =
|
|
ReadSignedIntegerFromMemory(proc + offset_p_pid, 4, -1, error);
|
|
// process' command-line string
|
|
char comm[fbsd_maxcomlen + 1];
|
|
ReadCStringFromMemory(proc + offset_p_comm, comm, sizeof(comm), error);
|
|
|
|
// iterate through a linked list of all process' threads
|
|
// the initial thread is found in process' p_threads, subsequent
|
|
// elements are linked via td_plist field
|
|
for (lldb::addr_t td =
|
|
ReadPointerFromMemory(proc + offset_p_threads, error);
|
|
td != 0; td = ReadPointerFromMemory(td + offset_td_plist, error)) {
|
|
int32_t tid =
|
|
ReadSignedIntegerFromMemory(td + offset_td_tid, 4, -1, error);
|
|
lldb::addr_t pcb_addr =
|
|
ReadPointerFromMemory(td + offset_td_pcb, error);
|
|
// whether process was on CPU (-1 if not, otherwise CPU number)
|
|
int32_t oncpu =
|
|
ReadSignedIntegerFromMemory(td + offset_td_oncpu, 4, -2, error);
|
|
// thread name
|
|
char thread_name[fbsd_maxcomlen + 1];
|
|
ReadCStringFromMemory(td + offset_td_name, thread_name,
|
|
sizeof(thread_name), error);
|
|
|
|
// if we failed to read TID, ignore this thread
|
|
if (tid == -1)
|
|
continue;
|
|
|
|
std::string thread_desc = llvm::formatv("(pid {0}) {1}", pid, comm);
|
|
if (*thread_name && strcmp(thread_name, comm)) {
|
|
thread_desc += '/';
|
|
thread_desc += thread_name;
|
|
}
|
|
|
|
// roughly:
|
|
// 1. if the thread crashed, its PCB is going to be at "dumppcb"
|
|
// 2. if the thread was on CPU, its PCB is going to be on the CPU
|
|
// 3. otherwise, its PCB is in the thread struct
|
|
if (tid == dumptid) {
|
|
// NB: dumppcb can be LLDB_INVALID_ADDRESS if reading it failed
|
|
pcb_addr = dumppcb;
|
|
thread_desc += " (crashed)";
|
|
} else if (oncpu != -1) {
|
|
// if we managed to read stoppcbs and pcb_size, use them to find
|
|
// the correct PCB
|
|
if (stoppcbs != LLDB_INVALID_ADDRESS && pcbsize > 0)
|
|
pcb_addr = stoppcbs + oncpu * pcbsize;
|
|
else
|
|
pcb_addr = LLDB_INVALID_ADDRESS;
|
|
thread_desc += llvm::formatv(" (on CPU {0})", oncpu);
|
|
}
|
|
|
|
auto thread =
|
|
new ThreadFreeBSDKernel(*this, tid, pcb_addr, thread_desc);
|
|
|
|
if (tid == dumptid)
|
|
thread->SetIsCrashedThread(true);
|
|
|
|
new_thread_list.AddThread(static_cast<ThreadSP>(thread));
|
|
}
|
|
}
|
|
} else {
|
|
const uint32_t num_threads = old_thread_list.GetSize(false);
|
|
for (uint32_t i = 0; i < num_threads; ++i)
|
|
new_thread_list.AddThread(old_thread_list.GetThreadAtIndex(i, false));
|
|
}
|
|
return new_thread_list.GetSize(false) > 0;
|
|
}
|
|
|
|
Status ProcessFreeBSDKernel::DoLoadCore() {
|
|
// The core is already loaded by CreateInstance().
|
|
return Status();
|
|
}
|
|
|
|
DynamicLoader *ProcessFreeBSDKernel::GetDynamicLoader() {
|
|
if (m_dyld_up.get() == nullptr)
|
|
m_dyld_up.reset(DynamicLoader::FindPlugin(
|
|
this, DynamicLoaderFreeBSDKernel::GetPluginNameStatic()));
|
|
return m_dyld_up.get();
|
|
}
|
|
|
|
lldb::addr_t ProcessFreeBSDKernel::FindSymbol(const char *name) {
|
|
ModuleSP mod_sp = GetTarget().GetExecutableModule();
|
|
const Symbol *sym = mod_sp->FindFirstSymbolWithNameAndType(ConstString(name));
|
|
return sym ? sym->GetLoadAddress(&GetTarget()) : LLDB_INVALID_ADDRESS;
|
|
}
|
|
|
|
#if LLDB_ENABLE_FBSDVMCORE
|
|
|
|
ProcessFreeBSDKernelFVC::ProcessFreeBSDKernelFVC(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp,
|
|
fvc_t *fvc,
|
|
const FileSpec &core_file)
|
|
: ProcessFreeBSDKernel(target_sp, listener_sp, crash_file), m_fvc(fvc) {}
|
|
|
|
ProcessFreeBSDKernelFVC::~ProcessFreeBSDKernelFVC() {
|
|
if (m_fvc)
|
|
fvc_close(m_fvc);
|
|
}
|
|
|
|
size_t ProcessFreeBSDKernelFVC::DoReadMemory(lldb::addr_t addr, void *buf,
|
|
size_t size, Status &error) {
|
|
ssize_t rd = 0;
|
|
rd = fvc_read(m_fvc, addr, buf, size);
|
|
if (rd < 0 || static_cast<size_t>(rd) != size) {
|
|
error = Status::FromErrorStringWithFormat("Reading memory failed: %s",
|
|
GetError());
|
|
return rd > 0 ? rd : 0;
|
|
}
|
|
return rd;
|
|
}
|
|
|
|
const char *ProcessFreeBSDKernelFVC::GetError() { return fvc_geterr(m_fvc); }
|
|
|
|
#endif // LLDB_ENABLE_FBSDVMCORE
|
|
|
|
#if defined(__FreeBSD__)
|
|
|
|
ProcessFreeBSDKernelKVM::ProcessFreeBSDKernelKVM(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp,
|
|
kvm_t *fvc,
|
|
const FileSpec &core_file)
|
|
: ProcessFreeBSDKernel(target_sp, listener_sp, core_file), m_kvm(fvc) {}
|
|
|
|
ProcessFreeBSDKernelKVM::~ProcessFreeBSDKernelKVM() {
|
|
if (m_kvm)
|
|
kvm_close(m_kvm);
|
|
}
|
|
|
|
size_t ProcessFreeBSDKernelKVM::DoReadMemory(lldb::addr_t addr, void *buf,
|
|
size_t size, Status &error) {
|
|
ssize_t rd = 0;
|
|
rd = kvm_read2(m_kvm, addr, buf, size);
|
|
if (rd < 0 || static_cast<size_t>(rd) != size) {
|
|
error = Status::FromErrorStringWithFormat("Reading memory failed: %s",
|
|
GetError());
|
|
return rd > 0 ? rd : 0;
|
|
}
|
|
return rd;
|
|
}
|
|
|
|
const char *ProcessFreeBSDKernelKVM::GetError() { return kvm_geterr(m_kvm); }
|
|
|
|
#endif // defined(__FreeBSD__)
|