[PATCH] Cygwin: Add AArch64 support for signal handling and ucontext routines
Aswin Kalies Ramkumar Mangayarkarasi <[email protected]> Mon, 1 Jun 2026 11:36:25 +0000
| Newsgroups | gmane.os.cygwin.patches |
|---|---|
| Message-ID | <PN3P287MB1320988B69BF5F0D12079191F7152@PN3P287MB1320.INDP287.PROD.OUTLOOK.COM> |
Hi Corinna,
This patch adds AArch64 support for signal handling and ucontext routines (call_signal_handler, setcontext, __cont_link_context, makecontext) in exceptions.cc, enabling Cygwin on ARM64 Windows.
Kindly review the patch.
Thanks and Regards,
Aswin Kalies
Inline patch
---
winsup/cygwin/exceptions.cc | 309 +++++++++++++++++++++++++++++++-----
1 file changed, 267 insertions(+), 42 deletions(-)
diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc
index 1e129b319..a2212cabd 100644
--- a/winsup/cygwin/exceptions.cc
+++ b/winsup/cygwin/exceptions.cc
@@ -1892,7 +1892,7 @@ _cygtls::call_signal_handler ()
/* In assembler: Save regs on new stack, move to alternate stack,
call thisfunc, revert stack regs. */
-#ifdef __x86_64__
+#if defined(__x86_64__)
/* Clobbered regs: rcx, rdx, r8, r9, r10, r11, rbp, rsp */
__asm__ ("\n\
movq %[NEW_SP], %%rax # Load alt stack into rax \n\
@@ -1930,6 +1930,56 @@ _cygtls::call_signal_handler ()
[FUNC] "o" (thisfunc),
[WRAPPER] "o" (altstack_wrapper)
: "memory");
+#elif defined(__aarch64__)
+ __asm__ ("\n\
+ mov x9, %[NEW_SP] // Load alt stack into x9 \n\
+ sub x9, x9, #0x60 // Make room on alt stack \n\
+ // for clobbered regs \n\
+ str x0, [x9, #0x00] // Save clobbered regs \n\
+ str x1, [x9, #0x08] \n\
+ str x2, [x9, #0x10] \n\
+ str x3, [x9, #0x18] \n\
+ str x4, [x9, #0x20] \n\
+ str x5, [x9, #0x28] \n\
+ str x6, [x9, #0x30] \n\
+ str x7, [x9, #0x38] \n\
+ str fp, [x9, #0x40] \n\
+ mov x10, sp // Copy sp into x10 for saving \n\
+ str x10, [x9, #0x48] \n\
+ str x30, [x9, #0x50] // Save link register \n\
+ mov x0, %[SIG] // thissig to 1st arg reg \n\
+ mov x1, %[SI] // &thissi to 2nd arg reg \n\
+ mov x2, %[CTX] // thiscontext to 3rd arg reg \n\
+ mov x3, %[FUNC] // thisfunc to x3 \n\
+ mov x4, %[WRAPPER] // wrapper address to x4 \n\
+ mov sp, x9 // Move alt stack into sp \n\
+ blr x4 // Call wrapper \n\
+ mov x9, sp // Restore clobbered regs \n\
+ ldr x30, [x9, #0x50] // Restore link register \n\
+ ldr x10, [x9, #0x48] \n\
+ ldr fp, [x9, #0x40] \n\
+ ldr x7, [x9, #0x38] \n\
+ ldr x6, [x9, #0x30] \n\
+ ldr x5, [x9, #0x28] \n\
+ ldr x4, [x9, #0x20] \n\
+ ldr x3, [x9, #0x18] \n\
+ ldr x2, [x9, #0x10] \n\
+ ldr x1, [x9, #0x08] \n\
+ ldr x0, [x9, #0x00] \n\
+ mov sp, x10 // Restore stack pointer \n"
+ : : [NEW_SP] "r" (new_sp),
+ [SIG] "r" (thissig),
+ [SI] "r" (&thissi),
+ [CTX] "r" (thiscontext),
+ [FUNC] "r" (thisfunc),
+ [WRAPPER] "r" (altstack_wrapper)
+ : "memory", "cc",
+ "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+ "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+ "x16", "x17", "x29", "x30",
+ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
+ "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
+ "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31");
#else
#error unimplemented for this target
#endif
@@ -2009,10 +2059,78 @@ setcontext (const ucontext_t *ucp)
{
PCONTEXT ctx = (PCONTEXT) &ucp->uc_mcontext;
set_signal_mask (_my_tls.sigmask, ucp->uc_sigmask);
+#if defined(__aarch64__)
+ /* On ARM64 Windows, RtlRestoreContext may fail (STATUS_ILLEGAL_INSTRUCTION)
+ when restoring a synthetic CONTEXT that was constructed by makecontext
+ rather than captured by RtlCaptureContext / GetThreadContext. This is
+ because RtlRestoreContext may perform validation or use internal
+ mechanisms (e.g. stack unwinding hints) that don't work with synthetic
+ contexts pointing to arbitrary PC addresses and non-thread stacks.
+
+ Instead, we manually restore all registers and branch to the saved PC.
+ This mirrors what glibc/musl do in their setcontext implementations
+ for aarch64-linux. */
+ register PCONTEXT base __asm__ ("x16") = ctx;
+ __asm__ __volatile__ ("\n\
+ add x17, x16, #272 \n\
+ ldp q0, q1, [x17, #0] \n\
+ ldp q2, q3, [x17, #32] \n\
+ ldp q4, q5, [x17, #64] \n\
+ ldp q6, q7, [x17, #96] \n\
+ ldp q8, q9, [x17, #128] \n\
+ ldp q10, q11, [x17, #160] \n\
+ ldp q12, q13, [x17, #192] \n\
+ ldp q14, q15, [x17, #224] \n\
+ ldp q16, q17, [x17, #256] \n\
+ ldp q18, q19, [x17, #288] \n\
+ ldp q20, q21, [x17, #320] \n\
+ ldp q22, q23, [x17, #352] \n\
+ ldp q24, q25, [x17, #384] \n\
+ ldp q26, q27, [x17, #416] \n\
+ ldp q28, q29, [x17, #448] \n\
+ ldp q30, q31, [x17, #480] \n\
+ /* Restore FPCR and FPSR */ \n\
+ ldr w17, [x16, #784] \n\
+ msr fpcr, x17 \n\
+ ldr w17, [x16, #788] \n\
+ msr fpsr, x17 \n\
+ /* Load PC into x17 (branch target, offset 264) */ \n\
+ ldr x17, [x16, #264] \n\
+ /* Restore callee-saved GPRs x18..x28, fp, lr */ \n\
+ ldp x18, x19, [x16, #152] \n\
+ ldp x20, x21, [x16, #168] \n\
+ ldp x22, x23, [x16, #184] \n\
+ ldp x24, x25, [x16, #200] \n\
+ ldp x26, x27, [x16, #216] \n\
+ ldp x28, x29, [x16, #232] \n\
+ ldr x30, [x16, #248] \n\
+ /* Restore caller-saved GPRs x2..x15 */ \n\
+ ldp x2, x3, [x16, #24] \n\
+ ldp x4, x5, [x16, #40] \n\
+ ldp x6, x7, [x16, #56] \n\
+ ldp x8, x9, [x16, #72] \n\
+ ldp x10, x11, [x16, #88] \n\
+ ldp x12, x13, [x16, #104] \n\
+ ldp x14, x15, [x16, #120] \n\
+ /* Restore x0, x1 */ \n\
+ ldp x0, x1, [x16, #8] \n\
+ /* Set SP from context (last use of x16 as base) */ \n\
+ ldr x16, [x16, #256] \n\
+ mov sp, x16 \n\
+ /* Branch to saved PC */ \n\
+ br x17 \n\
+"
+ : /* no outputs (noreturn) */
+ : "r" (base)
+ : "memory"
+ );
+ __builtin_unreachable ();
+#else
RtlRestoreContext (ctx, NULL);
/* If we got here, something was wrong. */
set_errno (EINVAL);
return -1;
+#endif
}
extern "C" int
@@ -2049,7 +2167,7 @@ swapcontext (ucontext_t *oucp, const ucontext_t *ucp)
/* Trampoline function to set the context to uc_link. The pointer to the
address of uc_link is stored in a callee-saved register, referenced by
_MC_uclinkReg from the C code. If uc_link is NULL, call exit. */
-#ifdef __x86_64__
+#if defined(__x86_64__)
/* _MC_uclinkReg == %rbx */
__asm__ (" \n\
.global __cont_link_context \n\
@@ -2070,7 +2188,21 @@ __cont_link_context: \n\
nop \n\
.seh_endproc \n\
");
-
+#elif defined(__aarch64__)
+__asm__ (" \n\
+ .global __cont_link_context \n\
+ .seh_proc __cont_link_context \n\
+__cont_link_context: \n\
+ .seh_endprologue \n\
+ ldr x0, [x19] \n\
+ and sp, x19, #~0xf \n\
+ cbnz x0, 1f \n\
+ mov w0, #0xff \n\
+ b cygwin_exit \n\
+1: \n\
+ b setcontext \n\
+ .seh_endproc \n"
+ );
#else
#error unimplemented for this target
#endif
@@ -2078,11 +2210,19 @@ __cont_link_context: \n\
/* makecontext is modelled after GLibc's makecontext. The stack from uc_stack
is prepared so that it starts with a pointer to the linked context uc_link,
followed by the arguments to func, and finally at the bottom the "return"
- address set to __cont_link_context. In the ucp context, rbx/ebx is set to
- point to the stack address where the pointer to uc_link is stored. The
- requirement to make this work is that rbx/ebx are callee-saved registers
- per the ABI. If any function is called which doesn't follow the ABI
- conventions, e.g. assembler code, this method will break. But that's ok. */
+ address set to __cont_link_context.
+
+ x86_64: In the ucp context, rbx is set to point to the stack address where
+ the pointer to uc_link is stored. The requirement to make this work is that
+ rbx is a callee-saved register per the ABI.
+
+ ARM64: In the ucp context, x19 is set to point to the stack address where
+ the pointer to uc_link is stored. The requirement is that x19 is a
+ callee-saved register per the ARM64 ABI.
+
+ If any function is called which doesn't follow the ABI conventions, e.g.
+ assembler code, this method will break. But that's ok. */
+
extern "C" void
makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...)
{
@@ -2090,65 +2230,150 @@ makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...)
uintptr_t *sp;
va_list ap;
+#if defined(__x86_64__)
+ /* x86_64: Arguments beyond the first 4 go on the stack.
+ However, we allocate shadow space for all args including register args. */
+ int stack_args = argc;
+
+#elif defined(__aarch64__)
+ /* ARM64: Arguments beyond the first 8 go on the stack.
+ We only allocate stack space for args beyond registers. */
+ int stack_args = (argc > 8) ? (argc - 8) : 0;
+
+#else
+#error unimplemented for this target
+#endif
+
/* Initialize sp to the top of the stack. */
sp = (uintptr_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size);
- /* Subtract slots required for arguments and the pointer to uc_link. */
- sp -= (argc + 1);
- /* Align. */
- sp = (uintptr_t *) ((uintptr_t) sp & ~0xf);
- /* Subtract one slot for setting the return address. */
+
+#if defined(__x86_64__)
+ /* x86_64: Subtract slots for all arguments + uc_link pointer
+ and return address. */
+ sp -= (stack_args + 1); /* argc + 1 for uc_link */
+ /* Align to 16 bytes. */
+ sp = (uintptr_t *) ((uintptr_t) sp & ~0xfUL);
+ /* Subtract one more slot for the return address. */
--sp;
- /* Set return address to the trampolin function __cont_link_context. */
+ /* Set return address to the trampoline function __cont_link_context. */
sp[0] = (uintptr_t) __cont_link_context;
- /* Fetch arguments and store them on the stack.
- x86_64:
+#elif defined(__aarch64__)
+ /* ARM64: Subtract slots for stack arguments + uc_link pointer. */
+ sp -= (stack_args + 1); /* stack_args + 1 for uc_link */
+ /* ARM64 requires 16-byte alignment at public interfaces. */
+ sp = (uintptr_t *) ((uintptr_t) sp & ~0xfUL);
- - Store first four args in the AMD64 ABI arg registers.
+#endif
+ /* Fetch arguments and store them.
+ x86_64:
+ - Store first four args in the AMD64 ABI arg registers (rcx, rdx, r8, r9).
- Note that the stack is not short by these four register args. The
reason is the shadow space for these regs required by the AMD64 ABI.
-
- The definition of makecontext only allows for "int" sized arguments to
func, 32 bit, likely for historical reasons. However, the argument
slots on x86_64 are 64 bit anyway, so we can fetch and store the args
as 64 bit values, and func can request 64 bit args without violating
the definition. This potentially allows porting 32 bit applications
- providing pointer values to func without additional porting effort. */
+ providing pointer values to func without additional porting effort.
+
+ ARM64:
+ - Store first eight args in ARM64 ABI arg registers (x0-x7).
+ - Arguments beyond 8 go on the stack.
+ - Similar to x86_64, we store as uintptr_t for pointer compatibility. */
+
va_start (ap, argc);
for (int i = 0; i < argc; ++i)
-#ifdef __x86_64__
- switch (i)
- {
- case 0:
- ucp->uc_mcontext.rcx = va_arg (ap, uintptr_t);
- break;
- case 1:
- ucp->uc_mcontext.rdx = va_arg (ap, uintptr_t);
- break;
- case 2:
- ucp->uc_mcontext.r8 = va_arg (ap, uintptr_t);
- break;
- case 3:
- ucp->uc_mcontext.r9 = va_arg (ap, uintptr_t);
- break;
- default:
- sp[i + 1] = va_arg (ap, uintptr_t);
- break;
- }
-#else
-#error unimplemented for this target
+ {
+#if defined(__x86_64__)
+ switch (i)
+ {
+ case 0:
+ ucp->uc_mcontext.rcx = va_arg (ap, uintptr_t);
+ break;
+ case 1:
+ ucp->uc_mcontext.rdx = va_arg (ap, uintptr_t);
+ break;
+ case 2:
+ ucp->uc_mcontext.r8 = va_arg (ap, uintptr_t);
+ break;
+ case 3:
+ ucp->uc_mcontext.r9 = va_arg (ap, uintptr_t);
+ break;
+ default:
+ /* Stack arguments start at sp[i + 1] because sp[0] is return address */
+ sp[i + 1] = va_arg (ap, uintptr_t);
+ break;
+ }
+
+#elif defined(__aarch64__)
+ switch (i)
+ {
+ case 0:
+ ucp->uc_mcontext.x0 = va_arg (ap, uintptr_t);
+ break;
+ case 1:
+ ucp->uc_mcontext.x1 = va_arg (ap, uintptr_t);
+ break;
+ case 2:
+ ucp->uc_mcontext.x2 = va_arg (ap, uintptr_t);
+ break;
+ case 3:
+ ucp->uc_mcontext.x3 = va_arg (ap, uintptr_t);
+ break;
+ case 4:
+ ucp->uc_mcontext.x4 = va_arg (ap, uintptr_t);
+ break;
+ case 5:
+ ucp->uc_mcontext.x5 = va_arg (ap, uintptr_t);
+ break;
+ case 6:
+ ucp->uc_mcontext.x6 = va_arg (ap, uintptr_t);
+ break;
+ case 7:
+ ucp->uc_mcontext.x7 = va_arg (ap, uintptr_t);
+ break;
+ default:
+ /* Stack arguments beyond the first 8 registers. */
+ sp[i - 8] = va_arg (ap, uintptr_t);
+ break;
+ }
#endif
+ }
va_end (ap);
- /* Store pointer to uc_link at the top of the stack. */
+
+#if defined(__x86_64__)
+ /* Store pointer to uc_link at sp[argc + 1], after return address
+ and args. */
sp[argc + 1] = (uintptr_t) ucp->uc_link;
+
+#elif defined(__aarch64__)
+ /* Store pointer to uc_link at the top of our allocated area. */
+ sp[stack_args] = (uintptr_t) ucp->uc_link;
+
+#endif
+
/* Last but not least set the register in the context at ucp so that a
subsequent setcontext or swapcontext picks up the right values:
- Set instruction pointer to the target function.
- Set stack pointer to the just computed stack pointer value.
- Set Cygwin-specific uclink register to the address of the pointer
- to uc_link. */
+ to uc_link.
+
+ x86_64: uclink register is rbx (callee-saved)
+ ARM64: uclink register is x19 (callee-saved) */
+
ucp->uc_mcontext._MC_instPtr = (uint64_t) func;
ucp->uc_mcontext._MC_stackPtr = (uint64_t) sp;
+
+#if defined(__x86_64__)
ucp->uc_mcontext._MC_uclinkReg = (uint64_t) (sp + argc + 1);
+
+#elif defined(__aarch64__)
+ /* Set LR to __cont_link_context for ARM64 (used as return address). */
+ ucp->uc_mcontext.lr = (uint64_t) __cont_link_context;
+ ucp->uc_mcontext._MC_uclinkReg = (uint64_t) (sp + stack_args);
+
+#endif
}
--
2.50.1.windows.1
Cygwin-Add-AArch64-support-for-signal-handling-and-u.patch
(application/octet-stream, 14.9 KB)
From 177874c775d257076ca204bca8ebecbb64d7cfdf Mon Sep 17 00:00:00 2001 From: Aswin Kalies <[email protected]> Date: Mon, 1 Jun 2026 16:30:36 +0530 Subject: [PATCH] Cygwin: Add AArch64 support for signal handling and ucontext routines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add AArch64 support for signal handling and ucontext routines (call_signal_handler, setcontext, __cont_link_context, makecontext) in exceptions.cc, enabling Cygwin on ARM64 Windows. setcontext uses manual asm instead of RtlRestoreContext, which fails with synthetic CONTEXT structs from makecontext. #ifdef guards are also updated to #if defined() for consistency. Signed-off-by: Radek BartoĊ <[email protected]> Signed-off-by: Thirumalai Nagalingam <[email protected]> Signed-off-by: Aswin Kalies <[email protected]> --- winsup/cygwin/exceptions.cc | 309 +++++++++++++++++++++++++++++++----- 1 file changed, 267 insertions(+), 42 deletions(-) diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc index 1e129b319..a2212cabd 100644 --- a/winsup/cygwin/exceptions.cc +++ b/winsup/cygwin/exceptions.cc @@ -1892,7 +1892,7 @@ _cygtls::call_signal_handler () /* In assembler: Save regs on new stack, move to alternate stack, call thisfunc, revert stack regs. */ -#ifdef __x86_64__ +#if defined(__x86_64__) /* Clobbered regs: rcx, rdx, r8, r9, r10, r11, rbp, rsp */ __asm__ ("\n\ movq %[NEW_SP], %%rax # Load alt stack into rax \n\ @@ -1930,6 +1930,56 @@ _cygtls::call_signal_handler () [FUNC] "o" (thisfunc), [WRAPPER] "o" (altstack_wrapper) : "memory"); +#elif defined(__aarch64__) + __asm__ ("\n\ + mov x9, %[NEW_SP] // Load alt stack into x9 \n\ + sub x9, x9, #0x60 // Make room on alt stack \n\ + // for clobbered regs \n\ + str x0, [x9, #0x00] // Save clobbered regs \n\ + str x1, [x9, #0x08] \n\ + str x2, [x9, #0x10] \n\ + str x3, [x9, #0x18] \n\ + str x4, [x9, #0x20] \n\ + str x5, [x9, #0x28] \n\ + str x6, [x9, #0x30] \n\ + str x7, [x9, #0x38] \n\ + str fp, [x9, #0x40] \n\ + mov x10, sp // Copy sp into x10 for saving \n\ + str x10, [x9, #0x48] \n\ + str x30, [x9, #0x50] // Save link register \n\ + mov x0, %[SIG] // thissig to 1st arg reg \n\ + mov x1, %[SI] // &thissi to 2nd arg reg \n\ + mov x2, %[CTX] // thiscontext to 3rd arg reg \n\ + mov x3, %[FUNC] // thisfunc to x3 \n\ + mov x4, %[WRAPPER] // wrapper address to x4 \n\ + mov sp, x9 // Move alt stack into sp \n\ + blr x4 // Call wrapper \n\ + mov x9, sp // Restore clobbered regs \n\ + ldr x30, [x9, #0x50] // Restore link register \n\ + ldr x10, [x9, #0x48] \n\ + ldr fp, [x9, #0x40] \n\ + ldr x7, [x9, #0x38] \n\ + ldr x6, [x9, #0x30] \n\ + ldr x5, [x9, #0x28] \n\ + ldr x4, [x9, #0x20] \n\ + ldr x3, [x9, #0x18] \n\ + ldr x2, [x9, #0x10] \n\ + ldr x1, [x9, #0x08] \n\ + ldr x0, [x9, #0x00] \n\ + mov sp, x10 // Restore stack pointer \n" + : : [NEW_SP] "r" (new_sp), + [SIG] "r" (thissig), + [SI] "r" (&thissi), + [CTX] "r" (thiscontext), + [FUNC] "r" (thisfunc), + [WRAPPER] "r" (altstack_wrapper) + : "memory", "cc", + "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", + "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", + "x16", "x17", "x29", "x30", + "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", + "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", + "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31"); #else #error unimplemented for this target #endif @@ -2009,10 +2059,78 @@ setcontext (const ucontext_t *ucp) { PCONTEXT ctx = (PCONTEXT) &ucp->uc_mcontext; set_signal_mask (_my_tls.sigmask, ucp->uc_sigmask); +#if defined(__aarch64__) + /* On ARM64 Windows, RtlRestoreContext may fail (STATUS_ILLEGAL_INSTRUCTION) + when restoring a synthetic CONTEXT that was constructed by makecontext + rather than captured by RtlCaptureContext / GetThreadContext. This is + because RtlRestoreContext may perform validation or use internal + mechanisms (e.g. stack unwinding hints) that don't work with synthetic + contexts pointing to arbitrary PC addresses and non-thread stacks. + + Instead, we manually restore all registers and branch to the saved PC. + This mirrors what glibc/musl do in their setcontext implementations + for aarch64-linux. */ + register PCONTEXT base __asm__ ("x16") = ctx; + __asm__ __volatile__ ("\n\ + add x17, x16, #272 \n\ + ldp q0, q1, [x17, #0] \n\ + ldp q2, q3, [x17, #32] \n\ + ldp q4, q5, [x17, #64] \n\ + ldp q6, q7, [x17, #96] \n\ + ldp q8, q9, [x17, #128] \n\ + ldp q10, q11, [x17, #160] \n\ + ldp q12, q13, [x17, #192] \n\ + ldp q14, q15, [x17, #224] \n\ + ldp q16, q17, [x17, #256] \n\ + ldp q18, q19, [x17, #288] \n\ + ldp q20, q21, [x17, #320] \n\ + ldp q22, q23, [x17, #352] \n\ + ldp q24, q25, [x17, #384] \n\ + ldp q26, q27, [x17, #416] \n\ + ldp q28, q29, [x17, #448] \n\ + ldp q30, q31, [x17, #480] \n\ + /* Restore FPCR and FPSR */ \n\ + ldr w17, [x16, #784] \n\ + msr fpcr, x17 \n\ + ldr w17, [x16, #788] \n\ + msr fpsr, x17 \n\ + /* Load PC into x17 (branch target, offset 264) */ \n\ + ldr x17, [x16, #264] \n\ + /* Restore callee-saved GPRs x18..x28, fp, lr */ \n\ + ldp x18, x19, [x16, #152] \n\ + ldp x20, x21, [x16, #168] \n\ + ldp x22, x23, [x16, #184] \n\ + ldp x24, x25, [x16, #200] \n\ + ldp x26, x27, [x16, #216] \n\ + ldp x28, x29, [x16, #232] \n\ + ldr x30, [x16, #248] \n\ + /* Restore caller-saved GPRs x2..x15 */ \n\ + ldp x2, x3, [x16, #24] \n\ + ldp x4, x5, [x16, #40] \n\ + ldp x6, x7, [x16, #56] \n\ + ldp x8, x9, [x16, #72] \n\ + ldp x10, x11, [x16, #88] \n\ + ldp x12, x13, [x16, #104] \n\ + ldp x14, x15, [x16, #120] \n\ + /* Restore x0, x1 */ \n\ + ldp x0, x1, [x16, #8] \n\ + /* Set SP from context (last use of x16 as base) */ \n\ + ldr x16, [x16, #256] \n\ + mov sp, x16 \n\ + /* Branch to saved PC */ \n\ + br x17 \n\ +" + : /* no outputs (noreturn) */ + : "r" (base) + : "memory" + ); + __builtin_unreachable (); +#else RtlRestoreContext (ctx, NULL); /* If we got here, something was wrong. */ set_errno (EINVAL); return -1; +#endif } extern "C" int @@ -2049,7 +2167,7 @@ swapcontext (ucontext_t *oucp, const ucontext_t *ucp) /* Trampoline function to set the context to uc_link. The pointer to the address of uc_link is stored in a callee-saved register, referenced by _MC_uclinkReg from the C code. If uc_link is NULL, call exit. */ -#ifdef __x86_64__ +#if defined(__x86_64__) /* _MC_uclinkReg == %rbx */ __asm__ (" \n\ .global __cont_link_context \n\ @@ -2070,7 +2188,21 @@ __cont_link_context: \n\ nop \n\ .seh_endproc \n\ "); - +#elif defined(__aarch64__) +__asm__ (" \n\ + .global __cont_link_context \n\ + .seh_proc __cont_link_context \n\ +__cont_link_context: \n\ + .seh_endprologue \n\ + ldr x0, [x19] \n\ + and sp, x19, #~0xf \n\ + cbnz x0, 1f \n\ + mov w0, #0xff \n\ + b cygwin_exit \n\ +1: \n\ + b setcontext \n\ + .seh_endproc \n" + ); #else #error unimplemented for this target #endif @@ -2078,11 +2210,19 @@ __cont_link_context: \n\ /* makecontext is modelled after GLibc's makecontext. The stack from uc_stack is prepared so that it starts with a pointer to the linked context uc_link, followed by the arguments to func, and finally at the bottom the "return" - address set to __cont_link_context. In the ucp context, rbx/ebx is set to - point to the stack address where the pointer to uc_link is stored. The - requirement to make this work is that rbx/ebx are callee-saved registers - per the ABI. If any function is called which doesn't follow the ABI - conventions, e.g. assembler code, this method will break. But that's ok. */ + address set to __cont_link_context. + + x86_64: In the ucp context, rbx is set to point to the stack address where + the pointer to uc_link is stored. The requirement to make this work is that + rbx is a callee-saved register per the ABI. + + ARM64: In the ucp context, x19 is set to point to the stack address where + the pointer to uc_link is stored. The requirement is that x19 is a + callee-saved register per the ARM64 ABI. + + If any function is called which doesn't follow the ABI conventions, e.g. + assembler code, this method will break. But that's ok. */ + extern "C" void makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...) { @@ -2090,65 +2230,150 @@ makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...) uintptr_t *sp; va_list ap; +#if defined(__x86_64__) + /* x86_64: Arguments beyond the first 4 go on the stack. + However, we allocate shadow space for all args including register args. */ + int stack_args = argc; + +#elif defined(__aarch64__) + /* ARM64: Arguments beyond the first 8 go on the stack. + We only allocate stack space for args beyond registers. */ + int stack_args = (argc > 8) ? (argc - 8) : 0; + +#else +#error unimplemented for this target +#endif + /* Initialize sp to the top of the stack. */ sp = (uintptr_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); - /* Subtract slots required for arguments and the pointer to uc_link. */ - sp -= (argc + 1); - /* Align. */ - sp = (uintptr_t *) ((uintptr_t) sp & ~0xf); - /* Subtract one slot for setting the return address. */ + +#if defined(__x86_64__) + /* x86_64: Subtract slots for all arguments + uc_link pointer + and return address. */ + sp -= (stack_args + 1); /* argc + 1 for uc_link */ + /* Align to 16 bytes. */ + sp = (uintptr_t *) ((uintptr_t) sp & ~0xfUL); + /* Subtract one more slot for the return address. */ --sp; - /* Set return address to the trampolin function __cont_link_context. */ + /* Set return address to the trampoline function __cont_link_context. */ sp[0] = (uintptr_t) __cont_link_context; - /* Fetch arguments and store them on the stack. - x86_64: +#elif defined(__aarch64__) + /* ARM64: Subtract slots for stack arguments + uc_link pointer. */ + sp -= (stack_args + 1); /* stack_args + 1 for uc_link */ + /* ARM64 requires 16-byte alignment at public interfaces. */ + sp = (uintptr_t *) ((uintptr_t) sp & ~0xfUL); - - Store first four args in the AMD64 ABI arg registers. +#endif + /* Fetch arguments and store them. + x86_64: + - Store first four args in the AMD64 ABI arg registers (rcx, rdx, r8, r9). - Note that the stack is not short by these four register args. The reason is the shadow space for these regs required by the AMD64 ABI. - - The definition of makecontext only allows for "int" sized arguments to func, 32 bit, likely for historical reasons. However, the argument slots on x86_64 are 64 bit anyway, so we can fetch and store the args as 64 bit values, and func can request 64 bit args without violating the definition. This potentially allows porting 32 bit applications - providing pointer values to func without additional porting effort. */ + providing pointer values to func without additional porting effort. + + ARM64: + - Store first eight args in ARM64 ABI arg registers (x0-x7). + - Arguments beyond 8 go on the stack. + - Similar to x86_64, we store as uintptr_t for pointer compatibility. */ + va_start (ap, argc); for (int i = 0; i < argc; ++i) -#ifdef __x86_64__ - switch (i) - { - case 0: - ucp->uc_mcontext.rcx = va_arg (ap, uintptr_t); - break; - case 1: - ucp->uc_mcontext.rdx = va_arg (ap, uintptr_t); - break; - case 2: - ucp->uc_mcontext.r8 = va_arg (ap, uintptr_t); - break; - case 3: - ucp->uc_mcontext.r9 = va_arg (ap, uintptr_t); - break; - default: - sp[i + 1] = va_arg (ap, uintptr_t); - break; - } -#else -#error unimplemented for this target + { +#if defined(__x86_64__) + switch (i) + { + case 0: + ucp->uc_mcontext.rcx = va_arg (ap, uintptr_t); + break; + case 1: + ucp->uc_mcontext.rdx = va_arg (ap, uintptr_t); + break; + case 2: + ucp->uc_mcontext.r8 = va_arg (ap, uintptr_t); + break; + case 3: + ucp->uc_mcontext.r9 = va_arg (ap, uintptr_t); + break; + default: + /* Stack arguments start at sp[i + 1] because sp[0] is return address */ + sp[i + 1] = va_arg (ap, uintptr_t); + break; + } + +#elif defined(__aarch64__) + switch (i) + { + case 0: + ucp->uc_mcontext.x0 = va_arg (ap, uintptr_t); + break; + case 1: + ucp->uc_mcontext.x1 = va_arg (ap, uintptr_t); + break; + case 2: + ucp->uc_mcontext.x2 = va_arg (ap, uintptr_t); + break; + case 3: + ucp->uc_mcontext.x3 = va_arg (ap, uintptr_t); + break; + case 4: + ucp->uc_mcontext.x4 = va_arg (ap, uintptr_t); + break; + case 5: + ucp->uc_mcontext.x5 = va_arg (ap, uintptr_t); + break; + case 6: + ucp->uc_mcontext.x6 = va_arg (ap, uintptr_t); + break; + case 7: + ucp->uc_mcontext.x7 = va_arg (ap, uintptr_t); + break; + default: + /* Stack arguments beyond the first 8 registers. */ + sp[i - 8] = va_arg (ap, uintptr_t); + break; + } #endif + } va_end (ap); - /* Store pointer to uc_link at the top of the stack. */ + +#if defined(__x86_64__) + /* Store pointer to uc_link at sp[argc + 1], after return address + and args. */ sp[argc + 1] = (uintptr_t) ucp->uc_link; + +#elif defined(__aarch64__) + /* Store pointer to uc_link at the top of our allocated area. */ + sp[stack_args] = (uintptr_t) ucp->uc_link; + +#endif + /* Last but not least set the register in the context at ucp so that a subsequent setcontext or swapcontext picks up the right values: - Set instruction pointer to the target function. - Set stack pointer to the just computed stack pointer value. - Set Cygwin-specific uclink register to the address of the pointer - to uc_link. */ + to uc_link. + + x86_64: uclink register is rbx (callee-saved) + ARM64: uclink register is x19 (callee-saved) */ + ucp->uc_mcontext._MC_instPtr = (uint64_t) func; ucp->uc_mcontext._MC_stackPtr = (uint64_t) sp; + +#if defined(__x86_64__) ucp->uc_mcontext._MC_uclinkReg = (uint64_t) (sp + argc + 1); + +#elif defined(__aarch64__) + /* Set LR to __cont_link_context for ARM64 (used as return address). */ + ucp->uc_mcontext.lr = (uint64_t) __cont_link_context; + ucp->uc_mcontext._MC_uclinkReg = (uint64_t) (sp + stack_args); + +#endif } -- 2.50.1.windows.1