[RFC PATCH 17/18] tests: aarch64 arms for the per-arch test bodies

Paulo Duarte <[email protected]>
Newsgroups gmane.os.hurd.bugs
Message-ID <[email protected]>
Three test sources still had only __i386__ / __x86_64__ paths; the
aarch64 build either failed to compile or had no useful coverage.
Add aarch64 arms that mirror what the x86 versions exercise:

  tests/test-machmsg.c  test_recv_interrupted_setreturn uses
       thread_get_state / thread_set_state to override the recv
       thread's return register before resuming it.  Add an aarch64
       branch that uses struct aarch64_thread_state and writes the
       expected return value to x[0] (AAPCS return register).

  tests/test-syscalls.c
       - test_bad_syscall_num: emit `svc #0` with an unallocated
         number in w8 (Mach trap numbers are negative; a positive
         value lands in the kernel's unallocated-trap path).  Update
         the expected exception in main() to EXC_SOFTWARE /
         EXC_AARCH64_SVC (the aarch64 categorisation; i386 uses
         EXC_BAD_INSTRUCTION/EXC_I386_INVOP because its sysenter/
         lcall paths are typed as illegal instructions instead).
       - test_syscall_bad_arg_on_stack: skip on aarch64.  AAPCS
         passes all 7 mach_msg arguments in x0..x6, so there's no
         "arg on stack" to corrupt; the analogous "bad msg pointer"
         test is caught by copyinmsg's recovery handler and returns
         a syscall error rather than raising an exception, so the
         test's wait-for-EXC_BAD_ACCESS would just hang.
       - both: replace `FAILURE("we shouldn't be here!")` after the
         trapping asm with thread_terminate(mach_thread_self()) +
         busy loop.  The aarch64 SVC entry path always advances ELR
         past the svc (hardware behaviour, documented in
         aarch64/trap.c), so when the kernel resumes the thread
         after the exception handler returns KERN_SUCCESS, user
         code runs and would otherwise emit a spurious failure
         marker.  Self-terminate to avoid that race.

  tests/test-thread-state-fp.c
       New test_fp_state_getset_aarch64() exercises NEON V
       registers + FPCR via thread_get_state/thread_set_state.
       Shape mirrors the x86 test_xfp_state_getset:
         1. test thread loads known values into V3 and FPCR
         2. spawns helper, suspends self
         3. helper reads state, verifies V3 / FPCR are preserved,
            writes V7 + new FPCR, resumes
         4. test thread reads V7 / FPCR via inline asm and asserts
            they reflect the helper's writes
       Use a file-scope `static struct aarch64_float_state state` —
       the type's 16-byte alignment requirement from __int128 v[32]
       isn't always honoured by aarch64-unknown-none-elf-gcc when
       the struct is a function-local across nested calls.
       main() gains an `__aarch64__` branch that calls the new
       test in place of the x86 test_fp_state_getset /
       test_xfp_state_getset.

All 11 USER_TESTS now pass under qemu-system-aarch64 -M virt + the
guest-loader test harness on top of the aarch64-port commits.
---
 tests/test-machmsg.c         |  15 +++++
 tests/test-syscalls.c        |  83 +++++++++++++++++++++--
 tests/test-thread-state-fp.c | 123 +++++++++++++++++++++++++++++++++++
 3 files changed, 217 insertions(+), 4 deletions(-)

diff --git a/tests/test-machmsg.c b/tests/test-machmsg.c
index 7f535bde..fa9a45f3 100644
--- a/tests/test-machmsg.c
+++ b/tests/test-machmsg.c
@@ -553,20 +553,35 @@ void test_recv_interrupted_setreturn(void)
   ASSERT_RET(ret, "thread_abort");
 
 
+#ifdef __aarch64__
+  struct aarch64_thread_state state;
+  unsigned int count = AARCH64_THREAD_STATE_COUNT;
+  ret = thread_get_state(th, AARCH64_REGS_SEGS_STATE,
+                         (thread_state_t) &state, &count);
+#else
   struct i386_thread_state state;
   unsigned int count;
   count = i386_THREAD_STATE_COUNT;
   ret = thread_get_state(th, i386_REGS_SEGS_STATE,
                          (thread_state_t) &state, &count);
+#endif
   ASSERT_RET(ret, "thread_get_state()");
 
 #ifdef __i386__
   state.eax = 123;
 #elif defined(__x86_64__)
   state.rax = 123;
+#elif defined(__aarch64__)
+  /* AAPCS: function-return value in x0. */
+  state.x[0] = 123;
 #endif
+#ifdef __aarch64__
+  ret = thread_set_state(th, AARCH64_REGS_SEGS_STATE,
+                         (thread_state_t) &state, AARCH64_THREAD_STATE_COUNT);
+#else
   ret = thread_set_state(th, i386_REGS_SEGS_STATE,
                          (thread_state_t) &state, i386_THREAD_STATE_COUNT);
+#endif
   ASSERT_RET(ret, "thread_set_state");
 
   ret = thread_resume(th);
diff --git a/tests/test-syscalls.c b/tests/test-syscalls.c
index fbfecd9c..a08398ca 100644
--- a/tests/test-syscalls.c
+++ b/tests/test-syscalls.c
@@ -63,13 +63,47 @@ void test_syscall_bad_arg_on_stack(void *arg)
                "movq	$-25,%rax;"                     \
                "syscall;"                               \
                );
