Skip to content
Open
30 changes: 28 additions & 2 deletions winsup/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 41 additions & 3 deletions winsup/configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 2 additions & 0 deletions winsup/cygserver/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 = \
Expand Down
203 changes: 203 additions & 0 deletions winsup/cygwin/aarch64/fastcwd.cc
Original file line number Diff line number Diff line change
@@ -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 <assert.h>

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;
}
13 changes: 11 additions & 2 deletions winsup/cygwin/dcrt0.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion winsup/cygwin/include/cygwin/exit_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions winsup/cygwin/uname.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 16 additions & 1 deletion winsup/doc/faq-programming.xml
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,23 @@ Building these programs can be disabled with the <literal>--without-cross-bootst
option to <literal>configure</literal>.
</para>

<para>
Build of <literal>cygserver</literal> can be skipped with
<literal>--disable-cygserver</literal> and build of other Cygwin utilities with
<literal>--disable-utils</literal>.
</para>

<para>
In combination, <literal>--disable-cygserver</literal>,
<literal>--disable-dumper</literal>, <literal>--disable-utils</literal>
and <literal>--without-cross-bootstrap</literal> allow building of just
<literal>cygwin1.dll</literal> and <literal>crt0.o</literal> for a stage2
compiler, when being built with stage1 compiler which does not support linking
executables yet (because those files are missing).
</para>

<!-- If you want to run the tests <literal>busybox</literal> and
<literal>cygutils-extra<literal> are also required. -->
<literal>cygutils-extra</literal> are also required. -->

<para>
Building the documentation also requires the <literal>dblatex</literal>,
Expand Down
Loading
Loading