[lldb][MachO] MachO corefile support for riscv32 binaries (#137092)

Add support for reading a macho corefile with CPU_TYPE_RISCV and the
riscv32 general purpose register file. I added code for the floating
point and exception registers too, but haven't exercised this. If we
start putting the full CSR register bank in a riscv corefile, it'll be
in separate 4k byte chunks, but I don't have a corefile to test against
that so I haven't written the code to read it.

The RegisterContextDarwin_riscv32 is copied & in the style of the other
RegisterContextDarwin classes; it's not the first choice I would make
for representing this, but it wasn't worth changing for this cputype.

rdar://145014653
This commit is contained in:
Jason Molenda
2025-04-23 22:10:15 -07:00
committed by GitHub
parent 31c7997a4a
commit 096ab51de0
7 changed files with 1931 additions and 0 deletions

View File

@@ -12,6 +12,7 @@
#include "Plugins/Process/Utility/RegisterContextDarwin_arm.h"
#include "Plugins/Process/Utility/RegisterContextDarwin_arm64.h"
#include "Plugins/Process/Utility/RegisterContextDarwin_i386.h"
#include "Plugins/Process/Utility/RegisterContextDarwin_riscv32.h"
#include "Plugins/Process/Utility/RegisterContextDarwin_x86_64.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
@@ -769,6 +770,147 @@ protected:
}
};
class RegisterContextDarwin_riscv32_Mach
: public RegisterContextDarwin_riscv32 {
public:
RegisterContextDarwin_riscv32_Mach(lldb_private::Thread &thread,
const DataExtractor &data)
: RegisterContextDarwin_riscv32(thread, 0) {
SetRegisterDataFrom_LC_THREAD(data);
}
void InvalidateAllRegisters() override {
// Do nothing... registers are always valid...
}
void SetRegisterDataFrom_LC_THREAD(const DataExtractor &data) {
lldb::offset_t offset = 0;
SetError(GPRRegSet, Read, -1);
SetError(FPURegSet, Read, -1);
SetError(EXCRegSet, Read, -1);
SetError(CSRRegSet, Read, -1);
bool done = false;
while (!done) {
int flavor = data.GetU32(&offset);
uint32_t count = data.GetU32(&offset);
lldb::offset_t next_thread_state = offset + (count * 4);
switch (flavor) {
case GPRRegSet:
// x0-x31 + pc
if (count >= 32) {
for (uint32_t i = 0; i < 32; ++i)
((uint32_t *)&gpr.x0)[i] = data.GetU32(&offset);
gpr.pc = data.GetU32(&offset);
SetError(GPRRegSet, Read, 0);
}
offset = next_thread_state;
break;
case FPURegSet: {
// f0-f31 + fcsr
if (count >= 32) {
for (uint32_t i = 0; i < 32; ++i)
((uint32_t *)&fpr.f0)[i] = data.GetU32(&offset);
fpr.fcsr = data.GetU32(&offset);
SetError(FPURegSet, Read, 0);
}
}
offset = next_thread_state;
break;
case EXCRegSet:
if (count == 3) {
exc.exception = data.GetU32(&offset);
exc.fsr = data.GetU32(&offset);
exc.far = data.GetU32(&offset);
SetError(EXCRegSet, Read, 0);
}
offset = next_thread_state;
break;
default:
done = true;
break;
}
}
}
static bool Create_LC_THREAD(Thread *thread, Stream &data) {
RegisterContextSP reg_ctx_sp(thread->GetRegisterContext());
if (reg_ctx_sp) {
RegisterContext *reg_ctx = reg_ctx_sp.get();
data.PutHex32(GPRRegSet); // Flavor
data.PutHex32(GPRWordCount);
PrintRegisterValue(reg_ctx, "x0", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x1", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x2", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x3", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x4", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x5", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x6", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x7", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x8", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x9", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x10", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x11", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x12", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x13", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x14", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x15", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x16", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x17", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x18", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x19", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x20", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x21", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x22", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x23", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x24", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x25", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x26", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x27", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x28", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x29", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x30", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "x31", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "pc", nullptr, 4, data);
data.PutHex32(0); // uint32_t pad at the end
// Write out the EXC registers
data.PutHex32(EXCRegSet);
data.PutHex32(EXCWordCount);
PrintRegisterValue(reg_ctx, "exception", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "fsr", nullptr, 4, data);
PrintRegisterValue(reg_ctx, "far", nullptr, 4, data);
return true;
}
return false;
}
protected:
int DoReadGPR(lldb::tid_t tid, int flavor, GPR &gpr) override { return -1; }
int DoReadFPU(lldb::tid_t tid, int flavor, FPU &fpu) override { return -1; }
int DoReadEXC(lldb::tid_t tid, int flavor, EXC &exc) override { return -1; }
int DoReadCSR(lldb::tid_t tid, int flavor, CSR &csr) override { return -1; }
int DoWriteGPR(lldb::tid_t tid, int flavor, const GPR &gpr) override {
return 0;
}
int DoWriteFPU(lldb::tid_t tid, int flavor, const FPU &fpu) override {
return 0;
}
int DoWriteEXC(lldb::tid_t tid, int flavor, const EXC &exc) override {
return 0;
}
int DoWriteCSR(lldb::tid_t tid, int flavor, const CSR &csr) override {
return 0;
}
};
static uint32_t MachHeaderSizeFromMagic(uint32_t magic) {
switch (magic) {
case MH_MAGIC:
@@ -5827,6 +5969,11 @@ ObjectFileMachO::GetThreadContextAtIndex(uint32_t idx,
reg_ctx_sp =
std::make_shared<RegisterContextDarwin_x86_64_Mach>(thread, data);
break;
case llvm::MachO::CPU_TYPE_RISCV:
reg_ctx_sp =
std::make_shared<RegisterContextDarwin_riscv32_Mach>(thread, data);
break;
}
}
}
@@ -6695,6 +6842,11 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
RegisterContextDarwin_x86_64_Mach::Create_LC_THREAD(
thread_sp.get(), LC_THREAD_datas[thread_idx]);
break;
case llvm::MachO::CPU_TYPE_RISCV:
RegisterContextDarwin_riscv32_Mach::Create_LC_THREAD(
thread_sp.get(), LC_THREAD_datas[thread_idx]);
break;
}
}
}