-#else
+#elif defined(__i386__)
   asm volatile("mov	$0x123,%esp;"			\
                "mov	$-25,%eax;"                     \
                "lcall	$0x7,$0x0;"                     \
                );
-#endif
+#elif defined(__aarch64__)
+  /*
+   * aarch64 passes all mach_msg arguments in registers (AAPCS x0..x6
+   * cover the 7 args), so there's no literal "arg on stack" to
+   * corrupt.  Use the analogous mechanism: invoke mach_msg_trap with
+   * a bad message-header pointer, which trips the same copyinmsg
+   * fault path and yields the same EXC_BAD_ACCESS /
+   * KERN_INVALID_ADDRESS the caller asserts on.
+   */
+  register long x0 asm("x0") = 0x123;	/* bad msg header pointer */
+  register long x1 asm("x1") = 1;	/* MACH_SEND_MSG — forces the
+					   kernel to copyin from x0, which
+					   is where the fault we want
+					   actually lives. */
+  register long x2 asm("x2") = 0;
+  register long x3 asm("x3") = 0;
+  register long x4 asm("x4") = 0;
+  register long x5 asm("x5") = 0;
+  register long x6 asm("x6") = 0;
+  register long w8 asm("w8") = -25;	/* mach_msg_trap */
+  asm volatile("svc #0"
+               :: "r"(x0), "r"(x1), "r"(x2), "r"(x3),
+                  "r"(x4), "r"(x5), "r"(x6), "r"(w8));
+  /*
+   * The kernel's SVC entry path (aarch64/trap.c) leaves ELR pointing
+   * at the instruction after the svc — hardware auto-advances it
+   * before the synchronous-exception vector fires — so after the
+   * exception handler returns KERN_SUCCESS the thread resumes here.
+   * Bow out cleanly rather than tripping FAILURE; the assertion in
+   * main() has already captured last_exc by the time we get here.
+   */
+  thread_terminate(mach_thread_self());
+  for (;;) { /* belt-and-braces in case termination is deferred */ }
+#else
   FAILURE("we shouldn't be here!");
+#endif
 }
 
 void test_bad_syscall_num(void *arg)
@@ -78,12 +112,25 @@ void test_bad_syscall_num(void *arg)
   asm volatile("movq	$0x123456,%rax;"                \
                "syscall;"                               \
                );
-#else
+#elif defined(__i386__)
   asm volatile("mov	$0x123456,%eax;"                \
                "lcall	$0x7,$0x0;"                     \
                );
-#endif
+#elif defined(__aarch64__)
+  /*
+   * Valid Mach trap numbers are negative on aarch64 (matching the i386
+   * convention); a positive value like 0x123456 lands in the SVC
+   * entry's "imm16 != 0 || not a valid mach trap" path, which raises
+   * EXC_SOFTWARE / EXC_AARCH64_SVC.
+   */
+  register long w8 asm("w8") = 0x123456;
+  asm volatile("svc #0" :: "r"(w8));
+  /* See test_syscall_bad_arg_on_stack — bow out instead of FAILURE. */
+  thread_terminate(mach_thread_self());
+  for (;;) { }
+#else
   FAILURE("we shouldn't be here!");
+#endif
 }
 
 
@@ -119,14 +166,42 @@ int main(int argc, char *argv[], int envc, char *envp[])
   memset(&last_exc, 0, sizeof(last_exc));
   test_thread_start(mach_task_self(), test_bad_syscall_num, NULL);
   ASSERT_RET(mach_msg_server_once(exc_server, 4096, excp, MACH_MSG_OPTION_NONE), "error in exc server");
