| //===----------------------------------------------------------------------===// | |
| // | |
| // 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/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/CommandObjectMultiword.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Symbol/Type.h" | |
| #include "lldb/Target/DynamicLoader.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/SaveAndRestore.h" | |
| #include "Plugins/DynamicLoader/FreeBSD-Kernel/DynamicLoaderFreeBSDKernel.h" | |
| #include "ProcessFreeBSDKernelCore.h" | |
| #include "ThreadFreeBSDKernelCore.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| LLDB_PLUGIN_DEFINE(ProcessFreeBSDKernelCore) | |
| namespace { | |
| #define LLDB_PROPERTIES_processfreebsdkernelcore | |
| #include "ProcessFreeBSDKernelCoreProperties.inc" | |
| enum { | |
| #define LLDB_PROPERTIES_processfreebsdkernelcore | |
| #include "ProcessFreeBSDKernelCorePropertiesEnum.inc" | |
| }; | |
| class PluginProperties : public Properties { | |
| public: | |
| static llvm::StringRef GetSettingName() { | |
| return ProcessFreeBSDKernelCore::GetPluginNameStatic(); | |
| } | |
| PluginProperties() : Properties() { | |
| m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); | |
| m_collection_sp->Initialize(g_processfreebsdkernelcore_properties_def); | |
| } | |
| ~PluginProperties() override = default; | |
| bool GetReadOnly() const { | |
| const uint32_t idx = ePropertyReadOnly; | |
| return GetPropertyAtIndexAs<bool>(idx, true); | |
| } | |
| }; | |
| } // namespace | |
| static PluginProperties &GetGlobalPluginProperties() { | |
| static PluginProperties g_settings; | |
| return g_settings; | |
| } | |
| class CommandObjectProcessFreeBSDKernelCoreRefreshThreads | |
| : public CommandObjectParsed { | |
| public: | |
| CommandObjectProcessFreeBSDKernelCoreRefreshThreads( | |
| CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "process plugin refresh-threads", | |
| "Refresh the thread list from the FreeBSD kernel core. The thread " | |
| "list and related data structures may be being read from live " | |
| "memory (/dev/mem), which may have changed since the last refresh. " | |
| "This command clears LLDB's thread list and memory cache then " | |
| "re-reads the kernel's allproc/zombie lists to rebuild the thread " | |
| "list from scratch.", | |
| "process plugin refresh-threads", | |
| eCommandRequiresProcess | eCommandTryTargetAPILock) {} | |
| ~CommandObjectProcessFreeBSDKernelCoreRefreshThreads() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| // TODO: Return early for elf-core based implementation. | |
| auto process = static_cast<ProcessFreeBSDKernelCore *>( | |
| m_interpreter.GetExecutionContext().GetProcessPtr()); | |
| // Clear the memory cache so DoUpdateThreadList() will re-read allproc, | |
| // zombproc, and all thread/proc structures fresh from the core dump instead | |
| // of getting stale cached values. | |
| process->m_memory_cache.Clear(); | |
| // Clear both thread lists to guarantee that UpdateThreadListIfNeeded() sees | |
| // size == 0 and enters the rebuild path regardless of stop-ID state. | |
| // UpdateThreadListIfNeeded() passes m_thread_list_real as old_thread_list | |
| // to DoUpdateThreadList(), and DoUpdateThreadList() only rebuilds from | |
| // scratch when old_thread_list is empty. m_thread_list is the public copy | |
| // that is sync'd from m_thread_list_real afterwards. | |
| process->m_thread_list_real.Clear(); | |
| process->m_thread_list.Clear(); | |
| // This calls UpdateThreadListIfNeeded() to rebuild the process thread list. | |
| const uint32_t num_threads = | |
| process->GetThreadList().GetSize(/*can_update=*/true); | |
| result.AppendMessageWithFormatv( | |
| "Thread list refreshed, {0} thread{1} found.", num_threads, | |
| num_threads == 1 ? "" : "s"); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| ProcessFreeBSDKernelCore::ProcessFreeBSDKernelCore(lldb::TargetSP target_sp, | |
| ListenerSP listener_sp, | |
| const FileSpec &core_file) | |
| : PostMortemProcess(target_sp, listener_sp, core_file) {} | |
| ProcessFreeBSDKernelCore::~ProcessFreeBSDKernelCore() { | |
| m_thread_list.Clear(); | |
| // We need to call finalize on the process before destroying ourselves to | |
| // make sure all of the broadcaster cleanup goes as planned. If we destruct | |
| // this class, then Process::~Process() might have problems trying to fully | |
| // destroy the broadcaster. | |
| Finalize(/*destructing=*/true); | |
| } | |
| lldb::ProcessSP ProcessFreeBSDKernelCore::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) { | |
| char errbuf[_POSIX2_LINE_MAX]; | |
| kvm_t *kvm = OpenKVM(executable, *crash_file, O_RDONLY, errbuf); | |
| if (kvm) { | |
| kvm_close(kvm); | |
| return std::make_shared<ProcessFreeBSDKernelCore>(target_sp, listener_sp, | |
| *crash_file); | |
| } | |
| LLDB_LOGF(GetLog(LLDBLog::Process), "FreeBSD-Kernel-Core: %s", errbuf); | |
| } | |
| return nullptr; | |
| } | |
| void ProcessFreeBSDKernelCore::Initialize() { | |
| PluginManager::RegisterPlugin(GetPluginNameStatic(), | |
| GetPluginDescriptionStatic(), CreateInstance, | |
| DebuggerInitialize); | |
| } | |
| void ProcessFreeBSDKernelCore::DebuggerInitialize(Debugger &debugger) { | |
| if (!PluginManager::GetSettingForProcessPlugin( | |
| debugger, PluginProperties::GetSettingName())) { | |
| const bool is_global_setting = true; | |
| PluginManager::CreateSettingForProcessPlugin( | |
| debugger, GetGlobalPluginProperties().GetValueProperties(), | |
| "Properties for the freebsd-kernel process plug-in.", | |
| is_global_setting); | |
| } | |
| } | |
| void ProcessFreeBSDKernelCore::Terminate() { | |
| PluginManager::UnregisterPlugin(ProcessFreeBSDKernelCore::CreateInstance); | |
| } | |
| bool ProcessFreeBSDKernelCore::CanDebug(lldb::TargetSP target_sp, | |
| bool plugin_specified_by_name) { | |
| return true; | |
| } | |
| CommandObject *ProcessFreeBSDKernelCore::GetPluginCommandObject() { | |
| if (!m_command_sp) { | |
| CommandInterpreter &interp = | |
| GetTarget().GetDebugger().GetCommandInterpreter(); | |
| m_command_sp = std::make_unique<CommandObjectMultiword>( | |
| interp, "process plugin", | |
| "Commands for the FreeBSD kernel process plug-in.", | |
| "process plugin <subcommand> [<subcommand-options>]"); | |
| m_command_sp->LoadSubCommand( | |
| "refresh-threads", | |
| CommandObjectSP( | |
| new CommandObjectProcessFreeBSDKernelCoreRefreshThreads(interp))); | |
| } | |
| return m_command_sp.get(); | |
| } | |
| Status ProcessFreeBSDKernelCore::DoLoadCore() { | |
| ModuleSP executable = GetTarget().GetExecutableModule(); | |
| if (!executable) | |
| return Status::FromErrorString( | |
| "ProcessFreeBSDKernelCore: no executable module set on target"); | |
| char errbuf[_POSIX2_LINE_MAX]; | |
| const int flags = | |
| GetGlobalPluginProperties().GetReadOnly() ? O_RDONLY : O_RDWR; | |
| m_kvm = OpenKVM(executable, GetCoreFile(), flags, errbuf); | |
| if (!m_kvm) { | |
| LLDB_LOGF(GetLog(LLDBLog::Process), "FreeBSD-Kernel-Core: %s", errbuf); | |
| return Status::FromErrorStringWithFormat( | |
| "ProcessFreeBSDKernelCore: kvm_open2 failed for core '%s' " | |
| "with kernel '%s'", | |
| GetCoreFile().GetPath().c_str(), | |
| executable->GetFileSpec().GetPath().c_str()); | |
| } | |
| SetKernelDisplacement(); | |
| return Status(); | |
| } | |
| DynamicLoader *ProcessFreeBSDKernelCore::GetDynamicLoader() { | |
| if (m_dyld_up.get() == nullptr) | |
| m_dyld_up.reset(DynamicLoader::FindPlugin( | |
| this, DynamicLoaderFreeBSDKernel::GetPluginNameStatic())); | |
| return m_dyld_up.get(); | |
| } | |
| Status ProcessFreeBSDKernelCore::DoDestroy() { | |
| if (!m_kvm) | |
| return Status::FromErrorString("kvm file descriptor is not set."); | |
| kvm_close(m_kvm); | |
| return Status(); | |
| } | |
| void ProcessFreeBSDKernelCore::RefreshStateAfterStop() { | |
| if (!m_printed_unread_message) { | |
| PrintUnreadMessage(); | |
| m_printed_unread_message = true; | |
| } | |
| } | |
| size_t ProcessFreeBSDKernelCore::DoWriteMemory(lldb::addr_t addr, | |
| const void *buf, size_t size, | |
| Status &error) { | |
| if (GetGlobalPluginProperties().GetReadOnly()) { | |
| error = Status::FromErrorString( | |
| "Memory writes are currently disabled. You can enable them with " | |
| "`settings set plugin.process.freebsd-kernel-core.read-only false`."); | |
| return 0; | |
| } | |
| ssize_t rd = 0; | |
| rd = kvm_write(m_kvm, addr, buf, size); | |
| if (rd < 0 || static_cast<size_t>(rd) != size) { | |
| error = Status::FromErrorStringWithFormat("Writing memory failed: %s", | |
| GetError()); | |
| return rd > 0 ? rd : 0; | |
| } | |
| return rd; | |
| } | |
| bool ProcessFreeBSDKernelCore::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::arm: | |
| case llvm::Triple::aarch64: | |
| case llvm::Triple::ppc64le: | |
| case llvm::Triple::riscv64: | |
| 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 | |
| // Process-related offsets: | |
| int32_t offset_p_list = ReadSignedIntegerFromMemory( | |
| FindSymbol("proc_off_p_list"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_p_pid = | |
| ReadSignedIntegerFromMemory(FindSymbol("proc_off_p_pid"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_p_threads = ReadSignedIntegerFromMemory( | |
| FindSymbol("proc_off_p_threads"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_p_comm = ReadSignedIntegerFromMemory( | |
| FindSymbol("proc_off_p_comm"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| // Thread-related offsets: | |
| int32_t offset_td_tid = ReadSignedIntegerFromMemory( | |
| FindSymbol("thread_off_td_tid"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_td_plist = ReadSignedIntegerFromMemory( | |
| FindSymbol("thread_off_td_plist"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_td_pcb = ReadSignedIntegerFromMemory( | |
| FindSymbol("thread_off_td_pcb"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_td_oncpu = ReadSignedIntegerFromMemory( | |
| FindSymbol("thread_off_td_oncpu"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| int32_t offset_td_name = ReadSignedIntegerFromMemory( | |
| FindSymbol("thread_off_td_name"), 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| // 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); | |
| if (error.Fail()) | |
| return false; | |
| 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); | |
| if (error.Fail()) | |
| return false; | |
| lldb::addr_t stoppcbs = FindSymbol("stoppcbs"); | |
| // In later FreeBSD versions stoppcbs is a pointer to the array. | |
| int32_t osreldate = | |
| ReadSignedIntegerFromMemory(FindSymbol("osreldate"), 4, -1, error); | |
| if (stoppcbs != LLDB_INVALID_ADDRESS && osreldate >= 1400089) { | |
| llvm::Expected<lldb::addr_t> stoppcbs_or_err = | |
| ReadPointerFromMemory(stoppcbs); | |
| if (!stoppcbs_or_err || *stoppcbs_or_err == 0) { | |
| LLDB_LOGF(GetLog(LLDBLog::Process), | |
| "FreeBSD-Kernel-Core: Could not find stoppcbs"); | |
| return false; | |
| } | |
| stoppcbs = *stoppcbs_or_err; | |
| } | |
| // Read stopped_cpus bitmask and mp_maxid for CPU validation. | |
| lldb::addr_t stopped_cpus = FindSymbol("stopped_cpus"); | |
| uint32_t mp_maxid = 0; | |
| if (stopped_cpus != LLDB_INVALID_ADDRESS) { | |
| // https://cgit.freebsd.org/src/tree/sys/kern/subr_smp.c | |
| mp_maxid = | |
| ReadSignedIntegerFromMemory(FindSymbol("mp_maxid"), 4, 0, error); | |
| if (error.Fail()) | |
| stopped_cpus = LLDB_INVALID_ADDRESS; | |
| } | |
| uint32_t long_size_bytes = GetAddressByteSize(); | |
| uint32_t long_bit = long_size_bytes * 8; | |
| if (auto type_system_or_err = | |
| GetTarget().GetScratchTypeSystemForLanguage(eLanguageTypeC)) { | |
| CompilerType long_type = | |
| (*type_system_or_err)->GetBasicTypeFromAST(eBasicTypeLong); | |
| if (long_type.IsValid()) | |
| if (auto size = long_type.GetByteSize(nullptr)) | |
| long_size_bytes = *size; | |
| long_bit = long_size_bytes * 8; | |
| } else | |
| llvm::consumeError(type_system_or_err.takeError()); | |
| // https://cgit.freebsd.org/src/tree/sys/sys/param.h | |
| constexpr size_t fbsd_maxcomlen = 19; | |
| // Iterate through a linked list of all processes then order incrementally | |
| // by pid. Though new processes are added to the head of this list, process | |
| // ids may be reused as well. So we cannot rely on it being in a particular | |
| // order. | |
| const lldb::addr_t allproc_addr = FindSymbol("allproc"); | |
| if (allproc_addr == LLDB_INVALID_ADDRESS) | |
| return false; | |
| std::vector<std::pair<lldb::addr_t, int32_t>> process_addrs; | |
| llvm::Expected<lldb::addr_t> proc_or_err = | |
| ReadPointerFromMemory(allproc_addr); | |
| for (; proc_or_err && *proc_or_err != 0; | |
| proc_or_err = ReadPointerFromMemory(*proc_or_err + offset_p_list)) { | |
| lldb::addr_t proc = *proc_or_err; | |
| int32_t pid = | |
| ReadSignedIntegerFromMemory(proc + offset_p_pid, 4, -1, error); | |
| if (error.Fail()) | |
| return false; | |
| process_addrs.emplace_back(proc, pid); | |
| } | |
| if (!proc_or_err) { | |
| llvm::consumeError(proc_or_err.takeError()); | |
| return false; | |
| } | |
| std::sort(process_addrs.begin(), process_addrs.end(), | |
| [](const auto &a, const auto &b) { return a.second < b.second; }); | |
| for (auto [proc, pid] : process_addrs) { | |
| // process' command-line string | |
| char comm[fbsd_maxcomlen + 1]; | |
| ReadCStringFromMemory(proc + offset_p_comm, comm, sizeof(comm), error); | |
| if (error.Fail()) | |
| continue; | |
| // 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. | |
| // If reading memory fails, skip to the next thread. | |
| llvm::Expected<lldb::addr_t> td_or_err = | |
| ReadPointerFromMemory(proc + offset_p_threads); | |
| for (; td_or_err && *td_or_err != 0; | |
| td_or_err = ReadPointerFromMemory(*td_or_err + offset_td_plist)) { | |
| lldb::addr_t td = *td_or_err; | |
| int32_t tid = | |
| ReadSignedIntegerFromMemory(td + offset_td_tid, 4, -1, error); | |
| if (error.Fail()) | |
| continue; | |
| llvm::Expected<lldb::addr_t> pcb_addr_or_err = | |
| ReadPointerFromMemory(td + offset_td_pcb); | |
| if (!pcb_addr_or_err) { | |
| llvm::consumeError(pcb_addr_or_err.takeError()); | |
| continue; | |
| } | |
| lldb::addr_t pcb_addr = *pcb_addr_or_err; | |
| // whether process was on CPU (-1 if not, otherwise CPU number) | |
| int32_t oncpu = | |
| ReadSignedIntegerFromMemory(td + offset_td_oncpu, 4, -2, error); | |
| if (error.Fail()) | |
| continue; | |
| // thread name | |
| char thread_name[fbsd_maxcomlen + 1]; | |
| ReadCStringFromMemory(td + offset_td_name, thread_name, | |
| sizeof(thread_name), error); | |
| if (error.Fail()) | |
| continue; | |
| // 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) { | |
| // Verify the CPU is actually in the stopped set before using | |
| // its stoppcbs entry. | |
| bool is_stopped = false; | |
| if (oncpu >= 0 && static_cast<uint32_t>(oncpu) <= mp_maxid && | |
| stopped_cpus != LLDB_INVALID_ADDRESS) { | |
| uint32_t bit = oncpu % long_bit; | |
| uint32_t word = oncpu / long_bit; | |
| lldb::addr_t mask_addr = stopped_cpus + word * long_size_bytes; | |
| uint64_t mask = ReadUnsignedIntegerFromMemory( | |
| mask_addr, long_size_bytes, 0, error); | |
| if (error.Success()) | |
| is_stopped = (mask & (1ULL << bit)) != 0; | |
| } | |
| // If we managed to read stoppcbs and pcb_size and the cpu is marked | |
| // as stopped, use them to find the correct PCB. | |
| if (is_stopped && 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 ThreadFreeBSDKernelCore(*this, tid, pcb_addr, thread_desc); | |
| if (tid == dumptid) | |
| thread->SetIsCrashedThread(true); | |
| new_thread_list.AddThread(static_cast<ThreadSP>(thread)); | |
| } | |
| // If reading thread list has failed, return with false. | |
| if (!td_or_err) { | |
| llvm::consumeError(td_or_err.takeError()); | |
| return false; | |
| } | |
| } | |
| } 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; | |
| } | |
| size_t | |
| ProcessFreeBSDKernelCore::DoReadMemory(const ProcessAddress &process_addr, | |
| void *buf, size_t size, Status &error) { | |
| lldb::addr_t addr = process_addr.GetValue(); | |
| 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; | |
| } | |
| lldb::addr_t ProcessFreeBSDKernelCore::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; | |
| } | |
| int ProcessFreeBSDKernelCore::ResolveKVMSymbol(const char *name, | |
| kvaddr_t *value) { | |
| if (!g_kvm_kernel_module) | |
| return 1; | |
| const Symbol *symbol = | |
| g_kvm_kernel_module->FindFirstSymbolWithNameAndType(ConstString(name)); | |
| if (!symbol) | |
| return 1; | |
| lldb::addr_t address = symbol->GetFileAddress(); | |
| if (address == LLDB_INVALID_ADDRESS) | |
| return 1; | |
| *value = address; | |
| return 0; | |
| } | |
| kvm_t *ProcessFreeBSDKernelCore::OpenKVM(const ModuleSP &kernel_module, | |
| const FileSpec &core_file, int flags, | |
| char *errbuf) { | |
| llvm::SaveAndRestore resolver_module(g_kvm_kernel_module, | |
| kernel_module.get()); | |
| return kvm_open2(kernel_module->GetFileSpec().GetPath().c_str(), | |
| core_file.GetPath().c_str(), flags, errbuf, | |
| ResolveKVMSymbol); | |
| } | |
| void ProcessFreeBSDKernelCore::SetKernelDisplacement() { | |
| kssize_t displacement = kvm_kerndisp(m_kvm); | |
| Target &target = GetTarget(); | |
| lldb::ModuleSP kernel_module_sp = target.GetExecutableModule(); | |
| if (!kernel_module_sp) | |
| return; | |
| bool changed = false; | |
| kernel_module_sp->SetLoadAddress(target, | |
| static_cast<lldb::addr_t>(displacement), | |
| /*value_is_offset=*/true, changed); | |
| if (changed) { | |
| ModuleList loaded_module_list; | |
| loaded_module_list.Append(kernel_module_sp); | |
| target.ModulesDidLoad(loaded_module_list); | |
| } | |
| } | |
| void ProcessFreeBSDKernelCore::PrintUnreadMessage() { | |
| Target &target = GetTarget(); | |
| Debugger &debugger = target.GetDebugger(); | |
| Status error; | |
| // Find msgbufp symbol (pointer to message buffer) | |
| lldb::addr_t msgbufp_addr = FindSymbol("msgbufp"); | |
| if (msgbufp_addr == LLDB_INVALID_ADDRESS) | |
| return; | |
| // Read the pointer value | |
| llvm::Expected<lldb::addr_t> msgbufp_or_err = | |
| ReadPointerFromMemory(msgbufp_addr); | |
| if (!msgbufp_or_err) { | |
| llvm::consumeError(msgbufp_or_err.takeError()); | |
| return; | |
| } | |
| lldb::addr_t msgbufp = *msgbufp_or_err; | |
| // Get the type information for struct msgbuf from DWARF | |
| TypeQuery query("msgbuf"); | |
| TypeResults results; | |
| target.GetImages().FindTypes(nullptr, query, results); | |
| uint64_t offset_msg_ptr = 0; | |
| uint64_t offset_msg_size = 0; | |
| uint64_t offset_msg_wseq = 0; | |
| uint64_t offset_msg_rseq = 0; | |
| if (results.GetTypeMap().GetSize() > 0) { | |
| // Found type info - use it to get field offsets | |
| CompilerType msgbuf_type = | |
| results.GetTypeMap().GetTypeAtIndex(0)->GetForwardCompilerType(); | |
| uint32_t num_fields = msgbuf_type.GetNumFields(); | |
| int field_found = 0; | |
| for (uint32_t i = 0; i < num_fields; i++) { | |
| std::string field_name; | |
| uint64_t field_offset = 0; | |
| msgbuf_type.GetFieldAtIndex(i, field_name, &field_offset, nullptr, | |
| nullptr); | |
| if (field_name == "msg_ptr") { | |
| offset_msg_ptr = field_offset / 8; // Convert bits to bytes | |
| field_found++; | |
| } else if (field_name == "msg_size") { | |
| offset_msg_size = field_offset / 8; | |
| field_found++; | |
| } else if (field_name == "msg_wseq") { | |
| offset_msg_wseq = field_offset / 8; | |
| field_found++; | |
| } else if (field_name == "msg_rseq") { | |
| offset_msg_rseq = field_offset / 8; | |
| field_found++; | |
| } | |
| } | |
| if (field_found != 4) { | |
| LLDB_LOGF( | |
| GetLog(LLDBLog::Process), | |
| "FreeBSD-Kernel-Core: Could not find all required fields for msgbuf"); | |
| return; | |
| } | |
| } else { | |
| // Fallback: use hardcoded offsets based on struct layout | |
| // struct msgbuf layout (from sys/sys/msgbuf.h): | |
| // char *msg_ptr; - offset 0 | |
| // u_int msg_magic; - offset ptr_size | |
| // u_int msg_size; - offset ptr_size + 4 | |
| // u_int msg_wseq; - offset ptr_size + 8 | |
| // u_int msg_rseq; - offset ptr_size + 12 | |
| uint32_t ptr_size = GetAddressByteSize(); | |
| offset_msg_ptr = 0; | |
| offset_msg_size = ptr_size + 4; | |
| offset_msg_wseq = ptr_size + 8; | |
| offset_msg_rseq = ptr_size + 12; | |
| } | |
| // Read struct msgbuf fields | |
| llvm::Expected<lldb::addr_t> bufp_or_err = | |
| ReadPointerFromMemory(msgbufp + offset_msg_ptr); | |
| if (!bufp_or_err) { | |
| llvm::consumeError(bufp_or_err.takeError()); | |
| return; | |
| } | |
| lldb::addr_t bufp = *bufp_or_err; | |
| uint32_t size = | |
| ReadUnsignedIntegerFromMemory(msgbufp + offset_msg_size, 4, 0, error); | |
| if (error.Fail() || size == 0) | |
| return; | |
| uint32_t wseq = | |
| ReadUnsignedIntegerFromMemory(msgbufp + offset_msg_wseq, 4, 0, error); | |
| if (error.Fail()) | |
| return; | |
| uint32_t rseq = | |
| ReadUnsignedIntegerFromMemory(msgbufp + offset_msg_rseq, 4, 0, error); | |
| if (error.Fail()) | |
| return; | |
| // Convert sequences to positions | |
| // MSGBUF_SEQ_TO_POS macro in FreeBSD: ((seq) % (size)) | |
| uint32_t rseq_pos = rseq % size; | |
| uint32_t wseq_pos = wseq % size; | |
| if (rseq_pos == wseq_pos) | |
| return; | |
| // Print crash info at once using stream | |
| lldb::StreamSP stream_sp = debugger.GetAsyncOutputStream(); | |
| if (!stream_sp) | |
| return; | |
| stream_sp->PutCString("\nUnread portion of the kernel message buffer:\n"); | |
| // Read ring buffer in at most two chunks | |
| if (rseq_pos < wseq_pos) { | |
| // No wrap: read from rseq_pos to wseq_pos | |
| size_t len = wseq_pos - rseq_pos; | |
| std::string buf(len, '\0'); | |
| size_t bytes_read = ReadMemory(bufp + rseq_pos, &buf[0], len, error); | |
| if (error.Success() && bytes_read > 0) { | |
| buf.resize(bytes_read); | |
| *stream_sp << buf; | |
| } | |
| } else { | |
| // Wrap around: read from rseq_pos to end, then from start to wseq_pos | |
| size_t len1 = size - rseq_pos; | |
| std::string buf1(len1, '\0'); | |
| size_t bytes_read1 = ReadMemory(bufp + rseq_pos, &buf1[0], len1, error); | |
| if (error.Success() && bytes_read1 > 0) { | |
| buf1.resize(bytes_read1); | |
| *stream_sp << buf1; | |
| } | |
| if (wseq_pos > 0) { | |
| std::string buf2(wseq_pos, '\0'); | |
| size_t bytes_read2 = ReadMemory(bufp, &buf2[0], wseq_pos, error); | |
| if (error.Success() && bytes_read2 > 0) { | |
| buf2.resize(bytes_read2); | |
| *stream_sp << buf2; | |
| } | |
| } | |
| } | |
| stream_sp->PutChar('\n'); | |
| stream_sp->Flush(); | |
| } | |
| const char *ProcessFreeBSDKernelCore::GetError() { return kvm_geterr(m_kvm); } |