[PATCH v3] Cygwin: autoload: fix ws2_32 chained init on AArch64

Chandru Kumaresan <[email protected]> Fri, 26 Jun 2026 09:03:03 +0000
Newsgroups gmane.os.cygwin.patches
Message-ID <PN0P287MB0295EC86F97A259F0C99818F92EB2@PN0P287MB0295.INDP287.PROD.OUTLOOK.COM>
Hi Jon,

> I've applied this patch.

Thanks!!

>Just so I'm doing my due diligence, I'd like you confirm that you are
>submitting this under an open source license as per [1], and you and
>your colleagues at multicoreware are authorized to do so.

We have been contributing to Cygwin for about a year, all modifying
existing files rather than creating new ones. We will confirm and get
back to you shortly.


>* Looking at the git history, the comment on the problem the
>no_resolve_on_fork flag is working around has disappeared, but the
>functionality is still there.  Is it still needed?

The no_resolve_on_fork removal addresses your question about whether
that flag is still needed -- it was always 0 at every call site, so
the patch drops it entirely.

>* Since it's just data, it seems to me that the initializations of the
>various instances of struct dll_info could actually all be written in C.

The LoadLibrary elision question and the "write dll_info inits in C"
note are out of scope for this patch; happy to look at those separately.

Inline patch

---
 winsup/cygwin/autoload.cc | 84 +++++++++++++++++++++++++--------------
 1 file changed, 54 insertions(+), 30 deletions(-)

diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc
index 7054511b6..c425191b9 100644
--- a/winsup/cygwin/autoload.cc
+++ b/winsup/cygwin/autoload.cc
@@ -85,13 +85,13 @@ bool NO_COPY wsock_started;
    The macro WORD64 stands in for .quad/.xword, and .balign (which means
    "align to N bytes" on all targets, unlike .align) is used for
    alignment.  */
-#define LoadDLLprime(dllname, init_also, no_resolve_on_fork) __asm__ ("\n\
+#define LoadDLLprime(dllname, init_also) __asm__ ("\n\
 .ifndef " #dllname "_primed                              \n\
   .section   .data_cygwin_nocopy,\"w\"                   \n\
   .balign    8                                           \n\
 ." #dllname "_info:                                      \n\
   " WORD64 "   _std_dll_init                             \n\
-  " WORD64 "   " #no_resolve_on_fork "                   \n\
+  " WORD64 "   0                                         \n\
   .long      -1                                          \n\
   .balign    8                                           \n\
   " WORD64 "   " #init_also "                            \n\
@@ -108,12 +108,12 @@ bool NO_COPY wsock_started;
 #define LoadDLLfuncEx(name, dllname, notimp) \
   LoadDLLfuncEx2(name, dllname, notimp, 0)
 #define LoadDLLfuncEx2(name, dllname, notimp, err) \
-  LoadDLLfuncEx3(name, dllname, notimp, err, 0)
+  LoadDLLfuncEx3(name, dllname, notimp, err)

 /* Main DLL setup stuff. */
 #if defined(__x86_64__)
