diff --git a/sysdeps/mach/hurd/aarch64/Makefile b/sysdeps/mach/hurd/aarch64/Makefile
index 9210d436..6cc831d6 100644
--- a/sysdeps/mach/hurd/aarch64/Makefile
+++ b/sysdeps/mach/hurd/aarch64/Makefile
@@ -22,3 +22,7 @@ endif
 ifeq ($(subdir),setjmp)
 gen-as-const-headers += signal-defines.sym
 endif
+
+ifeq ($(subdir),signal)
+CFLAGS-sigreturn.c += -mgeneral-regs-only
+endif
diff --git a/sysdeps/mach/hurd/aarch64/bits/sigcontext.h b/sysdeps/mach/hurd/aarch64/bits/sigcontext.h
new file mode 100644
index 00000000..163523fa
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/bits/sigcontext.h
@@ -0,0 +1,96 @@
+/* Machine-dependent signal context structure for GNU Hurd.  AArch64 version.
+   Copyright (C) 1991-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _BITS_SIGCONTEXT_H
+#define _BITS_SIGCONTEXT_H 1
+
+#if !defined _SIGNAL_H && !defined _SYS_UCONTEXT_H
+# error "Never use <bits/sigcontext.h> directly; include <signal.h> instead."
+#endif
+
+/* Signal handlers are actually called:
+   void handler (int sig, int code, struct sigcontext *scp);  */
+
+#include <bits/types/__sigset_t.h>
+#include <mach/machine/fp_reg.h>
+
+/* State of this thread when the signal was taken.  */
+struct sigcontext
+  {
+    /* These first members are machine-independent.  */
+
+    int sc_onstack;		/* Nonzero if running on sigstack.  */
+    __sigset_t sc_mask;		/* Blocked signals to restore.  */
+
+    /* MiG reply port this thread is using.  */
+    unsigned int sc_reply_port;
+
+    /* Port this thread is doing an interruptible RPC on.  */
+    unsigned int sc_intr_port;
+
+    /* Error code associated with this signal (interpreted as `error_t').  */
+    int sc_error;
+
+    /* Make sure the below members are properly aligned, and not packed
+       together with sc_error -- otherwise the layout won't match that of
+       aarch64_thread_state.  */
+    int sc_pad1;
+
+    /* All following members are machine-dependent.  The rest of this
+       structure is written to be laid out identically to:
+       {
+	 struct aarch64_thread_state basic;
+	 struct aarch64_float_state fpu;
+       }
+       trampoline.c knows this, so it must be changed if this changes.  */
+
+#define sc_aarch64_thread_state sc_x[0] /* Beginning of correspondence.  */
+    long sc_x[31];
+    long sc_sp;
+    long sc_pc;
+    long sc_tpidr_el0;
+    long sc_cpsr;
+
+#define sc_aarch64_float_state sc_v[0]
+    __int128_t sc_v[32];
+    long sc_fpsr;
+    long sc_fpcr;
+  };
+
+/* Traditional BSD names for some members.  */
+#define sc_fp	sc_x[29]	/* Frame pointer.  */
+#define sc_ps	sc_cpsr
+
+
+/* The deprecated sigcode values below are passed as an extra, non-portable
+   argument to regular signal handlers.  You should use SA_SIGINFO handlers
+   instead, which use the standard POSIX signal codes.  */
+
+/* Codes for SIGFPE.  */
+#define FPE_INTOVF_TRAP		0x1 /* integer overflow */
+#define FPE_INTDIV_FAULT	0x2 /* integer divide by zero */
+#define FPE_FLTOVF_FAULT	0x3 /* floating overflow */
+#define FPE_FLTDIV_FAULT	0x4 /* floating divide by zero */
+#define FPE_FLTUND_FAULT	0x5 /* floating underflow */
+#define FPE_SUBRNG_FAULT	0x7 /* BOUNDS instruction failed */
+#define FPE_FLTDNR_FAULT	0x8 /* denormalized operand */
+#define FPE_FLTINX_FAULT	0x9 /* floating loss of precision */
+#define FPE_EMERR_FAULT		0xa /* mysterious emulation error 33 */
+#define FPE_EMBND_FAULT		0xb /* emulation BOUNDS instruction failed */
+
+#endif /* bits/sigcontext.h */
diff --git a/sysdeps/mach/hurd/aarch64/exc2signal.c b/sysdeps/mach/hurd/aarch64/exc2signal.c
new file mode 100644
index 00000000..7027bbf7
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/exc2signal.c
@@ -0,0 +1,149 @@
+/* Translate Mach exception codes into signal numbers.  AArch64 version.
+   Copyright (C) 1991-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <hurd.h>
+#include <hurd/signal.h>
+#include <mach/exception.h>
+
+/* Translate the Mach exception codes, as received in an `exception_raise' RPC,
+   into a signal number and signal subcode.  */
+
+static void
+exception2signal (struct hurd_signal_detail *detail, int *signo, int posix)
+{
+  detail->error = 0;
+
+  switch (detail->exc)
+    {
+    default:
+      *signo = SIGIOT;
+      detail->code = detail->exc;
+      break;
+
+    case EXC_BAD_ACCESS:
+      switch (detail->exc_code)
+        {
+	case KERN_INVALID_ADDRESS:
+	case KERN_MEMORY_FAILURE:
+	  *signo = SIGSEGV;
+	  detail->code = posix ? SEGV_MAPERR : detail->exc_subcode;
+	  break;
+
+	case KERN_PROTECTION_FAILURE:
+	case KERN_WRITE_PROTECTION_FAILURE:
+	  *signo = SIGSEGV;
+	  detail->code = posix ? SEGV_ACCERR : detail->exc_subcode;
+	  break;
+
+	case EXC_AARCH64_MTE:
+	  *signo = SIGSEGV;
+	  /* TODO: Should be SEGV_MTESERR */
+	  detail->code = posix ? SEGV_ACCERR : detail->exc_subcode;
+	  break;
+
+	case EXC_AARCH64_BTI:
+	  *signo = SIGILL;
+	  /* XXX: Consider adopting ILL_BTCFI from OpenBSD.  */
+	  /* XXX: exc_subcode / signal code contains BTYPE */
+	  detail->code = posix ? ILL_ILLOPN : detail->exc_subcode;
+	  break;
+
+	case EXC_AARCH64_AL:
+	case EXC_AARCH64_AL_PC:
+	case EXC_AARCH64_AL_SP:
+	  *signo = SIGBUS;
+	  detail->code = posix ? BUS_ADRALN : detail->exc_subcode;
+	  break;
+
+	default:
+	  *signo = SIGBUS;
+	  detail->code = posix ? BUS_ADRERR : detail->exc_subcode;
+	  break;
+	}
+      detail->error = detail->exc_code;
+      break;
+
+    case EXC_BAD_INSTRUCTION:
+      *signo = SIGILL;
+      switch (detail->exc_code)
+        {
+        case EXC_AARCH64_SVC:
+          detail->code = posix ? ILL_ILLTRP : 0;
+          break;
+
+	default:
+	  detail->code = posix ? ILL_ILLOPC : 0;
+	  break;
+        }
+      break;
+
+    case EXC_ARITHMETIC:
+      *signo = SIGFPE;
+      switch (detail->exc_code)
+	{
+	case EXC_AARCH64_IDF:
+	  detail->code = posix ? FPE_FLTIDO : 0;
+	  break;
+	case EXC_AARCH64_IXF:
+	  detail->code = posix ? FPE_FLTRES : FPE_FLTINX_FAULT;
+	  break;
+	case EXC_AARCH64_UFF:
+	  detail->code = posix ? FPE_FLTUND : FPE_FLTDNR_FAULT;
+	  break;
+	case EXC_AARCH64_OFF:
+	  detail->code = posix ? FPE_FLTOVF : FPE_FLTOVF_FAULT;
+	  break;
+	case EXC_AARCH64_DZF:
+	  detail->code = posix ? FPE_FLTDIV : FPE_FLTDIV_FAULT;
+	  break;
+	case EXC_AARCH64_IOF:
+	  /* NB: We used to send SIGILL here but we can't distinguish
+	     POSIX vs. legacy with respect to what signal we send.  */
+	  detail->code = posix ? FPE_FLTINV : 0 /*ILL_FPEOPR_FAULT*/;
+	  break;
+	default:
+	  detail->code = 0;
+	}
+      break;
+
+    case EXC_EMULATION:
+    case EXC_SOFTWARE:
+      *signo = SIGEMT;
+      detail->code = 0;
+      break;
+
+
+    case EXC_BREAKPOINT:
+      *signo = SIGTRAP;
+      detail->code = posix ? TRAP_BRKPT : 0;
+      break;
+    }
+}
+libc_hidden_def (_hurd_exception2signal)
+
+void
+_hurd_exception2signal (struct hurd_signal_detail *detail, int *signo)
+{
+  exception2signal (detail, signo, 1);
+}
+
+void
+_hurd_exception2signal_legacy (struct hurd_signal_detail *detail, int *signo)
+{
+  exception2signal (detail, signo, 0);
+}
diff --git a/sysdeps/mach/hurd/aarch64/intr-msg.h b/sysdeps/mach/hurd/aarch64/intr-msg.h
new file mode 100644
index 00000000..b511d7d7
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/intr-msg.h
@@ -0,0 +1,123 @@
+/* Machine-dependent details of interruptible RPC messaging.  AArch64 version.
+   Copyright (C) 1995-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+
+/* Note that we must mark OPTION and TIMEOUT as outputs of this operation,
+   to indicate that the signal thread might mutate them as part
+   of sending us to a signal handler.  */
+
+#define INTR_MSG_TRAP(msg, option, send_size, rcv_size, rcv_name, timeout, notify, cancel_p, intr_port_p) \
+({									      \
+  register uintptr_t /* error_t */ err asm ("x0");			      \
+  register uintptr_t option_x1 asm ("x1") = option;			      \
+  register uintptr_t send_size_x2 asm ("x2") = send_size;		      \
+  register uintptr_t rcv_size_x3 asm ("x3") = rcv_size;			      \
+  register uintptr_t rcv_name_x4 asm ("x4") = rcv_name;			      \
+  register uintptr_t timeout_x5 asm ("x5") = timeout;			      \
+  register uintptr_t notify_x6 asm ("x6") = notify;			      \
+  register void *msg_x9 asm ("x9") = msg;  /* used by trampoline.c */	      \
+  asm volatile ("\n"							      \
+       ".globl _hurd_intr_rpc_msg_about_to\n"				      \
+       ".globl _hurd_intr_rpc_msg_setup_done\n"				      \
+       ".globl _hurd_intr_rpc_msg_in_trap\n"				      \
+       /* Clear x0 before we do the check for cancel below.  This is to
+          detect x0 being set to non-zero (actually MACH_SEND_INTERRUPTED)
+          from the outside (namely, _hurdsig_abort_rpcs), which signals us
+          to skip the trap we were about to enter.  */			      \
+       "	mov %[err], #0\n"					      \
+       "_hurd_intr_rpc_msg_about_to:\n"					      \
+       /* We need to make a last check of cancel, in case we got interrupted
+          right before _hurd_intr_rpc_msg_about_to.  */			      \
+       "	ldr w8, %[cancel]\n"					      \
+       "	cbz w8, _hurd_intr_rpc_msg_do\n"			      \
+       /* We got interrupted, note so and return EINTR.  */		      \
+       "	str wzr, %[intr_port]\n"				      \
+       "	mov %[err], %[eintr_lo]\n"				      \
+       "	movk %[err], %[eintr_hi], lsl 16\n"			      \
+       "	b _hurd_intr_rpc_msg_sp_restored\n"			      \
+       "_hurd_intr_rpc_msg_do:\n"					      \
+       /* Ok, prepare the mach_msg_trap arguments.  We pass all the 7 args
+          in registers.  */						      \
+       "_hurd_intr_rpc_msg_setup_done:\n"				      \
+       /* From here on, it is safe to make us jump over the syscall.  Now
+          check if we have been told to skip the syscall while running
+          the above.  */						      \
+       "	cbnz %[err], _hurd_intr_rpc_msg_in_trap\n"		      \
+       /* Do the actual syscall.  */					      \
+       "	mov %[err], %[msg]\n"					      \
+       "	mov x8, #-25\n"						      \
+       "_hurd_intr_rpc_msg_do_trap:\n"					      \
+       "	svc #0\n" /* status in %[err] */			      \
+       "_hurd_intr_rpc_msg_in_trap:\n"					      \
+       "_hurd_intr_rpc_msg_sp_restored:\n"				      \
+       : [err] "=r" (err), "+r" (option_x1),				      \
+         [intr_port] "=m" (*intr_port_p),				      \
+         "+r" (timeout_x5)						      \
+       : [msg] "r" (msg_x9), "r" (send_size_x2), "r" (rcv_size_x3),	      \
+         "r" (rcv_name_x4), "r" (notify_x6),				      \
+         [cancel] "m" (*cancel_p),					      \
+         [eintr_lo] "i" (EINTR & 0xffff), [eintr_hi] "i" (EINTR >> 16));      \
+  option = option_x1;							      \
+  timeout = timeout_x5;							      \
+  err;									      \
+})
+
+#include "hurdfault.h"
+
+/* This cannot be an inline function because it calls setjmp.  */
+#define SYSCALL_EXAMINE(state, callno)					      \
+({									      \
+  int result;								      \
+  unsigned int *p = (unsigned int *) (state)->pc - 4;			      \
+  if (_hurdsig_catch_memory_fault (p))					      \
+    return 0;								      \
+  if (result = (*p == 0xd4000001))					      \
+    /* The PC is just after a "svc #0" instruction.
+       This is a system call in progress; x8 holds the call number.  */	      \
+    *(callno) = (state)->x[8];						      \
+  _hurdsig_end_catch_fault ();						      \
+  result;								      \
+})
+
+
+/* This cannot be an inline function because it calls setjmp.  */
+#define MSG_EXAMINE(state, msgid, rcvname, send_name, opt, tmout)	      \
+({									      \
+  int ret = 0;								      \
+  const struct machine_thread_state *s = (state);			      \
+  const mach_msg_header_t *msg = (const void *) s->x[0];		      \
+  *(opt) = s->x[1];							      \
+  *(rcvname) = s->x[4];							      \
+  *(tmout) = s->x[5];							      \
+  if (msg == 0)								      \
+    {									      \
+      *(send_name) = MACH_PORT_NULL;					      \
+      *(msgid) = 0;							      \
+    }									      \
+  else									      \
+    {									      \
+      ret = _hurdsig_catch_memory_fault (msg) ? -1 : 0;			      \
+      if (ret == 0)							      \
+        {								      \
+          *(send_name) = msg->msgh_remote_port;				      \
+          *(msgid) = msg->msgh_id;					      \
+          _hurdsig_end_catch_fault ();					      \
+        }								      \
+    }									      \
+  ret;									      \
+})
diff --git a/sysdeps/mach/hurd/aarch64/sigreturn.c b/sysdeps/mach/hurd/aarch64/sigreturn.c
new file mode 100644
index 00000000..c58b9c49
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/sigreturn.c
@@ -0,0 +1,127 @@
+/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <hurd.h>
+#include <hurd/signal.h>
+#include <hurd/msg.h>
+#include <stdlib.h>
+
+/* This is run on the thread stack after restoring it, to be able to
+   unlock SS off sigstack.  */
+void
+__sigreturn2 (struct sigcontext *scp,
+	      struct hurd_sigstate *ss)
+{
+  error_t err;
+  mach_port_t reply_port;
+  _hurd_sigstate_unlock (ss);
+
+  /* Destroy the MiG reply port used by the signal handler, and restore the
+     reply port in use by the thread when interrupted.
+
+     We cannot use the original reply port for our RPCs that we do here, since
+     we could unexpectedly receive/consume a reply message meant for the user
+     (in particular, msg_sig_post_reply), and also since we would deallocate
+     the port if *our* RPC fails, which we don't want to do since the user
+     still has the old name.  And so, temporarily set MACH_PORT_DEAD as our
+     reply name, and make sure destroying the port is the very last RPC we
+     do.  */
+  reply_port = THREAD_GETMEM (THREAD_SELF, reply_port);
+  THREAD_SETMEM (THREAD_SELF, reply_port, MACH_PORT_DEAD);
+  if (__glibc_likely (MACH_PORT_VALID (reply_port)))
+    (void) __mach_port_mod_refs (__mach_task_self (), reply_port,
+                                 MACH_PORT_RIGHT_RECEIVE, -1);
+  THREAD_SETMEM (THREAD_SELF, reply_port, scp->sc_reply_port);
+
+  /* Restore thread state.  */
+  err = __thread_set_self_state (AARCH64_FLOAT_STATE,
+				 (thread_state_t) &scp->sc_aarch64_float_state,
+				 AARCH64_FLOAT_STATE_COUNT);
+  assert_perror (err);
+  err = __thread_set_self_state (AARCH64_THREAD_STATE,
+				 (thread_state_t) &scp->sc_aarch64_thread_state,
+				 AARCH64_THREAD_STATE_COUNT);
+  assert_perror (err);
+  __builtin_unreachable ();
+}
+
+int
+__sigreturn (struct sigcontext *scp)
+{
+  struct hurd_sigstate *ss;
+  struct hurd_userlink *link = (void *) &scp[1];
+  uintptr_t usp;
+
+  if (__glibc_unlikely (scp == NULL || (scp->sc_mask & _SIG_CANT_MASK)))
+    return __hurd_fail (EINVAL);
+
+  ss = _hurd_self_sigstate ();
+  _hurd_sigstate_lock (ss);
+
+  /* Remove the link on the `active resources' chain added by
+     _hurd_setup_sighandler.  Its purpose was to make sure
+     that we got called; now we have, it is done.  */
+  _hurd_userlink_unlink (link);
+
+  /* Restore the set of blocked signals, and the intr_port slot.  */
+  ss->blocked = scp->sc_mask;
+  ss->intr_port = scp->sc_intr_port;
+
+  /* Check for pending signals that were blocked by the old set.  */
+  if (_hurd_sigstate_pending (ss) & ~ss->blocked)
+    {
+      /* There are pending signals that just became unblocked.  Wake up the
+	 signal thread to deliver them.  But first, squirrel away SCP where
+	 the signal thread will notice it if it runs another handler, and
+	 arrange to have us called over again in the new reality.  */
+      ss->context = scp;
+      _hurd_sigstate_unlock (ss);
+      __msg_sig_post (_hurd_msgport, 0, 0, __mach_task_self ());
+      /* If a pending signal was handled, sig_post never returned.
+	 If it did return, the pending signal didn't run a handler;
+	 proceed as usual.  */
+      _hurd_sigstate_lock (ss);
+      ss->context = NULL;
+    }
+
+  if (scp->sc_onstack)
+    ss->sigaltstack.ss_flags &= ~SS_ONSTACK;
+
+  /* Copy the signal context onto the user's stack, to be able to release the
+     altstack (by unlocking sigstate).  Note that unless an altstack is used,
+     the sigcontext will itself be located on the user's stack, so we may well
+     be overwriting it here (or later in __sigreturn2).  */
+
+  usp = ALIGN_DOWN(scp->sc_sp - sizeof (struct sigcontext), 16);
+  memmove ((void *) usp, scp, sizeof (struct sigcontext));
+
+  /* Switch to the user's stack that we have just prepared, and call
+     __sigreturn2.  Clobber "memory" to make sure GCC flushes the stack
+     setup to actual memory.  */
+  {
+    register uintptr_t usp_x0 asm("x0") = usp;
+    register struct hurd_sigstate *ss_x1 asm("x1") = ss;
+
+    asm volatile ("mov sp, %0\n"
+		  "b __sigreturn2"
+		  :: "r" (usp_x0), "r" (ss_x1)
+		  : "memory");
+    __builtin_unreachable ();
+  }
+}
+
+weak_alias (__sigreturn, sigreturn)
diff --git a/sysdeps/mach/hurd/aarch64/trampoline.c b/sysdeps/mach/hurd/aarch64/trampoline.c
new file mode 100644
index 00000000..4b301335
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/trampoline.c
@@ -0,0 +1,325 @@
+/* Set thread_state for sighandler, and sigcontext to recover.  AArch64 version.
+   Copyright (C) 1994-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <hurd/signal.h>
+#include <hurd/userlink.h>
+#include <thread_state.h>
+#include <mach/exception.h>
+#include <assert.h>
+#include <errno.h>
+#include "hurdfault.h"
+#include <sys/ucontext.h>
+
+
+/* Fill in a siginfo_t structure for SA_SIGINFO-enabled handlers.  */
+static void fill_siginfo (siginfo_t *si, int signo,
+			  const struct hurd_signal_detail *detail,
+			  const struct machine_thread_all_state *state)
+{
+  si->si_signo = signo;
+  si->si_errno = detail->error;
+  si->si_code = detail->code;
+
+  /* XXX We would need a protocol change for sig_post to include
+   * this information.  */
+  si->si_pid = -1;
+  si->si_uid = -1;
+
+  /* Address of the faulting instruction or memory access.  */
+  if (detail->exc == EXC_BAD_ACCESS)
+    si->si_addr = (void *) detail->exc_subcode;
+  else
+    si->si_addr = (void *) state->basic.pc;
+
+  /* XXX On SIGCHLD, this should be the exit status of the child
+   * process.  We would need a protocol change for the proc server
+   * to send this information along with the signal.  */
+  si->si_status = 0;
+
+  si->si_band = 0;              /* SIGPOLL is not supported yet.  */
+  si->si_value.sival_int = 0;   /* sigqueue() is not supported yet.  */
+}
+
+/* Fill in a ucontext_t structure SA_SIGINFO-enabled handlers.  */
+static void fill_ucontext (ucontext_t *uc, const struct sigcontext *sc)
+{
+  uc->uc_flags = 0;
+  uc->uc_link = NULL;
+  uc->uc_sigmask = sc->sc_mask;
+  uc->uc_stack.ss_sp = (__ptr_t) sc->sc_sp;
+  uc->uc_stack.ss_size = 0;
+  uc->uc_stack.ss_flags = 0;
+
+  memcpy (&uc->uc_mcontext.gregs, &sc->sc_aarch64_thread_state,
+	  sizeof (struct aarch64_thread_state));
+  memcpy (&uc->uc_mcontext.fpregs, &sc->sc_aarch64_float_state,
+	  sizeof (struct aarch64_float_state));
+}
+
+struct sigcontext *
+_hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action,
+			__sighandler_t handler,
+			int signo, struct hurd_signal_detail *detail,
+			int rpc_wait,
+			struct machine_thread_all_state *state)
+{
+  void trampoline (void) attribute_hidden;
+  void rpc_wait_trampoline (void) attribute_hidden;
+  void *sigsp;
+  struct sigcontext *scp;
+  struct
+    {
+      union
+        {
+          int signo;
+          /* Make sure signo takes up a pointer-sized slot on the stack.
+             (This should already be the case considering the siginfop
+             pointer below, but better be explicit.)  */
+          void *_pointer_sized;
+        };
+      union
+	{
+	  /* Extra arguments for traditional signal handlers */
+	  struct
+	    {
+	      long int sigcode;
+	      struct sigcontext *scp;       /* Points to ctx, below.  */
+	    } legacy;
+
+	  /* Extra arguments for SA_SIGINFO handlers */
+	  struct
+	    {
+	      siginfo_t *siginfop;          /* Points to siginfo, below.  */
+	      ucontext_t *uctxp;            /* Points to uctx, below.  */
+	    } posix;
+	};
+
+      void *_pad;
+
+      /* NB: sigreturn assumes link is next to ctx.  */
+      struct sigcontext ctx;
+      struct hurd_userlink link;
+      ucontext_t ucontext;
+      siginfo_t siginfo;
+    } *stackframe;
+
+  if (ss->context)
+    {
+      /* We have a previous sigcontext that sigreturn was about
+	 to restore when another signal arrived.  We will just base
+	 our setup on that.  */
+      if (! _hurdsig_catch_memory_fault (ss->context))
+	{
+	  memcpy (&state->basic, &ss->context->sc_aarch64_thread_state,
+		  sizeof (state->basic));
+	  memcpy (&state->fpu, &ss->context->sc_aarch64_float_state,
+		  sizeof (state->fpu));
+	  state->set |= (1 << AARCH64_THREAD_STATE) | (1 << AARCH64_FLOAT_STATE);
+	}
+    }
+
+  if (! machine_get_basic_state (ss->thread, state))
+    return NULL;
+
+  if ((action->sa_flags & SA_ONSTACK)
+      && !(ss->sigaltstack.ss_flags & (SS_DISABLE|SS_ONSTACK)))
+    {
+      sigsp = ss->sigaltstack.ss_sp + ss->sigaltstack.ss_size;
+      ss->sigaltstack.ss_flags |= SS_ONSTACK;
+    }
+  else
+    /* No red zone on aarch64-gnu.  */
+    sigsp = (void *) state->basic.sp;
+
+  /* Push the arguments to call `trampoline' on the stack.
+     Note that user's SP doesn't strictly have to be 16-aligned, since
+     AArch64 only requires 16-alignment for the stack pointer when
+     making accesses relative to it.  */
+  sigsp = PTR_ALIGN_DOWN (sigsp - sizeof (*stackframe), 16);
+  stackframe = sigsp;
+
+  _Static_assert ((void *) (&stackframe->_pad + 1) == (void *) &stackframe->ctx,
+		  "trampoline expects no extra padding between "
+		  "_pad and ctx");
+
+  if (_hurdsig_catch_memory_fault (stackframe))
+    {
+      /* We got a fault trying to write the stack frame.
+	 We cannot set up the signal handler.
+	 Returning NULL tells our caller, who will nuke us with a SIGILL.  */
+      return NULL;
+    }
+  else
+    {
+      int ok;
+
+      extern void _hurdsig_longjmp_from_handler (void *, jmp_buf, int);
+
+      /* Add a link to the thread's active-resources list.  We mark this as
+	 the only user of the "resource", so the cleanup function will be
+	 called by any longjmp which is unwinding past the signal frame.
+	 The cleanup function (in sigunwind.c) will make sure that all the
+	 appropriate cleanups done by sigreturn are taken care of.  */
+      stackframe->link.cleanup = &_hurdsig_longjmp_from_handler;
+      stackframe->link.cleanup_data = &stackframe->ctx;
+      stackframe->link.resource.next = NULL;
+      stackframe->link.resource.prevp = NULL;
+      stackframe->link.thread.next = ss->active_resources;
+      stackframe->link.thread.prevp = &ss->active_resources;
+      if (stackframe->link.thread.next)
+	stackframe->link.thread.next->thread.prevp
+	  = &stackframe->link.thread.next;
+      ss->active_resources = &stackframe->link;
+
+      /* Set up the sigcontext from the current state of the thread.  */
+
+      scp = &stackframe->ctx;
+      scp->sc_onstack = ss->sigaltstack.ss_flags & SS_ONSTACK ? 1 : 0;
+
+      /* struct sigcontext is laid out so that starting at sc_x[0] mimics a
+	 struct aarch64_thread_state.  */
+      _Static_assert (offsetof (struct sigcontext, sc_aarch64_thread_state)
+		      % __alignof__ (struct aarch64_thread_state) == 0,
+		      "sc_aarch64_thread_state layout mismatch");
+      memcpy (&scp->sc_aarch64_thread_state,
+	      &state->basic, sizeof (state->basic));
+
+      /* struct sigcontext is laid out so that starting at sc_v[0] mimics a
+	 struct aarch64_float_state.  */
+      _Static_assert (offsetof (struct sigcontext, sc_aarch64_float_state)
+		      % __alignof__ (struct aarch64_float_state) == 0,
+		      "sc_aarch64_float_state layout mismatch");
+      ok = machine_get_state (ss->thread, state, AARCH64_FLOAT_STATE,
+			      &state->fpu, &scp->sc_aarch64_float_state,
+			      sizeof (state->fpu));
+
+      /* Set up the arguments for the signal handler.  */
+      stackframe->signo = signo;
+      if (action->sa_flags & SA_SIGINFO)
+	{
+	  stackframe->posix.siginfop = &stackframe->siginfo;
+	  stackframe->posix.uctxp = &stackframe->ucontext;
+	  fill_siginfo (&stackframe->siginfo, signo, detail, state);
+	  fill_ucontext (&stackframe->ucontext, scp);
+	}
+      else
+	{
+	  if (detail->exc)
+	    {
+	      int nsigno;
+	      _hurd_exception2signal_legacy (detail, &nsigno);
+	      assert (nsigno == signo);
+	    }
+	  else
+	    detail->code = 0;
+
+	  stackframe->legacy.sigcode = detail->code;
+	  stackframe->legacy.scp = &stackframe->ctx;
+	}
+
+      _hurdsig_end_catch_fault ();
+
+      if (!ok)
+	return NULL;
+    }
+
+  /* Modify the thread state to call the trampoline code on the new stack.  */
+  state->basic.sp = (uintptr_t) sigsp;
+
+  if (rpc_wait)
+    {
+      /* The signalee thread was blocked in a mach_msg_trap system call,
+         still waiting for a reply.  We will have it run the special
+         trampoline code which retries the message receive before running
+         the signal handler.
+
+         To do this we change the OPTION argument (in x1) to enable only
+         message reception, since the request message has already been
+         sent.  */
+
+      assert (state->basic.x[1] & MACH_RCV_MSG);
+      /* Disable the message-send, since it has already completed.  The
+         calls we retry need only wait to receive the reply message.  */
+      state->basic.x[1] &= ~MACH_SEND_MSG;
+
+      /* Limit the time to receive the reply message, in case the server
+         claimed that `interrupt_operation' succeeded but in fact the RPC
+         is hung.  */
+      state->basic.x[1] |= MACH_RCV_TIMEOUT;
+      state->basic.x[5] = _hurd_interrupted_rpc_timeout;
+
+      state->basic.pc = (uintptr_t) rpc_wait_trampoline;
+      /* After doing the message receive, the trampoline code will need to
+         update the x0 value to be restored by sigreturn.  To simplify
+         the assembly code, we pass the address of its slot in SCP to the
+         trampoline code in x21.  */
+      state->basic.x[21] = (uintptr_t) &scp->sc_x[0];
+    }
+  else
+    state->basic.pc = (uintptr_t) trampoline;
+
+  /* We pass the handler function to the trampoline code in x20.  */
+  state->basic.x[20] = (uintptr_t) handler;
+
+  /* Clear pstate, notably reset BTYPE to 0.  */
+  state->basic.cpsr = 0;
+
+  return scp;
+}
+
+asm ("rpc_wait_trampoline:\n"
+  /* This is the entry point when we have an RPC reply message to receive
+     before running the handler.  The MACH_MSG_SEND bit has already been
+     cleared in the OPTION argument in our x1.  For our convenience, x21
+     points to the sc_x[0] member of the sigcontext.
+
+     When the sigcontext was saved, x0 was MACH_RCV_INTERRUPTED.  To repeat
+     the message call, we need to restore the original message buffer
+     pointer; intr-msg.h keeps a backup copy of it in x9 specifically for
+     this purpose.  */
+     "mov x0, x9\n"
+     "svc #0\n"
+     /* Now the message receive has completed and the original caller of
+        the RPC (i.e. the code running when the signal arrived) needs to
+        see the final return value of the message receive in x0.  So store
+        the new x0 value into the sc_x[0] member of the sigcontext (whose
+        address is in x21 to make this code simpler).  */
+     "str x0, [x21]\n"
+
+     "trampoline:\n"
+     /* Entry point for running the handler normally.  The arguments to the
+        handler function are on the top of the stack:
+
+        [sp]		SIGNO
+        [sp, #8]	SIGCODE
+        [sp, #16]	SCP
+        [sp, #24]	_pad
+
+        Pop them off to the registers, to pass as arguments to the handler.
+     */
+     "ldp x0, x1, [sp], #16\n"
+     "ldr x2, [sp], #16\n"
+     /* Call handler (signo, sigcode, scp).  Note that this is an indirect
+        call not using x16/x17, so this requires the signal handler to start
+        with a proper "bti c" marker.  */
+     "blr x20\n"
+     /* Call __sigreturn (), passing &CTX as SCP.  CTX is on the top of
+        the stack.  If __sigreturn () fails, we're in trouble.  */
+     "mov x0, sp\n"
+     "bl __sigreturn\n"
+     "udf #0xdead");
