Skip to content

Commit e28157e

Browse files
[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.
1 parent 567c02a commit e28157e

File tree

11 files changed

+271
-5
lines changed

11 files changed

+271
-5
lines changed

lldb/include/lldb/Target/RegisterFlags.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ class RegisterFlags {
8484
RegisterFlags(std::string id, unsigned size,
8585
const std::vector<Field> &fields);
8686

87+
/// Replace all the fields with the new set of fields. All the assumptions
88+
/// and checks apply as when you use the constructor. Intended to only be used
89+
/// when runtime field detection is needed.
90+
void SetFields(const std::vector<Field> &fields);
91+
8792
// Reverse the order of the fields, keeping their values the same.
8893
// For example a field from bit 31 to 30 with value 0b10 will become bits
8994
// 1 to 0, with the same 0b10 value.

lldb/include/lldb/lldb-private-types.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ struct RegisterInfo {
6262
/// rax ax, ah, and al.
6363
uint32_t *invalidate_regs;
6464
/// If not nullptr, a type defined by XML descriptions.
65-
const RegisterFlags *flags_type;
65+
/// Register info tables are constructed as const, but this field may need to
66+
/// be updated if a specific target OS has a different layout. To enable that,
67+
/// this is mutable. The data pointed to is still const, so you must swap a
68+
/// whole set of flags for another.
69+
mutable const RegisterFlags *flags_type;
6670

6771
llvm::ArrayRef<uint8_t> data(const uint8_t *context_base) const {
6872
return llvm::ArrayRef<uint8_t>(context_base + byte_offset, byte_size);

lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
#include "Plugins/Process/Linux/Procfs.h"
2424
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
2525
#include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h"
26+
#include "Plugins/Process/Utility/RegisterFlagsLinux_arm64.h"
2627
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
2728

2829
// System includes - They have to be included after framework includes because
2930
// they define some macros which collide with variable names in other modules
3031
#include <sys/uio.h>
3132
// NT_PRSTATUS and NT_FPREGSET definition
3233
#include <elf.h>
34+
#include <mutex>
3335
#include <optional>
3436

3537
#ifndef NT_ARM_SVE
@@ -59,12 +61,20 @@
5961
#endif
6062

6163
#define HWCAP_PACA (1 << 30)
64+
6265
#define HWCAP2_MTE (1 << 18)
6366

6467
using namespace lldb;
6568
using namespace lldb_private;
6669
using namespace lldb_private::process_linux;
6770

71+
// A NativeRegisterContext is constructed per thread, but all threads' registers
72+
// will contain the same fields. Therefore this mutex prevents each instance
73+
// competing with the other, and subsequent instances from having to detect the
74+
// fields all over again.
75+
static std::mutex g_register_flags_mutex;
76+
static LinuxArm64RegisterFlags g_register_flags;
77+
6878
std::unique_ptr<NativeRegisterContextLinux>
6979
NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
7080
const ArchSpec &target_arch, NativeThreadLinux &native_thread) {
@@ -134,6 +144,11 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
134144

135145
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);
136146

147+
std::lock_guard<std::mutex> lock(g_register_flags_mutex);
148+
if (!g_register_flags.HasDetected())
149+
g_register_flags.DetectFields(auxv_at_hwcap.value_or(0),
150+
auxv_at_hwcap2.value_or(0));
151+
137152
auto register_info_up =
138153
std::make_unique<RegisterInfoPOSIX_arm64>(target_arch, opt_regsets);
139154
return std::make_unique<NativeRegisterContextLinux_arm64>(
@@ -156,6 +171,10 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
156171
: NativeRegisterContextRegisterInfo(native_thread,
157172
register_info_up.release()),
158173
NativeRegisterContextLinux(native_thread) {
174+
g_register_flags.UpdateRegisterInfo(
175+
GetRegisterInfoInterface().GetRegisterInfo(),
176+
GetRegisterInfoInterface().GetRegisterCount());
177+
159178
::memset(&m_fpr, 0, sizeof(m_fpr));
160179
::memset(&m_gpr_arm64, 0, sizeof(m_gpr_arm64));
161180
::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs));

lldb/source/Plugins/Process/Utility/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ add_lldb_library(lldbPluginProcessUtility
4747
RegisterContextThreadMemory.cpp
4848
RegisterContextWindows_i386.cpp
4949
RegisterContextWindows_x86_64.cpp
50+
RegisterFlagsLinux_arm64.cpp
5051
RegisterInfos_x86_64_with_base_shared.cpp
5152
RegisterInfoPOSIX_arm.cpp
5253
RegisterInfoPOSIX_arm64.cpp
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//===-- RegisterFlagsLinux_arm64.cpp --------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "RegisterFlagsLinux_arm64.h"
10+
#include "lldb/lldb-private-types.h"
11+
12+
// This file is built on all systems because it is used by native processes and
13+
// core files, so we manually define the needed HWCAP values here.
14+
15+
#define HWCAP_DIT (1 << 24)
16+
#define HWCAP_SSBS (1 << 28)
17+
18+
#define HWCAP2_BTI (1 << 17)
19+
#define HWCAP2_MTE (1 << 18)
20+
21+
using namespace lldb_private;
22+
23+
LinuxArm64RegisterFlags::Fields
24+
LinuxArm64RegisterFlags::DetectCPSRFields(uint64_t hwcap, uint64_t hwcap2) {
25+
// The fields here are a combination of the Arm manual's SPSR_EL1,
26+
// plus a few changes where Linux has decided not to make use of them at all,
27+
// or at least not from userspace.
28+
29+
// Status bits that are always present.
30+
std::vector<RegisterFlags::Field> cpsr_fields{
31+
{"N", 31}, {"Z", 30}, {"C", 29}, {"V", 28},
32+
// Bits 27-26 reserved.
33+
};
34+
35+
if (hwcap2 & HWCAP2_MTE)
36+
cpsr_fields.push_back({"TCO", 25});
37+
if (hwcap & HWCAP_DIT)
38+
cpsr_fields.push_back({"DIT", 24});
39+
40+
// UAO and PAN are bits 23 and 22 and have no meaning for userspace so
41+
// are treated as reserved by the kernel.
42+
43+
cpsr_fields.push_back({"SS", 21});
44+
cpsr_fields.push_back({"IL", 20});
45+
// Bits 19-14 reserved.
46+
47+
// Bit 13, ALLINT, requires FEAT_NMI that isn't relevant to userspace, and we
48+
// can't detect either, don't show this field.
49+
if (hwcap & HWCAP_SSBS)
50+
cpsr_fields.push_back({"SSBS", 12});
51+
if (hwcap2 & HWCAP2_BTI)
52+
cpsr_fields.push_back({"BTYPE", 10, 11});
53+
54+
cpsr_fields.push_back({"D", 9});
55+
cpsr_fields.push_back({"A", 8});
56+
cpsr_fields.push_back({"I", 7});
57+
cpsr_fields.push_back({"F", 6});
58+
// Bit 5 reserved
59+
// Called "M" in the ARMARM.
60+
cpsr_fields.push_back({"nRW", 4});
61+
// This is a 4 bit field M[3:0] in the ARMARM, we split it into parts.
62+
cpsr_fields.push_back({"EL", 2, 3});
63+
// Bit 1 is unused and expected to be 0.
64+
cpsr_fields.push_back({"SP", 0});
65+
66+
return cpsr_fields;
67+
}
68+
69+
void LinuxArm64RegisterFlags::DetectFields(uint64_t hwcap, uint64_t hwcap2) {
70+
for (auto &reg : m_registers)
71+
reg.m_flags.SetFields(reg.m_detector(hwcap, hwcap2));
72+
m_has_detected = true;
73+
}
74+
75+
void LinuxArm64RegisterFlags::UpdateRegisterInfo(const RegisterInfo *reg_info,
76+
uint32_t num_regs) {
77+
assert(m_has_detected &&
78+
"Must call DetectFields before updating register info.");
79+
80+
// Register names will not be duplicated, so we do not want to compare against
81+
// one if it has already been found. Each time we find one, we erase it from
82+
// this list.
83+
std::vector<std::pair<llvm::StringRef, const RegisterFlags *>>
84+
search_registers;
85+
for (const auto &reg : m_registers) {
86+
// It is possible that a register is all extension dependent fields, and
87+
// none of them are present.
88+
if (reg.m_flags.GetFields().size())
89+
search_registers.push_back({reg.m_name, &reg.m_flags});
90+
}
91+
92+
// Walk register information while there are registers we know need
93+
// to be updated. Example:
94+
// Register information: [a, b, c, d]
95+
// To be patched: [b, c]
96+
// * a != b, a != c, do nothing and move on.
97+
// * b == b, patch b, new patch list is [c], move on.
98+
// * c == c, patch c, patch list is empty, exit early without looking at d.
99+
for (uint32_t idx = 0; idx < num_regs && search_registers.size();
100+
++idx, ++reg_info) {
101+
auto reg_it = std::find_if(
102+
search_registers.cbegin(), search_registers.cend(),
103+
[reg_info](auto reg) { return reg.first == reg_info->name; });
104+
105+
if (reg_it != search_registers.end()) {
106+
// Attach the field information.
107+
reg_info->flags_type = reg_it->second;
108+
// We do not expect to see this name again so don't look for it again.
109+
search_registers.erase(reg_it);
110+
}
111+
}
112+
113+
// We do not assert that search_registers is empty here, because it may
114+
// contain registers from optional extensions that are not present on the
115+
// current target.
116+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===-- RegisterFlagsLinux_arm64.h ------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERFLAGSLINUX_ARM64_H
10+
#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERFLAGSLINUX_ARM64_H
11+
12+
#include "lldb/Target/RegisterFlags.h"
13+
#include "llvm/ADT/StringRef.h"
14+
#include <functional>
15+
16+
namespace lldb_private {
17+
18+
struct RegisterInfo;
19+
20+
/// This class manages the storage and detection of register field information
21+
/// for Arm64 Linux registers. The same register may have different fields on
22+
/// different CPUs. This class abstracts out the field detection process so we
23+
/// can use it on live processes and core files.
24+
///
25+
/// The general way to use this class is:
26+
/// * Make an instance somewhere that will last as long as the debug session
27+
/// (because your final register info will point to this instance).
28+
/// * Read hardware capabilities from a core note, binary, prctl, etc.
29+
/// * Pass those to DetectFields.
30+
/// * Call UpdateRegisterInfo with your RegisterInfo to add pointers
31+
/// to the detected fields for all registers listed in this class.
32+
///
33+
/// This must be done in that order, and you should ensure that if multiple
34+
/// threads will reference the information, a mutex is used to make sure only
35+
/// one calls DetectFields.
36+
class LinuxArm64RegisterFlags {
37+
public:
38+
/// For the registers listed in this class, detect which fields are
39+
/// present. Must be called before UpdateRegisterInfos.
40+
/// If called more than once, fields will be redetected each time from
41+
/// scratch. If you do not have access to hwcap, just pass 0 for each one, you
42+
/// will only get unconditional fields.
43+
void DetectFields(uint64_t hwcap, uint64_t hwcap2);
44+
45+
/// Add the field information of any registers named in this class,
46+
/// to the relevant RegisterInfo instances. Note that this will be done
47+
/// with a pointer to the instance of this class that you call this on, so
48+
/// the lifetime of that instance must be at least that of the register info.
49+
void UpdateRegisterInfo(const RegisterInfo *reg_info, uint32_t num_regs);
50+
51+
/// Returns true if field detection has been run at least once.
52+
bool HasDetected() const { return m_has_detected; }
53+
54+
private:
55+
using Fields = std::vector<RegisterFlags::Field>;
56+
using DetectorFn = std::function<Fields(uint64_t, uint64_t)>;
57+
58+
static Fields DetectCPSRFields(uint64_t hwcap, uint64_t hwcap2);
59+
60+
struct RegisterEntry {
61+
RegisterEntry(llvm::StringRef name, unsigned size, DetectorFn detector)
62+
: m_name(name), m_flags(std::string(name) + "_flags", size, {{"", 0}}),
63+
m_detector(detector) {}
64+
65+
llvm::StringRef m_name;
66+
RegisterFlags m_flags;
67+
DetectorFn m_detector;
68+
} m_registers[1] = {
69+
RegisterEntry("cpsr", 4, DetectCPSRFields),
70+
};
71+
72+
// Becomes true once field detection has been run for all registers.
73+
bool m_has_detected = false;
74+
};
75+
76+
} // namespace lldb_private
77+
78+
#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERFLAGSLINUX_ARM64_H

lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include "RegisterContextPOSIXCore_arm64.h"
1010
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
1111

12+
#include "Plugins/Process/Utility/AuxVector.h"
13+
#include "Plugins/Process/Utility/RegisterFlagsLinux_arm64.h"
14+
#include "Plugins/Process/elf-core/ProcessElfCore.h"
1215
#include "Plugins/Process/elf-core/RegisterUtilities.h"
1316
#include "lldb/Target/Thread.h"
1417
#include "lldb/Utility/RegisterValue.h"
@@ -74,6 +77,21 @@ RegisterContextCorePOSIX_arm64::RegisterContextCorePOSIX_arm64(
7477
: RegisterContextPOSIX_arm64(thread, std::move(register_info)) {
7578
::memset(&m_sme_pseudo_regs, 0, sizeof(m_sme_pseudo_regs));
7679

80+
ProcessElfCore *process =
81+
static_cast<ProcessElfCore *>(thread.GetProcess().get());
82+
if (process->GetArchitecture().GetTriple().getOS() == llvm::Triple::Linux) {
83+
AuxVector aux_vec(process->GetAuxvData());
84+
std::optional<uint64_t> auxv_at_hwcap =
85+
aux_vec.GetAuxValue(AuxVector::AUXV_AT_HWCAP);
86+
std::optional<uint64_t> auxv_at_hwcap2 =
87+
aux_vec.GetAuxValue(AuxVector::AUXV_AT_HWCAP2);
88+
89+
m_linux_register_flags.DetectFields(auxv_at_hwcap.value_or(0),
90+
auxv_at_hwcap2.value_or(0));
91+
m_linux_register_flags.UpdateRegisterInfo(GetRegisterInfo(),
92+
GetRegisterCount());
93+
}
94+
7795
m_gpr_data.SetData(std::make_shared<DataBufferHeap>(gpregset.GetDataStart(),
7896
gpregset.GetByteSize()));
7997
m_gpr_data.SetByteOrder(gpregset.GetByteOrder());

lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "Plugins/Process/Utility/LinuxPTraceDefines_arm64sve.h"
1313
#include "Plugins/Process/Utility/RegisterContextPOSIX_arm64.h"
14+
#include "Plugins/Process/Utility/RegisterFlagsLinux_arm64.h"
1415

1516
#include "Plugins/Process/elf-core/RegisterUtilities.h"
1617
#include "lldb/Utility/DataBufferHeap.h"
@@ -74,6 +75,8 @@ class RegisterContextCorePOSIX_arm64 : public RegisterContextPOSIX_arm64 {
7475

7576
struct sme_pseudo_regs m_sme_pseudo_regs;
7677

78+
lldb_private::LinuxArm64RegisterFlags m_linux_register_flags;
79+
7780
const uint8_t *GetSVEBuffer(uint64_t offset = 0);
7881

7982
void ConfigureRegisterContext();

lldb/source/Target/RegisterFlags.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ unsigned RegisterFlags::Field::PaddingDistance(const Field &other) const {
5353
return lhs_start - rhs_end - 1;
5454
}
5555

56-
RegisterFlags::RegisterFlags(std::string id, unsigned size,
57-
const std::vector<Field> &fields)
58-
: m_id(std::move(id)), m_size(size) {
56+
void RegisterFlags::SetFields(const std::vector<Field> &fields) {
5957
// We expect that the XML processor will discard anything describing flags but
6058
// with no fields.
6159
assert(fields.size() && "Some fields must be provided.");
6260

6361
// We expect that these are unsorted but do not overlap.
6462
// They could fill the register but may have gaps.
6563
std::vector<Field> provided_fields = fields;
64+
65+
m_fields.clear();
6666
m_fields.reserve(provided_fields.size());
6767

6868
// ProcessGDBRemote should have sorted these in descending order already.
@@ -71,7 +71,7 @@ RegisterFlags::RegisterFlags(std::string id, unsigned size,
7171
// Build a new list of fields that includes anonymous (empty name) fields
7272
// wherever there is a gap. This will simplify processing later.
7373
std::optional<Field> previous_field;
74-
unsigned register_msb = (size * 8) - 1;
74+
unsigned register_msb = (m_size * 8) - 1;
7575
for (auto field : provided_fields) {
7676
if (previous_field) {
7777
unsigned padding = previous_field->PaddingDistance(field);
@@ -96,6 +96,12 @@ RegisterFlags::RegisterFlags(std::string id, unsigned size,
9696
m_fields.push_back(Field("", 0, previous_field->GetStart() - 1));
9797
}
9898

99+
RegisterFlags::RegisterFlags(std::string id, unsigned size,
100+
const std::vector<Field> &fields)
101+
: m_id(std::move(id)), m_size(size) {
102+
SetFields(fields);
103+
}
104+
99105
void RegisterFlags::log(Log *log) const {
100106
LLDB_LOG(log, "ID: \"{0}\" Size: {1}", m_id.c_str(), m_size);
101107
for (const Field &field : m_fields)

0 commit comments

Comments
 (0)