GDB expects the TLS registers to be exposed via org.gnu.gdb.aarch64.tls,
which will contain either just "tpidr", or else "tpidr" and "tpidr2".
This will be important for SME in future, because the lazy state
restoration scheme requires GDB to use the TPIDR2 information.
GDB doesn't currently implement that, but we should provide the
register via the XML so that we are ready when future GDB versions
support it.
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
target/arm/cpu.h | 1 +
target/arm/internals.h | 3 ++
target/arm/gdbstub.c | 6 ++++
target/arm/gdbstub64.c | 63 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 73 insertions(+)
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index bf221e6f973..47be3076370 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -941,6 +941,7 @@ struct ArchCPU {
DynamicGDBFeatureInfo dyn_smereg_feature;
DynamicGDBFeatureInfo dyn_m_systemreg_feature;
DynamicGDBFeatureInfo dyn_m_secextreg_feature;
+ DynamicGDBFeatureInfo dyn_tls_feature;
/* Timers used by the generic (architected) timer */
QEMUTimer *gt_timer[NUM_GTIMERS];
diff --git a/target/arm/internals.h b/target/arm/internals.h
index b8b07e6477c..97ff54896e6 100644
--- a/target/arm/internals.h
+++ b/target/arm/internals.h
@@ -1715,6 +1715,7 @@ static inline uint64_t pmu_counter_mask(CPUARMState *env)
GDBFeature *arm_gen_dynamic_svereg_feature(CPUState *cpu, int base_reg);
GDBFeature *arm_gen_dynamic_smereg_feature(CPUState *cpu, int base_reg);
+GDBFeature *arm_gen_dynamic_tls_feature(CPUState *cpu, int base_reg);
int aarch64_gdb_get_sve_reg(CPUState *cs, GByteArray *buf, int reg);
int aarch64_gdb_set_sve_reg(CPUState *cs, uint8_t *buf, int reg);
int aarch64_gdb_get_sme_reg(CPUState *cs, GByteArray *buf, int reg);
@@ -1727,6 +1728,8 @@ int aarch64_gdb_get_pauth_reg(CPUState *cs, GByteArray *buf, int reg);
int aarch64_gdb_set_pauth_reg(CPUState *cs, uint8_t *buf, int reg);
int aarch64_gdb_get_tag_ctl_reg(CPUState *cs, GByteArray *buf, int reg);
int aarch64_gdb_set_tag_ctl_reg(CPUState *cs, uint8_t *buf, int reg);
+int aarch64_gdb_get_tls_reg(CPUState *cs, GByteArray *buf, int reg);
+int aarch64_gdb_set_tls_reg(CPUState *cs, uint8_t *buf, int reg);
void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp);
void arm_cpu_sme_finalize(ARMCPU *cpu, Error **errp);
void arm_cpu_pauth_finalize(ARMCPU *cpu, Error **errp);
diff --git a/target/arm/gdbstub.c b/target/arm/gdbstub.c
index 1ca3e647a84..8865f27089d 100644
--- a/target/arm/gdbstub.c
+++ b/target/arm/gdbstub.c
@@ -583,6 +583,12 @@ void arm_cpu_register_gdb_regs_for_features(ARMCPU *cpu)
0);
}
#endif
+
+ /* All AArch64 CPUs have at least TPIDR */
+ gdb_register_coprocessor(cs, aarch64_gdb_get_tls_reg,
+ aarch64_gdb_set_tls_reg,
+ arm_gen_dynamic_tls_feature(cs, cs->gdb_num_regs),
+ 0);
#endif
} else {
if (arm_feature(env, ARM_FEATURE_NEON)) {
diff --git a/target/arm/gdbstub64.c b/target/arm/gdbstub64.c
index 5ad00fe771d..3bc7ff45d57 100644
--- a/target/arm/gdbstub64.c
+++ b/target/arm/gdbstub64.c
@@ -387,6 +387,44 @@ int aarch64_gdb_set_sme2_reg(CPUState *cs, uint8_t *buf, int reg)
return 0;
}
+int aarch64_gdb_get_tls_reg(CPUState *cs, GByteArray *buf, int reg)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+
+ switch (reg) {
+ case 0: /* TPIDR_EL0 */
+ return gdb_get_reg64(buf, env->cp15.tpidr_el[0]);
+ case 1: /* TPIDR2_EL0 */
+ return gdb_get_reg64(buf, env->cp15.tpidr2_el0);
+ default:
+ /* gdbstub asked for something out of range */
+ break;
+ }
+
+ return 0;
+}
+
+int aarch64_gdb_set_tls_reg(CPUState *cs, uint8_t *buf, int reg)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+
+ switch (reg) {
+ case 0: /* TPIDR_EL0 */
+ env->cp15.tpidr_el[0] = ldq_p(buf);
+ return 8;
+ case 1: /* TPIDR2_EL0 */
+ env->cp15.tpidr2_el0 = ldq_p(buf);
+ return 8;
+ default:
+ /* gdbstub asked for something out of range */
+ break;
+ }
+
+ return 0;
+}
+
int aarch64_gdb_get_pauth_reg(CPUState *cs, GByteArray *buf, int reg)
{
ARMCPU *cpu = ARM_CPU(cs);
@@ -586,6 +624,31 @@ GDBFeature *arm_gen_dynamic_smereg_feature(CPUState *cs, int base_reg)
return &cpu->dyn_smereg_feature.desc;
}
+GDBFeature *arm_gen_dynamic_tls_feature(CPUState *cs, int base_reg)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ GDBFeatureBuilder builder;
+ int reg = 0;
+
+ gdb_feature_builder_init(&builder, &cpu->dyn_tls_feature.desc,
+ "org.gnu.gdb.aarch64.tls", "tls-registers.xml",
+ base_reg);
+
+ /*
+ * This feature must always have "tpidr", and may also have "tpidr2"
+ * if the CPU has that register.
+ */
+ gdb_feature_builder_append_reg(&builder, "tpidr", 64,
+ reg++, "data_ptr", NULL);
+ if (cpu_isar_feature(aa64_sme, cpu)) {
+ gdb_feature_builder_append_reg(&builder, "tpidr2", 64,
+ reg++, "data_ptr", NULL);
+ }
+ gdb_feature_builder_end(&builder);
+
+ return &cpu->dyn_tls_feature.desc;
+}
+
#ifdef CONFIG_USER_ONLY
int aarch64_gdb_get_tag_ctl_reg(CPUState *cs, GByteArray *buf, int reg)
{
--
2.43.0
On 10/17/25 08:30, Peter Maydell wrote: > GDB expects the TLS registers to be exposed via org.gnu.gdb.aarch64.tls, > which will contain either just "tpidr", or else "tpidr" and "tpidr2". > > This will be important for SME in future, because the lazy state > restoration scheme requires GDB to use the TPIDR2 information. > GDB doesn't currently implement that, but we should provide the > register via the XML so that we are ready when future GDB versions > support it. > > Signed-off-by: Peter Maydell <peter.maydell@linaro.org> > --- > target/arm/cpu.h | 1 + > target/arm/internals.h | 3 ++ > target/arm/gdbstub.c | 6 ++++ > target/arm/gdbstub64.c | 63 ++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 73 insertions(+) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~
© 2016 - 2025 Red Hat, Inc.