View File

@@ -20,6 +20,7 @@ add_lldb_library(lldbPluginProcessUtility
RegisterContextDarwin_arm.cpp
RegisterContextDarwin_arm64.cpp
RegisterContextDarwin_i386.cpp
RegisterContextDarwin_riscv32.cpp
RegisterContextDarwin_x86_64.cpp
RegisterContextDummy.cpp
RegisterContextFreeBSD_i386.cpp

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,260 @@
//===-- RegisterContextDarwin_riscv32.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_UTILITY_REGISTERCONTEXTDARWIN_RISCV32_H
#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTDARWIN_RISCV32_H
#include "lldb/Target/RegisterContext.h"
#include "lldb/lldb-private.h"
class RegisterContextDarwin_riscv32 : public lldb_private::RegisterContext {
public:
RegisterContextDarwin_riscv32(lldb_private::Thread &thread,
uint32_t concrete_frame_idx);
~RegisterContextDarwin_riscv32() override;
void InvalidateAllRegisters() override;
size_t GetRegisterCount() override;
const lldb_private::RegisterInfo *GetRegisterInfoAtIndex(size_t reg) override;
size_t GetRegisterSetCount() override;
const lldb_private::RegisterSet *GetRegisterSet(size_t set) override;
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;
bool ReadAllRegisterValues(lldb::WritableDataBufferSP &data_sp) override;
bool WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override;
uint32_t ConvertRegisterKindToRegisterNumber(lldb::RegisterKind kind,
uint32_t num) override;
struct GPR {
uint32_t x0;
uint32_t x1;
uint32_t x2;
uint32_t x3;
uint32_t x4;
uint32_t x5;
uint32_t x6;
uint32_t x7;
uint32_t x8;
uint32_t x9;
uint32_t x10;
uint32_t x11;
uint32_t x12;
uint32_t x13;
uint32_t x14;
uint32_t x15;
uint32_t x16;
uint32_t x17;
uint32_t x18;
uint32_t x19;
uint32_t x20;
uint32_t x21;
uint32_t x22;
uint32_t x23;
uint32_t x24;
uint32_t x25;
uint32_t x26;
uint32_t x27;
uint32_t x28;
uint32_t x29;
uint32_t x30;
uint32_t x31;
uint32_t pc;
};
struct FPU {
uint32_t f0;
uint32_t f1;
uint32_t f2;
uint32_t f3;
uint32_t f4;
uint32_t f5;
uint32_t f6;
uint32_t f7;
uint32_t f8;
uint32_t f9;
uint32_t f10;
uint32_t f11;
uint32_t f12;
uint32_t f13;
uint32_t f14;
uint32_t f15;
uint32_t f16;
uint32_t f17;
uint32_t f18;
uint32_t f19;
uint32_t f20;
uint32_t f21;
uint32_t f22;
uint32_t f23;
uint32_t f24;
uint32_t f25;
uint32_t f26;
uint32_t f27;
uint32_t f28;
uint32_t f29;
uint32_t f30;
uint32_t f31;
uint32_t fcsr;
};
struct EXC {
uint32_t exception;
uint32_t fsr;
uint32_t far;
};
struct CSR {
uint32_t csr[1024];
};
protected:
enum {
GPRRegSet = 2, // RV32_THREAD_STATE
EXCRegSet = 3, // RV32_EXCEPTION_STATE
FPURegSet = 4, // RV_FP32_STATE
CSRRegSet1 = 6, // RV_CSR_STATE1
CSRRegSet2 = 7, // RV_CSR_STATE2
CSRRegSet3 = 8, // RV_CSR_STATE3
CSRRegSet4 = 9, // RV_CSR_STATE4
CSRRegSet = 10 // full 16kbyte CSR reg bank
};
enum {
GPRWordCount = sizeof(GPR) / sizeof(uint32_t),
FPUWordCount = sizeof(FPU) / sizeof(uint32_t),
EXCWordCount = sizeof(EXC) / sizeof(uint32_t),
CSRWordCount = sizeof(CSR) / sizeof(uint32_t)
};
enum { Read = 0, Write = 1, kNumErrors = 2 };
GPR gpr;
FPU fpr;
EXC exc;
CSR csr;
int gpr_errs[2]; // Read/Write errors
int fpr_errs[2]; // Read/Write errors
int exc_errs[2]; // Read/Write errors
int csr_errs[2]; // Read/Write errors
void InvalidateAllRegisterStates() {
SetError(GPRRegSet, Read, -1);
SetError(FPURegSet, Read, -1);
SetError(EXCRegSet, Read, -1);
SetError(CSRRegSet, Read, -1);
}
int GetError(int flavor, uint32_t err_idx) const {
if (err_idx < kNumErrors) {
switch (flavor) {
// When getting all errors, just OR all values together to see if
// we got any kind of error.
case GPRRegSet:
return gpr_errs[err_idx];
case FPURegSet:
return fpr_errs[err_idx];
case EXCRegSet:
return exc_errs[err_idx];
case CSRRegSet:
return csr_errs[err_idx];
default:
break;
}
}
return -1;
}
bool SetError(int flavor, uint32_t err_idx, int err) {
if (err_idx < kNumErrors) {
switch (flavor) {
case GPRRegSet:
gpr_errs[err_idx] = err;
return true;
case FPURegSet:
fpr_errs[err_idx] = err;
return true;
case EXCRegSet:
exc_errs[err_idx] = err;
return true;
case CSRRegSet:
csr_errs[err_idx] = err;
return true;
default:
break;
}
}
return false;
}
bool RegisterSetIsCached(int set) const { return GetError(set, Read) == 0; }
void LogGPR(lldb_private::Log *log, const char *title);
int ReadGPR(bool force);
int ReadFPU(bool force);
int ReadEXC(bool force);
int ReadCSR(bool force);
int WriteGPR();
int WriteFPU();
int WriteEXC();
int WriteCSR();
// Subclasses override these to do the actual reading.
virtual int DoReadGPR(lldb::tid_t tid, int flavor, GPR &gpr) = 0;
virtual int DoReadFPU(lldb::tid_t tid, int flavor, FPU &fpr) = 0;
virtual int DoReadEXC(lldb::tid_t tid, int flavor, EXC &exc) = 0;
virtual int DoReadCSR(lldb::tid_t tid, int flavor, CSR &exc) = 0;
virtual int DoWriteGPR(lldb::tid_t tid, int flavor, const GPR &gpr) = 0;
virtual int DoWriteFPU(lldb::tid_t tid, int flavor, const FPU &fpr) = 0;
virtual int DoWriteEXC(lldb::tid_t tid, int flavor, const EXC &exc) = 0;
virtual int DoWriteCSR(lldb::tid_t tid, int flavor, const CSR &exc) = 0;
int ReadRegisterSet(uint32_t set, bool force);
int WriteRegisterSet(uint32_t set);
static uint32_t GetRegisterNumber(uint32_t reg_kind, uint32_t reg_num);
static int GetSetForNativeRegNum(int reg_num);
static size_t GetRegisterInfosCount();
static const lldb_private::RegisterInfo *GetRegisterInfos();
};
#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTDARWIN_RISCV32_H

View File

@@ -0,0 +1,7 @@
MAKE_DSYM := NO
CXX_SOURCES := create-empty-riscv-corefile.cpp
EXE := create-empty-riscv-corefile
all: create-empty-riscv-corefile
include Makefile.rules

View File

@@ -0,0 +1,82 @@
"""Test that all of the GPR registers are read correctly from a riscv32 corefile."""
import os
import re
import subprocess
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestRV32MachOCorefile(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@skipUnlessDarwin
def test_riscv32_gpr_corefile_registers(self):
self.build()
create_corefile = self.getBuildArtifact("create-empty-riscv-corefile")
corefile = self.getBuildArtifact("core")
call(create_corefile + " " + corefile, shell=True)
target = self.dbg.CreateTarget("")
process = target.LoadCore(corefile)
process = target.GetProcess()
self.assertEqual(process.GetNumThreads(), 1)
thread = process.GetThreadAtIndex(0)
self.assertEqual(thread.GetNumFrames(), 1)
frame = thread.GetFrameAtIndex(0)
gpr_regs = frame.registers.GetValueAtIndex(0)
self.assertEqual(gpr_regs.GetName(), "General Purpose Registers")
self.assertEqual(gpr_regs.GetNumChildren(), 33)
regnames = [
"zero",
"ra",
"sp",
"gp",
"tp",
"t0",
"t1",
"t2",
"fp",
"s1",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"t3",
"t4",
"t5",
"t6",
"pc",
]
idx = 0
while idx < len(regnames):
self.assertEqual(gpr_regs.GetChildAtIndex(idx).GetName(), regnames[idx])
idx = idx + 1
idx = 0
while idx < len(regnames):
val = idx | (idx << 8) | (idx << 16) | (idx << 24)
self.assertEqual(gpr_regs.GetChildAtIndex(idx).GetValueAsUnsigned(), val)
idx = idx + 1

View File

@@ -0,0 +1,116 @@
#include <inttypes.h>
#include <mach-o/loader.h>
#include <mach/thread_status.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/errno.h>
#include <uuid/uuid.h>
#include <vector>
#define CPU_TYPE_RISCV 24
#define CPU_SUBTYPE_RISCV_ALL 0
#define RV32_THREAD_STATE 2
// x0-x31 + pc, all 32-bit
#define RV32_THREAD_STATE_COUNT 33
union uint32_buf {
uint8_t bytebuf[4];
uint32_t val;
};
union uint64_buf {
uint8_t bytebuf[8];
uint64_t val;
};
void add_uint64(std::vector<uint8_t> &buf, uint64_t val) {
uint64_buf conv;
conv.val = val;
for (int i = 0; i < 8; i++)
buf.push_back(conv.bytebuf[i]);
}
void add_uint32(std::vector<uint8_t> &buf, uint32_t val) {
uint32_buf conv;
conv.val = val;
for (int i = 0; i < 4; i++)
buf.push_back(conv.bytebuf[i]);
}
std::vector<uint8_t> lc_thread_load_command() {
std::vector<uint8_t> data;
add_uint32(data, LC_THREAD); // thread_command.cmd
add_uint32(data, 4 + 4 + 4 + 4 +
(RV32_THREAD_STATE_COUNT * 4)); // thread_command.cmdsize
add_uint32(data, RV32_THREAD_STATE); // thread_command.flavor
add_uint32(data, RV32_THREAD_STATE_COUNT); // thread_command.count
for (int i = 0; i < RV32_THREAD_STATE_COUNT; i++) {
add_uint32(data, i | (i << 8) | (i << 16) | (i << 24));
}
return data;
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr,
"usage: create-empty-riscv-corefile output-corefile-name\n");
exit(1);
}
cpu_type_t cputype = CPU_TYPE_RISCV;
cpu_subtype_t cpusubtype = CPU_SUBTYPE_RISCV_ALL;
// An array of load commands (in the form of byte arrays)
std::vector<std::vector<uint8_t>> load_commands;
// An array of corefile contents (page data, lc_note data, etc)
std::vector<uint8_t> payload;
// First add all the load commands / payload so we can figure out how large
// the load commands will actually be.
load_commands.push_back(lc_thread_load_command());
int size_of_load_commands = 0;
for (const auto &lc : load_commands)
size_of_load_commands += lc.size();
int header_and_load_cmd_room =
sizeof(struct mach_header_64) + size_of_load_commands;
// Erase the load commands / payload now that we know how much space is
// needed, redo it.
load_commands.clear();
payload.clear();
load_commands.push_back(lc_thread_load_command());
struct mach_header mh;
mh.magic = MH_MAGIC;
mh.cputype = cputype;
mh.cpusubtype = cpusubtype;
mh.filetype = MH_CORE;
mh.ncmds = load_commands.size();
mh.sizeofcmds = size_of_load_commands;
mh.flags = 0;
FILE *f = fopen(argv[1], "w");
if (f == nullptr) {
fprintf(stderr, "Unable to open file %s for writing\n", argv[1]);
exit(1);
}
fwrite(&mh, sizeof(struct mach_header), 1, f);
for (const auto &lc : load_commands)
fwrite(lc.data(), lc.size(), 1, f);
fseek(f, header_and_load_cmd_room, SEEK_SET);
fwrite(payload.data(), payload.size(), 1, f);
fclose(f);
}