[PATCH v3 3/5] selftests: futex: Add support for aarch64 in robust_list_critical

From: André Almeida

Date: Fri May 29 2026 - 12:43:11 EST


Add support for aarch64 for robust_list_critical test.

Apart from the arch specific registers, as the cmpxchg is implemented
using LL/SC, doing single steps clears the exclusive monitor and then the
store fails.

To avoid that, insert a breakpoint just after the store and let it run
continuously until there.

Signed-off-by: André Almeida <andrealmeid@xxxxxxxxxx>
---
.../futex/functional/robust_list_critical.c | 92 ++++++++++++++++++++--
1 file changed, 87 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/futex/functional/robust_list_critical.c b/tools/testing/selftests/futex/functional/robust_list_critical.c
index b9490d24eb10..a4fe69a21297 100644
--- a/tools/testing/selftests/futex/functional/robust_list_critical.c
+++ b/tools/testing/selftests/futex/functional/robust_list_critical.c
@@ -65,8 +65,9 @@ static bool pc_is_within(struct user_regs_struct *regs, uint64_t start, uint64_t

#if defined(__x86_64__)
pc = regs->rip;
-#elif defined(__riscv)
- pc = reg->pc;
+#elif defined(__riscv) || defined(__aarch64__)
+ pc = regs->pc;
+#else
# error Missing ptrace support
#endif
if (pc >= (long) start && pc < end)
@@ -219,12 +220,79 @@ enum trace_state {
STATE_LEAVE_VDSO,
};

+static int ptrace_get_regs(struct user_regs_struct *regs, pid_t child)
+{
+#if defined(__x86_64__)
+
+ return ptrace(PTRACE_GETREGS, child, 0, regs);
+
+#elif defined(__aarch64__)
+
+ struct iovec io;
+ io.iov_base = regs;
+ io.iov_len = sizeof(struct user_regs_struct);
+
+ return ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &io);
+
+#endif
+}
+
+#if defined(__aarch64__)
+/*
+ * A cmpxchg in Arm64 implemented using LL/SC can't single step on every
+ * instruction. This clears the exclusive monitor and invalidates the store.
+ * Instead, we need to add a breakpoint just after the store (on the label
+ * __futex_list64_try_unlock_cs_success) and let it be executed continuously.
+ */
+
+#define AARCH64_BRK 0xD4200000
+static void set_breakpoint(pid_t child, uint64_t *inst)
+{
+
+ long ret;
+
+ ret = ptrace(PTRACE_PEEKDATA, child, __futex_list64_try_unlock_cs_success, 0);
+ if (ret == -1)
+ err(1, "PTRACE_PEEKDATA");
+
+ *inst = (uint64_t) ret;
+
+ ret = ptrace(PTRACE_POKEDATA, child, __futex_list64_try_unlock_cs_success, AARCH64_BRK);
+ if (ret == -1)
+ err(1, "PTRACE_POKEDATA");
+}
+
+static void remove_breakpoint(pid_t child, struct user_regs_struct *regs, uint64_t inst)
+{
+ uint64_t addr = regs->pc;
+ int ret;
+
+ ret = ptrace(PTRACE_POKEDATA, child, addr, inst);
+ if (ret == -1)
+ err(1, "PTRACE_POKEDATA");
+}
+
+#define ptrace_cont() \
+ if (ptrace(PTRACE_CONT, child, 0, 0)) \
+ err(1, "PTRACE_CONT"); \
+ continue;
+
+#else
+
+static void set_breakpoint(pid_t child, uint64_t *inst) {}
+static void remove_breakpoint(pid_t child, struct user_regs_struct *regs, uint64_t inst) {}
+
+#define ptrace_cont() {}
+
+#endif
+
static void trace_child(struct __test_metadata *_metadata, pid_t child, bool is_32bit)
{
int state = STATE_WAIT;
struct robust_list_head *rhead;
size_t sz;
- bool do_end = false;
+ bool do_end = false, enter_cs = false;
+ uint64_t inst = 0;

syscall(SYS_get_robust_list, 0, &rhead, &sz);
do {
@@ -235,6 +303,7 @@ static void trace_child(struct __test_metadata *_metadata, pid_t child, bool is_
pid_t rpid;

rpid = waitpid(child, &wstatus, 0);
+
if (rpid != child)
errx(1, "waitpid");
if (!do_end) {
@@ -244,11 +313,15 @@ static void trace_child(struct __test_metadata *_metadata, pid_t child, bool is_
if (!WIFEXITED(wstatus))
errx(1, "Did not exit, but we are done");
ASSERT_EQ(WEXITSTATUS(wstatus), 0);
+
+ /* check if the code really reached the critical section */
+ ASSERT_EQ(enter_cs, true);
+
return;
}

- if (ptrace(PTRACE_GETREGS, child, 0, &regs) != 0)
- errx(1, "PTRACE_GETREGS");
+ if (ptrace_get_regs(&regs, child))
+ errx(1, "ptrace_get_regs");

if (is_32bit) {
in_vdso = pc_is_within(&regs, (long)frtu32, frtu32_end);
@@ -261,6 +334,11 @@ static void trace_child(struct __test_metadata *_metadata, pid_t child, bool is_
if (state == STATE_WAIT) {
state = STATE_ENTER_VDSO;

+ /* set a bp first time we enter vdso */
+ set_breakpoint(child, &inst);
+
+ /* and let the execution continue until it reaches the CS */
+ ptrace_cont();
} else {
if (is_32bit) {
if (pc_is_within(&regs, __futex_list32_try_unlock_cs_start,
@@ -301,8 +379,12 @@ static void trace_child(struct __test_metadata *_metadata, pid_t child, bool is_
if (is_32bit)
rhead_val &= 0xffffffff;

+ enter_cs = true;
ASSERT_EQ(rhead_val, 0);
ASSERT_EQ(lock_val, 0);
+
+ /* remove the breakpoint and continue execution */
+ remove_breakpoint(child, &regs, inst);
}

if (ptrace(PTRACE_SINGLESTEP, child, 0, 0))

--
2.54.0