This is equivalent of kgdb_dmesg() in fbsd-kvm.c in FreeBSD kgdb(1) port. Unread kernel messages is only printed in interactive mode (i.e. not in batch mode) to mimic KGDB's behaviour. Example output: ``` ➜ sudo ./build/bin/lldb /boot/kernel/kernel -c /var/crash/vmcore.last (lldb) target create "/boot/kernel/kernel" --core "/var/crash/vmcore.last" Unread portion of the kernel message buffer: panic: kdb_sysctl_panic cpuid = 1 time = 1769364579 KDB: stack backtrace: db_trace_self_wrapper() at db_trace_self_wrapper+0x2b/frame 0xfffffe01b435fa20 vpanic() at vpanic+0x136/frame 0xfffffe01b435fb50 panic() at panic+0x43/frame 0xfffffe01b435fbb0 kdb_sysctl_panic() at kdb_sysctl_panic+0x63/frame 0xfffffe01b435fbe0 sysctl_root_handler_locked() at sysctl_root_handler_locked+0x9c/frame 0xfffffe01b435fc30 sysctl_root() at sysctl_root+0x22f/frame 0xfffffe01b435fcb0 userland_sysctl() at userland_sysctl+0x196/frame 0xfffffe01b435fd50 sys___sysctl() at sys___sysctl+0x65/frame 0xfffffe01b435fe00 amd64_syscall() at amd64_syscall+0x169/frame 0xfffffe01b435ff30 fast_syscall_common() at fast_syscall_common+0xf8/frame 0xfffffe01b435ff30 --- syscall (202, FreeBSD ELF64, __sysctl), rip = 0x3f67cad1c8da, rsp = 0x3f67c80261d8, rbp = 0x3f67c8026220 --- KDB: enter: panic Core file '/var/crash/vmcore.last' (x86_64) was loaded. (lldb) ``` --------- Signed-off-by: Minsoo Choo <minsoochoo0122@proton.me>
408 lines
15 KiB
C++
408 lines
15 KiB
C++
//===-- ProcessFreeBSDKernel.cpp ------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Core/Debugger.h"
|
|
#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 "ProcessFreeBSDKernel.h"
|
|
#include "ThreadFreeBSDKernel.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
LLDB_PLUGIN_DEFINE(ProcessFreeBSDKernel)
|
|
|
|
ProcessFreeBSDKernel::ProcessFreeBSDKernel(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp, kvm_t *kvm,
|
|
const FileSpec &core_file)
|
|
: PostMortemProcess(target_sp, listener_sp, core_file), m_kvm(kvm) {}
|
|
|
|
ProcessFreeBSDKernel::~ProcessFreeBSDKernel() {
|
|
if (m_kvm)
|
|
kvm_close(m_kvm);
|
|
}
|
|
|
|
lldb::ProcessSP ProcessFreeBSDKernel::CreateInstance(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp,
|
|
const FileSpec *crash_file,
|
|
bool can_connect) {
|
|
ModuleSP executable = target_sp->GetExecutableModule();
|
|
if (crash_file && !can_connect && executable) {
|
|
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<ProcessFreeBSDKernel>(target_sp, listener_sp, kvm,
|
|
*crash_file);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::Initialize() {
|
|
static llvm::once_flag g_once_flag;
|
|
|
|
llvm::call_once(g_once_flag, []() {
|
|
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
|
GetPluginDescriptionStatic(), CreateInstance);
|
|
});
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::Terminate() {
|
|
PluginManager::UnregisterPlugin(ProcessFreeBSDKernel::CreateInstance);
|
|
}
|
|
|
|
Status ProcessFreeBSDKernel::DoDestroy() { return Status(); }
|
|
|
|
bool ProcessFreeBSDKernel::CanDebug(lldb::TargetSP target_sp,
|
|
bool plugin_specified_by_name) {
|
|
return true;
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::RefreshStateAfterStop() {
|
|
if (!m_printed_unread_message) {
|
|
PrintUnreadMessage();
|
|
m_printed_unread_message = true;
|
|
}
|
|
}
|
|
|
|
bool ProcessFreeBSDKernel::DoUpdateThreadList(ThreadList &old_thread_list,
|
|
ThreadList &new_thread_list) {
|
|
if (old_thread_list.GetSize(false) == 0) {
|
|
// Make up the thread the first time this is called so we can set our one
|
|
// and only core thread state up.
|
|
|
|
// We cannot construct a thread without a register context as that crashes
|
|
// LLDB but we can construct a process without threads to provide minimal
|
|
// memory reading support.
|
|
switch (GetTarget().GetArchitecture().GetMachine()) {
|
|
case llvm::Triple::aarch64:
|
|
case llvm::Triple::x86:
|
|
case llvm::Triple::x86_64:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
Status error;
|
|
|
|
// struct field offsets are written as symbols so that we don't have
|
|
// to figure them out ourselves
|
|
int32_t offset_p_list = ReadSignedIntegerFromMemory(
|
|
FindSymbol("proc_off_p_list"), 4, -1, error);
|
|
int32_t offset_p_pid =
|
|
ReadSignedIntegerFromMemory(FindSymbol("proc_off_p_pid"), 4, -1, error);
|
|
int32_t offset_p_threads = ReadSignedIntegerFromMemory(
|
|
FindSymbol("proc_off_p_threads"), 4, -1, error);
|
|
int32_t offset_p_comm = ReadSignedIntegerFromMemory(
|
|
FindSymbol("proc_off_p_comm"), 4, -1, error);
|
|
|
|
int32_t offset_td_tid = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_tid"), 4, -1, error);
|
|
int32_t offset_td_plist = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_plist"), 4, -1, error);
|
|
int32_t offset_td_pcb = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_pcb"), 4, -1, error);
|
|
int32_t offset_td_oncpu = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_oncpu"), 4, -1, error);
|
|
int32_t offset_td_name = ReadSignedIntegerFromMemory(
|
|
FindSymbol("thread_off_td_name"), 4, -1, error);
|
|
|
|
// Fail if we were not able to read any of the offsets.
|
|
if (offset_p_list == -1 || offset_p_pid == -1 || offset_p_threads == -1 ||
|
|
offset_p_comm == -1 || offset_td_tid == -1 || offset_td_plist == -1 ||
|
|
offset_td_pcb == -1 || offset_td_oncpu == -1 || offset_td_name == -1)
|
|
return false;
|
|
|
|
// dumptid contains the thread-id of the crashing thread
|
|
// dumppcb contains its PCB
|
|
int32_t dumptid =
|
|
ReadSignedIntegerFromMemory(FindSymbol("dumptid"), 4, -1, error);
|
|
lldb::addr_t dumppcb = FindSymbol("dumppcb");
|
|
|
|
// stoppcbs is an array of PCBs on all CPUs.
|
|
// Each element is of size pcb_size.
|
|
int32_t pcbsize =
|
|
ReadSignedIntegerFromMemory(FindSymbol("pcb_size"), 4, -1, error);
|
|
lldb::addr_t stoppcbs = FindSymbol("stoppcbs");
|
|
|
|
// from FreeBSD sys/param.h
|
|
constexpr size_t fbsd_maxcomlen = 19;
|
|
|
|
// Iterate through a linked list of all processes. 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;
|
|
for (lldb::addr_t proc =
|
|
ReadPointerFromMemory(FindSymbol("allproc"), error);
|
|
proc != 0 && proc != LLDB_INVALID_ADDRESS;
|
|
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) {
|
|
// If we managed to read stoppcbs and pcb_size, use them to find
|
|
// the correct PCB.
|
|
if (stoppcbs != LLDB_INVALID_ADDRESS && pcbsize > 0)
|
|
pcb_addr = stoppcbs + oncpu * pcbsize;
|
|
else
|
|
pcb_addr = LLDB_INVALID_ADDRESS;
|
|
thread_desc += llvm::formatv(" (on CPU {0})", oncpu);
|
|
}
|
|
|
|
auto thread =
|
|
new ThreadFreeBSDKernel(*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;
|
|
}
|
|
|
|
Status ProcessFreeBSDKernel::DoLoadCore() {
|
|
// The core is already loaded by CreateInstance().
|
|
return Status();
|
|
}
|
|
|
|
DynamicLoader *ProcessFreeBSDKernel::GetDynamicLoader() {
|
|
if (m_dyld_up.get() == nullptr)
|
|
m_dyld_up.reset(DynamicLoader::FindPlugin(
|
|
this, DynamicLoaderFreeBSDKernel::GetPluginNameStatic()));
|
|
return m_dyld_up.get();
|
|
}
|
|
|
|
lldb::addr_t ProcessFreeBSDKernel::FindSymbol(const char *name) {
|
|
ModuleSP mod_sp = GetTarget().GetExecutableModule();
|
|
const Symbol *sym = mod_sp->FindFirstSymbolWithNameAndType(ConstString(name));
|
|
return sym ? sym->GetLoadAddress(&GetTarget()) : LLDB_INVALID_ADDRESS;
|
|
}
|
|
|
|
void ProcessFreeBSDKernel::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),
|
|
"FreeBSDKernel: 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();
|
|
}
|
|
|
|
size_t ProcessFreeBSDKernel::DoReadMemory(lldb::addr_t addr, void *buf,
|
|
size_t size, Status &error) {
|
|
ssize_t rd = 0;
|
|
rd = kvm_read2(m_kvm, addr, buf, size);
|
|
if (rd < 0 || static_cast<size_t>(rd) != size) {
|
|
error = Status::FromErrorStringWithFormat("Reading memory failed: %s",
|
|
GetError());
|
|
return rd > 0 ? rd : 0;
|
|
}
|
|
return rd;
|
|
}
|
|
|
|
const char *ProcessFreeBSDKernel::GetError() { return kvm_geterr(m_kvm); }
|