blob: 96594d1ce70b2431c81dc1e596e15e630bd6f283 [file]
//===----------------------------------------------------------------------===//
//
// 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 <optional>
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSystem.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 "Plugins/ObjectFile/ELF/ObjectFileELF.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;
}
static bool SetKernelDebugFile(const ModuleSP &module) {
if (!module)
return false;
FileSpec object_file = module->GetFileSpec();
FileSystem::Instance().Resolve(object_file);
FileSpec symbol_file = module->GetSymbolFileFileSpec();
if (symbol_file)
FileSystem::Instance().Resolve(symbol_file);
if (symbol_file && symbol_file != object_file)
return false;
auto *elf = llvm::dyn_cast_or_null<ObjectFileELF>(module->GetObjectFile());
if (!elf)
return false;
// GetDebugLink() returns information collected while parsing section
// headers, which might not have happened when a PT_NOTE supplied the UUID.
(void)elf->GetSectionList();
std::optional<FileSpec> debug_link = elf->GetDebugLink();
if (!debug_link || debug_link->GetFilename().empty())
return false;
// The kernel's linker_files list may require symbols from kernel.debug to
// locate. Infer an extracted filesystem's root from the local kernel path
// and load its FreeBSD-installed debug file before the dynamic loader runs.
std::string object_path = object_file.GetPath();
FileSpec sysroot = object_file;
while (sysroot.RemoveLastPathComponent()) {
std::string sysroot_path = sysroot.GetPath();
llvm::StringRef relative_path(object_path);
if (!relative_path.consume_front(sysroot_path))
continue;
relative_path = relative_path.ltrim("/\\");
FileSpec relative_file(relative_path);
FileSpec debug_file = sysroot;
debug_file.AppendPathComponent("usr/lib/debug");
if (!relative_file.GetDirectory().empty())
debug_file.AppendPathComponent(relative_file.GetDirectory());
debug_file.AppendPathComponent(debug_link->GetFilename());
if (!FileSystem::Instance().Exists(debug_file))
continue;
module->SetSymbolFileFileSpec(debug_file);
return true;
}
return false;
}
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");
bool symbols_changed = SetKernelDebugFile(executable);
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();
if (symbols_changed) {
ModuleList modules;
modules.Append(executable);
GetTarget().SymbolsDidLoad(modules);
}
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); }