From d3a9043ec2ee7ea278be4f3c86823512e44d01bf Mon Sep 17 00:00:00 2001 From: Alexey Merzlyakov <60094858+AlexeyMerzlyakov@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:14:48 +0300 Subject: [PATCH] [lldb][RISCV] Add RegisterContextPOSIXCore for RISC-V 64 (#93297) The PR adds the support of CoreDump debugging for RISC-V 64. It implements new `RegisterContextCorePOSIX_riscv64` class. Also, the contribution fixes `GetRegisterCount()` -> `GetRegisterSetCount()` misprint in `RegisterContextPOSIX_riscv64::GetRegisterSetCount()` method, which leaded to `set && "Register set should be valid."` assertion during `register info aX` command call. The patch was tested (on coredumps generated for simple Integer/FP calculation code) for _cross x86_64 -> RISCV_ and _native RISCV_ LLDB builds. There were performed basic LLDB functionality tests, such as: - CoreDump file load - Backtrace / frames - GP/FP registers read/info/list - Basic switch between threads - Disassembler code - Memory regions read / display --- .../Utility/RegisterContextPOSIX_riscv64.cpp | 2 +- .../Plugins/Process/elf-core/CMakeLists.txt | 1 + .../RegisterContextPOSIXCore_riscv64.cpp | 82 ++++++++++++++ .../RegisterContextPOSIXCore_riscv64.h | 60 +++++++++++ .../Process/elf-core/ThreadElfCore.cpp | 8 +- .../postmortem/elf-core/TestLinuxCore.py | 100 ++++++++++++++++++ .../postmortem/elf-core/linux-riscv64.core | Bin 0 -> 20480 bytes .../postmortem/elf-core/linux-riscv64.out | Bin 0 -> 3328 bytes 8 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.cpp create mode 100644 lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.h create mode 100644 lldb/test/API/functionalities/postmortem/elf-core/linux-riscv64.core create mode 100755 lldb/test/API/functionalities/postmortem/elf-core/linux-riscv64.out diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_riscv64.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_riscv64.cpp index 1834a94dc026..035ce00e1162 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_riscv64.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_riscv64.cpp @@ -58,7 +58,7 @@ RegisterContextPOSIX_riscv64::GetRegisterInfoAtIndex(size_t reg) { } size_t RegisterContextPOSIX_riscv64::GetRegisterSetCount() { - return m_register_info_up->GetRegisterCount(); + return m_register_info_up->GetRegisterSetCount(); } const lldb_private::RegisterSet * diff --git a/lldb/source/Plugins/Process/elf-core/CMakeLists.txt b/lldb/source/Plugins/Process/elf-core/CMakeLists.txt index 8ddc671e3ae6..72925c835b5c 100644 --- a/lldb/source/Plugins/Process/elf-core/CMakeLists.txt +++ b/lldb/source/Plugins/Process/elf-core/CMakeLists.txt @@ -9,6 +9,7 @@ add_lldb_library(lldbPluginProcessElfCore PLUGIN RegisterContextPOSIXCore_ppc64le.cpp RegisterContextPOSIXCore_s390x.cpp RegisterContextPOSIXCore_x86_64.cpp + RegisterContextPOSIXCore_riscv64.cpp RegisterUtilities.cpp LINK_LIBS diff --git a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.cpp b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.cpp new file mode 100644 index 000000000000..5ba18cdb9889 --- /dev/null +++ b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.cpp @@ -0,0 +1,82 @@ +//===-- RegisterContextPOSIXCore_riscv64.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 "RegisterContextPOSIXCore_riscv64.h" + +#include "lldb/Utility/DataBufferHeap.h" + +using namespace lldb_private; + +std::unique_ptr +RegisterContextCorePOSIX_riscv64::Create(Thread &thread, const ArchSpec &arch, + const DataExtractor &gpregset, + llvm::ArrayRef notes) { + return std::unique_ptr( + new RegisterContextCorePOSIX_riscv64( + thread, std::make_unique(arch, Flags()), + gpregset, notes)); +} + +RegisterContextCorePOSIX_riscv64::RegisterContextCorePOSIX_riscv64( + Thread &thread, std::unique_ptr register_info, + const DataExtractor &gpregset, llvm::ArrayRef notes) + : RegisterContextPOSIX_riscv64(thread, std::move(register_info)) { + + m_gpr_buffer = std::make_shared(gpregset.GetDataStart(), + gpregset.GetByteSize()); + m_gpr.SetData(m_gpr_buffer); + m_gpr.SetByteOrder(gpregset.GetByteOrder()); + + ArchSpec arch = m_register_info_up->GetTargetArchitecture(); + DataExtractor fpregset = getRegset(notes, arch.GetTriple(), FPR_Desc); + m_fpr_buffer = std::make_shared(fpregset.GetDataStart(), + fpregset.GetByteSize()); + m_fpr.SetData(m_fpr_buffer); + m_fpr.SetByteOrder(fpregset.GetByteOrder()); +} + +RegisterContextCorePOSIX_riscv64::~RegisterContextCorePOSIX_riscv64() = default; + +bool RegisterContextCorePOSIX_riscv64::ReadGPR() { return true; } + +bool RegisterContextCorePOSIX_riscv64::ReadFPR() { return true; } + +bool RegisterContextCorePOSIX_riscv64::WriteGPR() { + assert(false && "Writing registers is not allowed for core dumps"); + return false; +} + +bool RegisterContextCorePOSIX_riscv64::WriteFPR() { + assert(false && "Writing registers is not allowed for core dumps"); + return false; +} + +bool RegisterContextCorePOSIX_riscv64::ReadRegister( + const RegisterInfo *reg_info, RegisterValue &value) { + const uint8_t *src = nullptr; + lldb::offset_t offset = reg_info->byte_offset; + + if (IsGPR(reg_info->kinds[lldb::eRegisterKindLLDB])) { + src = m_gpr.GetDataStart(); + } else if (IsFPR(reg_info->kinds[lldb::eRegisterKindLLDB])) { + src = m_fpr.GetDataStart(); + offset -= GetGPRSize(); + } else { + return false; + } + + Status error; + value.SetFromMemoryData(*reg_info, src + offset, reg_info->byte_size, + lldb::eByteOrderLittle, error); + return error.Success(); +} + +bool RegisterContextCorePOSIX_riscv64::WriteRegister( + const RegisterInfo *reg_info, const RegisterValue &value) { + return false; +} diff --git a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.h b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.h new file mode 100644 index 000000000000..3cf9531df2c1 --- /dev/null +++ b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_riscv64.h @@ -0,0 +1,60 @@ +//===-- RegisterContextPOSIXCore_riscv64.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_ELF_CORE_REGISTERCONTEXTPOSIXCORE_RISCV64_H +#define LLDB_SOURCE_PLUGINS_PROCESS_ELF_CORE_REGISTERCONTEXTPOSIXCORE_RISCV64_H + +#include "Plugins/Process/Utility/RegisterContextPOSIX_riscv64.h" +#include "Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h" + +#include "Plugins/Process/elf-core/RegisterUtilities.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/RegisterValue.h" + +#include + +class RegisterContextCorePOSIX_riscv64 : public RegisterContextPOSIX_riscv64 { +public: + static std::unique_ptr + Create(lldb_private::Thread &thread, const lldb_private::ArchSpec &arch, + const lldb_private::DataExtractor &gpregset, + llvm::ArrayRef notes); + + ~RegisterContextCorePOSIX_riscv64() 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; + +protected: + RegisterContextCorePOSIX_riscv64( + lldb_private::Thread &thread, + std::unique_ptr register_info, + const lldb_private::DataExtractor &gpregset, + llvm::ArrayRef notes); + + bool ReadGPR() override; + + bool ReadFPR() override; + + bool WriteGPR() override; + + bool WriteFPR() override; + +private: + lldb::DataBufferSP m_gpr_buffer; + lldb::DataBufferSP m_fpr_buffer; + + lldb_private::DataExtractor m_gpr; + lldb_private::DataExtractor m_fpr; +}; + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_ELF_CORE_REGISTERCONTEXTPOSIXCORE_RISCV64_H diff --git a/lldb/source/Plugins/Process/elf-core/ThreadElfCore.cpp b/lldb/source/Plugins/Process/elf-core/ThreadElfCore.cpp index 3ce2a4a5c3fa..2a83163884e1 100644 --- a/lldb/source/Plugins/Process/elf-core/ThreadElfCore.cpp +++ b/lldb/source/Plugins/Process/elf-core/ThreadElfCore.cpp @@ -35,6 +35,7 @@ #include "RegisterContextPOSIXCore_mips64.h" #include "RegisterContextPOSIXCore_powerpc.h" #include "RegisterContextPOSIXCore_ppc64le.h" +#include "RegisterContextPOSIXCore_riscv64.h" #include "RegisterContextPOSIXCore_s390x.h" #include "RegisterContextPOSIXCore_x86_64.h" #include "ThreadElfCore.h" @@ -168,7 +169,8 @@ ThreadElfCore::CreateRegisterContextForFrame(StackFrame *frame) { } if (!reg_interface && arch.GetMachine() != llvm::Triple::aarch64 && - arch.GetMachine() != llvm::Triple::arm) { + arch.GetMachine() != llvm::Triple::arm && + arch.GetMachine() != llvm::Triple::riscv64) { LLDB_LOGF(log, "elf-core::%s:: Architecture(%d) or OS(%d) not supported", __FUNCTION__, arch.GetMachine(), arch.GetTriple().getOS()); assert(false && "Architecture or OS not supported"); @@ -184,6 +186,10 @@ ThreadElfCore::CreateRegisterContextForFrame(StackFrame *frame) { *this, std::make_unique(arch), m_gpregset_data, m_notes); break; + case llvm::Triple::riscv64: + m_thread_reg_ctx_sp = RegisterContextCorePOSIX_riscv64::Create( + *this, arch, m_gpregset_data, m_notes); + break; case llvm::Triple::mipsel: case llvm::Triple::mips: m_thread_reg_ctx_sp = std::make_shared( diff --git a/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py b/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py index 8ec0cbdd0fdd..1eaaa87d3b87 100644 --- a/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py +++ b/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py @@ -21,12 +21,14 @@ class LinuxCoreTestCase(TestBase): _x86_64_pid = 32259 _s390x_pid = 1045 _ppc64le_pid = 28147 + _riscv64_pid = 89328 _aarch64_regions = 4 _i386_regions = 4 _x86_64_regions = 5 _s390x_regions = 2 _ppc64le_regions = 2 + _riscv64_regions = 4 @skipIfLLVMTargetMissing("AArch64") def test_aarch64(self): @@ -58,6 +60,11 @@ class LinuxCoreTestCase(TestBase): """Test that lldb can read the process information from an s390x linux core file.""" self.do_test("linux-s390x", self._s390x_pid, self._s390x_regions, "a.out") + @skipIfLLVMTargetMissing("RISCV") + def test_riscv64(self): + """Test that lldb can read the process information from an riscv64 linux core file.""" + self.do_test("linux-riscv64", self._riscv64_pid, self._riscv64_regions, "a.out") + @skipIfLLVMTargetMissing("X86") def test_same_pid_running(self): """Test that we read the information from the core correctly even if we have a running @@ -629,6 +636,99 @@ class LinuxCoreTestCase(TestBase): self.expect("register read --all") + @skipIfLLVMTargetMissing("RISCV") + def test_riscv64_regs(self): + # check basic registers using 64 bit RISC-V core file + target = self.dbg.CreateTarget(None) + self.assertTrue(target, VALID_TARGET) + process = target.LoadCore("linux-riscv64.core") + + values = {} + values["pc"] = "0x000000000001015e" + values["ra"] = "0x000000000001018c" + values["sp"] = "0x0000003fffd132a0" + values["gp"] = "0x0000002ae919af50" + values["tp"] = "0x0000003fdceae3e0" + values["t0"] = "0x0" + values["t1"] = "0x0000002ae9187b1c" + values["t2"] = "0x0000000000000021" + values["fp"] = "0x0000003fffd132d0" + values["s1"] = "0x0000002ae919cd98" + values["a0"] = "0x0" + values["a1"] = "0x0000000000010144" + values["a2"] = "0x0000002ae919cdb0" + values["a3"] = "0x000000000000002f" + values["a4"] = "0x000000000000002f" + values["a5"] = "0x0" + values["a6"] = "0x7efefefefefefeff" + values["a7"] = "0x00000000000000dd" + values["s2"] = "0x0000002ae9196860" + values["s3"] = "0x0000002ae919cdb0" + values["s4"] = "0x0000003fffc63be8" + values["s5"] = "0x0000002ae919cb78" + values["s6"] = "0x0000002ae9196860" + values["s7"] = "0x0000002ae9196860" + values["s8"] = "0x0" + values["s9"] = "0x000000000000000f" + values["s10"] = "0x0000002ae919a8d0" + values["s11"] = "0x0000000000000008" + values["t3"] = "0x0000003fdce07df4" + values["t4"] = "0x0" + values["t5"] = "0x0000000000000020" + values["t6"] = "0x0000002ae919f1b0" + values["zero"] = "0x0" + values["fcsr"] = "0x00000000" + + fpr_names = { + "ft0", + "ft1", + "ft2", + "ft3", + "ft4", + "ft5", + "ft6", + "ft7", + "ft8", + "ft9", + "ft10", + "ft11", + "fa0", + "fa1", + "fa2", + "fa3", + "fa4", + "fa5", + "fa6", + "fa7", + "fs0", + "fs1", + "fs2", + "fs3", + "fs4", + "fs5", + "fs6", + "fs7", + "fs8", + "fs9", + "fs10", + "fs11", + } + fpr_value = "0x0000000000000000" + + for regname, value in values.items(): + self.expect( + "register read {}".format(regname), + substrs=["{} = {}".format(regname, value)], + ) + + for regname in fpr_names: + self.expect( + "register read {}".format(regname), + substrs=["{} = {}".format(regname, fpr_value)], + ) + + self.expect("register read --all") + def test_get_core_file_api(self): """ Test SBProcess::GetCoreFile() API can successfully get the core file. diff --git a/lldb/test/API/functionalities/postmortem/elf-core/linux-riscv64.core b/lldb/test/API/functionalities/postmortem/elf-core/linux-riscv64.core new file mode 100644 index 0000000000000000000000000000000000000000..0b159fcab931dca85bd5d09c282c2d6c217e2b83 GIT binary patch literal 20480 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 literal 0 HcmV?d00001