| //===----------------------------------------------------------------------===// |
| // |
| // 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/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; |
| } |
| |
| ProcessFreeBSDKernelCore::ProcessFreeBSDKernelCore(lldb::TargetSP target_sp, |
| ListenerSP listener_sp, |
| kvm_t *kvm, |
| const FileSpec &core_file) |
| : PostMortemProcess(target_sp, listener_sp, core_file), m_kvm(kvm) {} |
| |
| ProcessFreeBSDKernelCore::~ProcessFreeBSDKernelCore() { |
| if (m_kvm) |
| kvm_close(m_kvm); |
| } |
| |
| 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) { |
| 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<ProcessFreeBSDKernelCore>(target_sp, listener_sp, |
| kvm, *crash_file); |
| } |
| 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; |
| } |
| |
| Status ProcessFreeBSDKernelCore::DoLoadCore() { |
| // The core is already loaded by CreateInstance(). |
| 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() { 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 |
| 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"); |
| |
| // 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. New processes are added |
| // to the head of this list. Which means that earlier PIDs are actually at |
| // the end of the list, so we have to walk it backwards. First collect all |
| // the processes in the list order. |
| std::vector<lldb::addr_t> process_addrs; |
| if (lldb::addr_t allproc_addr = FindSymbol("allproc"); |
| allproc_addr != LLDB_INVALID_ADDRESS) { |
| for (lldb::addr_t proc = ReadPointerFromMemory(allproc_addr, error); |
| proc != 0 && proc != LLDB_INVALID_ADDRESS && error.Success(); |
| proc = ReadPointerFromMemory(proc + offset_p_list, error)) |
| process_addrs.push_back(proc); |
| } |
| |
| // Processes are in the linked list in descending PID order, so we must walk |
| // them in reverse to get ascending PID order. |
| for (auto proc_it = process_addrs.rbegin(); proc_it != process_addrs.rend(); |
| ++proc_it) { |
| lldb::addr_t proc = *proc_it; |
| 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) { |
| // 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)); |
| } |
| } |
| } 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::PrintUnreadMessage() { |
| Target &target = GetTarget(); |
| Debugger &debugger = target.GetDebugger(); |
| |
| if (!debugger.GetCommandInterpreter().IsInteractive()) |
| return; |
| |
| 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.Success() || 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::Object), |
| "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.Success() || bufp == LLDB_INVALID_ADDRESS) |
| return; |
| |
| uint32_t size = |
| ReadUnsignedIntegerFromMemory(msgbufp + offset_msg_size, 4, 0, error); |
| if (!error.Success() || size == 0) |
| return; |
| |
| uint32_t wseq = |
| ReadUnsignedIntegerFromMemory(msgbufp + offset_msg_wseq, 4, 0, error); |
| if (!error.Success()) |
| return; |
| |
| uint32_t rseq = |
| ReadUnsignedIntegerFromMemory(msgbufp + offset_msg_rseq, 4, 0, error); |
| if (!error.Success()) |
| 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); } |