| //===----------------------------------------------------------------------===// |
| // |
| // 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 "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 = |
| kvm_open2(executable->GetFileSpec().GetPath().c_str(), |
| crash_file->GetPath().c_str(), O_RDONLY, errbuf, nullptr); |
| 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]; |
| m_kvm = kvm_open2(executable->GetFileSpec().GetPath().c_str(), |
| GetCoreFile().GetPath().c_str(), O_RDWR, errbuf, nullptr); |
| |
| 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"); |
| |
| // 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; |
| for (lldb::addr_t proc = ReadPointerFromMemory(allproc_addr, error); |
| error.Success() && proc != 0 && proc != LLDB_INVALID_ADDRESS; |
| proc = ReadPointerFromMemory(proc + offset_p_list, error)) { |
| int32_t pid = |
| ReadSignedIntegerFromMemory(proc + offset_p_pid, 4, -1, error); |
| if (error.Fail()) |
| return false; |
| process_addrs.emplace_back(proc, pid); |
| } |
| |
| if (error.Fail()) |
| 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. |
| for (lldb::addr_t td = |
| ReadPointerFromMemory(proc + offset_p_threads, error); |
| error.Success() && td != 0; |
| td = ReadPointerFromMemory(td + offset_td_plist, error)) { |
| int32_t tid = |
| ReadSignedIntegerFromMemory(td + offset_td_tid, 4, -1, error); |
| if (error.Fail()) |
| continue; |
| |
| lldb::addr_t pcb_addr = |
| ReadPointerFromMemory(td + offset_td_pcb, error); |
| if (error.Fail()) |
| continue; |
| |
| // 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 (error.Fail()) |
| 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(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; |
| } |
| |
| 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; |
| } |
| |
| void ProcessFreeBSDKernelCore::SetKernelDisplacement() { |
| kssize_t displacement = kvm_kerndisp(m_kvm); |
| |
| if (displacement == 0) |
| return; |
| |
| 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 |
| lldb::addr_t msgbufp = ReadPointerFromMemory(msgbufp_addr, error); |
| if (error.Fail() || msgbufp == LLDB_INVALID_ADDRESS) |
| return; |
| |
| // 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 |
| lldb::addr_t bufp = ReadPointerFromMemory(msgbufp + offset_msg_ptr, error); |
| if (error.Fail() || bufp == LLDB_INVALID_ADDRESS) |
| return; |
| |
| 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); } |