[lldb][AArch64][Linux] Add field information for the CPSR register (#70300)

The contents of which are mostly SPSR_EL1 as shown in the Arm manual,
with a few adjustments for things Linux says userspace shouldn't concern
itself with.

```
(lldb) register read cpsr
    cpsr = 0x80001000
         = (N = 1, Z = 0, C = 0, V = 0, SS = 0, IL = 0, ...
```

Some fields are always present, some depend on extensions. I've checked
for those extensions using HWCAP and HWCAP2.

To provide this for core files and live processes I've added a new class
LinuxArm64RegisterFlags. This is a container for all the registers we'll
want to have fields and handles detecting fields and updating register
info.

This is used by the native process as follows:
* There is a global LinuxArm64RegisterFlags object.
* The first thread takes a mutex on it, and updates the fields.
* Subsequent threads see that detection is already done, and skip it.
* All threads then update their own copy of the register information
with pointers to the field information contained in the global object.

This means that even though every thread will have the same fields, we
only detect them once and have one copy of the information.

Core files instead have a LinuxArm64RegisterFlags as a member, because
each core file could have different saved capabilities. The logic from
there is the same but we get HWACP values from the corefile note.

This handler class is Linux specific right now, but it can easily be
made more generic if needed. For example by using LLVM's FeatureBitset
instead of HWCAPs.

Updating register info is done with string comparison, which isn't
ideal. For CPSR, we do know the register number ahead of time but we do
not for other registers in dynamic register sets. So in the interest of
consistency, I'm going to use string comparison for all registers
including cpsr.

I've added tests with a core file and live process. Only checking for
fields that are always present to account for CPU variance.
This commit is contained in:
David Spickett 2023-11-08 10:17:38 +00:00 committed by GitHub
parent 567c02a80e
commit e28157e778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 271 additions and 5 deletions

View File

@ -84,6 +84,11 @@ public:
RegisterFlags(std::string id, unsigned size,
const std::vector<Field> &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<Field> &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.

View File

@ -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<uint8_t> data(const uint8_t *context_base) const {
return llvm::ArrayRef<uint8_t>(context_base + byte_offset, byte_size);

View File

@ -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 <sys/uio.h>
// NT_PRSTATUS and NT_FPREGSET definition
#include <elf.h>
#include <mutex>
#include <optional>
#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>
NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
const ArchSpec &target_arch, NativeThreadLinux &native_thread) {
@ -134,6 +144,11 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);
std::lock_guard<std::mutex> 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<RegisterInfoPOSIX_arm64>(target_arch, opt_regsets);
return std::make_unique<NativeRegisterContextLinux_arm64>(
@ -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));

View File

@ -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

View File

@ -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<RegisterFlags::Field> 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 &reg : 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<std::pair<llvm::StringRef, const RegisterFlags *>>
search_registers;
for (const auto &reg : 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, &reg.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.
}

View File

@ -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 <functional>
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<RegisterFlags::Field>;
using DetectorFn = std::function<Fields(uint64_t, uint64_t)>;
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

View File

@ -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<ProcessElfCore *>(thread.GetProcess().get());
if (process->GetArchitecture().GetTriple().getOS() == llvm::Triple::Linux) {
AuxVector aux_vec(process->GetAuxvData());
std::optional<uint64_t> auxv_at_hwcap =
aux_vec.GetAuxValue(AuxVector::AUXV_AT_HWCAP);
std::optional<uint64_t> 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<DataBufferHeap>(gpregset.GetDataStart(),
gpregset.GetByteSize()));
m_gpr_data.SetByteOrder(gpregset.GetByteOrder());

View File

@ -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();

View File

@ -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<Field> &fields)
: m_id(std::move(id)), m_size(size) {
void RegisterFlags::SetFields(const std::vector<Field> &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<Field> 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<Field> 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<Field> &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)

View File

@ -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):

View File

@ -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