diff --git a/sysdeps/aarch64/htl/tcb-offsets.sym b/sysdeps/aarch64/htl/tcb-offsets.sym
new file mode 100644
index 00000000..56140780
--- /dev/null
+++ b/sysdeps/aarch64/htl/tcb-offsets.sym
@@ -0,0 +1,5 @@
+#include <sysdep.h>
+#include <tls.h>
+#include <kernel-features.h>
+
+SIGSTATE_OFFSET         offsetof (tcbprehead_t, _hurd_sigstate) - sizeof (tcbprehead_t)
diff --git a/sysdeps/mach/hurd/aarch64/Makefile b/sysdeps/mach/hurd/aarch64/Makefile
new file mode 100644
index 00000000..9210d436
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/Makefile
@@ -0,0 +1,24 @@
+# Copyright (C) 2020-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/>.
+
+ifeq ($(subdir),debug)
+gen-as-const-headers += signal-defines.sym
+endif
+
+ifeq ($(subdir),setjmp)
+gen-as-const-headers += signal-defines.sym
+endif
diff --git a/sysdeps/mach/hurd/aarch64/____longjmp_chk.S b/sysdeps/mach/hurd/aarch64/____longjmp_chk.S
new file mode 100644
index 00000000..90f062df
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/____longjmp_chk.S
@@ -0,0 +1,173 @@
+/* Copyright (C) 1997-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 <asm-syntax.h>
+#include <jmpbuf-offsets.h>
+#include <pointer_guard.h>
+#include <tcb-offsets.h>
+#include <signal-defines.h>
+#include <stap-probe.h>
+#include <sysdep.h>
+
+#define SS_ONSTACK	1
+#define SS_ONSTACK_BIT	0
+
+	.section .rodata.str1.1,"aMS",%progbits,1
+	.type   longjmp_msg,%object
+longjmp_msg:
+	.string "longjmp causes uninitialized stack frame"
+	.size   longjmp_msg, .-longjmp_msg
+	.text
+
+# define CALL_FAIL						\
+	adrp	x0, longjmp_msg;				\
+	add	x0, x0, :lo12:longjmp_msg;			\
+	b	HIDDEN_JUMPTARGET(__fortify_fail)		\
+
+/* Jump to the position specified by ENV, causing the
+   setjmp call there to return VAL, or 1 if VAL is 0.
+   void __longjmp (__jmp_buf env, int val).  */
+        .text
+ENTRY(____longjmp_chk)
+	cfi_def_cfa(x0, 0)
+	cfi_offset(x19, JB_X19<<3)
+	cfi_offset(x20, JB_X20<<3)
+	cfi_offset(x21, JB_X21<<3)
+	cfi_offset(x22, JB_X22<<3)
+	cfi_offset(x23, JB_X23<<3)
+	cfi_offset(x24, JB_X24<<3)
+	cfi_offset(x25, JB_X25<<3)
+	cfi_offset(x26, JB_X26<<3)
+	cfi_offset(x27, JB_X27<<3)
+	cfi_offset(x28, JB_X28<<3)
+	cfi_offset(x29, JB_X29<<3)
+	cfi_offset(x30, JB_LR<<3)
+
+	cfi_offset( d8, JB_D8<<3)
+	cfi_offset( d9, JB_D9<<3)
+	cfi_offset(d10, JB_D10<<3)
+	cfi_offset(d11, JB_D11<<3)
+	cfi_offset(d12, JB_D12<<3)
+	cfi_offset(d13, JB_D13<<3)
+	cfi_offset(d14, JB_D14<<3)
+	cfi_offset(d15, JB_D15<<3)
+
+	PTR_ARG (0)
+
+	ldp	x19, x20, [x0, #JB_X19<<3]
+	ldp	x21, x22, [x0, #JB_X21<<3]
+	ldp	x23, x24, [x0, #JB_X23<<3]
+	ldp	x25, x26, [x0, #JB_X25<<3]
+	ldp	x27, x28, [x0, #JB_X27<<3]
+#ifdef PTR_DEMANGLE
+	ldp	x29,  x4, [x0, #JB_X29<<3]
+	PTR_DEMANGLE (30, 4, 3, 2)
+#else
+	ldp	x29, x30, [x0, #JB_X29<<3]
+#endif
+	/* longjmp probe takes 3 arguments, address of jump buffer as
+	   first argument (8@x0), return value as second argument (-4@x1),
+	   and target address (8@x30), respectively.  */
+	LIBC_PROBE (longjmp, 3, 8@x0, -4@x1, 8@x30)
+	ldp	 d8,  d9, [x0, #JB_D8<<3]
+	ldp	d10, d11, [x0, #JB_D10<<3]
+	ldp	d12, d13, [x0, #JB_D12<<3]
+	ldp	d14, d15, [x0, #JB_D14<<3]
+
+        /* Originally this was implemented with a series of
+	   .cfi_restore() directives.
+
+           The theory was that cfi_restore should revert to previous
+           frame value is the same as the current value.  In practice
+           this doesn't work, even after cfi_restore() gdb continues
+           to try to recover a previous frame value offset from x0,
+           which gets stuffed after a few more instructions.  The
+           cfi_same_value() mechanism appears to work fine.  */
+
+	cfi_same_value(x19)
+	cfi_same_value(x20)
+	cfi_same_value(x21)
+	cfi_same_value(x22)
+	cfi_same_value(x23)
+	cfi_same_value(x24)
+	cfi_same_value(x25)
+	cfi_same_value(x26)
+	cfi_same_value(x27)
+	cfi_same_value(x28)
+	cfi_same_value(x29)
+	cfi_same_value(x30)
+	cfi_same_value(d8)
+	cfi_same_value(d9)
+	cfi_same_value(d10)
+	cfi_same_value(d11)
+	cfi_same_value(d12)
+	cfi_same_value(d13)
+	cfi_same_value(d14)
+	cfi_same_value(d15)
+#ifdef PTR_DEMANGLE
+	ldr	x4, [x0, #JB_SP<<3]
+	PTR_DEMANGLE (5, 4, 3, 2)
+#else
+	ldr	x5, [x0, #JB_SP<<3]
+#endif
+
+	mrs	x3, tpidr_el0
+#if !defined (SHARED) || IS_IN (rtld)
+	cbz	x3, L(ok)	/* TLS not initialized yet */
+#endif
+	ldr	x3, [x3, #SIGSTATE_OFFSET]
+	cbz	x3, L(ok)	/* sigstate not initialized yet */
+
+	ldrb	w4, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_FLAGS__OFFSET)]
+	tbnz	w4, #SS_ONSTACK_BIT, L(onstack)
+	/* We haven't been using an altstack.  Jumping to a higher-address
+	   frame is always allowed, otherwise it's not allowed.  */
+	mov	x6, sp
+	cmp	x5, x6
+	b.lt	L(ok)
+
+L(fail):
+	CALL_FAIL
+
+L(onstack):
+	/* We have been using an altstack.  Was it above or below ours? */
+	ldr	x2, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_SP__OFFSET)]
+	cmp	x2, x5
+	b.lt	L(oks)		/* Jumping below the altstack, switch */
+	ldr	x6, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_SIZE__OFFSET)]
+	add	x2, x2, x6
+	cmp	x2, x5
+	b.lt	L(ok)		/* Jumping inside the altstack, do not switch */
+
+	/* Jumping above the altstack, switch */
+L(oks):
+	and	w4, w4, #~(SS_ONSTACK)
+	str	w4, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_FLAGS__OFFSET)]
+L(ok):
+	mov	sp, x5
+
+	/* longjmp_target probe takes 3 arguments, address of jump buffer
+	   as first argument (8@x0), return value as second argument (-4@x1),
+	   and target address (8@x30), respectively.  */
+	LIBC_PROBE (longjmp_target, 3, 8@x0, -4@x1, 8@x30)
+	cmp	x1, #0
+	mov	x0, #1
+	csel	x0, x1, x0, ne
+	/* Use br instead of ret because ret is guaranteed to mispredict */
+	br	x30
+END (____longjmp_chk)
diff --git a/sysdeps/mach/hurd/aarch64/__longjmp.S b/sysdeps/mach/hurd/aarch64/__longjmp.S
new file mode 100644
index 00000000..c9f2e1ce
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/__longjmp.S
@@ -0,0 +1,150 @@
+/* Copyright (C) 1997-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 <asm-syntax.h>
+#include <jmpbuf-offsets.h>
+#include <pointer_guard.h>
+#include <tcb-offsets.h>
+#include <signal-defines.h>
+#include <stap-probe.h>
+#include <sysdep.h>
+
+#define SS_ONSTACK	1
+#define SS_ONSTACK_BIT	0
+
+/* __longjmp(jmpbuf, val) */
+
+ENTRY (__longjmp)
+	cfi_def_cfa(x0, 0)
+	cfi_offset(x19, JB_X19<<3)
+	cfi_offset(x20, JB_X20<<3)
+	cfi_offset(x21, JB_X21<<3)
+	cfi_offset(x22, JB_X22<<3)
+	cfi_offset(x23, JB_X23<<3)
+	cfi_offset(x24, JB_X24<<3)
+	cfi_offset(x25, JB_X25<<3)
+	cfi_offset(x26, JB_X26<<3)
+	cfi_offset(x27, JB_X27<<3)
+	cfi_offset(x28, JB_X28<<3)
+	cfi_offset(x29, JB_X29<<3)
+	cfi_offset(x30, JB_LR<<3)
+
+	cfi_offset( d8, JB_D8<<3)
+	cfi_offset( d9, JB_D9<<3)
+	cfi_offset(d10, JB_D10<<3)
+	cfi_offset(d11, JB_D11<<3)
+	cfi_offset(d12, JB_D12<<3)
+	cfi_offset(d13, JB_D13<<3)
+	cfi_offset(d14, JB_D14<<3)
+	cfi_offset(d15, JB_D15<<3)
+
+	PTR_ARG (0)
+
+	ldp	x19, x20, [x0, #JB_X19<<3]
+	ldp	x21, x22, [x0, #JB_X21<<3]
+	ldp	x23, x24, [x0, #JB_X23<<3]
+	ldp	x25, x26, [x0, #JB_X25<<3]
+	ldp	x27, x28, [x0, #JB_X27<<3]
+#ifdef PTR_DEMANGLE
+	ldp	x29,  x4, [x0, #JB_X29<<3]
+	PTR_DEMANGLE (30, 4, 3, 2)
+#else
+	ldp	x29, x30, [x0, #JB_X29<<3]
+#endif
+	/* longjmp probe takes 3 arguments, address of jump buffer as
+	   first argument (8@x0), return value as second argument (-4@x1),
+	   and target address (8@x30), respectively.  */
+	LIBC_PROBE (longjmp, 3, 8@x0, -4@x1, 8@x30)
+	ldp	 d8,  d9, [x0, #JB_D8<<3]
+	ldp	d10, d11, [x0, #JB_D10<<3]
+	ldp	d12, d13, [x0, #JB_D12<<3]
+	ldp	d14, d15, [x0, #JB_D14<<3]
+
+        /* Originally this was implemented with a series of
+	   .cfi_restore() directives.
+
+           The theory was that cfi_restore should revert to previous
+           frame value is the same as the current value.  In practice
+           this doesn't work, even after cfi_restore() gdb continues
+           to try to recover a previous frame value offset from x0,
+           which gets stuffed after a few more instructions.  The
+           cfi_same_value() mechanism appears to work fine.  */
+
+	cfi_same_value(x19)
+	cfi_same_value(x20)
+	cfi_same_value(x21)
+	cfi_same_value(x22)
+	cfi_same_value(x23)
+	cfi_same_value(x24)
+	cfi_same_value(x25)
+	cfi_same_value(x26)
+	cfi_same_value(x27)
+	cfi_same_value(x28)
+	cfi_same_value(x29)
+	cfi_same_value(x30)
+	cfi_same_value(d8)
+	cfi_same_value(d9)
+	cfi_same_value(d10)
+	cfi_same_value(d11)
+	cfi_same_value(d12)
+	cfi_same_value(d13)
+	cfi_same_value(d14)
+	cfi_same_value(d15)
+#ifdef PTR_DEMANGLE
+	ldr	x4, [x0, #JB_SP<<3]
+	PTR_DEMANGLE (5, 4, 3, 2)
+#else
+	ldr	x5, [x0, #JB_SP<<3]
+#endif
+
+	mrs	x3, tpidr_el0
+#if !defined (SHARED) || IS_IN (rtld)
+	cbz	x3, L(ok)	/* TLS not initialized yet */
+#endif
+	ldr	x3, [x3, #SIGSTATE_OFFSET]
+	cbz	x3, L(ok)	/* sigstate not initialized yet */
+	/* If we haven't been using an altstack, don't switch */
+	ldrb	w4, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_FLAGS__OFFSET)]
+	tbz	w4, #SS_ONSTACK_BIT, L(ok)
+
+	/* We have been using an altstack.  Was it above or below ours? */
+	ldr	x2, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_SP__OFFSET)]
+	cmp	x2, x5
+	b.lt	L(oks)		/* Jumping below the altstack, switch */
+	ldr	x6, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_SIZE__OFFSET)]
+	add	x2, x2, x6
+	cmp	x2, x5
+	b.lt	L(ok)		/* Jumping inside the altstack, do not switch */
+
+	/* Jumping above the altstack, switch */
+L(oks):
+	and	w4, w4, #~(SS_ONSTACK)
+	str	w4, [x3, # (HURD_SIGSTATE__SIGALTSTACK__OFFSET + SIGALTSTACK__SS_FLAGS__OFFSET)]
+L(ok):
+	mov	sp, x5
+
+	/* longjmp_target probe takes 3 arguments, address of jump buffer
+	   as first argument (8@x0), return value as second argument (-4@x1),
+	   and target address (8@x30), respectively.  */
+	LIBC_PROBE (longjmp_target, 3, 8@x0, -4@x1, 8@x30)
+	cmp	x1, #0
+	mov	x0, #1
+	csel	x0, x1, x0, ne
+	/* Use br instead of ret because ret is guaranteed to mispredict */
+	br	x30
+END (__longjmp)
diff --git a/sysdeps/mach/hurd/aarch64/signal-defines.sym b/sysdeps/mach/hurd/aarch64/signal-defines.sym
new file mode 100644
index 00000000..e42bbbe0
--- /dev/null
+++ b/sysdeps/mach/hurd/aarch64/signal-defines.sym
@@ -0,0 +1,10 @@
+#include <hurd/signal.h>
+#include <signal.h>
+
+--
+
+HURD_SIGSTATE__SIGALTSTACK__OFFSET	offsetof(struct hurd_sigstate, sigaltstack)
+
+SIGALTSTACK__SS_SP__OFFSET		offsetof(stack_t, ss_sp)
+SIGALTSTACK__SS_SIZE__OFFSET		offsetof(stack_t, ss_size)
+SIGALTSTACK__SS_FLAGS__OFFSET		offsetof(stack_t, ss_flags)
