[newlib-cygwin/main] Cygwin: exceptions: Fix AArch64 non-incyg signal handling

Jon Turney via Cygwin-cvs <[email protected]> Mon, 3 Aug 2026 15:28:39 +0000 (GMT)
Newsgroups gmane.os.cygwin.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3Dbc99666a4bc=
07963013c9fa21dfaf5c0dab5275d

commit bc99666a4bc07963013c9fa21dfaf5c0dab5275d
Author: Mate Dimand <[email protected]>
Date:   Fri Jul 10 11:16:52 2026 +0200

    Cygwin: exceptions: Fix AArch64 non-incyg signal handling
   =20
    This patch fixes crashes that occur when a signal interrupts sigfe or
    any non-cygwin function that does not preserve the LR register in its
    prologue/epilogue. This crash was discovered through the "run-heredoc"
    testcase in bash's testsuite, which caused bash to call "read"
    frequently, leading to a high chance of a signal interrupting sigfe.
   =20
    The original "sigdelayed" function in gendef clobbered the LR register
    to return to the instruction where the thread was interrupted. Picking
    any other register for branching back would also clobber said register.
    Leaf functions are not guaranteed to be compiled with LR being preserved
    on the stack. The solution is to use RtlRestoreContext to restore all
    registers without needing to sacrifice any.
   =20
    The patch replaces sigdelayed with an implementation that consists of
    both C and assembly functions. The "sigdelayed" assembly function was
    replaced with a simple wrapper function that was kept as assembly code
    to guarantee total control of the registers that get clobbered prior to
    capturing the context. This function calls RtlCaptureContext first, with
    the context being stored on the stack. The context is then passed to a
    C++ function which fixes the clobbered registers. Finally, this calls a
    C++ implementation of the original x64/AArch64 assembly version of
    sigdelayed. These functions were not written in assembly for the sake of
    readability. Once it completed handling the signals, it uses
    RtlRestoreContext to jump back.
   =20
    Signed-off-by: Mate Dimand <[email protected]>

Diff:
---
 winsup/cygwin/exceptions.cc           |  61 ++++++++++++
 winsup/cygwin/local_includes/cygtls.h |   5 +
 winsup/cygwin/scripts/gendef          | 168 ++++--------------------------=
----
 3 files changed, 82 insertions(+), 152 deletions(-)

diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc
index 1e129b319..d89cf2c06 100644
--- a/winsup/cygwin/exceptions.cc
+++ b/winsup/cygwin/exceptions.cc
@@ -947,6 +947,67 @@ singlestep_handler (EXCEPTION_POINTERS *ep)
 }
 #endif
=20
+#ifdef __aarch64__
+/* This function uses RtlRestoreContext to ensure that LR does not get
+   clobbered. Note that this function should not return, and the stack
+   contents created by this function are left un-popped. This should
+   not be a problem, since the context restoration also restores SP. */
+void
+_cygtls::sigdelayed_impl(PCONTEXT ctx)
+{
+  int backup_errno =3D saved_errno;
+
+  call_signal_handler();
+
+  lock();
+
+  if(backup_errno >=3D 0)
+  {
+    *errno_addr =3D backup_errno;
+  }
+
+  ctx->Pc =3D pop();
+
+  /* Atomically clear the return address. */
+  InterlockedExchange64 ((LONG64*)stackptr, 0);
+
+  incyg =3D 0;
+  unlock();
+
+  RtlRestoreContext(ctx, NULL);
+
+  /* If we got here, something was wrong. */
+  api_fatal ("Failed to restore context in sigdelayed_impl");
+}
+
+/* This function restores the context's clobbered registers and
+   calls the actual sigdelayed implementation. */
+extern "C" void
+sigdelayed_init(PCONTEXT ctx)
+{
+  /* Retrieving the registers stored on stack by sigdelayed. */
+  const DWORD64* sp =3D ((DWORD64*)ctx->Sp);
+  const DWORD64 stack_x16 =3D sp[0];
+  const DWORD64 stack_x17 =3D sp[1];
+  const DWORD64 stack_x0 =3D sp[2];
+  const DWORD64 stack_lr =3D sp[3];
+
+  ctx->X16 =3D stack_x16; // x16 clobbered by RtlCaptureContext
+  ctx->X17 =3D stack_x17;
+  ctx->X0 =3D stack_x0; // x0 isn't set by RtlCaptureContext
+  ctx->Lr =3D stack_lr; // LR is zeroed out by RtlCaptureContext
+
+  /* "sigdelayed" allocates 0x390 bytes for the context, matching the
+     struct's size. This should cause an error if the size of the struct
+     happened to change. */
+  static_assert(sizeof(CONTEXT) =3D=3D 0x390);
+
+  ctx->Sp +=3D sizeof(CONTEXT) + 32; // undo stack pushes from sigdelayed
+
+  _my_tls.sigdelayed_impl(ctx);
+}
+#endif
+
 bool
 _cygtls::interrupt_now (CONTEXT *cx, siginfo_t& si, void *handler,
 			struct sigaction& siga)
