From 6fb141226cfc50eb2c64635118b6429140a333cd Mon Sep 17 00:00:00 2001 From: chandru-mcw Date: Tue, 28 Jul 2026 16:22:33 +0530 Subject: [PATCH 1/8] Cygwin: add an AArch64 implementation of fast_cwd Add winsup/cygwin/aarch64/fastcwd.cc, the AArch64 counterpart to the x86_64 fast current-working-directory probing used by path_conv. (cherry picked from commit 61007db2903b93514383add6a283b34e0168e7c8) --- winsup/cygwin/aarch64/fastcwd.cc | 203 +++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 winsup/cygwin/aarch64/fastcwd.cc diff --git a/winsup/cygwin/aarch64/fastcwd.cc b/winsup/cygwin/aarch64/fastcwd.cc new file mode 100644 index 0000000000..866f82e579 --- /dev/null +++ b/winsup/cygwin/aarch64/fastcwd.cc @@ -0,0 +1,203 @@ +/* aarch64/fastcwd.cc: find the fast cwd pointer on aarch64 hosts. + + This file is part of Cygwin. + + This software is a copyrighted work licensed under the terms of the + Cygwin license. Please consult the file "CYGWIN_LICENSE" for + details. */ + +/* You might well wonder why this file is included in x86_64 target files + in Makefile.am. It turns out that this code works when built for i686, + x86_64, or aarch64 with just the small #if/#elif block in + GetArm64ProcAddress below caring which. */ + +#include "winsup.h" +#include + +class fcwd_access_t; + +static LPCVOID +GetArm64ProcAddress (HMODULE hModule, LPCSTR procname) +{ + const BYTE *proc = (const BYTE *) GetProcAddress (hModule, procname); +#if defined (__aarch64__) + return proc; +#else +#if defined (__x86_64__) + /* see + https://learn.microsoft.com/en-us/windows/arm/arm64ec-abi#fast-forward-sequences */ + static const BYTE thunk[] = "\x48\x8b\xc4\x48\x89\x58\x20\x55\x5d\xe9"; + /* on windows 11 22000 the thunk is different than documented on that page */ + static const BYTE thunk2[] = "\x48\x8b\xff\x55\x48\x8b\xec\x5d\x90\xe9"; +#else +#error "Unhandled architecture for thunk detection" +#endif + if (proc && (memcmp (proc, thunk, sizeof (thunk) - 1) == 0 || + (sizeof(thunk2) && memcmp (proc, thunk2, sizeof (thunk2) - 1) == 0))) + { + proc += sizeof (thunk) - 1; + proc += 4 + *(const int32_t *) proc; + } + return proc; +#endif +} + +/* these ids and masks, as well as the names of the various other parts of + instructions used in this file, came from + https://developer.arm.com/documentation/ddi0602/2024-09/Index-by-Encoding + (Arm A-profile A64 Instruction Set Architecture) +*/ +#define IS_INSN(pc, name) ((*(pc) & name##_mask) == name##_id) +static const uint32_t add_id = 0x11000000; +static const uint32_t add_mask = 0x7fc00000; +static const uint32_t adrp_id = 0x90000000; +static const uint32_t adrp_mask = 0x9f000000; +static const uint32_t b_id = 0x14000000; +static const uint32_t b_mask = 0xfc000000; +static const uint32_t bl_id = 0x94000000; +static const uint32_t bl_mask = 0xfc000000; +/* matches both cbz and cbnz */ +static const uint32_t cbz_id = 0x34000000; +static const uint32_t cbz_mask = 0x7e000000; +static const uint32_t ldr_id = 0xb9400000; +static const uint32_t ldr_mask = 0xbfc00000; +/* matches both ret and br (which are the same except ret is a 'hint' that + it's a subroutine return */ +static const uint32_t ret_id = 0xd61f0000; +static const uint32_t ret_mask = 0xffbffc1f; + +/* this would work for either bl or b, but we only use it for bl */ +static inline LPCVOID +extract_bl_target (const uint32_t *pc) +{ + assert (IS_INSN (pc, bl) || IS_INSN (pc, b)); + int32_t offset = *pc & ~bl_mask; + /* sign extend */ + if (offset & (1 << 25)) + offset |= bl_mask; + /* Note uint32_t * artithmatic will implicitly multiply the offset by 4 */ + return pc + offset; +} + +static inline uint64_t +extract_adrp_address (const uint32_t *pc) +{ + assert (IS_INSN (pc, adrp)); + uint64_t adrp_base = (uint64_t) pc & ~0xFFF; + int64_t adrp_imm = (*pc >> (5+19+5)) & 0x3; + adrp_imm |= ((*pc >> 5) & 0x7FFFF) << 2; + /* sign extend */ + if (adrp_imm & (1 << 20)) + adrp_imm |= ~((1 << 21) - 1); + adrp_imm <<= 12; + return adrp_base + adrp_imm; +} + +/* This function scans the code in ntdll.dll to find the address of the + global variable used to access the CWD. While the pointer is global, + it's not exported from the DLL, unfortunately. Therefore we have to + use some knowledge to figure out the address. */ + +fcwd_access_t ** +find_fast_cwd_pointer_aarch64 () +{ + /* Fetch entry points of relevant functions in ntdll.dll. */ + HMODULE ntdll = GetModuleHandle ("ntdll.dll"); + if (!ntdll) + return NULL; + LPCVOID get_dir = GetArm64ProcAddress (ntdll, "RtlGetCurrentDirectory_U"); + LPCVOID ent_crit = GetArm64ProcAddress (ntdll, "RtlEnterCriticalSection"); + if (!get_dir || !ent_crit) + return NULL; + + LPCVOID use_cwd = NULL; + const uint32_t *start = (const uint32_t *) get_dir; + const uint32_t *pc = start; + /* find the call to RtlpReferenceCurrentDirectory, and get its address */ + for (; pc < start + 20 && !IS_INSN (pc, ret) && !IS_INSN (pc, b); pc++) + { + if (IS_INSN (pc, bl)) + { + use_cwd = extract_bl_target (pc); + break; + } + } + if (!use_cwd) + return NULL; + + start = pc = (const uint32_t *) use_cwd; + + const uint32_t *ldrpc = NULL; + uint32_t ldroffset, ldrsz; + uint32_t ldrrn, ldrrd; + + /* find the ldr (immediate unsigned offset) for RtlpCurDirRef */ + for (; pc < start + 20 && !IS_INSN (pc, ret) && !IS_INSN (pc, b); pc++) + { + if (IS_INSN (pc, ldr)) + { + ldrpc = pc; + ldrsz = (*pc & 0x40000000); + ldroffset = (*pc >> (5+5)) & 0xFFF; + ldroffset <<= ldrsz ? 3 : 2; + ldrrn = (*pc >> 5) & 0x1F; + ldrrd = *pc & 0x1F; + break; + } + } + if (ldrpc == NULL) + return NULL; + + /* the next instruction after the ldr should be checking if it was NULL: + either a compare and branch if zero or not zero (hence why cbz_mask is 7e + instead of 7f) */ + if (!IS_INSN (pc + 1, cbz) || (*(pc + 1) & 0x1F) != ldrrd + || (*(pc + 1) & 0x80000000) != (ldrsz << 1)) + return NULL; + + /* work backwards, find a bl to RtlEnterCriticalSection whose argument + is the fast peb lock */ + + for (pc = ldrpc; pc >= start; pc--) + { + if (IS_INSN (pc, bl) && extract_bl_target (pc) == ent_crit) + break; + } + uint32_t addoffset; + uint32_t addrn; + for (; pc >= start; pc--) + { + if (IS_INSN (pc, add) && (*pc & 0x1F) == 0) + { + addoffset = (*pc >> (5+5)) & 0xFFF; + addrn = (*pc >> 5) & 0x1F; + break; + } + } + PRTL_CRITICAL_SECTION lockaddr = NULL; + for (; pc >= start; pc--) + { + if (IS_INSN (pc, adrp) && (*pc & 0x1F) == addrn) + { + lockaddr = (PRTL_CRITICAL_SECTION) (extract_adrp_address (pc) + + addoffset); + break; + } + } + if (lockaddr != NtCurrentTeb ()->Peb->FastPebLock) + return NULL; + + /* work backwards from the ldr to find the corresponding adrp */ + fcwd_access_t **RtlpCurDirRef = NULL; + for (pc = ldrpc; pc >= start; pc--) + { + if (IS_INSN (pc, adrp) && (*pc & 0x1F) == ldrrn) + { + RtlpCurDirRef = (fcwd_access_t **) (extract_adrp_address (pc) + + ldroffset); + break; + } + } + + return RtlpCurDirRef; +} \ No newline at end of file From 0d1021cae79d275a27303189e3b5c55ba6bc4396 Mon Sep 17 00:00:00 2001 From: mayagr Date: Tue, 28 Jul 2026 16:22:34 +0530 Subject: [PATCH 2/8] Cygwin: uname: report "aarch64" for PROCESSOR_ARCHITECTURE_ARM64 uname() and uname_x() returned "unknown" as the machine name on ARM64 Windows. Map PROCESSOR_ARCHITECTURE_ARM64 to "aarch64". (cherry picked from commit 5bc1d338f9febccc14ad8733b24b8842b5be8474) --- winsup/cygwin/uname.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/winsup/cygwin/uname.cc b/winsup/cygwin/uname.cc index 8f984fac9b..619ee800ac 100644 --- a/winsup/cygwin/uname.cc +++ b/winsup/cygwin/uname.cc @@ -83,6 +83,9 @@ uname_x (struct utsname *name) case PROCESSOR_ARCHITECTURE_AMD64: strcpy (name->machine, "x86_64"); break; + case PROCESSOR_ARCHITECTURE_ARM64: + strcpy (name->machine, "aarch64"); + break; default: strcpy (name->machine, "unknown"); break; @@ -165,6 +168,9 @@ uname (struct utsname *in_name) case PROCESSOR_ARCHITECTURE_AMD64: strcpy (name->machine, "x86_64"); break; + case PROCESSOR_ARCHITECTURE_ARM64: + strcpy (name->machine, "aarch64"); + break; default: strcpy (name->machine, "unknown"); break; From 95c0d84db5fc10f40686a407b3e58e6869182111 Mon Sep 17 00:00:00 2001 From: mayagr Date: Tue, 28 Jul 2026 16:22:34 +0530 Subject: [PATCH 3/8] Cygwin: exit_process: narrow the stale TODO to the missing ARMNT case getprocaddrarm64.exe is now provided, so the "provide exes for these" TODO only applies to the 32-bit ARM (ARMNT) helper. Reword the comment accordingly. (cherry picked from commit 345e3682340d0245b372b4bb52672fac5b4ac3a2) --- winsup/cygwin/include/cygwin/exit_process.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winsup/cygwin/include/cygwin/exit_process.h b/winsup/cygwin/include/cygwin/exit_process.h index 0486a0c74a..1a70bd471b 100644 --- a/winsup/cygwin/include/cygwin/exit_process.h +++ b/winsup/cygwin/include/cygwin/exit_process.h @@ -69,7 +69,7 @@ kill_via_console_helper (HANDLE process, wchar_t *function_name, int exit_code, case IMAGE_FILE_MACHINE_AMD64: name = "/usr/libexec/getprocaddr64.exe"; break; - /* TODO: provide exes for these */ + /* TODO: provide a getprocaddrarm32.exe for ARMNT targets */ case IMAGE_FILE_MACHINE_ARMNT: name = "/usr/libexec/getprocaddrarm32.exe"; break; From 8b6362288275a8891ffbb47a80fc901b9ed92de9 Mon Sep 17 00:00:00 2001 From: mayagr Date: Wed, 29 Jul 2026 23:08:24 +0530 Subject: [PATCH 4/8] build: getprocaddrarm64: build when the aarch64 MinGW compiler is present Native ARM64 (PID_NOTCYGWIN) processes should be terminated gently via a console helper instead of TerminateProcess. Probe for aarch64-w64-mingw32-gcc in winsup/configure.ac and build getprocaddrarm64 under the HAVE_MINGWARM64_CC conditional. The probe is done unconditionally (outside the with_cross_bootstrap / mingw-progs block) and never errors when the compiler is absent, so the helper builds on Windows-on-ARM cross setups (e.g. --with-cross-bootstrap) without requiring the x86 MinGW compilers, and x86 hosts are unaffected. Note: winsup/configure is generated; run autoreconf so the new AC_CHECK_PROGS/AM_CONDITIONAL take effect. Co-authored-by: chandru-mcw (cherry picked from commit 4b38982c5c1f46e4b6d2494c968a75ae6abd3864) --- winsup/configure.ac | 7 +++++++ winsup/utils/mingw/Makefile.am | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/winsup/configure.ac b/winsup/configure.ac index 4dd5ccb9f9..6025731f57 100644 --- a/winsup/configure.ac +++ b/winsup/configure.ac @@ -147,6 +147,13 @@ if test "x$with_cross_bootstrap" != "xyes"; then fi AM_CONDITIONAL(CROSS_BOOTSTRAP, [test "x$with_cross_bootstrap" != "xyes"]) +# Optional aarch64 MinGW compiler (present only on Windows-on-ARM hosts). +# When available, build getprocaddrarm64 for gentle kill of native ARM64 +# processes; probed even under --with-cross-bootstrap and not an error when +# missing, so x86 hosts are unaffected. +AC_CHECK_PROGS(MINGWARM64_CC, aarch64-w64-mingw32-gcc) +AM_CONDITIONAL(HAVE_MINGWARM64_CC, [test -n "$MINGWARM64_CC"]) + AC_EXEEXT AC_ARG_ENABLE([dumper], diff --git a/winsup/utils/mingw/Makefile.am b/winsup/utils/mingw/Makefile.am index 07b9f928d4..13ea1574f4 100644 --- a/winsup/utils/mingw/Makefile.am +++ b/winsup/utils/mingw/Makefile.am @@ -27,6 +27,9 @@ bin_PROGRAMS = \ strace libexec_PROGRAMS = getprocaddr32 getprocaddr64 +if HAVE_MINGWARM64_CC +libexec_PROGRAMS += getprocaddrarm64 +endif # Must *not* use -O2 here, as it screws up the stack backtrace getprocaddr32.o: %32.o: %.c @@ -41,6 +44,14 @@ getprocaddr64.o: %64.o: %.c getprocaddr64.exe: %.exe: %.o $(MINGW64_CC) -o $@ $^ -static -ldbghelp +# Native ARM64 helper for gentle kill of PID_NOTCYGWIN processes; only built +# when the aarch64 MinGW compiler is present (HAVE_MINGWARM64_CC). +getprocaddrarm64.o: %arm64.o: %.c + $(MINGWARM64_CC) -c -o $@ $< + +getprocaddrarm64.exe: %.exe: %.o + $(MINGWARM64_CC) -o $@ $^ -static -ldbghelp + cygcheck_SOURCES = \ bloda.cc \ cygcheck.cc \ From 4aecfc3a5cb9e050a4f78486b76837b3c25d21c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Barto=C5=88?= Date: Sat, 21 Jun 2025 22:56:58 +0200 Subject: [PATCH 5/8] Cygwin: configure: add possibility to skip build of cygserver and utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds configure options allowing to disable build of cygserver and Cygwin utilities. This is useful when one needs to build only cygwin1.dll and crt0.o with stage1 compiler that is not yet capable of linking executables as it is missing cygwin1.dll and crt0.o. Signed-off-by: Radek BartoĊˆ --- winsup/Makefile.am | 20 ++++++++++++++++++-- winsup/configure.ac | 12 ++++++++++++ winsup/cygserver/Makefile.am | 2 ++ winsup/doc/faq-programming.xml | 17 ++++++++++++++++- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/winsup/Makefile.am b/winsup/Makefile.am index 9efdd4cb17..877c4e6b90 100644 --- a/winsup/Makefile.am +++ b/winsup/Makefile.am @@ -14,10 +14,26 @@ cygdoc_DATA = \ CYGWIN_LICENSE \ COPYING -SUBDIRS = cygwin cygserver utils testsuite +SUBDIRS = cygwin testsuite + +if BUILD_CYGSERVER +SUBDIRS += cygserver +endif + +if BUILD_UTILS +SUBDIRS += utils +endif if BUILD_DOC SUBDIRS += doc endif -cygserver utils testsuite: cygwin +testsuite: cygwin + +if BUILD_CYGSERVER +cygserver: cygwin +endif + +if BUILD_UTILS +utils: cygwin +endif diff --git a/winsup/configure.ac b/winsup/configure.ac index 6025731f57..40b3644bb8 100644 --- a/winsup/configure.ac +++ b/winsup/configure.ac @@ -115,6 +115,18 @@ AC_ARG_ENABLE(doc, enable_doc=yes) AM_CONDITIONAL(BUILD_DOC, [test $enable_doc != "no"]) +# Disabling build of cygserver and utils is needed for zero-bootstrap build of +# stage 1 Cygwin toolchain where the linker is not able to produce executables +# yet. +AC_ARG_ENABLE(cygserver, + [AS_HELP_STRING([--disable-cygserver], [do not build cygserver])],, + enable_cygserver=yes) +AM_CONDITIONAL(BUILD_CYGSERVER, [test $enable_cygserver != "no"]) +AC_ARG_ENABLE(utils, + [AS_HELP_STRING([--disable-utils], [do not build utils])],, + enable_utils=yes) +AM_CONDITIONAL(BUILD_UTILS, [test $enable_utils != "no"]) + AC_CHECK_PROGS([DOCBOOK2XTEXI], [docbook2x-texi db2x_docbook2texi]) if test -z "$DOCBOOK2XTEXI" ; then if test "x$enable_doc" != "xno"; then diff --git a/winsup/cygserver/Makefile.am b/winsup/cygserver/Makefile.am index ec7a62240c..efb578e53e 100644 --- a/winsup/cygserver/Makefile.am +++ b/winsup/cygserver/Makefile.am @@ -12,7 +12,9 @@ cygserver_flags=$(cxxflags_common) -Wimplicit-fallthrough=5 -Werror -DSYSCONFDIR AM_CXXFLAGS = $(CFLAGS) noinst_LIBRARIES = libcygserver.a +if BUILD_CYGSERVER sbin_PROGRAMS = cygserver +endif bin_SCRIPTS = cygserver-config cygserver_SOURCES = \ diff --git a/winsup/doc/faq-programming.xml b/winsup/doc/faq-programming.xml index a834298599..c295aa649c 100644 --- a/winsup/doc/faq-programming.xml +++ b/winsup/doc/faq-programming.xml @@ -697,8 +697,23 @@ Building these programs can be disabled with the --without-cross-bootst option to configure. + +Build of cygserver can be skipped with +--disable-cygserver and build of other Cygwin utilities with +--disable-utils. + + + +In combination, --disable-cygserver, +--disable-dumper, --disable-utils +and --without-cross-bootstrap allow building of just +cygwin1.dll and crt0.o for a stage2 +compiler, when being built with stage1 compiler which does not support linking +executables yet (because those files are missing). + + + cygutils-extra are also required. --> Building the documentation also requires the dblatex, From a81f4df7829280f142a1a52dd005d69186bc8396 Mon Sep 17 00:00:00 2001 From: Mayank Agrawal Date: Tue, 28 Jul 2026 14:35:56 +0530 Subject: [PATCH 6/8] console-helper(arm64): build the MinGW host tools the x86_64 way The ARM64 runtime shipped zero winsup utils because it configures --disable-utils --without-mingw-progs --with-cross-bootstrap, which skips the entire utils/ tree (including the utils/mingw/ MinGW host tools). This adds a scoped opt-in so the cross build produces the MinGW host tools through automake, exactly as the native x86_64 build does -- no hand-written compile line -- while the ~26 cygwin-linked utils in utils/ stay deferred. winsup/configure.ac: - new --enable-mingw-utils (default off) => AM_CONDITIONAL(BUILD_MINGW_UTILS) - enter the MinGW probe block when --enable-mingw-utils is given even under --without-mingw-progs, so @MINGW_CXX@ resolves (the PKGBUILD overrides it to aarch64-w64-mingw32-g++) - the i686/x86_64 MinGW compilers become optional under the opt-in (they are absent on the ARM64 box); add HAVE_MINGW32_CC / HAVE_MINGW64_CC, mirroring the existing HAVE_MINGWARM64_CC winsup/Makefile.am: enter utils/ under BUILD_MINGW_UTILS as well as BUILD_UTILS. winsup/utils/Makefile.am: build the cygwin-linked bin_PROGRAMS only under BUILD_UTILS (unchanged for x86_64); descend into mingw/ under CROSS_BOOTSTRAP or BUILD_MINGW_UTILS. winsup/utils/mingw/Makefile.am: in mingw-utils-only mode build just cygwin-console-helper and ldh; build getprocaddr32/64/arm64 each only when its compiler is present. Default off => a strict no-op for every existing configuration. --- winsup/Makefile.am | 10 ++++++++ winsup/configure.ac | 25 ++++++++++++++++--- winsup/utils/Makefile.am | 19 ++++++++++++--- winsup/utils/mingw/Makefile.am | 44 +++++++++++++++++++++++++++------- 4 files changed, 84 insertions(+), 14 deletions(-) diff --git a/winsup/Makefile.am b/winsup/Makefile.am index 877c4e6b90..0a97dfb288 100644 --- a/winsup/Makefile.am +++ b/winsup/Makefile.am @@ -22,6 +22,12 @@ endif if BUILD_UTILS SUBDIRS += utils +else +# Enter utils/ for the MinGW host-tool subset even when the cygwin-linked utils +# are disabled (--enable-mingw-utils; utils/Makefile.am descends only into mingw). +if BUILD_MINGW_UTILS +SUBDIRS += utils +endif endif if BUILD_DOC @@ -36,4 +42,8 @@ endif if BUILD_UTILS utils: cygwin +else +if BUILD_MINGW_UTILS +utils: cygwin +endif endif diff --git a/winsup/configure.ac b/winsup/configure.ac index 40b3644bb8..b610ff0d83 100644 --- a/winsup/configure.ac +++ b/winsup/configure.ac @@ -126,6 +126,16 @@ AC_ARG_ENABLE(utils, [AS_HELP_STRING([--disable-utils], [do not build utils])],, enable_utils=yes) AM_CONDITIONAL(BUILD_UTILS, [test $enable_utils != "no"]) +# --enable-mingw-utils: opt-in to build only the MinGW *host* tools in +# utils/mingw (e.g. cygwin-console-helper) without the cygwin-linked utils in +# utils/ (which stay behind --disable-utils). Used by the ARM64 cross build to +# ship individual MinGW utilities the same way the native x86_64 build does +# (via `make`, not a hand-written compile line), while the ~26 cygwin-linked +# utils remain deferred. Default off => a no-op for every existing config. +AC_ARG_ENABLE(mingw-utils, + [AS_HELP_STRING([--enable-mingw-utils], [build the MinGW host tools in utils/mingw (e.g. cygwin-console-helper) without the cygwin-linked utils])],, + enable_mingw_utils=no) +AM_CONDITIONAL(BUILD_MINGW_UTILS, [test "x$enable_mingw_utils" = "xyes"]) AC_CHECK_PROGS([DOCBOOK2XTEXI], [docbook2x-texi db2x_docbook2texi]) if test -z "$DOCBOOK2XTEXI" ; then @@ -146,18 +156,27 @@ if test -z "$XMLTO"; then fi fi -if test "x$with_cross_bootstrap" != "xyes"; then +if test "x$with_cross_bootstrap" != "xyes" || test "x$enable_mingw_utils" = "xyes"; then AC_CHECK_PROGS(MINGW_CXX, ${target_cpu}-w64-mingw32-g++) test -n "$MINGW_CXX" || AC_MSG_ERROR([no acceptable MinGW g++ found in \$PATH]) AC_CHECK_PROGS(MINGW_CC, ${target_cpu}-w64-mingw32-gcc) test -n "$MINGW_CC" || AC_MSG_ERROR([no acceptable MinGW gcc found in \$PATH]) + # The x86 MinGW compilers are required for the getprocaddr32/getprocaddr64 + # helpers. When building only the MinGW host-tool subset (--enable-mingw-utils, + # used by the ARM64 cross build where the i686/x86_64 MinGW compilers are not + # installed) they are optional: the corresponding helpers are then skipped via + # the HAVE_MINGW32_CC/HAVE_MINGW64_CC conditionals below. AC_CHECK_PROGS(MINGW32_CC, i686-w64-mingw32-gcc) - test -n "$MINGW32_CC" || AC_MSG_ERROR([no acceptable mingw32 gcc found in \$PATH]) AC_CHECK_PROGS(MINGW64_CC, x86_64-w64-mingw32-gcc) - test -n "$MINGW64_CC" || AC_MSG_ERROR([no acceptable mingw64 gcc found in \$PATH]) + if test "x$enable_mingw_utils" != "xyes"; then + test -n "$MINGW32_CC" || AC_MSG_ERROR([no acceptable mingw32 gcc found in \$PATH]) + test -n "$MINGW64_CC" || AC_MSG_ERROR([no acceptable mingw64 gcc found in \$PATH]) + fi fi AM_CONDITIONAL(CROSS_BOOTSTRAP, [test "x$with_cross_bootstrap" != "xyes"]) +AM_CONDITIONAL(HAVE_MINGW32_CC, [test -n "$MINGW32_CC"]) +AM_CONDITIONAL(HAVE_MINGW64_CC, [test -n "$MINGW64_CC"]) # Optional aarch64 MinGW compiler (present only on Windows-on-ARM hosts). # When available, build getprocaddrarm64 for gentle kill of native ARM64 diff --git a/winsup/utils/Makefile.am b/winsup/utils/Makefile.am index 57a4f377c5..90987d1c57 100644 --- a/winsup/utils/Makefile.am +++ b/winsup/utils/Makefile.am @@ -13,8 +13,15 @@ AM_CFLAGS=$(cflags_common) $(CFLAGS_COMMON) AM_CXXFLAGS=$(cxxflags_common) $(CFLAGS_COMMON) AM_CXXFLAGS += $(CFLAGS) +# Rename cygps to ps at install time (inert unless cygps is built below) +transform = s/cygps/ps/; $(program_transform_name) + # Note that we call ps cygps here, so as not to collide with the # autogenerated texinfo rule with the name 'ps'... +# The cygwin-linked utilities are built only when --disable-utils is not given. +# The MinGW host tools in mingw/ are built independently (see the mingw descent +# at the bottom, gated on CROSS_BOOTSTRAP or --enable-mingw-utils). +if BUILD_UTILS bin_PROGRAMS = \ chattr \ cygpath \ @@ -42,9 +49,6 @@ bin_PROGRAMS = \ tzset \ umount -# Rename cygps to ps at install time -transform = s/cygps/ps/; $(program_transform_name) - # dumper is only built if libbfd.a available if BUILD_DUMPER bin_PROGRAMS += dumper @@ -89,7 +93,16 @@ profiler_CXXFLAGS = -I$(srcdir) -idirafter ${top_srcdir}/cygwin/local_includes - profiler_LDADD = $(LDADD) -lntdll cygps_LDADD = $(LDADD) -lpsapi -lntdll newgrp_LDADD = $(LDADD) -luserenv +endif +# Descend into mingw/ for the MinGW host tools when cross-bootstrapping (as +# before) or when only the MinGW host-tool subset was requested via +# --enable-mingw-utils (ARM64 cross build; utils/ was entered but BUILD_UTILS +# is off). if CROSS_BOOTSTRAP SUBDIRS = mingw +else +if BUILD_MINGW_UTILS +SUBDIRS = mingw +endif endif diff --git a/winsup/utils/mingw/Makefile.am b/winsup/utils/mingw/Makefile.am index 13ea1574f4..e2b4aebbed 100644 --- a/winsup/utils/mingw/Makefile.am +++ b/winsup/utils/mingw/Makefile.am @@ -20,13 +20,33 @@ AM_CXXFLAGS=-fno-exceptions -fno-rtti -fno-use-cxa-atexit $(flags_common) $(CFLA AM_LDFLAGS = -static +if BUILD_MINGW_UTILS +# --enable-mingw-utils: build only the MinGW host-tool subset that has been +# ported/validated for the ARM64 cross build (cygwin-console-helper, ldh). +# cygcheck and strace are added here as they migrate onto this path; until then +# they are built by their own recipe. The cygwin-linked utils in ../ stay +# disabled. +bin_PROGRAMS = \ + cygwin-console-helper \ + ldh +else bin_PROGRAMS = \ cygcheck \ cygwin-console-helper \ ldh \ strace +endif -libexec_PROGRAMS = getprocaddr32 getprocaddr64 +# The x86 getprocaddr helpers require the i686/x86_64 MinGW compilers; the ARM64 +# helper requires the aarch64 one. Build each only when its compiler is present +# so a host lacking some of them (e.g. the ARM64 cross build) still configures. +libexec_PROGRAMS = +if HAVE_MINGW32_CC +libexec_PROGRAMS += getprocaddr32 +endif +if HAVE_MINGW64_CC +libexec_PROGRAMS += getprocaddr64 +endif if HAVE_MINGWARM64_CC libexec_PROGRAMS += getprocaddrarm64 endif @@ -44,14 +64,26 @@ getprocaddr64.o: %64.o: %.c getprocaddr64.exe: %.exe: %.o $(MINGW64_CC) -o $@ $^ -static -ldbghelp -# Native ARM64 helper for gentle kill of PID_NOTCYGWIN processes; only built -# when the aarch64 MinGW compiler is present (HAVE_MINGWARM64_CC). +# Native ARM64 helper, used to gently kill native ARM64 (PID_NOTCYGWIN) +# processes on Windows-on-ARM. Only built when the aarch64 MinGW compiler is +# available (HAVE_MINGWARM64_CC, see configure.ac). getprocaddrarm64.o: %arm64.o: %.c $(MINGWARM64_CC) -c -o $@ $< getprocaddrarm64.exe: %.exe: %.o $(MINGWARM64_CC) -o $@ $^ -static -ldbghelp +# cygwin-console-helper and ldh are in the mingw-utils subset, so their +# sources/flags are defined unconditionally (built in both modes). +cygwin_console_helper_SOURCES = cygwin-console-helper.cc + +ldh_SOURCES = ldh.cc +ldh_LDFLAGS = ${AM_LDFLAGS} -Wl,--disable-high-entropy-va + +# The following targets and their per-target flags are only defined when the +# full MinGW host-tool set is built (not the --enable-mingw-utils subset), so +# automake does not warn about per-target flags for programs that are not built. +if !BUILD_MINGW_UTILS cygcheck_SOURCES = \ bloda.cc \ cygcheck.cc \ @@ -61,11 +93,6 @@ cygcheck_CPPFLAGS=-I$(srcdir)/.. -idirafter ${top_srcdir}/cygwin/include cygcheck_LDFLAGS = ${AM_LDFLAGS} -Wl,--disable-high-entropy-va cygcheck_LDADD = -lz -lwininet -lshlwapi -lpsapi -lntdll -cygwin_console_helper_SOURCES = cygwin-console-helper.cc - -ldh_SOURCES = ldh.cc -ldh_LDFLAGS = ${AM_LDFLAGS} -Wl,--disable-high-entropy-va - strace_SOURCES = \ path.cc \ strace.cc @@ -82,3 +109,4 @@ path_testsuite_CPPFLAGS=-I$(srcdir)/.. -idirafter ${top_srcdir}/cygwin/include path_testsuite_CXXFLAGS = -DTESTSUITE TESTS = path-testsuite +endif From 87b5d73191cced65a35b2db2cb1e21acb1ac2fc3 Mon Sep 17 00:00:00 2001 From: Mayank Agrawal Date: Tue, 28 Jul 2026 14:35:57 +0530 Subject: [PATCH 7/8] strace(arm64): add crash reporter to localize the ARM64 segfault (#20) strace.exe segfaults on aarch64-pc-msys when tracing a process (issue #20). The only signal today is the shell's bare "Segmentation fault" line; the fault reproduces only on the ARM64 box, so root-causing it needs a fault address we currently don't have. Install a vectored exception handler plus a last-resort unhandled-filter in main2() that, on a genuine fault, print the exception code, the faulting instruction address, and -- decisively -- the module + offset that address resolves to (via GetModuleHandleExW FROM_ADDRESS), so we can tell immediately whether the PC is in strace.exe's own logic or inside msys-2.0.dll. A few registers are dumped from the CONTEXT record (ARM64 and x86_64 branches). GCC-SEH pseudo-exceptions, breakpoints and the SetThreadName marker are skipped so only real crashes are reported; the trace file is flushed first so no captured lines are lost. Inert unless a fault occurs, pure win32/ntdll (no cygwin runtime dependency), so it is safe in this -lntdll util and stays permanently. Compile-checked for both aarch64-w64-mingw32 and x86_64 with -Wall -Wextra. --- winsup/utils/mingw/strace.cc | 126 +++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/winsup/utils/mingw/strace.cc b/winsup/utils/mingw/strace.cc index a6b2e5d548..eaab4af0b9 100644 --- a/winsup/utils/mingw/strace.cc +++ b/winsup/utils/mingw/strace.cc @@ -1063,6 +1063,125 @@ struct option longopts[] = { static const char *const opts = "+b:dehHfm:no:p:qS:tTuVw"; +/* Crash reporter (issue #20). strace.exe is a native (aarch64-pc-cygwin / + x86_64) PE that drives a debug loop and calls into msys-2.0.dll via + cygwin_internal for every debug event. When it faults there is no + useful signal beyond the shell's "Segmentation fault" line, and the + crash reproduces only on the ARM64 box. This handler turns a fault + into an actionable report: exception code, faulting instruction + address, and -- decisively -- the module + offset that address lands + in, so we can tell instantly whether the PC is inside strace.exe's own + logic or inside msys-2.0.dll. It is inert unless a genuine fault + occurs, so it stays in the tree permanently. Pure win32/ntdll: safe in + a -lntdll util with no cygwin runtime dependency. */ + +static FILE *crash_ofile; /* trace file to flush before we die */ +static LONG crash_reported; /* guard: report a fault only once */ + +/* True for control-flow "exceptions" that are not crashes: the GCC SEH + pseudo-exceptions (see gcc_seh.h), debugger breakpoints, and the + SetThreadName marker. These must not be reported. */ +static bool +is_noncrash_exception (DWORD code) +{ + switch (code) + { + case STATUS_GCC_THROW: + case STATUS_GCC_UNWIND: + case STATUS_GCC_FORCED: + case STATUS_BREAKPOINT: + case (DWORD) 0x406d1388: /* SetThreadName exception. */ + return true; + } + return false; +} + +/* Core reporter: dump the fault to stderr, once. No cygwin runtime + dependency -- pure win32/ntdll, safe in a -lntdll util. */ +static void +strace_report_fault (EXCEPTION_POINTERS *ep) +{ + if (InterlockedExchange (&crash_reported, 1)) + return; /* already reported by the other hook */ + + /* Don't lose already-captured trace lines. */ + if (crash_ofile && crash_ofile != stdout) + fflush (crash_ofile); + fflush (stderr); + + const DWORD code = ep->ExceptionRecord->ExceptionCode; + void *addr = ep->ExceptionRecord->ExceptionAddress; + fprintf (stderr, "\n%s: *** fatal exception 0x%08lx at %p ***\n", + pgm, code, addr); + + if (code == EXCEPTION_ACCESS_VIOLATION + && ep->ExceptionRecord->NumberParameters >= 2) + { + static const char *const acc[] = { "read", "write", "execute" }; + ULONG_PTR kind = ep->ExceptionRecord->ExceptionInformation[0]; + fprintf (stderr, "%s: access violation: %s at %p\n", pgm, + kind < 3 ? acc[kind] : "?", + (void *) ep->ExceptionRecord->ExceptionInformation[1]); + } + + /* Resolve the faulting PC to module + offset -- the decisive datum: + tells us instantly whether the PC is in strace.exe or msys-2.0.dll. */ + HMODULE mod = NULL; + if (GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR) addr, &mod) + && mod) + { + WCHAR path[MAX_PATH + 1] = L""; + GetModuleFileNameW (mod, path, MAX_PATH); + fprintf (stderr, "%s: in module %ls + 0x%tx (base %p)\n", pgm, + path[0] ? path : L"(unknown)", + (char *) addr - (char *) mod, (void *) mod); + } + else + fprintf (stderr, "%s: PC is not in any loaded module\n", pgm); + +#ifdef __aarch64__ + CONTEXT *c = ep->ContextRecord; + if (c) + fprintf (stderr, + "%s: Pc=%p Sp=%p Lr(x30)=%p Fp(x29)=%p X0=%p X1=%p\n", pgm, + (void *) c->Pc, (void *) c->Sp, (void *) c->Lr, (void *) c->Fp, + (void *) c->X0, (void *) c->X1); +#elif defined(__x86_64__) + CONTEXT *c = ep->ContextRecord; + if (c) + fprintf (stderr, "%s: Rip=%p Rsp=%p Rbp=%p Rcx=%p Rdx=%p\n", pgm, + (void *) c->Rip, (void *) c->Rsp, (void *) c->Rbp, + (void *) c->Rcx, (void *) c->Rdx); +#endif + + fflush (stderr); +} + +/* First-chance vectored hook: catches the fault even if some frame would + otherwise swallow it, but must not claim the exception -- report and + let the search continue. */ +static LONG WINAPI +strace_vectored_report (EXCEPTION_POINTERS *ep) +{ + if (!is_noncrash_exception (ep->ExceptionRecord->ExceptionCode)) + strace_report_fault (ep); + return EXCEPTION_CONTINUE_SEARCH; +} + +/* Last-resort top-level filter: report (if the vectored hook somehow + didn't) and terminate cleanly, suppressing the WER dialog that would + otherwise hang an unattended box/CI run. */ +static LONG WINAPI +strace_unhandled_filter (EXCEPTION_POINTERS *ep) +{ + if (is_noncrash_exception (ep->ExceptionRecord->ExceptionCode)) + return EXCEPTION_CONTINUE_SEARCH; + strace_report_fault (ep); + return EXCEPTION_EXECUTE_HANDLER; +} + static void print_version () { @@ -1088,6 +1207,12 @@ main2 (int argc, char **argv) int sawquiet = -1; DWORD ret = 0; + /* Install the crash reporter before we do anything else, so a fault + anywhere in the debug loop yields code+address+module instead of a + bare "Segmentation fault" (issue #20). */ + AddVectoredExceptionHandler (0, strace_vectored_report); + SetUnhandledExceptionFilter (strace_unhandled_filter); + if (load_cygwin ()) { char **av = (char **) cygwin_internal (CW_ARGV); @@ -1203,6 +1328,7 @@ character #%d.\n", optarg, (int) (endptr - optarg), endptr); if (!ofile) ofile = stdout; + crash_ofile = ofile; /* so the crash reporter can flush it */ if (bufsize) setvbuf (ofile, (char *) alloca (bufsize), _IOFBF, bufsize); From 93905e88d19740a716c465912c99f9e015e035a2 Mon Sep 17 00:00:00 2001 From: aswin-mcw Date: Wed, 9 Sep 2026 17:51:01 +0530 Subject: [PATCH 8/8] Cygwin: fix argv corruption from overlapping strcpy in quoted() quoted() deletes an in-place quote with strcpy (cmd, cmd + 1), where source and destination overlap. That is undefined, and only works by accident on implementations that copy a byte at a time. The AArch64 SIMD strcpy rounds the source pointer down to a 16-byte boundary and loads that entire block, so storing it back one byte lower duplicates bytes inside the block while keeping the string length. Command lines from a native parent such as cmd.exe therefore arrived mangled, with the damage depending on where the string happened to sit in memory. Use memmove, which is defined for overlapping buffers, in both places. --- winsup/cygwin/dcrt0.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc index 33cad1e3fe..e81eeda6b9 100644 --- a/winsup/cygwin/dcrt0.cc +++ b/winsup/cygwin/dcrt0.cc @@ -162,9 +162,18 @@ quoted (char *cmd, int winshell, int glob) if (!winshell || !glob) { char *p; - strcpy (cmd, cmd + 1); + /* These two copies delete a quote in place, so source and destination + overlap. strcpy is undefined for overlapping buffers: it only happens + to work for dst < src when the implementation copies one byte at a + time. The AArch64 strcpy is SIMD -- it rounds the source pointer down + to a 16-byte boundary and loads the whole block, so storing it back + one byte lower duplicates bytes within the block while preserving the + total length. The result is a mangled command line whenever a native + (non-Cygwin) parent such as cmd.exe launches us. memmove is defined + for overlap and is the correct call in both places. */ + memmove (cmd, cmd + 1, strlen (cmd + 1) + 1); if (*(p = strchrnul (cmd, quote))) - strcpy (p, p + 1); + memmove (p, p + 1, strlen (p + 1) + 1); return p; }