| //===-- 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); |
| } |
| |
| ThreadSP thread_sp{ |
| new ThreadFreeBSDKernel(*this, tid, pcb_addr, thread_desc)}; |
| new_thread_list.AddThread(thread_sp); |
| } |
| } |
| } 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__) |