diff --git a/lldb/include/lldb/Target/RegisterFlags.h b/lldb/include/lldb/Target/RegisterFlags.h index a088981918cb..9b343e445678 100644 --- a/lldb/include/lldb/Target/RegisterFlags.h +++ b/lldb/include/lldb/Target/RegisterFlags.h @@ -84,6 +84,11 @@ public: RegisterFlags(std::string id, unsigned size, const std::vector &fields); + /// Replace all the fields with the new set of fields. All the assumptions + /// and checks apply as when you use the constructor. Intended to only be used + /// when runtime field detection is needed. + void SetFields(const std::vector &fields); + // Reverse the order of the fields, keeping their values the same. // For example a field from bit 31 to 30 with value 0b10 will become bits // 1 to 0, with the same 0b10 value. diff --git a/lldb/include/lldb/lldb-private-types.h b/lldb/include/lldb/lldb-private-types.h index e6717836331f..7d301666df1a 100644 --- a/lldb/include/lldb/lldb-private-types.h +++ b/lldb/include/lldb/lldb-private-types.h @@ -62,7 +62,11 @@ struct RegisterInfo { /// rax ax, ah, and al. uint32_t *invalidate_regs; /// If not nullptr, a type defined by XML descriptions. - const RegisterFlags *flags_type; + /// Register info tables are constructed as const, but this field may need to + /// be updated if a specific target OS has a different layout. To enable that, + /// this is mutable. The data pointed to is still const, so you must swap a + /// whole set of flags for another. + mutable const RegisterFlags *flags_type; llvm::ArrayRef data(const uint8_t *context_base) const { return llvm::ArrayRef(context_base + byte_offset, byte_size); diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp index e23165933c22..9b5f7aef1efe 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -23,6 +23,7 @@ #include "Plugins/Process/Linux/Procfs.h" #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" #include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h" +#include "Plugins/Process/Utility/RegisterFlagsLinux_arm64.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" // System includes - They have to be included after framework includes because @@ -30,6 +31,7 @@ #include // NT_PRSTATUS and NT_FPREGSET definition #include +#include #include #ifndef NT_ARM_SVE @@ -59,12 +61,20 @@ #endif #define HWCAP_PACA (1 << 30) + #define HWCAP2_MTE (1 << 18) using namespace lldb; using namespace lldb_private; using namespace lldb_private::process_linux; +// A NativeRegisterContext is constructed per thread, but all threads' registers +// will contain the same fields. Therefore this mutex prevents each instance +// competing with the other, and subsequent instances from having to detect the +// fields all over again. +static std::mutex g_register_flags_mutex; +static LinuxArm64RegisterFlags g_register_flags; + std::unique_ptr NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux( const ArchSpec &target_arch, NativeThreadLinux &native_thread) { @@ -134,6 +144,11 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux( opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS); + std::lock_guard lock(g_register_flags_mutex); + if (!g_register_flags.HasDetected()) + g_register_flags.DetectFields(auxv_at_hwcap.value_or(0), + auxv_at_hwcap2.value_or(0)); + auto register_info_up = std::make_unique(target_arch, opt_regsets); return std::make_unique( @@ -156,6 +171,10 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64( : NativeRegisterContextRegisterInfo(native_thread, register_info_up.release()), NativeRegisterContextLinux(native_thread) { + g_register_flags.UpdateRegisterInfo( + GetRegisterInfoInterface().GetRegisterInfo(), + GetRegisterInfoInterface().GetRegisterCount()); + ::memset(&m_fpr, 0, sizeof(m_fpr)); ::memset(&m_gpr_arm64, 0, sizeof(m_gpr_arm64)); ::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs)); diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt index 1ebd0484f021..37b53b7e3e7e 100644 --- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -47,6 +47,7 @@ add_lldb_library(lldbPluginProcessUtility RegisterContextThreadMemory.cpp RegisterContextWindows_i386.cpp RegisterContextWindows_x86_64.cpp + RegisterFlagsLinux_arm64.cpp RegisterInfos_x86_64_with_base_shared.cpp RegisterInfoPOSIX_arm.cpp RegisterInfoPOSIX_arm64.cpp diff --git a/lldb/source/Plugins/Process/Utility/RegisterFlagsLinux_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterFlagsLinux_arm64.cpp new file mode 100644 index 000000000000..043789bd6d21 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/RegisterFlagsLinux_arm64.cpp @@ -0,0 +1,116 @@ +//===-- RegisterFlagsLinux_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 "RegisterFlagsLinux_arm64.h" +#include "lldb/lldb-private-types.h" + +// This file is built on all systems because it is used by native processes and +// core files, so we manually define the needed HWCAP values here. + +#define HWCAP_DIT (1 << 24) +#define HWCAP_SSBS (1 << 28) + +#define HWCAP2_BTI (1 << 17) +#define HWCAP2_MTE (1 << 18) + +using namespace lldb_private; + +LinuxArm64RegisterFlags::Fields +LinuxArm64RegisterFlags::DetectCPSRFields(uint64_t hwcap, uint64_t hwcap2) { + // The fields here are a combination of the Arm manual's SPSR_EL1, + // plus a few changes where Linux has decided not to make use of them at all, + // or at least not from userspace. + + // Status bits that are always present. + std::vector cpsr_fields{ + {"N", 31}, {"Z", 30}, {"C", 29}, {"V", 28}, + // Bits 27-26 reserved. + }; + + if (hwcap2 & HWCAP2_MTE) + cpsr_fields.push_back({"TCO", 25}); + if (hwcap & HWCAP_DIT) + cpsr_fields.push_back({"DIT", 24}); + + // UAO and PAN are bits 23 and 22 and have no meaning for userspace so + // are treated as reserved by the kernel. + + cpsr_fields.push_back({"SS", 21}); + cpsr_fields.push_back({"IL", 20}); + // Bits 19-14 reserved. + + // Bit 13, ALLINT, requires FEAT_NMI that isn't relevant to userspace, and we + // can't detect either, don't show this field. + if (hwcap & HWCAP_SSBS) + cpsr_fields.push_back({"SSBS", 12}); + if (hwcap2 & HWCAP2_BTI) + cpsr_fields.push_back({"BTYPE", 10, 11}); + + cpsr_fields.push_back({"D", 9}); + cpsr_fields.push_back({"A", 8}); + cpsr_fields.push_back({"I", 7}); + cpsr_fields.push_back({"F", 6}); + // Bit 5 reserved + // Called "M" in the ARMARM. + cpsr_fields.push_back({"nRW", 4}); + // This is a 4 bit field M[3:0] in the ARMARM, we split it into parts. + cpsr_fields.push_back({"EL", 2, 3}); + // Bit 1 is unused and expected to be 0. + cpsr_fields.push_back({"SP", 0}); + + return cpsr_fields; +} + +void LinuxArm64RegisterFlags::DetectFields(uint64_t hwcap, uint64_t hwcap2) { + for (auto ® : m_registers) + reg.m_flags.SetFields(reg.m_detector(hwcap, hwcap2)); + m_has_detected = true; +} + +void LinuxArm64RegisterFlags::UpdateRegisterInfo(const RegisterInfo *reg_info, + uint32_t num_regs) { + assert(m_has_detected && + "Must call DetectFields before updating register info."); + + // Register names will not be duplicated, so we do not want to compare against + // one if it has already been found. Each time we find one, we erase it from + // this list. + std::vector> + search_registers; + for (const auto ® : m_registers) { + // It is possible that a register is all extension dependent fields, and + // none of them are present. + if (reg.m_flags.GetFields().size()) + search_registers.push_back({reg.m_name, ®.m_flags}); + } + + // Walk register information while there are registers we know need + // to be updated. Example: + // Register information: [a, b, c, d] + // To be patched: [b, c] + // * a != b, a != c, do nothing and move on. + // * b == b, patch b, new patch list is [c], move on. + // * c == c, patch c, patch list is empty, exit early without looking at d. + for (uint32_t idx = 0; idx < num_regs && search_registers.size(); + ++idx, ++reg_info) { + auto reg_it = std::find_if( + search_registers.cbegin(), search_registers.cend(), + [reg_info](auto reg) { return reg.first == reg_info->name; }); + + if (reg_it != search_registers.end()) { + // Attach the field information. + reg_info->flags_type = reg_it->second; + // We do not expect to see this name again so don't look for it again. + search_registers.erase(reg_it); + } + } + + // We do not assert that search_registers is empty here, because it may + // contain registers from optional extensions that are not present on the + // current target. +} \ No newline at end of file diff --git a/lldb/source/Plugins/Process/Utility/RegisterFlagsLinux_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterFlagsLinux_arm64.h new file mode 100644 index 000000000000..6c7a3b61a142 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/RegisterFlagsLinux_arm64.h @@ -0,0 +1,78 @@ +//===-- RegisterFlagsLinux_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_UTILITY_REGISTERFLAGSLINUX_ARM64_H +#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERFLAGSLINUX_ARM64_H + +#include "lldb/Target/RegisterFlags.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace lldb_private { + +struct RegisterInfo; + +/// This class manages the storage and detection of register field information +/// for Arm64 Linux registers. The same register may have different fields on +/// different CPUs. This class abstracts out the field detection process so we +/// can use it on live processes and core files. +/// +/// The general way to use this class is: +/// * Make an instance somewhere that will last as long as the debug session +/// (because your final register info will point to this instance). +/// * Read hardware capabilities from a core note, binary, prctl, etc. +/// * Pass those to DetectFields. +/// * Call UpdateRegisterInfo with your RegisterInfo to add pointers +/// to the detected fields for all registers listed in this class. +/// +/// This must be done in that order, and you should ensure that if multiple +/// threads will reference the information, a mutex is used to make sure only +/// one calls DetectFields. +class LinuxArm64RegisterFlags { +public: + /// For the registers listed in this class, detect which fields are + /// present. Must be called before UpdateRegisterInfos. + /// If called more than once, fields will be redetected each time from + /// scratch. If you do not have access to hwcap, just pass 0 for each one, you + /// will only get unconditional fields. + void DetectFields(uint64_t hwcap, uint64_t hwcap2); + + /// Add the field information of any registers named in this class, + /// to the relevant RegisterInfo instances. Note that this will be done + /// with a pointer to the instance of this class that you call this on, so + /// the lifetime of that instance must be at least that of the register info. + void UpdateRegisterInfo(const RegisterInfo *reg_info, uint32_t num_regs); + + /// Returns true if field detection has been run at least once. + bool HasDetected() const { return m_has_detected; } + +private: + using Fields = std::vector; + using DetectorFn = std::function; + + static Fields DetectCPSRFields(uint64_t hwcap, uint64_t hwcap2); + + struct RegisterEntry { + RegisterEntry(llvm::StringRef name, unsigned size, DetectorFn detector) + : m_name(name), m_flags(std::string(name) + "_flags", size, {{"", 0}}), + m_detector(detector) {} + + llvm::StringRef m_name; + RegisterFlags m_flags; + DetectorFn m_detector; + } m_registers[1] = { + RegisterEntry("cpsr", 4, DetectCPSRFields), + }; + + // Becomes true once field detection has been run for all registers. + bool m_has_detected = false; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERFLAGSLINUX_ARM64_H \ No newline at end of file diff --git a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp index 85073b56f64b..07501c10ec3c 100644 --- a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp +++ b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp @@ -9,6 +9,9 @@ #include "RegisterContextPOSIXCore_arm64.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" +#include "Plugins/Process/Utility/AuxVector.h" +#include "Plugins/Process/Utility/RegisterFlagsLinux_arm64.h" +#include "Plugins/Process/elf-core/ProcessElfCore.h" #include "Plugins/Process/elf-core/RegisterUtilities.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/RegisterValue.h" @@ -74,6 +77,21 @@ RegisterContextCorePOSIX_arm64::RegisterContextCorePOSIX_arm64( : RegisterContextPOSIX_arm64(thread, std::move(register_info)) { ::memset(&m_sme_pseudo_regs, 0, sizeof(m_sme_pseudo_regs)); + ProcessElfCore *process = + static_cast(thread.GetProcess().get()); + if (process->GetArchitecture().GetTriple().getOS() == llvm::Triple::Linux) { + AuxVector aux_vec(process->GetAuxvData()); + std::optional auxv_at_hwcap = + aux_vec.GetAuxValue(AuxVector::AUXV_AT_HWCAP); + std::optional auxv_at_hwcap2 = + aux_vec.GetAuxValue(AuxVector::AUXV_AT_HWCAP2); + + m_linux_register_flags.DetectFields(auxv_at_hwcap.value_or(0), + auxv_at_hwcap2.value_or(0)); + m_linux_register_flags.UpdateRegisterInfo(GetRegisterInfo(), + GetRegisterCount()); + } + m_gpr_data.SetData(std::make_shared(gpregset.GetDataStart(), gpregset.GetByteSize())); m_gpr_data.SetByteOrder(gpregset.GetByteOrder()); diff --git a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h index 95527af74fb5..38e958851dfe 100644 --- a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h +++ b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h @@ -11,6 +11,7 @@ #include "Plugins/Process/Utility/LinuxPTraceDefines_arm64sve.h" #include "Plugins/Process/Utility/RegisterContextPOSIX_arm64.h" +#include "Plugins/Process/Utility/RegisterFlagsLinux_arm64.h" #include "Plugins/Process/elf-core/RegisterUtilities.h" #include "lldb/Utility/DataBufferHeap.h" @@ -74,6 +75,8 @@ private: struct sme_pseudo_regs m_sme_pseudo_regs; + lldb_private::LinuxArm64RegisterFlags m_linux_register_flags; + const uint8_t *GetSVEBuffer(uint64_t offset = 0); void ConfigureRegisterContext(); diff --git a/lldb/source/Target/RegisterFlags.cpp b/lldb/source/Target/RegisterFlags.cpp index 49974718ccb5..b1669b85fd2f 100644 --- a/lldb/source/Target/RegisterFlags.cpp +++ b/lldb/source/Target/RegisterFlags.cpp @@ -53,9 +53,7 @@ unsigned RegisterFlags::Field::PaddingDistance(const Field &other) const { return lhs_start - rhs_end - 1; } -RegisterFlags::RegisterFlags(std::string id, unsigned size, - const std::vector &fields) - : m_id(std::move(id)), m_size(size) { +void RegisterFlags::SetFields(const std::vector &fields) { // We expect that the XML processor will discard anything describing flags but // with no fields. assert(fields.size() && "Some fields must be provided."); @@ -63,6 +61,8 @@ RegisterFlags::RegisterFlags(std::string id, unsigned size, // We expect that these are unsorted but do not overlap. // They could fill the register but may have gaps. std::vector provided_fields = fields; + + m_fields.clear(); m_fields.reserve(provided_fields.size()); // ProcessGDBRemote should have sorted these in descending order already. @@ -71,7 +71,7 @@ RegisterFlags::RegisterFlags(std::string id, unsigned size, // Build a new list of fields that includes anonymous (empty name) fields // wherever there is a gap. This will simplify processing later. std::optional previous_field; - unsigned register_msb = (size * 8) - 1; + unsigned register_msb = (m_size * 8) - 1; for (auto field : provided_fields) { if (previous_field) { unsigned padding = previous_field->PaddingDistance(field); @@ -96,6 +96,12 @@ RegisterFlags::RegisterFlags(std::string id, unsigned size, m_fields.push_back(Field("", 0, previous_field->GetStart() - 1)); } +RegisterFlags::RegisterFlags(std::string id, unsigned size, + const std::vector &fields) + : m_id(std::move(id)), m_size(size) { + SetFields(fields); +} + void RegisterFlags::log(Log *log) const { LLDB_LOG(log, "ID: \"{0}\" Size: {1}", m_id.c_str(), m_size); for (const Field &field : m_fields) diff --git a/lldb/test/API/commands/register/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register/register_command/TestRegisters.py index f2ee3c4a047a..f29b2ab5d8f2 100644 --- a/lldb/test/API/commands/register/register/register_command/TestRegisters.py +++ b/lldb/test/API/commands/register/register/register_command/TestRegisters.py @@ -618,6 +618,18 @@ class RegisterCommandsTestCase(TestBase): # This has an alternative name according to the ABI. self.expect("register info x30", substrs=["Name: lr (x30)"]) + @skipIfXmlSupportMissing + @skipUnlessPlatform(["linux"]) + @skipIf(archs=no_match(["aarch64"])) + def test_register_read_fields(self): + """Test that when debugging a live process, we see the fields of the + CPSR register.""" + self.build() + self.common_setup() + + # N/Z/C/V bits will always be present, so check only for those. + self.expect("register read cpsr", substrs=["= (N = 0, Z = 1, C = 1, V = 0"]) + @skipUnlessPlatform(["linux"]) @skipIf(archs=no_match(["x86_64"])) def test_fs_gs_base(self): diff --git a/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py b/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py index 4ff288ad49c0..a083fab18eab 100644 --- a/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py +++ b/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py @@ -574,6 +574,10 @@ class LinuxCoreTestCase(TestBase): self.expect("register read --all") + # Register field information should work with core files as it does a live process. + # The N/Z/C/V bits are always present so just check for those. + self.expect("register read cpsr", substrs=["= (N = 0, Z = 0, C = 0, V = 0"]) + @skipIfLLVMTargetMissing("AArch64") def test_aarch64_pac_regs(self): # Test AArch64/Linux Pointer Authentication register read