diff --git a/winsup/cygwin/local_includes/cygtls.h b/winsup/cygwin/local_in=
cludes/cygtls.h
index 0b5255495..366ff00cc 100644
--- a/winsup/cygwin/local_includes/cygtls.h
+++ b/winsup/cygwin/local_includes/cygtls.h
@@ -228,6 +228,11 @@ public: /* Do NOT remove this public: line, it's a mar=
ker for gentls_offsets. */
   {
     return initialized =3D=3D CYGTLS_INITIALIZED;
   }
+
+#ifdef __aarch64__
+  void sigdelayed_impl(PCONTEXT ctx);
+#endif
+
   bool interrupt_now (CONTEXT *, siginfo_t&, void *, struct sigaction&);
   void interrupt_setup (siginfo_t&, void *, struct sigaction&);
=20
diff --git a/winsup/cygwin/scripts/gendef b/winsup/cygwin/scripts/gendef
index 25698404b..233847e1a 100755
--- a/winsup/cygwin/scripts/gendef
+++ b/winsup/cygwin/scripts/gendef
@@ -461,162 +461,26 @@ _sigbe:
 	.global	sigdelayed
 	.seh_proc sigdelayed
 sigdelayed:
-	stp	x0,  x1,  [sp, #-16]!
-	stp	x2,  x3,  [sp, #-16]!
-	stp	x4,  x5,  [sp, #-16]!
-	stp	x6,  x7,  [sp, #-16]!
-	stp	x8,  x9,  [sp, #-16]!
-	stp	x10, x11, [sp, #-16]!
-	stp	x12, x13, [sp, #-16]!
-	stp	x14, x15, [sp, #-16]!
-	stp	x16, x17, [sp, #-16]!
-	stp	x18, x19, [sp, #-16]!
-	.seh_stackalloc 160
-	.seh_save_reg x19, 152
-	stp	x20, x21, [sp, #-16]!
-	.seh_save_regp_x x20, 16
-	stp	x22, x23, [sp, #-16]!
-	.seh_save_regp_x x22, 16
-	stp	x24, x25, [sp, #-16]!
-	.seh_save_regp_x x24, 16
-	stp	x26, x27, [sp, #-16]!
-	.seh_save_regp_x x26, 16
-	stp	x28, x29, [sp, #-16]!
-	.seh_save_regp_x x28, 16
-
-	mov	x1, sp
-	str	x1, [sp, #-16]!
-
-	stp	q0,  q1,  [sp, #-32]!
-	stp	q2,  q3,  [sp, #-32]!
-	stp	q4,  q5,  [sp, #-32]!
-	stp	q6,  q7,  [sp, #-32]!
-	stp	q8,  q9,  [sp, #-32]!
-	stp	q10, q11, [sp, #-32]!
-	stp	q12, q13, [sp, #-32]!
-	stp	q14, q15, [sp, #-32]!
-	stp	q16, q17, [sp, #-32]!
-	stp	q18, q19, [sp, #-32]!
-	stp	q20, q21, [sp, #-32]!
-	stp	q22, q23, [sp, #-32]!
-	stp	q24, q25, [sp, #-32]!
-	stp	q26, q27, [sp, #-32]!
-	stp	q28, q29, [sp, #-32]!
-	stp	q30, q31, [sp, #-32]!
-
-	mrs	x1, fpcr
-	mrs	x2, fpsr
-	stp	x1, x2, [sp, #-16]!
-
-	.seh_stackalloc 544
-
+	sub	sp,	sp,	0x390 	// sizeof(ARM64_NT_CONTEXT)
+	.seh_stackalloc 0x390
+	stp	x0,	x30,	[sp, #-16]!
+
+	// x16 gets clobbered after calling RtlCaptureContext.
+	// Both x16 and x17 are considered volatile registers, so we preserve bot=
h.
+	stp	x16,	x17,	[sp, #-16]!
+	.seh_save_regp_x x16, 16
 	.seh_endprologue
=20
-	ldr	x12, [x18, #8]			// get TLS pointer
-	ldr	x13, =3D_cygtls.saved_errno	// get offset to saved_errno
-	add	x13, x12, x13			// set x13 to &TLS.saved_errno
-	ldr	w19, [x13]			// preserve saved_errno in w19
-
-	ldr	x13, =3D_cygtls.start_offset	// get offset to beginning of TLS block
-	add	x0, x12, x13			// store offset as first arg to method
-	bl	_ZN7_cygtls19call_signal_handlerEv	// call handler
-	ldr	x12, [x18, #8]			// restore clobbered TLS pointer
+	// RtlCaptureContext captures the current CPU state
+	// at the time of calling it, letting us restore it
+	// later with adjusted registers in sigdelayed_impl.
+	add	x0,	sp,	32		// set x0 to the stack space allocated for the context
+	bl RtlCaptureContext
=20
-	mov	w11, #1				// set w11 to 1 (locked)
-	ldr	x13, =3D_cygtls.stacklock		// get offset to stacklock
-	add	x13, x12, x13			// set x13 to &TLS.stacklock
-1:
-	ldaxr	w14, [x13]			// read lock value with acquire
-	cbnz	w14, 2f				// wait if already locked
-	stxr	w14, w11, [x13]			// attempt to store 1
-	cbnz	w14, 1b				// retry if locking not succeeded
-	b	3f				// continue to critical region
-2:
-	yield					// hint to CPU (spin-wait)
-	b	1b				// try again
+	add	x0,	sp,	32		// reset x0 in case it got clobbered
+	bl sigdelayed_init
=20
-3:
-	tst	w19, w19			// was saved_errno < 0
-	blt	4f				// if yes, ignore it
-	ldr	x13, =3D_cygtls.errno_addr	// get offset to errno_addr
-	add	x13, x12, x13			// set x13 to &TLS.errno_addr
-	ldr	x11, [x13]			// set x11 to TLS->errno_addr
-	str	w19, [x11]			// store saved_errno to errno_addr
-
-4:
-	ldr	x13, =3D_cygtls.stackptr		// get offset to stackptr
-	add	x13, x12, x13			// set x13 to &TLS.stackptr
-5:
-	ldxr	x11, [x13]			// get aux stack address
-	sub	x11, x11, #8			// decrement aux stack address
-	stxr	w14, x11, [x13]			// attempt to store decremented value
-	cbnz	w14, 5b				// retry if not succeeded
-
-6:
-	ldxr	x30, [x11]			// get return address from signal stack
-	stxr	w14, xzr, [x11]			// attempt to clear return address
-	cbnz	w14, 6b				// retry if not succeeded
-
-	ldr	x13, =3D_cygtls.incyg		// get offset to incyg
-	add	x13, x12, x13			// set x13 to &TLS.incyg
-	str	wzr, [x13]			// set TLS.incyg to 0 (not in cygwin)
-	ldr	x13, =3D_cygtls.stacklock		// get offset to stacklock
-	add	x13, x12, x13			// set x13 to &TLS.stacklock
-	stlr	wzr, [x13]			// release lock
-
-	.seh_startepilogue
-
-	.seh_stackalloc 544
-
-	ldp	x1, x2, [sp], #16
-	msr	fpcr, x1
-	msr	fpsr, x2
-
-	ldp	q30, q31, [sp], #32
-	ldp	q28, q29, [sp], #32
-	ldp	q26, q27, [sp], #32
-	ldp	q24, q25, [sp], #32
-	ldp	q22, q23, [sp], #32
-	ldp	q20, q21, [sp], #32
-	ldp	q18, q19, [sp], #32
-	ldp	q16, q17, [sp], #32
-	ldp	q14, q15, [sp], #32
-	ldp	q12, q13, [sp], #32
-	ldp	q10, q11, [sp], #32
-	ldp	q8,  q9,  [sp], #32
-	ldp	q6,  q7,  [sp], #32
-	ldp	q4,  q5,  [sp], #32
-	ldp	q2,  q3,  [sp], #32
-	ldp	q0,  q1,  [sp], #32
-
-	ldr	x1, [sp], #16
-	mov	sp, x1
-
-	ldp	x28, x29, [sp], #16
-	.seh_save_regp_x x28, 16
-	ldp	x26, x27, [sp], #16
-	.seh_save_regp_x x26, 16
-	ldp	x24, x25, [sp], #16
-	.seh_save_regp_x x24, 16
-	ldp	x22, x23, [sp], #16
-	.seh_save_regp_x x22, 16
-	ldp	x20, x21, [sp], #16
-	.seh_save_regp_x x20, 16
-	ldp	x18, x19, [sp], #16
-	.seh_save_reg x19, 152
-	.seh_stackalloc 160
-	ldp	x16, x17, [sp], #16
-	ldp	x14, x15, [sp], #16
-	ldp	x12, x13, [sp], #16
-	ldp	x10, x11, [sp], #16
-	ldp	x8,  x9,  [sp], #16
-	ldp	x6,  x7,  [sp], #16
-	ldp	x4,  x5,  [sp], #16
-	ldp	x2,  x3,  [sp], #16
-	ldp	x0,  x1,  [sp], #16
-
-	.seh_endepilogue
-	ret
+	// unreachable
 	.seh_endproc
 _sigdelayed_end:
 	.global _sigdelayed_end