-#define LoadDLLfuncEx3(name, dllname, notimp, err, no_resolve_on_fork) \
-  LoadDLLprime (dllname, dll_func_load, no_resolve_on_fork) \
+#define LoadDLLfuncEx3(name, dllname, notimp, err) \
+  LoadDLLprime (dllname, dll_func_load) \
   __asm__ ("           \n\
   .section ." #dllname "_autoload_text,\"wx\"  \n\
   .global  " #name "       \n\
@@ -138,8 +138,8 @@ _win32_" #name ":         \n\
   .text              \n\
 ");
 #elif defined(__aarch64__)
-#define LoadDLLfuncEx3(name, dllname, notimp, err, no_resolve_on_fork) \
-  LoadDLLprime (dllname, dll_func_load, no_resolve_on_fork) \
+#define LoadDLLfuncEx3(name, dllname, notimp, err) \
+  LoadDLLprime (dllname, dll_func_load) \
   __asm__ ( "\n\
   .section   ." #dllname "_autoload_text,\"wx\"          \n\
   .global    " #name "                                   \n\
@@ -302,6 +302,10 @@ dll_func_load:                                           \n\
   .global    dll_chain                                   \n\
 dll_chain:                                               \n\
   stp        x0, xzr, [sp, #-16]! // x0 = func_info* (= ret.high); push for dll_func_load\n\
+  mov        x30, x0          // also pass func_info in x30: a chained INIT_WRAPPER\n\
+                              // (e.g. _wsock_init) reads its arg from x30, but is\n\
+                              // reached here via 'br' which would otherwise leave\n\
+                              // x30 stale.  dll_func_load ignores x30 (reads [sp]).\n\
   br         x1                   // x1 = dll->init (= ret.low); tail-call resolver\n\
 ");
 #else
@@ -438,7 +442,7 @@ std_dll_init (struct func_info *func)
  yield ();
       }
     while (InterlockedIncrement (&dll->here));
-  else if ((uintptr_t) dll->handle <= 1)
+  else if (!dll->handle)
     {
       fenv_t fpuenv;
       fegetenv (&fpuenv);
@@ -461,7 +465,7 @@ std_dll_init (struct func_info *func)
    if (i < RETRY_COUNT)
      yield ();
  }
-      if ((uintptr_t) dll->handle <= 1)
+      if (!dll->handle)
  {
    if ((func->decoration & 1))
      dll->handle = INVALID_HANDLE_VALUE;
@@ -481,9 +485,29 @@ std_dll_init (struct func_info *func)

 /* Initialization function for winsock stuff. */

-#if defined(__x86_64__) || defined(__aarch64__)
+#if defined(__x86_64__)
 /* See above comment preceeding std_dll_init. */
 INIT_WRAPPER (wsock_init)
+#elif defined(__aarch64__)
+__asm__ ( "\n\
+  .text                                                  \n\
+  .p2align 2                                             \n\
+  .seh_proc _wsock_init                                  \n\
+_wsock_init:                                             \n\
+  stp        x29, x30, [sp, #-16]!  // save fp/lr, open 16-byte frame\n\
+  .seh_save_fplr_x 16                                    \n\
+  .seh_endprologue                                       \n\
+  mov        x0, x30           // x0 = func_info  (the wsock_init() argument)\n\
+  bl         wsock_init        // run WSAStartup; returns x0=func_info, x1=dll_func_load\n\
+  ldp        x29, xzr, [sp], #16  // restore fp, discard saved lr, close frame\n\
+  add        sp, sp, #16       // drop the stranded dll_chain frame so the\n\
+                               // downstream dll_func_load sees exactly one\n\
+                               // dll_chain frame above the trampoline frame\n\
+  adrp       x30, dll_chain    // x30 = &dll_chain so the 'ret' below tail-chains there\n\
+  add        x30, x30, #:lo12:dll_chain // -> dll_chain, which tail-calls x1 (dll_func_load)\n\
+  ret                                                   \n\
+  .seh_endproc                                          \n\
+");
 #else
 #error unimplemented for this target
 #endif
@@ -534,7 +558,7 @@ wsock_init (struct func_info *func)
   return ret.ll;
 }

-LoadDLLprime (ws2_32, _wsock_init, 0)
+LoadDLLprime (ws2_32, _wsock_init)

 LoadDLLfunc (CheckTokenMembership, advapi32)
 LoadDLLfunc (CreateProcessAsUserW, advapi32)
@@ -711,25 +735,25 @@ LoadDLLfuncEx2 (CreateProfile, userenv, 1, 1)
 LoadDLLfunc (DestroyEnvironmentBlock, userenv)
 LoadDLLfunc (LoadUserProfileW, userenv)

-LoadDLLfuncEx3 (waveInAddBuffer, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInClose, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInGetNumDevs, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInOpen, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInPrepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInReset, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInStart, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInUnprepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutClose, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutGetNumDevs, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutGetVolume, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutOpen, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutPrepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutReset, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutSetVolume, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutUnprepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutWrite, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutMessage, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutGetDevCapsA, winmm, 1, 0, 1)
+LoadDLLfuncEx3 (waveInAddBuffer, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInClose, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInGetNumDevs, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInOpen, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInPrepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInReset, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInStart, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInUnprepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutClose, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutGetNumDevs, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutGetVolume, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutOpen, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutPrepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutReset, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutSetVolume, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutUnprepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutWrite, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutMessage, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutGetDevCapsA, winmm, 1, 0)

 LoadDLLfunc (accept, ws2_32)
 LoadDLLfunc (bind, ws2_32)
--
2.49.0.windows.1
Cygwin-autoload-fix-ws2_32-chained-init-on-AArch64.patch (application/octet-stream, 9.1 KB)
From ecad9c50ab7b59ee553ed8c851f5a98256a6bdea Mon Sep 17 00:00:00 2001
From: chandru-mcw <[email protected]>
Date: Fri, 26 Jun 2026 11:21:02 +0530
Subject: [PATCH v3] Cygwin: autoload: fix ws2_32 chained init on AArch64
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

On AArch64, the autoload INIT_WRAPPER passes func_info* via x30 ("mov
x0, x30").  This works for the first init stage (std_dll_init), reached
by the trampoline's "blr".  It breaks for a chained second init, because
dll_chain reaches it via "br x1" without updating x30.

ws2_32 is the only DLL with such a chain (std_dll_init -> wsock_init ->
dll_func_load).  wsock_init received garbage in x0, so WSAStartup was
never called; every ws2_32 call failed with WSANOTINITIALISED / EFAULT.

dll_chain also runs twice for ws2_32, pushing a 16-byte frame each time.
wsock_init consumes its arg from x30, not the stack, so the first frame
strands.  dll_func_load then restores caller-save registers from a
shifted offset, corrupting the first call's arguments.

Fix: copy func_info into x30 in dll_chain before the tail-branch, and
give AArch64 a dedicated _wsock_init wrapper that drops the stranded
frame ("add sp, sp, #16") before chaining to dll_func_load.

Also remove the no_resolve_on_fork parameter from LoadDLLprime /
LoadDLLfuncEx3 (was always 0) and fix the std_dll_init handle sentinel
from "(uintptr_t)handle <= 1" to "!handle".

Signed-off-by: Radek BartoĊˆ <[email protected]>
Signed-off-by: Thirumalai Nagalingam <[email protected]>
Signed-off-by: Chandru Kumaresan <[email protected]>
---
 winsup/cygwin/autoload.cc | 84 +++++++++++++++++++++++++--------------
 1 file changed, 54 insertions(+), 30 deletions(-)

diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc
index 7054511b6..c425191b9 100644
--- a/winsup/cygwin/autoload.cc
+++ b/winsup/cygwin/autoload.cc
@@ -85,13 +85,13 @@ bool NO_COPY wsock_started;
    The macro WORD64 stands in for .quad/.xword, and .balign (which means
    "align to N bytes" on all targets, unlike .align) is used for
    alignment.  */
-#define LoadDLLprime(dllname, init_also, no_resolve_on_fork) __asm__ ("\n\
+#define LoadDLLprime(dllname, init_also) __asm__ ("\n\
 .ifndef " #dllname "_primed                              \n\
   .section   .data_cygwin_nocopy,\"w\"                   \n\
   .balign    8                                           \n\
 ." #dllname "_info:                                      \n\
   " WORD64 "   _std_dll_init                             \n\
-  " WORD64 "   " #no_resolve_on_fork "                   \n\
+  " WORD64 "   0                                         \n\
   .long      -1                                          \n\
   .balign    8                                           \n\
   " WORD64 "   " #init_also "                            \n\
@@ -108,12 +108,12 @@ bool NO_COPY wsock_started;
 #define LoadDLLfuncEx(name, dllname, notimp) \
   LoadDLLfuncEx2(name, dllname, notimp, 0)
 #define LoadDLLfuncEx2(name, dllname, notimp, err) \
-  LoadDLLfuncEx3(name, dllname, notimp, err, 0)
+  LoadDLLfuncEx3(name, dllname, notimp, err)
 
 /* Main DLL setup stuff. */
 #if defined(__x86_64__)
-#define LoadDLLfuncEx3(name, dllname, notimp, err, no_resolve_on_fork) \
-  LoadDLLprime (dllname, dll_func_load, no_resolve_on_fork) \
+#define LoadDLLfuncEx3(name, dllname, notimp, err) \
+  LoadDLLprime (dllname, dll_func_load) \
   __asm__ ("						\n\
   .section	." #dllname "_autoload_text,\"wx\"	\n\
   .global	" #name "				\n\
@@ -138,8 +138,8 @@ _win32_" #name ":					\n\
   .text							\n\
 ");
 #elif defined(__aarch64__)
-#define LoadDLLfuncEx3(name, dllname, notimp, err, no_resolve_on_fork) \
-  LoadDLLprime (dllname, dll_func_load, no_resolve_on_fork) \
+#define LoadDLLfuncEx3(name, dllname, notimp, err) \
+  LoadDLLprime (dllname, dll_func_load) \
   __asm__ ( "\n\
   .section   ." #dllname "_autoload_text,\"wx\"          \n\
   .global    " #name "                                   \n\
@@ -302,6 +302,10 @@ dll_func_load:                                           \n\
   .global    dll_chain                                   \n\
 dll_chain:                                               \n\
   stp        x0, xzr, [sp, #-16]! // x0 = func_info* (= ret.high); push for dll_func_load\n\
+  mov        x30, x0          // also pass func_info in x30: a chained INIT_WRAPPER\n\
+                              // (e.g. _wsock_init) reads its arg from x30, but is\n\
+                              // reached here via 'br' which would otherwise leave\n\
+                              // x30 stale.  dll_func_load ignores x30 (reads [sp]).\n\
   br         x1                   // x1 = dll->init (= ret.low); tail-call resolver\n\
 ");
 #else
@@ -438,7 +442,7 @@ std_dll_init (struct func_info *func)
 	yield ();
       }
     while (InterlockedIncrement (&dll->here));
-  else if ((uintptr_t) dll->handle <= 1)
+  else if (!dll->handle)
     {
       fenv_t fpuenv;
       fegetenv (&fpuenv);
@@ -461,7 +465,7 @@ std_dll_init (struct func_info *func)
 	  if (i < RETRY_COUNT)
 	    yield ();
 	}
-      if ((uintptr_t) dll->handle <= 1)
+      if (!dll->handle)
 	{
 	  if ((func->decoration & 1))
 	    dll->handle = INVALID_HANDLE_VALUE;
@@ -481,9 +485,29 @@ std_dll_init (struct func_info *func)
 
 /* Initialization function for winsock stuff. */
 
-#if defined(__x86_64__) || defined(__aarch64__)
+#if defined(__x86_64__)
 /* See above comment preceeding std_dll_init. */
 INIT_WRAPPER (wsock_init)
+#elif defined(__aarch64__)
+__asm__ ( "\n\
+  .text                                                  \n\
+  .p2align 2                                             \n\
+  .seh_proc _wsock_init                                  \n\
+_wsock_init:                                             \n\
+  stp        x29, x30, [sp, #-16]!  // save fp/lr, open 16-byte frame\n\
+  .seh_save_fplr_x 16                                    \n\
+  .seh_endprologue                                       \n\
+  mov        x0, x30           // x0 = func_info  (the wsock_init() argument)\n\
+  bl         wsock_init        // run WSAStartup; returns x0=func_info, x1=dll_func_load\n\
+  ldp        x29, xzr, [sp], #16  // restore fp, discard saved lr, close frame\n\
+  add        sp, sp, #16       // drop the stranded dll_chain frame so the\n\
+                               // downstream dll_func_load sees exactly one\n\
+                               // dll_chain frame above the trampoline frame\n\
+  adrp       x30, dll_chain    // x30 = &dll_chain so the 'ret' below tail-chains there\n\
+  add        x30, x30, #:lo12:dll_chain // -> dll_chain, which tail-calls x1 (dll_func_load)\n\
+  ret                                                   \n\
+  .seh_endproc                                          \n\
+");
 #else
 #error unimplemented for this target
 #endif
@@ -534,7 +558,7 @@ wsock_init (struct func_info *func)
   return ret.ll;
 }
 
-LoadDLLprime (ws2_32, _wsock_init, 0)
+LoadDLLprime (ws2_32, _wsock_init)
 
 LoadDLLfunc (CheckTokenMembership, advapi32)
 LoadDLLfunc (CreateProcessAsUserW, advapi32)
@@ -711,25 +735,25 @@ LoadDLLfuncEx2 (CreateProfile, userenv, 1, 1)
 LoadDLLfunc (DestroyEnvironmentBlock, userenv)
 LoadDLLfunc (LoadUserProfileW, userenv)
 
-LoadDLLfuncEx3 (waveInAddBuffer, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInClose, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInGetNumDevs, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInOpen, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInPrepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInReset, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInStart, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveInUnprepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutClose, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutGetNumDevs, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutGetVolume, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutOpen, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutPrepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutReset, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutSetVolume, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutUnprepareHeader, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutWrite, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutMessage, winmm, 1, 0, 1)
-LoadDLLfuncEx3 (waveOutGetDevCapsA, winmm, 1, 0, 1)
+LoadDLLfuncEx3 (waveInAddBuffer, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInClose, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInGetNumDevs, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInOpen, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInPrepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInReset, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInStart, winmm, 1, 0)
+LoadDLLfuncEx3 (waveInUnprepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutClose, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutGetNumDevs, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutGetVolume, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutOpen, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutPrepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutReset, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutSetVolume, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutUnprepareHeader, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutWrite, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutMessage, winmm, 1, 0)
+LoadDLLfuncEx3 (waveOutGetDevCapsA, winmm, 1, 0)
 
 LoadDLLfunc (accept, ws2_32)
 LoadDLLfunc (bind, ws2_32)
-- 
2.49.0.windows.1