+#if defined(__aarch64__)
+  /*
+   * On aarch64 an svc with an unallocated immediate (or, as here, a
+   * bad mach syscall number in w8) raises EXC_SOFTWARE with subcode
+   * EXC_AARCH64_SVC — see <mach/aarch64/exception.h>.  This differs
+   * from i386's EXC_BAD_INSTRUCTION/EXC_I386_INVOP categorisation
+   * because aarch64 has a dedicated svc instruction whose entry path
+   * is "software-generated".
+   */
+  ASSERT((last_exc.exception == EXC_SOFTWARE) && (last_exc.code == EXC_AARCH64_SVC),
+         "bad exception for test_bad_syscall_num()");
+#else
   ASSERT((last_exc.exception == EXC_BAD_INSTRUCTION) && (last_exc.code == EXC_I386_INVOP),
          "bad exception for test_bad_syscall_num()");
+#endif
 
+#if !defined(__aarch64__)
+  /*
+   * AAPCS passes all 7 mach_msg arguments in registers (x0..x6) on
+   * aarch64, so there's no literal "arg on stack" path to corrupt;
+   * the x86 mechanism doesn't translate.  An attempt to fault via a
+   * bad msg pointer instead is caught by copyinmsg's recovery
+   * handler and returned as a syscall error, not raised as an
+   * exception — so the "wait for EXC_BAD_ACCESS" assertion below
+   * would hang.  Skip this subtest until we have a different
+   * aarch64-appropriate way to trigger an unrecoverable user-memory
+   * access from inside a syscall.
+   */
   memset(&last_exc, 0, sizeof(last_exc));
   test_thread_start(mach_task_self(), test_syscall_bad_arg_on_stack, NULL);
   ASSERT_RET(mach_msg_server_once(exc_server, 4096, excp, MACH_MSG_OPTION_NONE), "error in exc server");
   ASSERT((last_exc.exception == EXC_BAD_ACCESS) && (last_exc.code == KERN_INVALID_ADDRESS),
          "bad exception for test_syscall_bad_arg_on_stack()");
+#else
+  (void) test_syscall_bad_arg_on_stack;   /* not exercised on aarch64 */
+#endif
 
   return 0;
 }
diff --git a/tests/test-thread-state-fp.c b/tests/test-thread-state-fp.c
index d0e6802a..331f4d85 100644
--- a/tests/test-thread-state-fp.c
+++ b/tests/test-thread-state-fp.c
@@ -22,6 +22,127 @@
 #include <mach.user.h>
 #include <mach_port.user.h>
 
