[lldb] Introduce a FreeBSDKernel plugin for vmcores

Introduce a FreeBSDKernel plugin that provides the ability to read
FreeBSD kernel core dumps.  The plugin utilizes libfbsdvmcore to provide
support for both "full memory dump" and minidump formats across variety
of architectures supported by FreeBSD.  It provides the ability to read
kernel memory, as well as the crashed thread status with registers
on arm64, i386 and x86_64.

Differential Revision: https://reviews.llvm.org/D114911
This commit is contained in:
Michał Górny 2021-12-01 23:04:59 +01:00
parent 84f2ef2f29
commit aedb328a4d
26 changed files with 1235 additions and 1 deletions

View File

@ -58,6 +58,7 @@ add_optional_dependency(LLDB_ENABLE_LZMA "Enable LZMA compression support in LLD
add_optional_dependency(LLDB_ENABLE_LUA "Enable Lua scripting support in LLDB" LuaAndSwig LUAANDSWIG_FOUND)
add_optional_dependency(LLDB_ENABLE_PYTHON "Enable Python scripting support in LLDB" PythonAndSwig PYTHONANDSWIG_FOUND)
add_optional_dependency(LLDB_ENABLE_LIBXML2 "Enable Libxml 2 support in LLDB" LibXml2 LIBXML2_FOUND VERSION 2.8)
add_optional_dependency(LLDB_ENABLE_FBSDVMCORE "Enable libfbsdvmcore support in LLDB" FBSDVMCore FBSDVMCORE_FOUND)
option(LLDB_USE_SYSTEM_SIX "Use six.py shipped with system and do not install a copy of it" OFF)
option(LLDB_USE_ENTITLEMENTS "When codesigning, use entitlements if available" ON)

View File

@ -1570,7 +1570,7 @@ class Base(unittest2.TestCase):
return os.environ["CC"]
def yaml2obj(self, yaml_path, obj_path):
def yaml2obj(self, yaml_path, obj_path, max_size=None):
"""
Create an object file at the given path from a yaml file.
@ -1580,6 +1580,8 @@ class Base(unittest2.TestCase):
if not yaml2obj_bin:
self.assertTrue(False, "No valid yaml2obj executable specified")
command = [yaml2obj_bin, "-o=%s" % obj_path, yaml_path]
if max_size is not None:
command += ["--max-size=%d" % max_size]
self.runBuildCommand(command)
def getBuildFlags(

View File

@ -18,3 +18,4 @@ add_subdirectory(Utility)
add_subdirectory(elf-core)
add_subdirectory(mach-core)
add_subdirectory(minidump)
add_subdirectory(FreeBSDKernel)

View File

@ -0,0 +1,19 @@
if (NOT FBSDVMCore_FOUND)
message(STATUS "Skipping FreeBSDKernel plugin due to missing libfbsdvmcore")
return()
endif()
add_lldb_library(lldbPluginProcessFreeBSDKernel PLUGIN
ProcessFreeBSDKernel.cpp
RegisterContextFreeBSDKernel_arm64.cpp
RegisterContextFreeBSDKernel_i386.cpp
RegisterContextFreeBSDKernel_x86_64.cpp
ThreadFreeBSDKernel.cpp
LINK_LIBS
lldbCore
lldbTarget
fbsdvmcore
LINK_COMPONENTS
Support
)

View File

@ -0,0 +1,126 @@
//===-- 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/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Target/DynamicLoader.h"
#include "ProcessFreeBSDKernel.h"
#include "ThreadFreeBSDKernel.h"
#include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h"
#include <fvc.h>
using namespace lldb;
using namespace lldb_private;
LLDB_PLUGIN_DEFINE(ProcessFreeBSDKernel)
ProcessFreeBSDKernel::ProcessFreeBSDKernel(lldb::TargetSP target_sp,
ListenerSP listener_sp,
const FileSpec &core_file, void *fvc)
: PostMortemProcess(target_sp, listener_sp), m_fvc(fvc) {}
ProcessFreeBSDKernel::~ProcessFreeBSDKernel() {
if (m_fvc)
fvc_close(static_cast<fvc_t *>(m_fvc));
}
lldb::ProcessSP ProcessFreeBSDKernel::CreateInstance(lldb::TargetSP target_sp,
ListenerSP listener_sp,
const FileSpec *crash_file,
bool can_connect) {
lldb::ProcessSP process_sp;
ModuleSP executable = target_sp->GetExecutableModule();
if (crash_file && !can_connect && executable) {
fvc_t *fvc = fvc_open(
executable->GetFileSpec().GetPath().c_str(),
crash_file->GetPath().c_str(), nullptr, nullptr, nullptr);
if (fvc)
process_sp = std::make_shared<ProcessFreeBSDKernel>(
target_sp, listener_sp, *crash_file, fvc);
}
return process_sp;
}
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() {}
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;
}
const Symbol *pcb_sym =
GetTarget().GetExecutableModule()->FindFirstSymbolWithNameAndType(
ConstString("dumppcb"));
ThreadSP thread_sp(new ThreadFreeBSDKernel(
*this, 1, pcb_sym ? pcb_sym->GetFileAddress() : LLDB_INVALID_ADDRESS));
new_thread_list.AddThread(thread_sp);
} 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();
}
size_t ProcessFreeBSDKernel::DoReadMemory(lldb::addr_t addr, void *buf,
size_t size, Status &error) {
ssize_t rd = fvc_read(static_cast<fvc_t *>(m_fvc), addr, buf, size);
if (rd < 0 || static_cast<size_t>(rd) != size) {
error.SetErrorStringWithFormat("Reading memory failed: %s",
fvc_geterr(static_cast<fvc_t *>(m_fvc)));
return rd > 0 ? rd : 0;
}
return rd;
}
DynamicLoader *ProcessFreeBSDKernel::GetDynamicLoader() {
if (m_dyld_up.get() == nullptr)
m_dyld_up.reset(DynamicLoader::FindPlugin(
this, DynamicLoaderStatic::GetPluginNameStatic()));
return m_dyld_up.get();
}

View File

@ -0,0 +1,60 @@
//===-- ProcessFreeBSDKernel.h ----------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_PROCESSFREEBSDKERNEL_H
#define LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_PROCESSFREEBSDKERNEL_H
#include "lldb/Target/PostMortemProcess.h"
class ProcessFreeBSDKernel : public lldb_private::PostMortemProcess {
public:
ProcessFreeBSDKernel(lldb::TargetSP target_sp, lldb::ListenerSP listener,
const lldb_private::FileSpec &core_file, void *fvc);
~ProcessFreeBSDKernel() override;
static lldb::ProcessSP
CreateInstance(lldb::TargetSP target_sp, lldb::ListenerSP listener,
const lldb_private::FileSpec *crash_file_path,
bool can_connect);
static void Initialize();
static void Terminate();
static llvm::StringRef GetPluginNameStatic() { return "freebsd-kernel"; }
static llvm::StringRef GetPluginDescriptionStatic() {
return "FreeBSD kernel vmcore debugging plug-in.";
}
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
lldb_private::Status DoDestroy() override;
bool CanDebug(lldb::TargetSP target_sp,
bool plugin_specified_by_name) override;
void RefreshStateAfterStop() override;
lldb_private::Status DoLoadCore() override;
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
lldb_private::Status &error) override;
lldb_private::DynamicLoader *GetDynamicLoader() override;
protected:
bool DoUpdateThreadList(lldb_private::ThreadList &old_thread_list,
lldb_private::ThreadList &new_thread_list) override;
private:
void *m_fvc;
};
#endif // LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_PROCESSFREEBSDKERNEL_H

View File

@ -0,0 +1,110 @@
//===-- RegisterContextFreeBSDKernel_arm64.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 "RegisterContextFreeBSDKernel_arm64.h"
#include "Plugins/Process/Utility/lldb-arm64-register-enums.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/RegisterValue.h"
#include "llvm/Support/Endian.h"
using namespace lldb;
using namespace lldb_private;
RegisterContextFreeBSDKernel_arm64::RegisterContextFreeBSDKernel_arm64(
Thread &thread, std::unique_ptr<RegisterInfoPOSIX_arm64> register_info_up,
lldb::addr_t pcb_addr)
: RegisterContextPOSIX_arm64(thread, std::move(register_info_up)),
m_pcb_addr(pcb_addr) {}
bool RegisterContextFreeBSDKernel_arm64::ReadGPR() { return true; }
bool RegisterContextFreeBSDKernel_arm64::ReadFPR() { return true; }
bool RegisterContextFreeBSDKernel_arm64::WriteGPR() {
assert(0);
return false;
}
bool RegisterContextFreeBSDKernel_arm64::WriteFPR() {
assert(0);
return false;
}
bool RegisterContextFreeBSDKernel_arm64::ReadRegister(
const RegisterInfo *reg_info, RegisterValue &value) {
if (m_pcb_addr == LLDB_INVALID_ADDRESS)
return false;
struct {
llvm::support::ulittle64_t x[30];
llvm::support::ulittle64_t lr;
llvm::support::ulittle64_t _reserved;
llvm::support::ulittle64_t sp;
} pcb;
Status error;
size_t rd =
m_thread.GetProcess()->ReadMemory(m_pcb_addr, &pcb, sizeof(pcb), error);
if (rd != sizeof(pcb))
return false;
uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
switch (reg) {
case gpr_x0_arm64:
case gpr_x1_arm64:
case gpr_x2_arm64:
case gpr_x3_arm64:
case gpr_x4_arm64:
case gpr_x5_arm64:
case gpr_x6_arm64:
case gpr_x7_arm64:
case gpr_x8_arm64:
case gpr_x9_arm64:
case gpr_x10_arm64:
case gpr_x11_arm64:
case gpr_x12_arm64:
case gpr_x13_arm64:
case gpr_x14_arm64:
case gpr_x15_arm64:
case gpr_x16_arm64:
case gpr_x17_arm64:
case gpr_x18_arm64:
case gpr_x19_arm64:
case gpr_x20_arm64:
case gpr_x21_arm64:
case gpr_x22_arm64:
case gpr_x23_arm64:
case gpr_x24_arm64:
case gpr_x25_arm64:
case gpr_x26_arm64:
case gpr_x27_arm64:
case gpr_x28_arm64:
case gpr_fp_arm64:
static_assert(gpr_fp_arm64 - gpr_x0_arm64 == 29,
"nonconsecutive arm64 register numbers");
value = pcb.x[reg - gpr_x0_arm64];
break;
case gpr_sp_arm64:
value = pcb.sp;
break;
case gpr_pc_arm64:
// The pc of crashing thread is stored in lr.
value = pcb.lr;
break;
default:
return false;
}
return true;
}
bool RegisterContextFreeBSDKernel_arm64::WriteRegister(
const RegisterInfo *reg_info, const RegisterValue &value) {
return false;
}

View File

@ -0,0 +1,41 @@
//===-- RegisterContextFreeBSDKernel_arm64.h --------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_ARM64_H
#define LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_ARM64_H
#include "Plugins/Process/Utility/RegisterContextPOSIX_arm64.h"
#include "Plugins/Process/elf-core/RegisterUtilities.h"
class RegisterContextFreeBSDKernel_arm64 : public RegisterContextPOSIX_arm64 {
public:
RegisterContextFreeBSDKernel_arm64(
lldb_private::Thread &thread,
std::unique_ptr<RegisterInfoPOSIX_arm64> register_info_up,
lldb::addr_t pcb_addr);
bool ReadRegister(const lldb_private::RegisterInfo *reg_info,
lldb_private::RegisterValue &value) override;
bool WriteRegister(const lldb_private::RegisterInfo *reg_info,
const lldb_private::RegisterValue &value) override;
protected:
bool ReadGPR() override;
bool ReadFPR() override;
bool WriteGPR() override;
bool WriteFPR() override;
private:
lldb::addr_t m_pcb_addr;
};
#endif // LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_ARM64_H

View File

@ -0,0 +1,83 @@
//===-- RegisterContextFreeBSDKernel_i386.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 "RegisterContextFreeBSDKernel_i386.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/RegisterValue.h"
#include "llvm/Support/Endian.h"
using namespace lldb;
using namespace lldb_private;
RegisterContextFreeBSDKernel_i386::RegisterContextFreeBSDKernel_i386(
Thread &thread, RegisterInfoInterface *register_info, lldb::addr_t pcb_addr)
: RegisterContextPOSIX_x86(thread, 0, register_info), m_pcb_addr(pcb_addr) {
}
bool RegisterContextFreeBSDKernel_i386::ReadGPR() { return true; }
bool RegisterContextFreeBSDKernel_i386::ReadFPR() { return true; }
bool RegisterContextFreeBSDKernel_i386::WriteGPR() {
assert(0);
return false;
}
bool RegisterContextFreeBSDKernel_i386::WriteFPR() {
assert(0);
return false;
}
bool RegisterContextFreeBSDKernel_i386::ReadRegister(
const RegisterInfo *reg_info, RegisterValue &value) {
if (m_pcb_addr == LLDB_INVALID_ADDRESS)
return false;
struct {
llvm::support::ulittle32_t edi;
llvm::support::ulittle32_t esi;
llvm::support::ulittle32_t ebp;
llvm::support::ulittle32_t esp;
llvm::support::ulittle32_t ebx;
llvm::support::ulittle32_t eip;
} pcb;
Status error;
size_t rd =
m_thread.GetProcess()->ReadMemory(m_pcb_addr, &pcb, sizeof(pcb), error);
if (rd != sizeof(pcb))
return false;
uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
switch (reg) {
#define REG(x) \
case lldb_##x##_i386: \
value = pcb.x; \
break;
REG(edi);
REG(esi);
REG(ebp);
REG(esp);
REG(eip);
#undef REG
default:
return false;
}
return true;
}
bool RegisterContextFreeBSDKernel_i386::WriteRegister(
const RegisterInfo *reg_info, const RegisterValue &value) {
return false;
}

View File

@ -0,0 +1,41 @@
//===-- RegisterContextFreeBSDKernel_i386.h ---------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_I386_H
#define LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_I386_H
#include "Plugins/Process/Utility/RegisterContextPOSIX_x86.h"
#include "Plugins/Process/elf-core/RegisterUtilities.h"
class RegisterContextFreeBSDKernel_i386 : public RegisterContextPOSIX_x86 {
public:
RegisterContextFreeBSDKernel_i386(
lldb_private::Thread &thread,
lldb_private::RegisterInfoInterface *register_info,
lldb::addr_t pcb_addr);
bool ReadRegister(const lldb_private::RegisterInfo *reg_info,
lldb_private::RegisterValue &value) override;
bool WriteRegister(const lldb_private::RegisterInfo *reg_info,
const lldb_private::RegisterValue &value) override;
protected:
bool ReadGPR() override;
bool ReadFPR() override;
bool WriteGPR() override;
bool WriteFPR() override;
private:
lldb::addr_t m_pcb_addr;
};
#endif // LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_I386_H

View File

@ -0,0 +1,88 @@
//===-- RegisterContextFreeBSDKernel_x86_64.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 "RegisterContextFreeBSDKernel_x86_64.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/RegisterValue.h"
#include "llvm/Support/Endian.h"
using namespace lldb;
using namespace lldb_private;
RegisterContextFreeBSDKernel_x86_64::RegisterContextFreeBSDKernel_x86_64(
Thread &thread, RegisterInfoInterface *register_info, lldb::addr_t pcb_addr)
: RegisterContextPOSIX_x86(thread, 0, register_info), m_pcb_addr(pcb_addr) {
}
bool RegisterContextFreeBSDKernel_x86_64::ReadGPR() { return true; }
bool RegisterContextFreeBSDKernel_x86_64::ReadFPR() { return true; }
bool RegisterContextFreeBSDKernel_x86_64::WriteGPR() {
assert(0);
return false;
}
bool RegisterContextFreeBSDKernel_x86_64::WriteFPR() {
assert(0);
return false;
}
bool RegisterContextFreeBSDKernel_x86_64::ReadRegister(
const RegisterInfo *reg_info, RegisterValue &value) {
if (m_pcb_addr == LLDB_INVALID_ADDRESS)
return false;
struct {
llvm::support::ulittle64_t r15;
llvm::support::ulittle64_t r14;
llvm::support::ulittle64_t r13;
llvm::support::ulittle64_t r12;
llvm::support::ulittle64_t rbp;
llvm::support::ulittle64_t rsp;
llvm::support::ulittle64_t rbx;
llvm::support::ulittle64_t rip;
} pcb;
Status error;
size_t rd =
m_thread.GetProcess()->ReadMemory(m_pcb_addr, &pcb, sizeof(pcb), error);
if (rd != sizeof(pcb))
return false;
uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
switch (reg) {
#define REG(x) \
case lldb_##x##_x86_64: \
value = pcb.x; \
break;
REG(r15);
REG(r14);
REG(r13);
REG(r12);
REG(rbp);
REG(rsp);
REG(rbx);
REG(rip);
#undef REG
default:
return false;
}
return true;
}
bool RegisterContextFreeBSDKernel_x86_64::WriteRegister(
const RegisterInfo *reg_info, const RegisterValue &value) {
return false;
}

View File

@ -0,0 +1,41 @@
//===-- RegisterContextFreeBSDKernel_x86_64.h -------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_X86_64_H
#define LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_X86_64_H
#include "Plugins/Process/Utility/RegisterContextPOSIX_x86.h"
#include "Plugins/Process/elf-core/RegisterUtilities.h"
class RegisterContextFreeBSDKernel_x86_64 : public RegisterContextPOSIX_x86 {
public:
RegisterContextFreeBSDKernel_x86_64(
lldb_private::Thread &thread,
lldb_private::RegisterInfoInterface *register_info,
lldb::addr_t pcb_addr);
bool ReadRegister(const lldb_private::RegisterInfo *reg_info,
lldb_private::RegisterValue &value) override;
bool WriteRegister(const lldb_private::RegisterInfo *reg_info,
const lldb_private::RegisterValue &value) override;
protected:
bool ReadGPR() override;
bool ReadFPR() override;
bool WriteGPR() override;
bool WriteFPR() override;
private:
lldb::addr_t m_pcb_addr;
};
#endif // LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_REGISTERCONTEXTFREEBSDKERNEL_X86_64_H

View File

@ -0,0 +1,85 @@
//===-- ThreadFreeBSDKernel.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 "ThreadFreeBSDKernel.h"
#include "lldb/Target/Unwind.h"
#include "lldb/Utility/Log.h"
#include "Plugins/Process/Utility/RegisterContextFreeBSD_i386.h"
#include "Plugins/Process/Utility/RegisterContextFreeBSD_x86_64.h"
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
#include "ProcessFreeBSDKernel.h"
#include "RegisterContextFreeBSDKernel_arm64.h"
#include "RegisterContextFreeBSDKernel_i386.h"
#include "RegisterContextFreeBSDKernel_x86_64.h"
#include "ThreadFreeBSDKernel.h"
using namespace lldb;
using namespace lldb_private;
ThreadFreeBSDKernel::ThreadFreeBSDKernel(Process &process, lldb::tid_t tid,
lldb::addr_t pcb_addr)
: Thread(process, tid), m_pcb_addr(pcb_addr) {}
ThreadFreeBSDKernel::~ThreadFreeBSDKernel() {}
void ThreadFreeBSDKernel::RefreshStateAfterStop() {}
lldb::RegisterContextSP ThreadFreeBSDKernel::GetRegisterContext() {
if (!m_reg_context_sp)
m_reg_context_sp = CreateRegisterContextForFrame(nullptr);
return m_reg_context_sp;
}
lldb::RegisterContextSP
ThreadFreeBSDKernel::CreateRegisterContextForFrame(StackFrame *frame) {
RegisterContextSP reg_ctx_sp;
uint32_t concrete_frame_idx = 0;
if (frame)
concrete_frame_idx = frame->GetConcreteFrameIndex();
if (concrete_frame_idx == 0) {
if (m_thread_reg_ctx_sp)
return m_thread_reg_ctx_sp;
ProcessFreeBSDKernel *process =
static_cast<ProcessFreeBSDKernel *>(GetProcess().get());
ArchSpec arch = process->GetTarget().GetArchitecture();
switch (arch.GetMachine()) {
case llvm::Triple::aarch64:
m_thread_reg_ctx_sp =
std::make_shared<RegisterContextFreeBSDKernel_arm64>(
*this, std::make_unique<RegisterInfoPOSIX_arm64>(arch, 0),
m_pcb_addr);
break;
case llvm::Triple::x86:
m_thread_reg_ctx_sp =
std::make_shared<RegisterContextFreeBSDKernel_i386>(
*this, new RegisterContextFreeBSD_i386(arch), m_pcb_addr);
break;
case llvm::Triple::x86_64:
m_thread_reg_ctx_sp =
std::make_shared<RegisterContextFreeBSDKernel_x86_64>(
*this, new RegisterContextFreeBSD_x86_64(arch), m_pcb_addr);
break;
default:
assert(false && "Unsupported architecture passed to ThreadFreeBSDKernel");
break;
}
reg_ctx_sp = m_thread_reg_ctx_sp;
} else {
reg_ctx_sp = GetUnwinder().CreateRegisterContextForFrame(frame);
}
return reg_ctx_sp;
}
bool ThreadFreeBSDKernel::CalculateStopInfo() { return false; }

View File

@ -0,0 +1,36 @@
//===-- ThreadFreeBSDKernel.h ------------------------------------- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_THREADFREEBSDKERNEL_H
#define LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_THREADFREEBSDKERNEL_H
#include "lldb/Target/Thread.h"
class ThreadFreeBSDKernel : public lldb_private::Thread {
public:
ThreadFreeBSDKernel(lldb_private::Process &process, lldb::tid_t tid,
lldb::addr_t pcb_addr);
~ThreadFreeBSDKernel() override;
void RefreshStateAfterStop() override;
lldb::RegisterContextSP GetRegisterContext() override;
lldb::RegisterContextSP
CreateRegisterContextForFrame(lldb_private::StackFrame *frame) override;
protected:
bool CalculateStopInfo() override;
private:
lldb::RegisterContextSP m_thread_reg_ctx_sp;
lldb::addr_t m_pcb_addr;
};
#endif // LLDB_SOURCE_PLUGINS_PROCESS_FREEBSDKERNEL_THREADFREEBSDKERNEL_H

View File

@ -0,0 +1,153 @@
import bz2
import shutil
import struct
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class FreeBSDKernelVMCoreTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
mydir = TestBase.compute_mydir(__file__)
def make_target(self, src_filename):
src = self.getSourcePath(src_filename)
dest = self.getBuildArtifact("kernel")
self.yaml2obj(src, dest, max_size=30*1024*1024)
return self.dbg.CreateTarget(dest)
def make_vmcore(self, src_filename):
src = self.getSourcePath(src_filename)
dest = self.getBuildArtifact("vmcore")
with bz2.open(src, "rb") as inf:
with open(dest, "wb") as outf:
shutil.copyfileobj(inf, outf)
return dest
def do_test(self, kernel_yaml, vmcore_bz2, bt_expected, regs_expected,
hz_value=100):
target = self.make_target(kernel_yaml)
vmcore_file = self.make_vmcore(vmcore_bz2)
process = target.LoadCore(vmcore_file)
self.assertTrue(process, PROCESS_IS_VALID)
self.assertEqual(process.GetNumThreads(), 1)
self.assertEqual(process.GetProcessID(), 0)
# test memory reading
self.expect("expr -- *(int *) &hz",
substrs=["(int) $0 = %d" % hz_value])
main_mod = target.GetModuleAtIndex(0)
hz_addr = (main_mod.FindSymbols("hz")[0].symbol.addr
.GetLoadAddress(target))
error = lldb.SBError()
self.assertEqual(process.ReadMemory(hz_addr, 4, error),
struct.pack("<I", hz_value))
# test backtrace
self.assertEqual(
[process.GetThreadAtIndex(0).GetFrameAtIndex(i).addr
.GetLoadAddress(target) for i in range(len(bt_expected))],
bt_expected)
# test registers
regs = process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetRegisters()
reg_values = {}
for regset in regs:
for reg in regset:
if reg.value is None:
continue
reg_values[reg.name] = reg.value
self.assertEqual(reg_values, regs_expected)
self.dbg.DeleteTarget(target)
def test_amd64_full_vmcore(self):
self.do_test("kernel-amd64.yaml", "vmcore-amd64-full.bz2",
[0xffffffff80c09ade, 0xffffffff80c09916,
0xffffffff80c09d90, 0xffffffff80c09b93,
0xffffffff80c57d91, 0xffffffff80c19e71,
0xffffffff80c192bc, 0xffffffff80c19933,
0xffffffff80c1977f, 0xffffffff8108ba8c,
0xffffffff810620ce],
{"rbx": "0x0000000000000000",
"rbp": "0xfffffe0085cb2760",
"rsp": "0xfffffe0085cb2748",
"r12": "0xfffffe0045a6c300",
"r13": "0xfffff800033693a8",
"r14": "0x0000000000000000",
"r15": "0xfffff80003369380",
"rip": "0xffffffff80c09ade",
})
def test_amd64_minidump(self):
self.do_test("kernel-amd64.yaml", "vmcore-amd64-minidump.bz2",
[0xffffffff80c09ade, 0xffffffff80c09916,
0xffffffff80c09d90, 0xffffffff80c09b93,
0xffffffff80c57d91, 0xffffffff80c19e71,
0xffffffff80c192bc, 0xffffffff80c19933,
0xffffffff80c1977f, 0xffffffff8108ba8c,
0xffffffff810620ce],
{"rbx": "0x0000000000000000",
"rbp": "0xfffffe00798c4760",
"rsp": "0xfffffe00798c4748",
"r12": "0xfffffe0045b11c00",
"r13": "0xfffff800033693a8",
"r14": "0x0000000000000000",
"r15": "0xfffff80003369380",
"rip": "0xffffffff80c09ade",
})
def test_arm64_minidump(self):
self.do_test("kernel-arm64.yaml", "vmcore-arm64-minidump.bz2",
[0xffff0000004b6e78], # TODO: fix unwinding
{"x0": "0x0000000000000000",
"x1": "0x0000000000000000",
"x2": "0x0000000000000000",
"x3": "0x0000000000000000",
"x4": "0x0000000000000000",
"x5": "0x0000000000000000",
"x6": "0x0000000000000000",
"x7": "0x0000000000000000",
"x8": "0xffffa00001548700",
"x9": "0x0000000000000000",
"x10": "0xffffa00000e04580",
"x11": "0x0000000000000000",
"x12": "0x000000000008950a",
"x13": "0x0000000000089500",
"x14": "0x0000000000000039",
"x15": "0x0000000000000000",
"x16": "0x00000000ffffffd8",
"x17": "0x0000000000000000",
"x18": "0xffff000000e6d380",
"x19": "0xffff000000af9000",
"x20": "0xffff000000b82000",
"x21": "0xffffa00000319da8",
"x22": "0xffff000000b84000",
"x23": "0xffff000000b84000",
"x24": "0xffff000000b55000",
"x25": "0x0000000000000000",
"x26": "0x0000000000040800",
"x27": "0x0000000000000000",
"x28": "0x00000000002019ca",
"fp": "0xffff0000d58f23b0",
"sp": "0xffff0000d58f23b0",
"pc": "0xffff0000004b6e78",
},
hz_value=1000)
def test_i386_minidump(self):
self.do_test("kernel-i386.yaml", "vmcore-i386-minidump.bz2",
[0x010025c5, 0x01002410, 0x010027d5, 0x01002644,
0x01049a2f, 0x01011077, 0x01010780, 0x01010c7a,
0x01010ab2, 0x013e9e2d, 0xffc033f9],
{"ebp": "0x151968e4",
"esp": "0x151968d8",
"esi": "0x0c77aa80",
"edi": "0x03f0dc80",
"eip": "0x010025c5",
})

View File

@ -0,0 +1,38 @@
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
OSABI: ELFOSABI_FREEBSD
Type: ET_EXEC
Machine: EM_X86_64
Entry: 0xFFFFFFFF8037C000
Sections:
- Name: .bss
Type: SHT_NOBITS
Flags: [ SHF_WRITE, SHF_ALLOC ]
Address: 0xFFFFFFFF819BA380
AddressAlign: 0x80
Offset: 0x17BA348
Size: 0x445C80
Symbols:
- Name: kernbase
Index: SHN_ABS
Binding: STB_GLOBAL
Value: 0xFFFFFFFF80000000
- Name: KPML4phys
Type: STT_OBJECT
Section: .bss
Binding: STB_GLOBAL
Value: 0xFFFFFFFF81D47EB8
Size: 0x8
- Name: dumppcb
Type: STT_OBJECT
Section: .bss
Value: 0xFFFFFFFF81CA6868
Size: 0x140
- Name: hz
Type: STT_OBJECT
Section: .bss
Binding: STB_GLOBAL
Value: 0xFFFFFFFF81CD4C0C
Size: 0x4

View File

@ -0,0 +1,30 @@
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Machine: EM_AARCH64
Entry: 0xFFFF000000000800
Sections:
- Name: .bss
Type: SHT_NOBITS
Flags: [ SHF_WRITE, SHF_ALLOC ]
Address: 0xFFFF000000C35000
AddressAlign: 0x1000
Size: 0x37F000
Symbols:
- Name: kernbase
Index: SHN_ABS
Binding: STB_GLOBAL
Value: 0xFFFF000000000000
- Name: dumppcb
Type: STT_OBJECT
Section: .bss
Value: 0xFFFF000000DF3790
Size: 0x560
- Name: hz
Type: STT_OBJECT
Section: .bss
Binding: STB_GLOBAL
Value: 0xFFFF000000E2651C
Size: 0x4

View File

@ -0,0 +1,38 @@
--- !ELF
FileHeader:
Class: ELFCLASS32
Data: ELFDATA2LSB
OSABI: ELFOSABI_FREEBSD
Type: ET_EXEC
Machine: EM_386
Entry: 0x8F9000
Sections:
- Name: .bss
Type: SHT_NOBITS
Flags: [ SHF_WRITE, SHF_ALLOC ]
Address: 0x1AB7B00
AddressAlign: 0x80
Offset: 0x12B7AB0
Size: 0x2D48D8
Symbols:
- Name: kernbase
Index: SHN_ABS
Binding: STB_GLOBAL
Value: 0x800000
- Name: dumppcb
Type: STT_OBJECT
Section: .bss
Value: 0x1D2D9A0
Size: 0xC0
- Name: hz
Type: STT_OBJECT
Section: .bss
Binding: STB_GLOBAL
Value: 0x1D4053C
Size: 0x4
- Name: IdlePDPT
Type: STT_OBJECT
Section: .bss
Binding: STB_GLOBAL
Value: 0x1D8B044
Size: 0x4

View File

@ -0,0 +1,36 @@
How to create vmcores for tests
===============================
1. Boot a FreeBSD VM with as little memory as possible and create a core dump
per `FreeBSD Handbook Kernel Debugging Chapter`_. Note that you may need to
reboot with more memory after the kernel panic as otherwise savecore(8) may
fail.
For instance, I was able to boot FreeBSD and qemu-system-x86_64 with 128 MiB
RAM but had to increase it to 256 MiB for the boot after kernel panic.
2. Transfer the kernel image (``/boot/kernel/kernel``) and vmcore
(``/var/crash/vmcore.latest``) from the VM.
3. Patch libfbsdvmcore using ``libfbsdvmcore-print-offsets.patch`` and build
LLDB against the patched library.
4. Do a test run of ``test.script`` in LLDB against the kernel + vmcore::
lldb -b -s test.script --core /path/to/core /path/to/kernel
If everything works fine, the LLDB output should be interspersed with
``%RD`` lines.
5. Use the ``copy-sparse.py`` tool to create a sparse version of the vmcore::
lldb -b -s test.script --core /path/to/core /path/to/kernel |
grep '^% RD' | python copy-sparse.py /path/to/core vmcore.sparse
6. Compress the sparse vmcore file using ``bzip2``::
bzip2 -9 vmcore.sparse
.. _FreeBSD Handbook Kernel Debugging Chapter:
https://docs.freebsd.org/en/books/developers-handbook/kerneldebug/

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
import argparse
import re
import sys
def main():
argp = argparse.ArgumentParser()
argp.add_argument('infile', type=argparse.FileType('rb'),
help='Input vmcore file')
argp.add_argument('outfile', type=argparse.FileType('wb'),
help='Output vmcore file')
args = argp.parse_args()
inf = args.infile
outf = args.outfile
line_re = re.compile(r"^% RD: (\d+) (\d+)")
# copy the first chunk that usually includes ELF headers
# (not output by patched libfbsdvmcore since libelf reads this)
outf.write(inf.read(1024))
for l in sys.stdin:
m = line_re.match(l)
offset, size = [int(x) for x in m.groups()]
inf.seek(offset)
outf.seek(offset)
outf.write(inf.read(size))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,166 @@
diff --git a/lib/fvc.c b/lib/fvc.c
index e6b96c1..4033f78 100644
--- a/lib/fvc.c
+++ b/lib/fvc.c
@@ -297,6 +297,7 @@ fvc_read(fvc_t *kd, fvc_addr_t kva, void *buf, size_t len)
_fvc_syserr(kd, kd->program, "fvc_read");
break;
}
+ printf("%% RD: %zu %d\n", pa, cc);
/*
* If ka_kvatop returns a bogus value or our core file is
* truncated, we might wind up seeking beyond the end of the
@@ -331,3 +332,8 @@ fvc_kerndisp(fvc_t *kd)
return (kd->arch->ka_kerndisp(kd));
}
+
+ssize_t xpread(int fd, void *buf, size_t count, off_t offset) {
+ printf("%% RD: %zu %zu\n", offset, count);
+ return pread(fd, buf, count, offset);
+}
diff --git a/lib/fvc.h b/lib/fvc.h
index 8680079..ff1e0f0 100644
--- a/lib/fvc.h
+++ b/lib/fvc.h
@@ -54,6 +54,8 @@ typedef unsigned char fvc_vm_prot_t;
#define FVC_VM_PROT_WRITE ((fvc_vm_prot_t) 0x02)
#define FVC_VM_PROT_EXECUTE ((fvc_vm_prot_t) 0x04)
+ssize_t xpread(int fd, void *buf, size_t count, off_t offset);
+
struct fvc_page {
unsigned int kp_version;
fvc_addr_t kp_paddr;
diff --git a/lib/fvc_amd64.c b/lib/fvc_amd64.c
index 4d27998..69f1807 100644
--- a/lib/fvc_amd64.c
+++ b/lib/fvc_amd64.c
@@ -205,7 +205,7 @@ _amd64_vatop(fvc_t *kd, fvc_addr_t va, off_t *pa)
_fvc_err(kd, kd->program, "_amd64_vatop: pdpe_pa not found");
goto invalid;
}
- if (pread(kd->pmfd, &pdpe, sizeof(pdpe), ofs) != sizeof(pdpe)) {
+ if (xpread(kd->pmfd, &pdpe, sizeof(pdpe), ofs) != sizeof(pdpe)) {
_fvc_syserr(kd, kd->program, "_amd64_vatop: read pdpe");
goto invalid;
}
@@ -237,7 +237,7 @@ _amd64_vatop(fvc_t *kd, fvc_addr_t va, off_t *pa)
_fvc_syserr(kd, kd->program, "_amd64_vatop: pde_pa not found");
goto invalid;
}
- if (pread(kd->pmfd, &pde, sizeof(pde), ofs) != sizeof(pde)) {
+ if (xpread(kd->pmfd, &pde, sizeof(pde), ofs) != sizeof(pde)) {
_fvc_syserr(kd, kd->program, "_amd64_vatop: read pde");
goto invalid;
}
@@ -269,7 +269,7 @@ _amd64_vatop(fvc_t *kd, fvc_addr_t va, off_t *pa)
_fvc_err(kd, kd->program, "_amd64_vatop: pte_pa not found");
goto invalid;
}
- if (pread(kd->pmfd, &pte, sizeof(pte), ofs) != sizeof(pte)) {
+ if (xpread(kd->pmfd, &pte, sizeof(pte), ofs) != sizeof(pte)) {
_fvc_syserr(kd, kd->program, "_amd64_vatop: read");
goto invalid;
}
diff --git a/lib/fvc_minidump_aarch64.c b/lib/fvc_minidump_aarch64.c
index 4b8477a..a1c5b42 100644
--- a/lib/fvc_minidump_aarch64.c
+++ b/lib/fvc_minidump_aarch64.c
@@ -86,7 +86,7 @@ _aarch64_minidump_initvtop(fvc_t *kd)
return (-1);
}
kd->vmst = vmst;
- if (pread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
+ if (xpread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
sizeof(vmst->hdr)) {
_fvc_err(kd, kd->program, "cannot read dump header");
return (-1);
diff --git a/lib/fvc_minidump_amd64.c b/lib/fvc_minidump_amd64.c
index 93e8238..0d2237f 100644
--- a/lib/fvc_minidump_amd64.c
+++ b/lib/fvc_minidump_amd64.c
@@ -126,7 +126,7 @@ _amd64_minidump_initvtop(fvc_t *kd)
return (-1);
}
kd->vmst = vmst;
- if (pread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
+ if (xpread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
sizeof(vmst->hdr)) {
_fvc_err(kd, kd->program, "cannot read dump header");
return (-1);
@@ -269,7 +269,7 @@ _amd64_minidump_vatop(fvc_t *kd, fvc_addr_t va, off_t *pa)
(uintmax_t)a);
goto invalid;
}
- if (pread(kd->pmfd, &pt, AMD64_PAGE_SIZE, ofs) !=
+ if (xpread(kd->pmfd, &pt, AMD64_PAGE_SIZE, ofs) !=
AMD64_PAGE_SIZE) {
_fvc_err(kd, kd->program,
"cannot read page table entry for %ju",
diff --git a/lib/fvc_minidump_i386.c b/lib/fvc_minidump_i386.c
index 61cc3db..b3ab955 100644
--- a/lib/fvc_minidump_i386.c
+++ b/lib/fvc_minidump_i386.c
@@ -94,7 +94,7 @@ _i386_minidump_initvtop(fvc_t *kd)
return (-1);
}
kd->vmst = vmst;
- if (pread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
+ if (xpread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
sizeof(vmst->hdr)) {
_fvc_err(kd, kd->program, "cannot read dump header");
return (-1);
diff --git a/lib/fvc_private.c b/lib/fvc_private.c
index 0069a54..fc798fe 100644
--- a/lib/fvc_private.c
+++ b/lib/fvc_private.c
@@ -130,7 +130,7 @@ _fvc_is_minidump(fvc_t *kd)
{
char minihdr[8];
- if (pread(kd->pmfd, &minihdr, 8, 0) == 8 &&
+ if (xpread(kd->pmfd, &minihdr, 8, 0) == 8 &&
memcmp(&minihdr, "minidump", 8) == 0)
return (1);
return (0);
@@ -256,6 +256,7 @@ _fvc_pmap_get(fvc_t *kd, u_long idx, size_t len)
if ((off_t)off >= kd->pt_sparse_off)
return (NULL);
+ printf("%% RD: %zu %zu\n", kd->page_map_off+off, len);
return (void *)((uintptr_t)kd->page_map + off);
}
@@ -270,8 +271,13 @@ _fvc_map_get(fvc_t *kd, u_long pa, unsigned int page_size)
return NULL;
addr = (uintptr_t)kd->page_map + off;
- if (off >= kd->pt_sparse_off)
+ if (off >= kd->pt_sparse_off) {
+
addr = (uintptr_t)kd->sparse_map + (off - kd->pt_sparse_off);
+ printf("%% RD: %zu %u\n", off, page_size);
+ }
+ else
+ printf("%% RD: %zu %u\n", kd->page_map_off+off, page_size);
return (void *)addr;
}
@@ -289,6 +295,7 @@ _fvc_pt_init(fvc_t *kd, size_t dump_avail_size, off_t dump_avail_off,
if (dump_avail_size > 0) {
kd->dump_avail = mmap(NULL, kd->dump_avail_size, PROT_READ,
MAP_PRIVATE, kd->pmfd, dump_avail_off);
+ printf("%% RD: %zu %zu\n", dump_avail_off, dump_avail_size);
} else {
/*
* Older version minidumps don't provide dump_avail[],
@@ -309,7 +316,7 @@ _fvc_pt_init(fvc_t *kd, size_t dump_avail_size, off_t dump_avail_off,
map_len);
return (-1);
}
- rd = pread(kd->pmfd, kd->pt_map, map_len, map_off);
+ rd = xpread(kd->pmfd, kd->pt_map, map_len, map_off);
if (rd < 0 || rd != (ssize_t)map_len) {
_fvc_err(kd, kd->program, "cannot read %zu bytes for bitmap",
map_len);

View File

@ -0,0 +1,5 @@
thread list
register read pc
bt
p *(int*)&hz
memory read &hz