diff --git a/winsup/Makefile.am b/winsup/Makefile.am index 9efdd4cb17..0a97dfb288 100644 --- a/winsup/Makefile.am +++ b/winsup/Makefile.am @@ -14,10 +14,36 @@ cygdoc_DATA = \ CYGWIN_LICENSE \ COPYING -SUBDIRS = cygwin cygserver utils testsuite +SUBDIRS = cygwin testsuite + +if BUILD_CYGSERVER +SUBDIRS += cygserver +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 SUBDIRS += doc endif -cygserver utils testsuite: cygwin +testsuite: cygwin + +if BUILD_CYGSERVER +cygserver: cygwin +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 4dd5ccb9f9..b610ff0d83 100644 --- a/winsup/configure.ac +++ b/winsup/configure.ac @@ -115,6 +115,28 @@ 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"]) +# --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 if test "x$enable_doc" != "xno"; then @@ -134,18 +156,34 @@ 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 +# 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 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/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 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; } 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; 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; 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, 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 07b9f928d4..e2b4aebbed 100644 --- a/winsup/utils/mingw/Makefile.am +++ b/winsup/utils/mingw/Makefile.am @@ -20,13 +20,36 @@ 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 - -libexec_PROGRAMS = getprocaddr32 getprocaddr64 +endif + +# 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 # Must *not* use -O2 here, as it screws up the stack backtrace getprocaddr32.o: %32.o: %.c @@ -41,6 +64,26 @@ getprocaddr64.o: %64.o: %.c getprocaddr64.exe: %.exe: %.o $(MINGW64_CC) -o $@ $^ -static -ldbghelp +# 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 \ @@ -50,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 @@ -71,3 +109,4 @@ path_testsuite_CPPFLAGS=-I$(srcdir)/.. -idirafter ${top_srcdir}/cygwin/include path_testsuite_CXXFLAGS = -DTESTSUITE TESTS = path-testsuite +endif 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);