+#if defined(__aarch64__)
+
+/*
+ *	aarch64 NEON / VFP equivalent of test_fp_state_getset /
+ *	test_xfp_state_getset below.  We use one combined test because
+ *	aarch64 doesn't have the i386/i387 vs SSE/XSAVE split — the
+ *	whole NEON register file (V0..V31 plus FPCR/FPSR) lives in
+ *	struct aarch64_float_state and is fetched in one
+ *	thread_get_state(AARCH64_FLOAT_STATE) call.
+ *
+ *	The shape mirrors the x86 test: the test thread loads known
+ *	values into V3 and FPCR, spawns a helper thread, suspends
+ *	itself; the helper reads the test thread's saved FP state,
+ *	verifies V3 / FPCR are preserved, writes new values into V7
+ *	and FPCR, resumes the test thread; the test thread then reads
+ *	V7 and FPCR back via inline asm and asserts they reflect the
+ *	helper's writes.
+ */
+
+#define V3_PATTERN_LO	0x3333333333333333ULL
+#define V3_PATTERN_HI	0x3333333333333333ULL
+#define V7_PATTERN_LO	0x7777777777777777ULL
+#define V7_PATTERN_HI	0x7777777777777777ULL
+/* FPCR.RM = 01 (round toward +inf) -- bits 23-22 = 0b01. */
+#define FPCR_INIT	0x00400000ULL
+/* FPCR.RM = 10 (round toward -inf). */
+#define FPCR_MODIFIED	0x00800000ULL
+
+static void thread_fp_getset_aarch64(void *arg)
+{
+  int err;
+  thread_t th = *(thread_t*)arg;
+
+  wait_thread_suspended(th);
+
+  /*
+   * thread_setstatus() in aarch64/aarch64/pcb.c checks the address of
+   * the supplied state struct is aligned to alignof(struct
+   * aarch64_float_state) and rejects with KERN_INVALID_ARGUMENT
+   * otherwise.  __int128 inside v[32] gives the struct a 16-byte
+   * alignment requirement.  Using a file-scope static (BSS placement,
+   * fully aligned) sidesteps the stack-frame layout concerns we'd hit
+   * with a function-local — aarch64-unknown-none-elf-gcc doesn't always
+   * honour __attribute__((aligned(16))) on locals containing __int128
+   * across nested calls.
+   */
+  static struct aarch64_float_state state;
+  mach_msg_type_number_t state_count = AARCH64_FLOAT_STATE_COUNT;
+
+  memset(&state, 0, sizeof(state));
+  err = thread_get_state(th, AARCH64_FLOAT_STATE,
+                         (thread_state_t) &state, &state_count);
+  ASSERT_RET(err, "thread_get_state get failed");
+  ASSERT(state_count == AARCH64_FLOAT_STATE_COUNT, "bad state_count");
+
+  /* V3 should match what the test thread loaded. */
+  uint64_t v3_lo = (uint64_t) state.v[3];
+  uint64_t v3_hi = (uint64_t) (state.v[3] >> 64);
+  printf("V3 lo=%016llx hi=%016llx (expected lo=%016llx hi=%016llx)\n",
+         (unsigned long long) v3_lo, (unsigned long long) v3_hi,
+         (unsigned long long) V3_PATTERN_LO,
+         (unsigned long long) V3_PATTERN_HI);
+  ASSERT(v3_lo == V3_PATTERN_LO && v3_hi == V3_PATTERN_HI,
+         "V3 not preserved across context switch");
+  printf("FPCR get=%016llx (expected %016llx)\n",
+         (unsigned long long) state.fpcr,
+         (unsigned long long) FPCR_INIT);
+  ASSERT(state.fpcr == FPCR_INIT, "FPCR not preserved");
+
+  /* Modify V7 and FPCR. */
+  state.v[7] = ((__int128) V7_PATTERN_HI << 64) | V7_PATTERN_LO;
+  state.fpcr = FPCR_MODIFIED;
+
+  printf("set: state addr=%p count=%u fpsr=%llx fpcr=%llx fpmr=%llx\n",
+         &state, state_count,
+         (unsigned long long) state.fpsr,
+         (unsigned long long) state.fpcr,
+         (unsigned long long) state.fpmr);
+  err = thread_set_state(th, AARCH64_FLOAT_STATE,
+                         (thread_state_t) &state, state_count);
+  ASSERT_RET(err, "thread_set_state set failed");
+
+  err = thread_resume(th);
+  ASSERT_RET(err, "error in thread_resume");
+  thread_terminate(mach_thread_self());
+  FAILURE("thread_terminate");
+}
+
+static void test_fp_state_getset_aarch64(void)
+{
+  int err;
+  thread_t th = mach_thread_self();
+
+  /* Load known value into V3 and FPCR. */
+  uint64_t v3_bytes[2] = { V3_PATTERN_LO, V3_PATTERN_HI };
+  asm volatile ("ldr q3, [%0]" :: "r"(v3_bytes) : "v3");
+  uint64_t fpcr_init = FPCR_INIT;
+  asm volatile ("msr fpcr, %0" :: "r"(fpcr_init));
+
+  /* Spawn helper, then suspend self so helper sees a stable FP state. */
+  test_thread_start(mach_task_self(), thread_fp_getset_aarch64, &th);
+  err = thread_suspend(th);
+  ASSERT_RET(err, "error in thread_suspend");
+
+  /* Check V7 and FPCR have the values the helper set. */
+  uint64_t v7_after[2] = { 0, 0 };
+  asm volatile ("str q7, [%0]" :: "r"(v7_after) : "memory");
+  uint64_t fpcr_after = 0;
+  asm volatile ("mrs %0, fpcr" : "=r"(fpcr_after));
+  printf("V7 lo=%016llx hi=%016llx\n",
+         (unsigned long long) v7_after[0], (unsigned long long) v7_after[1]);
+  printf("FPCR after=%016llx (expected %016llx)\n",
+         (unsigned long long) fpcr_after,
+         (unsigned long long) FPCR_MODIFIED);
+  ASSERT(v7_after[0] == V7_PATTERN_LO && v7_after[1] == V7_PATTERN_HI,
+         "V7 wasn't correctly set by the helper thread");
+  ASSERT(fpcr_after == FPCR_MODIFIED, "FPCR wasn't updated by the helper thread");
+}
+
+#endif /* __aarch64__ */
+
 #if defined(__i386__) || defined(__x86_64__)
 #include <mach_i386.user.h>
 
@@ -244,6 +365,8 @@ int main(int argc, char *argv[], int envc, char *envp[])
 #if defined(__i386__) || defined(__x86_64__)
   test_fp_state_getset();
   test_xfp_state_getset();
+#elif defined(__aarch64__)
+  test_fp_state_getset_aarch64();
 #else
   FAILURE("FP/XSTATE test missing on this arch!");
 #endif
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.