mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
6077 lines
242 KiB
Diff
6077 lines
242 KiB
Diff
diff --git a/darling/src/libsystem_kernel/CMakeLists.txt b/darling/src/libsystem_kernel/CMakeLists.txt
|
|
index e14e7ea..024981b 100644
|
|
--- a/darling/src/libsystem_kernel/CMakeLists.txt
|
|
+++ b/darling/src/libsystem_kernel/CMakeLists.txt
|
|
@@ -40,7 +40,11 @@ add_circular(system_kernel FAT
|
|
$<IF:$<BOOL:${BUILD_TARGET_64BIT}>,$<TARGET_OBJECTS:libsyscall_64>,>
|
|
$<TARGET_OBJECTS:libsyscall>
|
|
$<TARGET_OBJECTS:libsyscall_dynamic>
|
|
- #$<TARGET_OBJECTS:kqueue>
|
|
+ # darling-lol: link kqueue emulation (see libkqueue/CMakeLists.txt).
|
|
+ # Without this, kqueue()/kevent() are ENOSYS weak stubs and all
|
|
+ # dispatch-source / FilePathWatcher / socket-server event delivery
|
|
+ # silently never fires.
|
|
+ $<TARGET_OBJECTS:kqueue>
|
|
$<TARGET_OBJECTS:emulation>
|
|
SIBLINGS
|
|
system_c
|
|
@@ -48,6 +52,12 @@ add_circular(system_kernel FAT
|
|
system_dyld
|
|
DEPENDENCIES
|
|
libsimple_darling
|
|
+ # darling-lol: kqueue emulation objects (see OBJECTS above) use
|
|
+ # calloc/pthread, which live in upper layers. UPWARD defers binding
|
|
+ # to runtime instead of failing the link (or creating a cycle).
|
|
+ UPWARD
|
|
+ system_malloc
|
|
+ system_pthread
|
|
)
|
|
#target_link_libraries(system_kernel system_duct platform_static32 platform_static64)
|
|
make_fat(system_kernel)
|
|
diff --git a/darling/src/libsystem_kernel/emulation/CMakeLists.txt b/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
|
index 5b2e765..40308eb 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
|
+++ b/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
|
@@ -6,13 +6,12 @@ enable_language(ASM)
|
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -fvisibility=hidden -fPIC -fno-builtin -ggdb -Wno-int-conversion -Wno-compare-distinct-pointer-types")
|
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -nostdlib")
|
|
|
|
-add_definitions(-DBSDTHREAD_WRAP_LINUX_PTHREAD
|
|
- -DEMULATED_SYSNAME="Darwin"
|
|
- -DEMULATED_RELEASE="20.6.0"
|
|
- -DEMULATED_VERSION="Darwin Kernel Version 20.6.0"
|
|
- -DEMULATED_OSVERSION="20G1120"
|
|
- -DEMULATED_OSPRODUCTVERSION="11.7.4"
|
|
-)
|
|
+add_definitions(-DBSDTHREAD_WRAP_LINUX_PTHREAD)
|
|
+
|
|
+# Quoted values are kept off the directory-wide definitions: those are pasted unescaped
|
|
+# into the mig command lines, which breaks the shell quoting.
|
|
+set_source_files_properties(src/xnu_syscall/bsd/helper/misc/sysctl_kern.c PROPERTIES COMPILE_DEFINITIONS
|
|
+ "EMULATED_SYSNAME=\"Darwin\";EMULATED_RELEASE=\"23.6.0\";EMULATED_VERSION=\"Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024\\; root:xnu-10063.141.2~1/RELEASE_X86_64\";EMULATED_OSVERSION=\"23H124\";EMULATED_OSPRODUCTVERSION=\"14.7\"")
|
|
|
|
# include src/startup for rtsig.h
|
|
include_directories(
|
|
@@ -134,6 +133,7 @@ set(xnu_syscall_bsd_sources
|
|
src/xnu_syscall/bsd/impl/guarded/guarded_close_np.c
|
|
src/xnu_syscall/bsd/impl/guarded/guarded_kqueue_np.c
|
|
src/xnu_syscall/bsd/impl/guarded/guarded_open_np.c
|
|
+ src/xnu_syscall/bsd/impl/guarded/change_fdguard_np.c
|
|
src/xnu_syscall/bsd/impl/hfs/stub.c
|
|
src/xnu_syscall/bsd/impl/ioctl/ioctl.c
|
|
src/xnu_syscall/bsd/impl/kqueue/kevent.c
|
|
@@ -153,6 +153,7 @@ set(xnu_syscall_bsd_sources
|
|
src/xnu_syscall/bsd/impl/misc/getrlimit.c
|
|
src/xnu_syscall/bsd/impl/misc/getrusage.c
|
|
src/xnu_syscall/bsd/impl/misc/iopolicysys.c
|
|
+ src/xnu_syscall/bsd/impl/misc/mac_syscall.c
|
|
src/xnu_syscall/bsd/impl/misc/proc_info.c
|
|
src/xnu_syscall/bsd/impl/misc/ptrace.c
|
|
src/xnu_syscall/bsd/impl/misc/reboot.c
|
|
@@ -359,6 +360,11 @@ set(emulation_sources
|
|
${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c
|
|
)
|
|
|
|
+# darling-lol: keep these at -O0 even when the tree is built optimized. bsdthread_register.c
|
|
+# sets %rsp from the thread argument in inline assembly and sigexc.c runs on the fault and
|
|
+# suspend signal stacks; both depend on the unoptimized stack layout.
|
|
+set_source_files_properties(src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c
|
|
+ src/linux_premigration/signal/sigexc.c PROPERTIES COMPILE_OPTIONS "-O0")
|
|
set_source_files_properties(src/legacy_path/signal/duct_signals.c PROPERTIES COMPILE_FLAGS "-nostdinc")
|
|
set_source_files_properties(src/legacy_path/signal/sigexc.c PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/src/legacy_path/signal/exc.h)
|
|
set_source_files_properties(${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c PROPERTIES
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/common/base.h b/darling/src/libsystem_kernel/emulation/include/common/base.h
|
|
index 207dae6..f5e96db 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/include/common/base.h
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/common/base.h
|
|
@@ -11,13 +11,13 @@
|
|
#define CPP_EXTERN_END
|
|
#endif
|
|
|
|
-#define LINUX_SYSCALL0(nr) linux_syscall(0, 0, 0, 0, 0, 0, nr)
|
|
-#define LINUX_SYSCALL1(nr, a1) linux_syscall((long)a1, 0, 0, 0, 0, 0, nr)
|
|
-#define LINUX_SYSCALL2(nr, a1, a2) linux_syscall((long)a1, (long)a2, 0, 0, 0, 0, nr)
|
|
-#define LINUX_SYSCALL3(nr, a1, a2, a3) linux_syscall((long)a1, (long)a2, (long)a3, 0, 0, 0, nr)
|
|
-#define LINUX_SYSCALL4(nr, a1, a2, a3, a4) linux_syscall((long)a1, (long)a2, (long)a3, (long)a4, 0, 0, nr)
|
|
-#define LINUX_SYSCALL5(nr, a1, a2, a3, a4, a5) linux_syscall((long)a1, (long)a2, (long)a3, (long)a4, (long)a5, 0, nr)
|
|
-#define LINUX_SYSCALL6(nr, a1, a2, a3, a4, a5, a6) linux_syscall((long)a1, (long)a2, (long)a3, (long)a4, (long)a5, (long)a6, nr)
|
|
+#define LINUX_SYSCALL0(nr) __lolsys_syscall(0, 0, 0, 0, 0, 0, nr)
|
|
+#define LINUX_SYSCALL1(nr, a1) __lolsys_syscall((long)a1, 0, 0, 0, 0, 0, nr)
|
|
+#define LINUX_SYSCALL2(nr, a1, a2) __lolsys_syscall((long)a1, (long)a2, 0, 0, 0, 0, nr)
|
|
+#define LINUX_SYSCALL3(nr, a1, a2, a3) __lolsys_syscall((long)a1, (long)a2, (long)a3, 0, 0, 0, nr)
|
|
+#define LINUX_SYSCALL4(nr, a1, a2, a3, a4) __lolsys_syscall((long)a1, (long)a2, (long)a3, (long)a4, 0, 0, nr)
|
|
+#define LINUX_SYSCALL5(nr, a1, a2, a3, a4, a5) __lolsys_syscall((long)a1, (long)a2, (long)a3, (long)a4, (long)a5, 0, nr)
|
|
+#define LINUX_SYSCALL6(nr, a1, a2, a3, a4, a5, a6) __lolsys_syscall((long)a1, (long)a2, (long)a3, (long)a4, (long)a5, (long)a6, nr)
|
|
|
|
#define __SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n
|
|
#define __SYSCALL_NARGS(...) __SYSCALL_NARGS_X(__VA_ARGS__,7,6,5,4,3,2,1,0)
|
|
@@ -27,6 +27,9 @@
|
|
|
|
long linux_syscall(long a1, long a2, long a3, long a4, long a5, long a6, int nr);
|
|
|
|
+/* darling-lol M58x: traced wrapper the LINUX_SYSCALL* macros route through */
|
|
+long __lolsys_syscall(long a1, long a2, long a3, long a4, long a5, long a6, int nr);
|
|
+
|
|
#ifndef BUILDING_BASE_C
|
|
|
|
VISIBLE
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/common/cpu_topology.h b/darling/src/libsystem_kernel/emulation/include/common/cpu_topology.h
|
|
new file mode 100644
|
|
index 0000000..8ae1665
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/common/cpu_topology.h
|
|
@@ -0,0 +1,272 @@
|
|
+#ifndef DARLING_COMMON_CPU_TOPOLOGY_H
|
|
+#define DARLING_COMMON_CPU_TOPOLOGY_H
|
|
+
|
|
+#include <stdbool.h>
|
|
+
|
|
+#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
+
|
|
+/*
|
|
+ * darling-lol: the host's real CPU topology for the hw.* / machdep.cpu.* sysctls.
|
|
+ *
|
|
+ * The guest used to be told the fixed MacBookPro16,1 configuration (8 logical
|
|
+ * and 8 physical CPUs). The game and the clients size their worker pools from
|
|
+ * hw.ncpu / hw.activecpu, so on this 6-core/12-thread host the game created 7
|
|
+ * workers where the same PC runs 12 threads under Windows. Report what the
|
|
+ * host really has instead, while the emulated Mac identity (hw.model, brand
|
|
+ * string) stays unchanged.
|
|
+ *
|
|
+ * Values are cached once per process. avail/active reflect the process's
|
|
+ * affinity mask (cgroup or taskset restrictions); max values reflect the
|
|
+ * online CPUs. The host flag file /tmp/rclol-fixed-cpu-count (checked once
|
|
+ * with a raw access() syscall) restores the old fixed values for A/B runs.
|
|
+ */
|
|
+
|
|
+/* From src/xnu_syscall/bsd/helper/misc/sysctl_proc.c */
|
|
+bool read_string(const char* path, char* dst, int maxlen);
|
|
+
|
|
+#define DARLING_CPU_FIXED_COUNT 8
|
|
+#define DARLING_CPU_MASK_BYTES 128 /* 1024 CPUs, enough for any League host */
|
|
+#define DARLING_CPU_MAX_SCAN 256
|
|
+
|
|
+/* The A/B opt-out flag file; the host check overrides the path. */
|
|
+#ifndef DARLING_CPU_FIXED_FLAG
|
|
+#define DARLING_CPU_FIXED_FLAG "/tmp/rclol-fixed-cpu-count"
|
|
+#endif
|
|
+
|
|
+struct darling_cpu_topology
|
|
+{
|
|
+ unsigned int online; /* CPUs online on the host (hw.ncpu) */
|
|
+ unsigned int available; /* CPUs this process may run on (hw.activecpu) */
|
|
+ unsigned int physical_online; /* physical cores among the online CPUs */
|
|
+ unsigned int physical_available; /* physical cores among the available CPUs */
|
|
+ bool fixed; /* the opt-out flag file is present */
|
|
+ bool detected; /* the host answered; false means "use the old 8s" */
|
|
+};
|
|
+
|
|
+/* Number of CPUs in a sysfs list such as "0-1,4-5" or "0,6" */
|
|
+static inline unsigned int darling_cpu_count_list(const char* list)
|
|
+{
|
|
+ unsigned int count = 0;
|
|
+ const char* p = list;
|
|
+
|
|
+ for (;;)
|
|
+ {
|
|
+ unsigned long long first = 0, last;
|
|
+ const char* start;
|
|
+
|
|
+ while (*p == ',' || *p == ' ' || *p == '\t' || *p == '\n')
|
|
+ ++p;
|
|
+ start = p;
|
|
+ while (*p >= '0' && *p <= '9')
|
|
+ first = first * 10 + (unsigned int)(*p++ - '0');
|
|
+ if (p == start)
|
|
+ break;
|
|
+
|
|
+ last = first;
|
|
+ if (*p == '-')
|
|
+ {
|
|
+ ++p;
|
|
+ last = 0;
|
|
+ while (*p >= '0' && *p <= '9')
|
|
+ last = last * 10 + (unsigned int)(*p++ - '0');
|
|
+ }
|
|
+
|
|
+ if (last >= first && last - first < 4096)
|
|
+ count += (unsigned int)(last - first + 1);
|
|
+
|
|
+ if (*p != ',' && *p != ' ' && *p != '\t' && *p != '\n')
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static inline bool darling_cpu_mask_has(const unsigned char* mask, long mask_bytes, unsigned int cpu)
|
|
+{
|
|
+ long byte = (long)(cpu >> 3);
|
|
+
|
|
+ if (byte >= mask_bytes)
|
|
+ return false;
|
|
+ return (mask[byte] & (1u << (cpu & 7))) != 0;
|
|
+}
|
|
+
|
|
+static inline unsigned int darling_cpu_mask_count(const unsigned char* mask, long mask_bytes)
|
|
+{
|
|
+ unsigned int count = 0;
|
|
+
|
|
+ for (long i = 0; i < mask_bytes; ++i)
|
|
+ count += (unsigned int)__builtin_popcount(mask[i]);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Count distinct (physical_package_id, core_id) pairs among the CPUs in
|
|
+ * [0, online). With a mask, only the CPUs it selects are counted and their
|
|
+ * pair count is returned through available_cores. Returns the total pair
|
|
+ * count, or 0 when the host does not expose the topology (the caller then
|
|
+ * falls back to an SMT estimate).
|
|
+ */
|
|
+static inline unsigned int darling_cpu_scan_cores(const unsigned char* mask, long mask_bytes,
|
|
+ unsigned int online, unsigned int* available_cores)
|
|
+{
|
|
+ unsigned long long pairs[DARLING_CPU_MAX_SCAN], available_pairs[DARLING_CPU_MAX_SCAN];
|
|
+ unsigned int pair_count = 0, available_count = 0;
|
|
+
|
|
+ if (available_cores != NULL)
|
|
+ *available_cores = 0;
|
|
+ if (online > DARLING_CPU_MAX_SCAN)
|
|
+ return 0;
|
|
+
|
|
+ for (unsigned int cpu = 0; cpu < online; ++cpu)
|
|
+ {
|
|
+ char path[112], buf[32];
|
|
+ unsigned long long package, core, pair;
|
|
+ bool seen = false;
|
|
+
|
|
+ __simple_sprintf(path, "/sys/devices/system/cpu/cpu%u/topology/physical_package_id", cpu);
|
|
+ if (!read_string(path, buf, sizeof(buf)))
|
|
+ return 0;
|
|
+ package = __simple_atoi(buf, NULL);
|
|
+
|
|
+ __simple_sprintf(path, "/sys/devices/system/cpu/cpu%u/topology/core_id", cpu);
|
|
+ if (!read_string(path, buf, sizeof(buf)))
|
|
+ return 0;
|
|
+ core = __simple_atoi(buf, NULL);
|
|
+
|
|
+ pair = (package << 32) | (core & 0xffffffffull);
|
|
+
|
|
+ for (unsigned int i = 0; i < pair_count; ++i)
|
|
+ {
|
|
+ if (pairs[i] == pair)
|
|
+ {
|
|
+ seen = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!seen)
|
|
+ pairs[pair_count++] = pair;
|
|
+
|
|
+ if (mask != NULL && darling_cpu_mask_has(mask, mask_bytes, cpu))
|
|
+ {
|
|
+ seen = false;
|
|
+ for (unsigned int i = 0; i < available_count; ++i)
|
|
+ {
|
|
+ if (available_pairs[i] == pair)
|
|
+ {
|
|
+ seen = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!seen)
|
|
+ available_pairs[available_count++] = pair;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (available_cores != NULL)
|
|
+ *available_cores = available_count;
|
|
+ return pair_count;
|
|
+}
|
|
+
|
|
+/* SMT estimate from cpu0's sibling list, used when the per-CPU scan fails. */
|
|
+static inline unsigned int darling_cpu_smt_estimate(unsigned int online)
|
|
+{
|
|
+ char buf[128];
|
|
+ unsigned int siblings;
|
|
+
|
|
+ if (!read_string("/sys/devices/system/cpu/cpu0/topology/thread_siblings_list", buf, sizeof(buf)))
|
|
+ return 0;
|
|
+
|
|
+ siblings = darling_cpu_count_list(buf);
|
|
+ if (siblings == 0 || online % siblings != 0)
|
|
+ return 0;
|
|
+ return online / siblings;
|
|
+}
|
|
+
|
|
+static inline void darling_cpu_topology_probe(struct darling_cpu_topology* topo)
|
|
+{
|
|
+ unsigned char mask[DARLING_CPU_MASK_BYTES];
|
|
+ char buf[512];
|
|
+ long mask_bytes;
|
|
+
|
|
+ *topo = (struct darling_cpu_topology) { 0 };
|
|
+
|
|
+ /* The A/B opt-out is a host flag file, like the other darling-lol opt-ins. */
|
|
+ if (LINUX_SYSCALL(__NR_access, DARLING_CPU_FIXED_FLAG, 0) == 0)
|
|
+ {
|
|
+ topo->fixed = true;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (read_string("/sys/devices/system/cpu/online", buf, sizeof(buf)))
|
|
+ topo->online = darling_cpu_count_list(buf);
|
|
+ else if (read_string("/sys/devices/system/cpu/present", buf, sizeof(buf)))
|
|
+ topo->online = darling_cpu_count_list(buf);
|
|
+
|
|
+ mask_bytes = LINUX_SYSCALL(__NR_sched_getaffinity, 0, (long)sizeof(mask), mask);
|
|
+ if (mask_bytes > 0)
|
|
+ {
|
|
+ if (mask_bytes > (long)sizeof(mask))
|
|
+ mask_bytes = (long)sizeof(mask);
|
|
+ topo->available = darling_cpu_mask_count(mask, mask_bytes);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ mask_bytes = 0;
|
|
+ }
|
|
+
|
|
+ if (topo->online == 0)
|
|
+ topo->online = topo->available;
|
|
+ if (topo->available == 0)
|
|
+ topo->available = topo->online;
|
|
+ if (topo->online == 0)
|
|
+ return; /* detection failed: the caller keeps the fixed 8s */
|
|
+
|
|
+ if (topo->available > topo->online)
|
|
+ topo->available = topo->online;
|
|
+
|
|
+ topo->physical_online = darling_cpu_scan_cores(NULL, 0, topo->online, NULL);
|
|
+ if (topo->physical_online == 0)
|
|
+ topo->physical_online = darling_cpu_smt_estimate(topo->online);
|
|
+ /* libmalloc divides logical by physical and only accepts ratios 1/2/4. */
|
|
+ if (topo->physical_online == 0 || topo->online % topo->physical_online != 0)
|
|
+ topo->physical_online = topo->online;
|
|
+
|
|
+ if (mask_bytes > 0)
|
|
+ {
|
|
+ unsigned int available_cores = 0;
|
|
+ unsigned int total = darling_cpu_scan_cores(mask, mask_bytes, topo->online, &available_cores);
|
|
+
|
|
+ topo->physical_available = (total > 0) ? available_cores : topo->physical_online;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ topo->physical_available = topo->physical_online;
|
|
+ }
|
|
+ if (topo->physical_available > topo->physical_online)
|
|
+ topo->physical_available = topo->physical_online;
|
|
+
|
|
+ topo->detected = true;
|
|
+}
|
|
+
|
|
+/* Cached for the life of the process; the probe runs at most once per copy. */
|
|
+static inline const struct darling_cpu_topology* darling_host_cpu_topology(void)
|
|
+{
|
|
+ static struct darling_cpu_topology cached;
|
|
+ static int decided;
|
|
+
|
|
+ if (__atomic_load_n(&decided, __ATOMIC_ACQUIRE) == 0)
|
|
+ {
|
|
+ struct darling_cpu_topology probe;
|
|
+
|
|
+ darling_cpu_topology_probe(&probe);
|
|
+ cached = probe;
|
|
+ __atomic_store_n(&decided, 1, __ATOMIC_RELEASE);
|
|
+ }
|
|
+
|
|
+ return &cached;
|
|
+}
|
|
+
|
|
+#endif // DARLING_COMMON_CPU_TOPOLOGY_H
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/common/private_mutex.h b/darling/src/libsystem_kernel/emulation/include/common/private_mutex.h
|
|
new file mode 100644
|
|
index 0000000..5379dd4
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/common/private_mutex.h
|
|
@@ -0,0 +1,20 @@
|
|
+#ifndef DARLING_EMULATION_PRIVATE_MUTEX_H
|
|
+#define DARLING_EMULATION_PRIVATE_MUTEX_H
|
|
+#include <darling/emulation/linux_premigration/ext/futex.h>
|
|
+/* Bootstrap-safe private mutex. Kernel emulation must not export placeholder
|
|
+ * os_unfair_lock functions: umbrella lookup can bind app calls to those stubs
|
|
+ * instead of libplatform, silently removing allocator mutual exclusion. */
|
|
+typedef struct { int value; } emulation_mutex;
|
|
+#define EMULATION_MUTEX_INITIALIZER {0}
|
|
+static inline void emulation_mutex_lock(emulation_mutex *mutex) {
|
|
+ int expected = 0;
|
|
+ if (__atomic_compare_exchange_n(&mutex->value, &expected, 1, 0,
|
|
+ __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) return;
|
|
+ while (__atomic_exchange_n(&mutex->value, 2, __ATOMIC_ACQUIRE) != 0)
|
|
+ __linux_futex_reterr(&mutex->value, FUTEX_WAIT | 128, 2, 0, 0, 0);
|
|
+}
|
|
+static inline void emulation_mutex_unlock(emulation_mutex *mutex) {
|
|
+ if (__atomic_exchange_n(&mutex->value, 0, __ATOMIC_RELEASE) == 2)
|
|
+ __linux_futex_reterr(&mutex->value, FUTEX_WAKE | 128, 1, 0, 0, 0);
|
|
+}
|
|
+#endif
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/common/slowwait.h b/darling/src/libsystem_kernel/emulation/include/common/slowwait.h
|
|
new file mode 100644
|
|
index 0000000..c310159
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/common/slowwait.h
|
|
@@ -0,0 +1,55 @@
|
|
+#ifndef _DARLING_SLOWWAIT_H_
|
|
+#define _DARLING_SLOWWAIT_H_
|
|
+/* darling-lol diagnostic: with /tmp/metal-debug-timing present, log main-thread blocking waits over 400 ms. */
|
|
+#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
+
|
|
+/* darling-lol diagnostic: with /tmp/metal-debug-timing present, log mach semaphore waits that block for more than
|
|
+ * 400 ms, with the caller's frame-pointer chain, to /tmp/semwait.log. */
|
|
+static inline int slowwait_enabled(void)
|
|
+{
|
|
+ static int state = -1;
|
|
+ if (state < 0) {
|
|
+ state = LINUX_SYSCALL(__NR_access, "/tmp/metal-debug-timing", 0) == 0 ? 1 : 0;
|
|
+ }
|
|
+ return state;
|
|
+}
|
|
+
|
|
+static inline unsigned long long slowwait_now(void)
|
|
+{
|
|
+ struct { long tv_sec; long tv_nsec; } ts;
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1, &ts);
|
|
+ return (unsigned long long) ts.tv_sec * 1000000000ULL + (unsigned long long) ts.tv_nsec;
|
|
+}
|
|
+
|
|
+static inline void slowwait_log(const char* kind, unsigned int name, unsigned long long start, void** frame, unsigned int extra1, unsigned int extra2)
|
|
+{
|
|
+ unsigned long long elapsed = slowwait_now() - start;
|
|
+ /* only the main thread (tid == pid), the frame loop; other threads idle in these waits by design */
|
|
+ if (elapsed < 400ULL * 1000000ULL || LINUX_SYSCALL(__NR_gettid) != LINUX_SYSCALL(__NR_getpid)) {
|
|
+ return;
|
|
+ }
|
|
+ char line[512];
|
|
+ int n = __simple_sprintf(line, "%s name=%u x1=%x x2=%u ms=%llu tid=%d frames:", kind, name, extra1, extra2, elapsed / 1000000ULL, (int) LINUX_SYSCALL(__NR_gettid));
|
|
+ void** fp = frame;
|
|
+ for (int i = 0; i < 10 && fp && ((unsigned long) fp & 7) == 0; ++i) {
|
|
+ void* ret = fp[1];
|
|
+ n += __simple_sprintf(line + n, " %lx", (unsigned long) ret);
|
|
+ void** next = (void**) fp[0];
|
|
+ if (next <= fp || (unsigned long) next - (unsigned long) fp > 0x100000) {
|
|
+ break;
|
|
+ }
|
|
+ fp = next;
|
|
+ }
|
|
+ n += __simple_sprintf(line + n, "\n");
|
|
+ long fd = LINUX_SYSCALL(__NR_open, "/tmp/semwait.log", 02101 /* O_WRONLY|O_CREAT|O_APPEND */, 0666);
|
|
+ if (fd >= 0) {
|
|
+ LINUX_SYSCALL(__NR_write, fd, line, n);
|
|
+ LINUX_SYSCALL(__NR_close, fd);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+#endif
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/conversion/xattr/name.h b/darling/src/libsystem_kernel/emulation/include/conversion/xattr/name.h
|
|
new file mode 100644
|
|
index 0000000..976e5d0
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/conversion/xattr/name.h
|
|
@@ -0,0 +1,69 @@
|
|
+#ifndef DARLING_CONVERSION_XATTR_NAME
|
|
+#define DARLING_CONVERSION_XATTR_NAME
|
|
+
|
|
+#include <darling/emulation/conversion/errno.h>
|
|
+
|
|
+/*
|
|
+ * darling-lol: macOS xattr names have no namespace prefix
|
|
+ * (e.g. "com.apple.quarantine", "org.chromium.crashpad.database.initialized").
|
|
+ * Linux requires the "user." namespace for names an unprivileged process may
|
|
+ * set/get, and returns ENOTSUP (95) otherwise. Darwin's own xattr layer is
|
|
+ * namespace-less, so translate on the way in/out.
|
|
+ *
|
|
+ * Rules:
|
|
+ * - "" (Darwin: list/remove all) is passed through unchanged.
|
|
+ * - names already carrying a Linux namespace ("user.", "trusted.",
|
|
+ * "system.", "security.") are passed through (avoid double-prefixing).
|
|
+ * - everything else gets "user." prepended.
|
|
+ *
|
|
+ * OUT must have room for 5 extra bytes + NUL. Returns `name` or `out`.
|
|
+ */
|
|
+static inline const char* xattr_name_to_linux(const char* name, char* out, unsigned long outsz)
|
|
+{
|
|
+ unsigned long i = 0;
|
|
+
|
|
+ if (name == 0 || name[0] == '\0')
|
|
+ return name;
|
|
+
|
|
+ if (__builtin_strncmp(name, "user.", 5) == 0 ||
|
|
+ __builtin_strncmp(name, "trusted.", 8) == 0 ||
|
|
+ __builtin_strncmp(name, "system.", 7) == 0 ||
|
|
+ __builtin_strncmp(name, "security.", 9) == 0)
|
|
+ return name;
|
|
+
|
|
+ if (out == 0 || outsz < 6)
|
|
+ return name;
|
|
+
|
|
+ out[0] = 'u'; out[1] = 's'; out[2] = 'e'; out[3] = 'r'; out[4] = '.';
|
|
+ for (i = 0; name[i] != '\0' && i + 5 < outsz - 1; i++)
|
|
+ out[i + 5] = name[i];
|
|
+ out[i + 5] = '\0';
|
|
+ return out;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Reverse: strip a leading "user." when returning names to Darwin callers
|
|
+ * (listxattr). Mutates `name` in place (the buffer is caller-owned and the
|
|
+ * caller only needs a valid NUL-terminated name at the same address).
|
|
+ * Returns the (possibly advanced) pointer for convenience.
|
|
+ */
|
|
+static inline char* xattr_name_from_linux(char* name)
|
|
+{
|
|
+ if (name != 0 && __builtin_strncmp(name, "user.", 5) == 0)
|
|
+ return name + 5;
|
|
+ return name;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Linux reports a missing attribute as ENODATA; Darwin reports ENOATTR (93),
|
|
+ * which callers test for to tell "no such attribute" from a real failure.
|
|
+ * LINUX_ERRNO is the negative value returned by the Linux xattr syscall.
|
|
+ */
|
|
+static inline long xattr_errno_to_bsd(long linux_errno)
|
|
+{
|
|
+ if (linux_errno == -61 /* LINUX_ENODATA */)
|
|
+ return -93 /* ENOATTR */;
|
|
+ return errno_linux_to_bsd(linux_errno);
|
|
+}
|
|
+
|
|
+#endif /* DARLING_CONVERSION_XATTR_NAME */
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/linux_premigration/resources/dserver-rpc-defs.h b/darling/src/libsystem_kernel/emulation/include/linux_premigration/resources/dserver-rpc-defs.h
|
|
index f6588cc..c60d8f9 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/include/linux_premigration/resources/dserver-rpc-defs.h
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/linux_premigration/resources/dserver-rpc-defs.h
|
|
@@ -12,6 +12,7 @@
|
|
#include <darling/emulation/linux_premigration/elfcalls_wrapper.h>
|
|
#include <darling/emulation/common/simple.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/signal/sigprocmask.h>
|
|
+#include <darling/emulation/conversion/signal/duct_signals.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/unistd/close.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
@@ -75,7 +76,67 @@ struct linux_sockaddr_un {
|
|
|
|
#define dserver_rpc_hooks_memcpy memcpy
|
|
|
|
+#include <darling/emulation/common/slowwait.h>
|
|
+
|
|
+/* darling-lol diagnostic (/tmp/metal-debug-timing): time main-thread RPCs to darlingserver */
|
|
+static int slowrpc_pending_call = -1;
|
|
+static unsigned long long slowrpc_start = 0;
|
|
+
|
|
+/* darling-lol diagnostic (host file /tmp/rclol-rpc-trace): every 16th darlingserver request from a process's main thread is
|
|
+ * appended to /tmp/rclol-rpc-trace.log with its call number and up to 10 caller addresses (frame-pointer walk). Offline,
|
|
+ * the addresses are resolved with /proc/<pid>/maps to attribute the ~40 round trips per frame to code. */
|
|
+static int rclol_rpc_trace_state = -1;
|
|
+static unsigned long rclol_rpc_trace_count = 0;
|
|
+
|
|
+static void rclol_rpc_trace_sample(const dserver_rpc_hooks_msghdr_t* message, void** frame) {
|
|
+ char line[400];
|
|
+ int n, call;
|
|
+ if (rclol_rpc_trace_state < 0)
|
|
+ rclol_rpc_trace_state = LINUX_SYSCALL(__NR_access, "/tmp/rclol-rpc-trace", 0) == 0;
|
|
+ if (!rclol_rpc_trace_state || message == 0 || message->msg_iov == 0 || message->msg_iovlen < 1 || message->msg_iov->iov_base == 0 ||
|
|
+ message->msg_iov->iov_len < sizeof(int))
|
|
+ return;
|
|
+ if (LINUX_SYSCALL(__NR_gettid) != LINUX_SYSCALL(__NR_getpid))
|
|
+ return;
|
|
+ if ((__atomic_add_fetch(&rclol_rpc_trace_count, 1, __ATOMIC_RELAXED) & 15) != 0)
|
|
+ return;
|
|
+ call = *(const int*) message->msg_iov->iov_base;
|
|
+ n = __simple_sprintf(line, "rpc pid=%d call=%d frames:", (int) LINUX_SYSCALL(__NR_getpid), call & 0xffff);
|
|
+ for (int i = 0; i < 10 && frame && ((unsigned long) frame & 7) == 0; ++i) {
|
|
+ void** next = (void**) frame[0];
|
|
+ n += __simple_sprintf(line + n, " %lx", (unsigned long) frame[1]);
|
|
+ if (next <= frame || (unsigned long) next - (unsigned long) frame > 0x100000)
|
|
+ break;
|
|
+ frame = next;
|
|
+ }
|
|
+ line[n++] = '\n';
|
|
+ line[n] = 0;
|
|
+ long fd = LINUX_SYSCALL(__NR_open, "/tmp/rclol-rpc-trace.log", 02101, 0666);
|
|
+ if (fd >= 0) {
|
|
+ LINUX_SYSCALL(__NR_write, fd, line, n);
|
|
+ LINUX_SYSCALL(__NR_close, fd);
|
|
+ }
|
|
+}
|
|
+
|
|
static long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hooks_msghdr_t* message) {
|
|
+ rclol_rpc_trace_sample(message, (void**) __builtin_frame_address(0));
|
|
+ if (slowwait_enabled() && LINUX_SYSCALL(__NR_gettid) == LINUX_SYSCALL(__NR_getpid) &&
|
|
+ message != 0 && message->msg_iov != 0 && message->msg_iovlen > 0 && message->msg_iov->iov_base != 0 && message->msg_iov->iov_len >= sizeof(int)) {
|
|
+ slowrpc_pending_call = *(const int*) message->msg_iov->iov_base;
|
|
+ slowrpc_start = slowwait_now();
|
|
+ }
|
|
+#if 0 /* darling-lol M58F145: disabled (was a hot per-RPC stdout flood that
|
|
+ * slowed the Riot launcher's update into its 30 s no-progress timeout) */
|
|
+ if (message != 0 && message->msg_iov != 0 && message->msg_iovlen > 0 &&
|
|
+ message->msg_iov->iov_base != 0 && message->msg_iov->iov_len >= sizeof(int)) {
|
|
+ static unsigned long m58ar_sn = 0;
|
|
+ if (m58ar_sn++ < 1000000) {
|
|
+ int callnum = *(const int*) message->msg_iov->iov_base;
|
|
+ __simple_printf("M58AR send tid=%d sock=%d call=%d\n",
|
|
+ (int) dserver_rpc_hooks_get_tid(), socket, callnum);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
#ifdef __NR_socketcall
|
|
return LINUX_SYSCALL(__NR_socketcall, LINUX_SYS_SENDMSG, ((long[6]) { socket, message, 0 }));
|
|
#else
|
|
@@ -83,19 +144,108 @@ static long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hoo
|
|
#endif
|
|
};
|
|
|
|
+/* darling-lol: wait for the reply by polling for a short time before sleeping (main thread only).
|
|
+ *
|
|
+ * Most requests are answered within tens of microseconds, but the caller went to sleep in recvmsg at once, so every
|
|
+ * round trip also paid for descheduling the thread, idling the core and waking it again. The game's main thread makes
|
|
+ * ~40-50 round trips per frame (mutex and condition-variable calls, run-loop port operations, timers). Polling the
|
|
+ * socket for a bounded time catches the quick replies without that sleep; a reply that takes longer (a real blocking
|
|
+ * wait) falls back to the normal blocking receive.
|
|
+ * Host file /tmp/rclol-rpc-spin-us holds the polling budget in microseconds (0 or no file = off), re-read every 4096
|
|
+ * main-thread receives so both settings can be compared inside one match. */
|
|
+static int rclol_spin_main_socket = -2; /* -2: main thread not identified yet */
|
|
+static unsigned long rclol_spin_ticks = 0; /* TSC ticks to poll for; 0 = off */
|
|
+static unsigned long rclol_spin_calls = 0;
|
|
+static unsigned long rclol_spin_hits = 0, rclol_spin_misses = 0;
|
|
+
|
|
+static void rclol_spin_refresh(void) {
|
|
+ char digits[16];
|
|
+ long value = 0;
|
|
+ long fd = LINUX_SYSCALL(__NR_open, "/tmp/rclol-rpc-spin-us", 0, 0);
|
|
+ if (fd >= 0) {
|
|
+ long n = LINUX_SYSCALL(__NR_read, fd, digits, sizeof(digits) - 1);
|
|
+ LINUX_SYSCALL(__NR_close, fd);
|
|
+ for (long i = 0; i < n && digits[i] >= '0' && digits[i] <= '9'; ++i) value = value * 10 + (digits[i] - '0');
|
|
+ if (value > 500) value = 500;
|
|
+ }
|
|
+ /* the TSC runs at a fixed rate near the nominal clock; ~2300 ticks per microsecond on this machine. A wrong
|
|
+ * guess only changes the budget, not the result. */
|
|
+ rclol_spin_ticks = (unsigned long) value * 2300ul;
|
|
+ if (value > 0) {
|
|
+ /* counters for the A/B: how often the reply arrived while polling */
|
|
+ char line[120];
|
|
+ int len = __simple_sprintf(line, "spin_us=%ld hits=%lu misses=%lu\n", value, rclol_spin_hits, rclol_spin_misses);
|
|
+ long log = LINUX_SYSCALL(__NR_open, "/tmp/rclol-rpc-spin.log", 02101, 0666);
|
|
+ if (log >= 0) { LINUX_SYSCALL(__NR_write, log, line, len); LINUX_SYSCALL(__NR_close, log); }
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline unsigned long rclol_rdtsc(void) {
|
|
+ unsigned int lo, hi;
|
|
+ __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
|
+ return ((unsigned long) hi << 32) | lo;
|
|
+}
|
|
+
|
|
static long int dserver_rpc_hooks_receive_message(int socket, dserver_rpc_hooks_msghdr_t* out_message) {
|
|
long int ret = 0;
|
|
|
|
retry:
|
|
+#if defined(__x86_64__) && !defined(__NR_socketcall)
|
|
+ if (rclol_spin_main_socket == -2) {
|
|
+ if (LINUX_SYSCALL(__NR_gettid) == LINUX_SYSCALL(__NR_getpid)) {
|
|
+ rclol_spin_main_socket = socket;
|
|
+ rclol_spin_refresh();
|
|
+ }
|
|
+ }
|
|
+ if (socket == rclol_spin_main_socket) {
|
|
+ if ((++rclol_spin_calls & 4095) == 0)
|
|
+ rclol_spin_refresh();
|
|
+ if (rclol_spin_ticks != 0) {
|
|
+ const unsigned long deadline = rclol_rdtsc() + rclol_spin_ticks;
|
|
+ for (;;) {
|
|
+ ret = LINUX_SYSCALL(__NR_recvmsg, socket, out_message, 0x40 /* MSG_DONTWAIT */);
|
|
+ if (ret != -11 /* EAGAIN */) {
|
|
+ ++rclol_spin_hits;
|
|
+ goto received;
|
|
+ }
|
|
+ if (rclol_rdtsc() >= deadline)
|
|
+ break;
|
|
+ for (int i = 0; i < 24; ++i) __asm__ volatile("pause");
|
|
+ }
|
|
+ ++rclol_spin_misses;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
#ifdef __NR_socketcall
|
|
ret = LINUX_SYSCALL(__NR_socketcall, LINUX_SYS_RECVMSG, ((long[6]) { socket, out_message, 0 }));
|
|
#else
|
|
ret = LINUX_SYSCALL(__NR_recvmsg, socket, out_message, 0);
|
|
+#endif
|
|
+#if defined(__x86_64__) && !defined(__NR_socketcall)
|
|
+received:
|
|
#endif
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
+#if 0 /* darling-lol M58F145: disabled (hot per-RPC stdout flood; see above) */
|
|
+ if (ret >= (long int) sizeof(int) && out_message->msg_iov != 0 &&
|
|
+ out_message->msg_iov->iov_base != 0) {
|
|
+ static unsigned long m58ar_rn = 0;
|
|
+ if (m58ar_rn++ < 1000000) {
|
|
+ int m58ar_rcall = *(const int*) out_message->msg_iov->iov_base;
|
|
+ __simple_printf("M58AR recv tid=%d sock=%d call=%d\n",
|
|
+ (int) dserver_rpc_hooks_get_tid(), socket, m58ar_rcall);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (slowrpc_pending_call >= 0 && ret >= (long int) sizeof(int) && out_message->msg_iov != 0 && out_message->msg_iov->iov_base != 0 &&
|
|
+ *(const int*) out_message->msg_iov->iov_base != 0x52cca11 && LINUX_SYSCALL(__NR_gettid) == LINUX_SYSCALL(__NR_getpid)) {
|
|
+ slowwait_log("rpc", (unsigned int) slowrpc_pending_call, slowrpc_start, (void**) __builtin_frame_address(0), 0, 0);
|
|
+ slowrpc_pending_call = -1;
|
|
+ }
|
|
+
|
|
if (ret >= sizeof(dserver_s2c_callhdr_t)) {
|
|
dserver_s2c_callhdr_t* callhdr = out_message->msg_iov->iov_base;
|
|
|
|
@@ -280,16 +430,21 @@ __attribute__((format(printf, 1, 2)))
|
|
void __dserver_rpc_hooks_printf(const char* format, ...);
|
|
#define dserver_rpc_hooks_printf __dserver_rpc_hooks_printf
|
|
|
|
-#define dserver_rpc_hooks_atomic_save_t sigset_t
|
|
+#define dserver_rpc_hooks_atomic_save_t linux_sigset_t
|
|
|
|
static void dserver_rpc_hooks_atomic_begin(dserver_rpc_hooks_atomic_save_t* atomic_save) {
|
|
- // block standard unix signals (not real-time signals, though)
|
|
- sigset_t set = 0x7fffffff;
|
|
- sys_sigprocmask(SIG_BLOCK, &set, atomic_save);
|
|
+ /* Block standard signals, leaving real-time S2C signals available. The
|
|
+ * kernel ignores SIGKILL/SIGSTOP. Keep the original full native mask:
|
|
+ * converting it through a BSD mask loses unsupported real-time bits and
|
|
+ * needlessly scans/converts three masks on every RPC round trip. */
|
|
+ linux_sigset_t set = 0x7fffffff;
|
|
+ LINUX_SYSCALL(__NR_rt_sigprocmask, 0 /* SIG_BLOCK */, &set,
|
|
+ atomic_save, sizeof(set));
|
|
};
|
|
|
|
static void dserver_rpc_hooks_atomic_end(dserver_rpc_hooks_atomic_save_t* atomic_save) {
|
|
- sys_sigprocmask(SIG_SETMASK, atomic_save, NULL);
|
|
+ LINUX_SYSCALL(__NR_rt_sigprocmask, 2 /* SIG_SETMASK */, atomic_save,
|
|
+ NULL, sizeof(*atomic_save));
|
|
};
|
|
|
|
#define dserver_rpc_hooks_get_interrupt_status() (-LINUX_EINTR)
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/guarded/change_fdguard_np.h b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/guarded/change_fdguard_np.h
|
|
new file mode 100644
|
|
index 0000000..ac5a0e6
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/guarded/change_fdguard_np.h
|
|
@@ -0,0 +1,8 @@
|
|
+#ifndef LINUX_CHANGE_FDGUARD_H
|
|
+#define LINUX_CHANGE_FDGUARD_H
|
|
+
|
|
+#include <stdint.h>
|
|
+
|
|
+long sys_change_fdguard_np(int fd, uint64_t* guard, uint64_t* nguard, unsigned guardflags, int* fdflagsp);
|
|
+
|
|
+#endif // LINUX_CHANGE_FDGUARD_H
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/mac_syscall.h b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/mac_syscall.h
|
|
new file mode 100644
|
|
index 0000000..b4c307a
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/mac_syscall.h
|
|
@@ -0,0 +1,6 @@
|
|
+#ifndef LINUX_MAC_SYSCALL_H
|
|
+#define LINUX_MAC_SYSCALL_H
|
|
+
|
|
+long sys___mac_syscall(const char* policy, int call, void* arg);
|
|
+
|
|
+#endif // LINUX_MAC_SYSCALL_H
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/ptrace.h b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/ptrace.h
|
|
index 5d4beb5..9d5de46 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/ptrace.h
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/ptrace.h
|
|
@@ -9,6 +9,7 @@
|
|
#define XNU_PT_DETACH 11
|
|
#define XNU_PT_SIGEXC 12
|
|
#define XNU_PT_THUPDATE 13
|
|
+#define XNU_PT_DENY_ATTACH 31
|
|
|
|
long sys_ptrace(int request, int pid, void* addr, int data);
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/psynch/ulock_wait.h b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/psynch/ulock_wait.h
|
|
index 90bc146..5bcea21 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/psynch/ulock_wait.h
|
|
+++ b/darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/psynch/ulock_wait.h
|
|
@@ -5,6 +5,8 @@
|
|
|
|
long sys_ulock_wait(uint32_t operation, void* addr, uint64_t value, uint32_t timeout);
|
|
|
|
+long sys_ulock_wait2(uint32_t operation, void* addr, uint64_t value, uint64_t timeout, uint64_t value2);
|
|
+
|
|
#define XNU_UL_OPCODE_MASK 0xff
|
|
#define XNU_UL_COMPARE_AND_WAIT 1
|
|
#define XNU_UL_UNFAIR_LOCK 2
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/common/base.c b/darling/src/libsystem_kernel/emulation/src/common/base.c
|
|
index d077d04..3dcafea 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/common/base.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/common/base.c
|
|
@@ -23,6 +23,284 @@ int __linux_syscall(int nr, long a1, long a2, long a3, long a4, long a5, long a6
|
|
return linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
}
|
|
|
|
+/* darling-lol M58x: bounded per-thread trace of the host syscalls that can
|
|
+ * block. Every guest host-syscall goes through the LINUX_SYSCALL* macros,
|
|
+ * which now route here. We print the syscall number, its first argument
|
|
+ * (usually the fd) and the host tid for the first few calls of each thread
|
|
+ * and then sparsely, so a stalled run names the syscall each parked thread
|
|
+ * is in without flooding the log. */
|
|
+#define LOLSYS_SLOTS 256
|
|
+
|
|
+struct lolsys_slot {
|
|
+ int tid;
|
|
+ unsigned int count;
|
|
+};
|
|
+
|
|
+static struct lolsys_slot lolsys_slots[LOLSYS_SLOTS];
|
|
+
|
|
+static int lolsys_traced(int nr)
|
|
+{
|
|
+ switch (nr) {
|
|
+#ifdef __NR_read
|
|
+ case __NR_read: return 1;
|
|
+#endif
|
|
+#ifdef __NR_recvmsg
|
|
+ case __NR_recvmsg: return 1;
|
|
+#endif
|
|
+#ifdef __NR_recvfrom
|
|
+ case __NR_recvfrom: return 1;
|
|
+#endif
|
|
+#ifdef __NR_poll
|
|
+ case __NR_poll: return 1;
|
|
+#endif
|
|
+#ifdef __NR_select
|
|
+ case __NR_select: return 1;
|
|
+#endif
|
|
+#ifdef __NR_accept
|
|
+ case __NR_accept: return 1;
|
|
+#endif
|
|
+#ifdef __NR_connect
|
|
+ case __NR_connect: return 1;
|
|
+#endif
|
|
+#ifdef __NR_nanosleep
|
|
+ case __NR_nanosleep: return 1;
|
|
+#endif
|
|
+ /* darling-lol M58F73: widen the trace for the Riot launcher's early
|
|
+ * startup. The launcher dies with exit 252 before writing any log,
|
|
+ * after open()ing /proc/<pid>/mounts; these calls name the failing step
|
|
+ * (filesystem/space probes, sysctl, anti-debug). */
|
|
+#ifdef __NR_statfs
|
|
+ case __NR_statfs: return 1;
|
|
+#endif
|
|
+#ifdef __NR_fstatfs
|
|
+ case __NR_fstatfs: return 1;
|
|
+#endif
|
|
+#ifdef __NR_statvfs
|
|
+ case __NR_statvfs: return 1;
|
|
+#endif
|
|
+#ifdef __NR_fstatvfs
|
|
+ case __NR_fstatvfs: return 1;
|
|
+#endif
|
|
+#ifdef __NR_open
|
|
+ case __NR_open: return 1;
|
|
+#endif
|
|
+#ifdef __NR_openat
|
|
+ case __NR_openat: return 1;
|
|
+#endif
|
|
+#ifdef __NR__sysctl
|
|
+ case __NR__sysctl: return 1;
|
|
+#endif
|
|
+#ifdef __NR_sysctl
|
|
+ case __NR_sysctl: return 1;
|
|
+#endif
|
|
+#ifdef __NR_ptrace
|
|
+ case __NR_ptrace: return 1;
|
|
+#endif
|
|
+#ifdef __NR_access
|
|
+ case __NR_access: return 1;
|
|
+#endif
|
|
+#ifdef __NR_newfstatat
|
|
+ case __NR_newfstatat: return 1;
|
|
+#endif
|
|
+#ifdef __NR_fstat
|
|
+ case __NR_fstat: return 1;
|
|
+#endif
|
|
+#ifdef __NR_readlink
|
|
+ case __NR_readlink: return 1;
|
|
+#endif
|
|
+#ifdef __NR_exit
|
|
+ case __NR_exit: return 1;
|
|
+#endif
|
|
+#ifdef __NR_exit_group
|
|
+ case __NR_exit_group: return 1;
|
|
+#endif
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* darling-lol M58F76: for the path-bearing syscalls, return the pointer to the
|
|
+ * guest C string so the LOLSC line names the file/directory the launcher
|
|
+ * touches right before it exits. */
|
|
+static const char* __attribute__((unused)) lolsys_path(int nr, long a1, long a2)
|
|
+{
|
|
+ switch (nr) {
|
|
+#ifdef __NR_open
|
|
+ case __NR_open: return (const char*) a1;
|
|
+#endif
|
|
+#ifdef __NR_openat
|
|
+ case __NR_openat: return (const char*) a2; /* (dirfd, path, flags, mode) */
|
|
+#endif
|
|
+#ifdef __NR_readlink
|
|
+ case __NR_readlink: return (const char*) a1;
|
|
+#endif
|
|
+#ifdef __NR_statfs
|
|
+ case __NR_statfs: return (const char*) a1;
|
|
+#endif
|
|
+#ifdef __NR_access
|
|
+ case __NR_access: return (const char*) a1;
|
|
+#endif
|
|
+#ifdef __NR_newfstatat
|
|
+ case __NR_newfstatat: return (const char*) a2; /* (dirfd, path, stat, flags) */
|
|
+#endif
|
|
+ default: return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* darling-lol M58F121: microsecond timestamp (gettimeofday) shared with the
|
|
+ * dserver M58RD HANDOFF/QUEUED log clock, for wake-to-run correlation. */
|
|
+static long __attribute__((unused)) lolsrv_us(void)
|
|
+{
|
|
+ struct { long tv_sec; long tv_usec; } tv;
|
|
+ tv.tv_sec = 0;
|
|
+ tv.tv_usec = 0;
|
|
+ linux_syscall((long) &tv, 0, 0, 0, 0, 0, __NR_gettimeofday);
|
|
+ return tv.tv_sec * 1000000L + tv.tv_usec;
|
|
+}
|
|
+
|
|
+/* darling-lol M58F159: opt-in long-wait trace flag. Enabled from outside (the
|
|
+ * injected shim calls __lolsys_set_wake_trace(1)); the syscall wrapper itself
|
|
+ * must not call libc/getenv (M58F158: that wedged the container). */
|
|
+static int lolsys_wake_on = 0;
|
|
+
|
|
+VISIBLE
|
|
+void __lolsys_set_wake_trace(int on)
|
|
+{
|
|
+ lolsys_wake_on = on;
|
|
+}
|
|
+
|
|
+VISIBLE
|
|
+long __lolsys_syscall(long a1, long a2, long a3, long a4, long a5, long a6, int nr)
|
|
+{
|
|
+ /* Disabled diagnostics must not add another host syscall or update shared
|
|
+ * counters on every receive/wait. Keep the opt-in trace path below. */
|
|
+ if (!lolsys_wake_on)
|
|
+ return linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
+ long ret = 0;
|
|
+ /* darling-lol M58F166: flag-gated recvmsg rate/errno counter (no libc), to
|
|
+ * confirm the busy recvmsg loop independently of the SIGPROF profiler. */
|
|
+#ifdef __NR_recvmsg
|
|
+ if (lolsys_wake_on && nr == __NR_recvmsg) {
|
|
+ static unsigned long rn = 0, rok = 0, reintr = 0, reagain = 0, rother = 0, rlines = 0;
|
|
+ long rr = linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
+ rn++;
|
|
+ if (rr >= 0) rok++;
|
|
+ else if (rr == -4) reintr++;
|
|
+ else if (rr == -11) reagain++;
|
|
+ else rother++;
|
|
+ if ((rn & 4095) == 0 && rlines < 500) {
|
|
+ int rpid = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_getpid);
|
|
+ int rtid = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_gettid);
|
|
+ rlines++;
|
|
+ __simple_printf("LOLRCV pid=%d tid=%d fd=%ld n=%lu ok=%lu eintr=%lu eagain=%lu other=%lu\n",
|
|
+ rpid, rtid, a1, rn, rok, reintr, reagain, rother);
|
|
+ }
|
|
+ return rr;
|
|
+ }
|
|
+#endif
|
|
+ /* darling-lol M58F155: opt-in long-wait trace (RCLOL_WAKE=1). For the
|
|
+ * blocking waits that make up the browser main thread's runloop, log only
|
|
+ * waits longer than ~200 ms (low volume), so an intermittent multi-second
|
|
+ * loop stall names itself. Capped. */
|
|
+#ifdef __NR_recvmsg
|
|
+ if (lolsys_wake_on && (nr == __NR_recvmsg
|
|
+#ifdef __NR_poll
|
|
+ || nr == __NR_poll
|
|
+#endif
|
|
+#ifdef __NR_epoll_wait
|
|
+ || nr == __NR_epoll_wait
|
|
+#endif
|
|
+ )) {
|
|
+ static unsigned long lw_cap = 0;
|
|
+ int lw_tid = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_gettid);
|
|
+ long lw_t0 = lolsrv_us();
|
|
+ long lw_ret = linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
+ long lw_dt = lolsrv_us() - lw_t0;
|
|
+ if (lw_dt > 200000 && lw_cap++ < 5000) {
|
|
+ int lw_pid = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_getpid);
|
|
+ __simple_printf("LOLWAKE pid=%d tid=%d nr=%d fd=%ld dt_ms=%ld ret=%ld\n",
|
|
+ lw_pid, lw_tid, nr, a1, lw_dt / 1000, lw_ret);
|
|
+ }
|
|
+ return lw_ret;
|
|
+ }
|
|
+#endif
|
|
+ /* M58F121: timestamp slot-port mach receives (fd >= 524000) to measure the
|
|
+ * guest-side wake-to-run; correlate with dserver M58RD HANDOFF on the same
|
|
+ * port. Bounded per thread. */
|
|
+#if 0 /* darling-lol M58F145: disabled (hot mach-recv stdout flood; see M58F140/141) */
|
|
+#ifdef __NR_recvmsg
|
|
+ if (nr == __NR_recvmsg && a1 >= 524000) {
|
|
+ int tid2 = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_gettid);
|
|
+ struct lolsys_slot* s2 = &lolsys_slots[((unsigned int) tid2) & (LOLSYS_SLOTS - 1)];
|
|
+ if (s2->tid != tid2) {
|
|
+ s2->tid = tid2;
|
|
+ s2->count = 0;
|
|
+ }
|
|
+ ret = linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
+ if (s2->count < 512) {
|
|
+ int pid2 = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_getpid);
|
|
+ long t = lolsrv_us();
|
|
+ __simple_printf("LOLSRV pid=%d tid=%d fd=%ld sec=%ld usec=%ld ret=%ld\n",
|
|
+ pid2, tid2, a1, t / 1000000L, t % 1000000L, ret);
|
|
+ }
|
|
+ s2->count++;
|
|
+ return ret;
|
|
+ }
|
|
+#endif
|
|
+#endif
|
|
+ if (lolsys_traced(nr)) {
|
|
+ int tid = (int) linux_syscall(0, 0, 0, 0, 0, 0, __NR_gettid);
|
|
+ struct lolsys_slot* s = &lolsys_slots[((unsigned int) tid) & (LOLSYS_SLOTS - 1)];
|
|
+ if (s->tid != tid) {
|
|
+ s->tid = tid;
|
|
+ s->count = 0;
|
|
+ }
|
|
+ int log_it = (s->count < 3) || ((s->count & 255) == 0);
|
|
+ /* M58F73: the "naming" calls (fs probes / open / sysctl / ptrace) are
|
|
+ * logged every time so the launcher's exact early sequence is visible;
|
|
+ * exit/exit_group are logged so the exact exit code is captured. */
|
|
+ switch (nr) {
|
|
+#ifdef __NR_statfs
|
|
+ case __NR_statfs: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_fstatfs
|
|
+ case __NR_fstatfs: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_open
|
|
+ case __NR_open: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_openat
|
|
+ case __NR_openat: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR__sysctl
|
|
+ case __NR__sysctl: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_ptrace
|
|
+ case __NR_ptrace: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_newfstatat
|
|
+ case __NR_newfstatat: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_exit
|
|
+ case __NR_exit: log_it = 1; break;
|
|
+#endif
|
|
+#ifdef __NR_exit_group
|
|
+ case __NR_exit_group: log_it = 1; break;
|
|
+#endif
|
|
+ default: break;
|
|
+ }
|
|
+ if (log_it) {
|
|
+ /* darling-lol M58F145: LOLSC stdout logging disabled (hot; it
|
|
+ * flooded the launcher's stdout). Keep the syscall + counter. */
|
|
+ ret = linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
+ s->count++;
|
|
+ return ret;
|
|
+ }
|
|
+ s->count++;
|
|
+ }
|
|
+ return linux_syscall(a1, a2, a3, a4, a5, a6, nr);
|
|
+}
|
|
+
|
|
#ifdef __TESTING
|
|
void _start()
|
|
{
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/clock_gettime.c b/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/clock_gettime.c
|
|
index f27a888..1bbfc73 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/clock_gettime.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/clock_gettime.c
|
|
@@ -3,13 +3,334 @@
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
+#include <elfcalls.h>
|
|
+
|
|
extern long cerror(int __err);
|
|
|
|
+extern struct elf_calls* _elfcalls;
|
|
+
|
|
+#if defined(__x86_64__)
|
|
+/* darling-lol: read the clock through the host vDSO.
|
|
+ *
|
|
+ * The guest process is a native Linux process, so the kernel's vDSO is mapped
|
|
+ * and its __vdso_clock_gettime can be called directly - the same ELF bridge
|
|
+ * libmalloc uses for __vdso_getcpu. A vDSO read uses the same clocksource as
|
|
+ * the syscall, so no calibration and no /tmp opt-in are involved.
|
|
+ *
|
|
+ * The pointer is resolved once per process. Resolution runs dlopen/dlsym
|
|
+ * through the native loader, so it is also done eagerly from mach_driver_init()
|
|
+ * where the ELF bridge is known to be usable; the steady-state path is one
|
|
+ * atomic load and an indirect call, which keeps clock reads from signal
|
|
+ * handlers and RPC-guarded callers safe. A missing bridge is not cached: the
|
|
+ * next call retries. */
|
|
+static void* vdso_clock_gettime_fn; /* 0 unresolved, -1 absent, else the vDSO entry */
|
|
+
|
|
+/* Some glibc versions expose the __vdso_* entries only through the LINUX_2.6
|
|
+ * symbol version (libmalloc's __vdso_getcpu lookup uses dlvsym for the same
|
|
+ * reason). */
|
|
+static void* vdso_symbol(struct elf_calls* calls, void* vdso, const char* name, const char* version)
|
|
+{
|
|
+ void* function = calls->dlsym(vdso, name);
|
|
+ if (function == 0 && version != 0)
|
|
+ {
|
|
+ void* libc = calls->dlopen("libc.so.6");
|
|
+ void* (*dlvsym)(void*, const char*, const char*) = libc ? calls->dlsym(libc, "dlvsym") : 0;
|
|
+ if (dlvsym != 0)
|
|
+ function = dlvsym(vdso, name, version);
|
|
+ }
|
|
+ return function;
|
|
+}
|
|
+
|
|
+static void* vdso_clock_gettime_resolve(void)
|
|
+{
|
|
+ void* function = __atomic_load_n(&vdso_clock_gettime_fn, __ATOMIC_ACQUIRE);
|
|
+ struct elf_calls* calls;
|
|
+
|
|
+ if (function != 0)
|
|
+ return function == (void*) -1 ? 0 : function;
|
|
+
|
|
+ calls = _elfcalls;
|
|
+ if (calls == 0 || calls->dlopen == 0 || calls->dlsym == 0)
|
|
+ return 0; /* the bridge is not up yet; retry on the next call */
|
|
+
|
|
+ if (!__atomic_compare_exchange_n(&vdso_clock_gettime_fn, &function, (void*) -1, 0,
|
|
+ __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE))
|
|
+ return function == (void*) -1 ? 0 : function;
|
|
+
|
|
+ {
|
|
+ void* vdso = calls->dlopen("linux-vdso.so.1");
|
|
+ void* resolved = vdso ? vdso_symbol(calls, vdso, "__vdso_clock_gettime", "LINUX_2.6") : 0;
|
|
+ __atomic_store_n(&vdso_clock_gettime_fn, resolved ? resolved : (void*) -1, __ATOMIC_RELEASE);
|
|
+ return resolved;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* The guest syscall trampoline does not promise ELF stack alignment (sys_fork
|
|
+ * realigns for the same reason) and the vDSO is System V ABI code: realign the
|
|
+ * stack for the call. noinline keeps the realignment when this is inlined. */
|
|
+__attribute__((noinline))
|
|
+__attribute__((force_align_arg_pointer))
|
|
+static int vdso_clock_gettime_call(void* function, int clockid, struct timespec* res)
|
|
+{
|
|
+ return ((int (*)(int, struct timespec*)) function)(clockid, res);
|
|
+}
|
|
+
|
|
+/* 0: *res filled by the vDSO; 1: no vDSO answer, the caller must fall back */
|
|
+__attribute__((visibility("hidden")))
|
|
+int __darling_vdso_clock_gettime(int clockid, struct timespec* res)
|
|
+{
|
|
+ void* function = vdso_clock_gettime_resolve();
|
|
+
|
|
+ if (function == 0)
|
|
+ return 1;
|
|
+ if (vdso_clock_gettime_call(function, clockid, res) != 0)
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Resolve before the first clock read can happen in a signal handler or with
|
|
+ * the RPC signal mask held. Called from mach_driver_init(). */
|
|
+__attribute__((visibility("hidden")))
|
|
+void __darling_vdso_clock_init(void)
|
|
+{
|
|
+ vdso_clock_gettime_resolve();
|
|
+}
|
|
+#endif /* __x86_64__ */
|
|
+
|
|
+#if defined(__x86_64__)
|
|
+/* darling-lol: user-space CLOCK_MONOTONIC / CLOCK_REALTIME fallback.
|
|
+ *
|
|
+ * The host vDSO is tried first (see above); this path is the fallback for a host whose vDSO cannot serve
|
|
+ * the clock (for example an HPET kernel clocksource where the vDSO falls back to the syscall as well and
|
|
+ * one read costs ~1.4 us), and the League main thread makes ~200 clock reads per frame.
|
|
+ *
|
|
+ * Each core's TSC still counts at one constant rate; only the offsets between cores differ. RDTSCP returns the TSC and
|
|
+ * the CPU it was read on in one instruction, so the clock is kept per CPU: a (TSC, monotonic ns) pair sampled with a
|
|
+ * real syscall, extrapolated for at most FC_WINDOW_NS and then sampled again. The tick rate is measured between such
|
|
+ * samples of one CPU. Results never go backwards (the sampling error between two cores is about a microsecond).
|
|
+ * OPT-IN: only with the host file /tmp/rclol-fast-clock (read once per process), on CPUs with an invariant TSC, for
|
|
+ * these two clock ids. Measured 2026-10-10 in League with it on: darlingserver 113-120% CPU with continuous
|
|
+ * mach_vm_region_recurse traffic against 38-57% with it off, and slower fights. With a TSC kernel clocksource a real
|
|
+ * read is ~0.35 us, so little is lost by leaving it off. */
|
|
+#define FC_CPUS 256
|
|
+#define FC_WINDOW_NS 100000000ull
|
|
+#define FC_FIRST_SPAN_NS 20000000ull
|
|
+#define FC_GOOD_SPAN_NS 500000000ull
|
|
+#define FC_MAX_SPAN_NS 2000000000ull
|
|
+#define FC_MAX_SAMPLE_TICKS 60000ull
|
|
+#define FC_SAMPLE_SLACK_TICKS 400ull
|
|
+
|
|
+struct fc_cpu {
|
|
+ volatile unsigned int seq; /* odd while a writer is inside */
|
|
+ volatile unsigned int valid;
|
|
+ volatile unsigned long long tsc;
|
|
+ volatile unsigned long long mono;
|
|
+ volatile long long real_offset;
|
|
+ unsigned long long origin_tsc;
|
|
+ unsigned long long origin_mono;
|
|
+} __attribute__((aligned(64)));
|
|
+
|
|
+static struct fc_cpu fc_cpus[FC_CPUS];
|
|
+static volatile unsigned long long fc_mult; /* ns per tick, 32.32 fixed point; 0 until measured */
|
|
+static volatile unsigned long long fc_window; /* FC_WINDOW_NS in ticks */
|
|
+static volatile unsigned long long fc_last_mono __attribute__((aligned(64)));
|
|
+static volatile unsigned long long fc_fastest = FC_MAX_SAMPLE_TICKS; /* shortest bracketed clock read seen, in ticks */
|
|
+static volatile int fc_state; /* 0 not probed, 1 usable, -1 unusable */
|
|
+
|
|
+static inline unsigned long long fc_rdtscp(unsigned int* cpu)
|
|
+{
|
|
+ unsigned int lo, hi, aux;
|
|
+ __asm__ volatile("rdtscp" : "=a"(lo), "=d"(hi), "=c"(aux));
|
|
+ *cpu = aux & 0xfff;
|
|
+ return ((unsigned long long) hi << 32) | lo;
|
|
+}
|
|
+
|
|
+static int fc_probe(void)
|
|
+{
|
|
+ unsigned int a, b, c, d;
|
|
+ int usable = 0;
|
|
+ __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(0x80000000u), "c"(0));
|
|
+ if (a >= 0x80000007u) {
|
|
+ __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(0x80000001u), "c"(0));
|
|
+ usable = (d >> 27) & 1; /* RDTSCP */
|
|
+ __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(0x80000007u), "c"(0));
|
|
+ usable &= (d >> 8) & 1; /* invariant TSC */
|
|
+ }
|
|
+ if (usable && LINUX_SYSCALL(__NR_access, "/tmp/rclol-fast-clock", 0) != 0)
|
|
+ usable = 0;
|
|
+ fc_state = usable ? 1 : -1;
|
|
+ return fc_state;
|
|
+}
|
|
+
|
|
+static inline unsigned long long fc_monotonic(unsigned long long ns)
|
|
+{
|
|
+ unsigned long long last = __atomic_load_n(&fc_last_mono, __ATOMIC_RELAXED);
|
|
+ while (ns > last) {
|
|
+ if (__atomic_compare_exchange_n(&fc_last_mono, &last, ns, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
|
|
+ return ns;
|
|
+ }
|
|
+ return last;
|
|
+}
|
|
+
|
|
+static inline void fc_store(int clockid, struct timespec* res, unsigned long long mono, long long real_offset)
|
|
+{
|
|
+ unsigned long long ns = fc_monotonic(mono);
|
|
+ if (clockid == CLOCK_REALTIME)
|
|
+ ns += real_offset;
|
|
+ res->tv_sec = ns / 1000000000ull;
|
|
+ res->tv_nsec = ns % 1000000000ull;
|
|
+}
|
|
+
|
|
+static int fc_resync(int clockid, struct timespec* res)
|
|
+{
|
|
+ struct timespec mono, real;
|
|
+ unsigned int c0, c1, c2;
|
|
+ unsigned long long t0, t1, t2, mono_ns, real_ns, mult, fastest, limit;
|
|
+ int rv;
|
|
+
|
|
+ t0 = fc_rdtscp(&c0);
|
|
+ rv = LINUX_SYSCALL(__NR_clock_gettime, CLOCK_MONOTONIC, &mono);
|
|
+ if (rv < 0)
|
|
+ return rv;
|
|
+ t1 = fc_rdtscp(&c1);
|
|
+ mono_ns = (unsigned long long) mono.tv_sec * 1000000000ull + mono.tv_nsec;
|
|
+
|
|
+ /* A sample is only a calibration point when the read ran undisturbed on one CPU: not much slower than the fastest
|
|
+ * read seen. The reference creeps up on every rejection, so a machine that became slower (clock throttled) is
|
|
+ * followed within a few calls, and a rejected call costs the one syscall it would have cost anyway. */
|
|
+ fastest = fc_fastest;
|
|
+ if (c0 == c1 && t1 - t0 < fastest)
|
|
+ fc_fastest = fastest = t1 - t0;
|
|
+ limit = fastest + fastest / 2 + FC_SAMPLE_SLACK_TICKS;
|
|
+ if (c0 != c1 || c0 >= FC_CPUS || t1 - t0 >= limit) {
|
|
+ if (c0 == c1 && fastest < FC_MAX_SAMPLE_TICKS)
|
|
+ fc_fastest = fastest + fastest / 8 + 1;
|
|
+ if (clockid == CLOCK_REALTIME) {
|
|
+ rv = LINUX_SYSCALL(__NR_clock_gettime, CLOCK_REALTIME, res);
|
|
+ return rv < 0 ? rv : 0;
|
|
+ }
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ rv = LINUX_SYSCALL(__NR_clock_gettime, CLOCK_REALTIME, &real);
|
|
+ if (rv < 0)
|
|
+ return rv;
|
|
+ t2 = fc_rdtscp(&c2);
|
|
+ real_ns = (unsigned long long) real.tv_sec * 1000000000ull + real.tv_nsec;
|
|
+ mult = fc_mult;
|
|
+
|
|
+ if (c1 == c2 && t2 - t1 < limit) {
|
|
+ struct fc_cpu* c = &fc_cpus[c0];
|
|
+ unsigned int seq = __atomic_load_n(&c->seq, __ATOMIC_RELAXED);
|
|
+ if (!(seq & 1) && __atomic_compare_exchange_n(&c->seq, &seq, seq + 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
|
|
+ /* The kernel reads the clock early in the syscall and most of the round trip is the return path. */
|
|
+ unsigned long long mid = t0 + (t1 - t0) / 4;
|
|
+ unsigned long long real_mid = t1 + (t2 - t1) / 4;
|
|
+ int restart = !c->valid || mid <= c->origin_tsc || mono_ns <= c->origin_mono;
|
|
+ if (!restart) {
|
|
+ unsigned long long span = mono_ns - c->origin_mono;
|
|
+ unsigned long long ticks = mid - c->origin_tsc;
|
|
+ if (span > 2 * FC_MAX_SPAN_NS) {
|
|
+ restart = 1; /* long idle or suspend: too long for the fixed-point division */
|
|
+ } else if (span >= FC_FIRST_SPAN_NS) {
|
|
+ unsigned long long measured = (span << 32) / ticks;
|
|
+ /* plausible tick rates only: 0.4 .. 16 GHz */
|
|
+ int plausible = measured > (1ull << 28) && measured < (5ull << 31);
|
|
+ if (plausible && mult) {
|
|
+ unsigned long long diff = measured > mult ? measured - mult : mult - measured;
|
|
+ if (diff > mult / 128)
|
|
+ plausible = 0; /* the TSC and the kernel clock diverged (suspend): start over */
|
|
+ }
|
|
+ if (!plausible) {
|
|
+ restart = 1;
|
|
+ } else if (!mult || span >= FC_GOOD_SPAN_NS) {
|
|
+ mult = measured;
|
|
+ __atomic_store_n(&fc_window, (FC_WINDOW_NS << 32) / measured, __ATOMIC_RELAXED);
|
|
+ __atomic_store_n(&fc_mult, measured, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ if (span >= FC_MAX_SPAN_NS)
|
|
+ restart = 1;
|
|
+ }
|
|
+ }
|
|
+ if (restart) {
|
|
+ c->origin_tsc = mid;
|
|
+ c->origin_mono = mono_ns;
|
|
+ }
|
|
+ c->tsc = mid;
|
|
+ c->mono = mono_ns;
|
|
+ c->real_offset = (long long) (real_ns - mono_ns) - (long long) (((real_mid - mid) * mult) >> 32);
|
|
+ c->valid = 1;
|
|
+ __atomic_store_n(&c->seq, seq + 2, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (clockid == CLOCK_REALTIME) {
|
|
+ *res = real;
|
|
+ return 0;
|
|
+ }
|
|
+done:
|
|
+ mono_ns = fc_monotonic(mono_ns);
|
|
+ res->tv_sec = mono_ns / 1000000000ull;
|
|
+ res->tv_nsec = mono_ns % 1000000000ull;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* 0: *res filled; 1: not handled here (caller makes the syscall); negative: Linux errno */
|
|
+__attribute__((visibility("hidden")))
|
|
+int __darling_fast_clock(int clockid, struct timespec* res)
|
|
+{
|
|
+ unsigned int cpu, seq;
|
|
+ unsigned long long tsc, mult;
|
|
+ int state = fc_state;
|
|
+
|
|
+ if (__builtin_expect(state <= 0, 0)) {
|
|
+ if (state == 0)
|
|
+ state = fc_probe();
|
|
+ if (state < 0)
|
|
+ return 1;
|
|
+ }
|
|
+ if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME)
|
|
+ return 1;
|
|
+
|
|
+ mult = __atomic_load_n(&fc_mult, __ATOMIC_ACQUIRE);
|
|
+ tsc = fc_rdtscp(&cpu);
|
|
+ if (__builtin_expect(mult != 0 && cpu < FC_CPUS, 1)) {
|
|
+ struct fc_cpu* c = &fc_cpus[cpu];
|
|
+ seq = __atomic_load_n(&c->seq, __ATOMIC_ACQUIRE);
|
|
+ if (!(seq & 1) && c->valid) {
|
|
+ unsigned long long base_tsc = c->tsc, base_mono = c->mono;
|
|
+ long long real_offset = c->real_offset;
|
|
+ if (__atomic_load_n(&c->seq, __ATOMIC_ACQUIRE) == seq) {
|
|
+ unsigned long long delta = tsc - base_tsc;
|
|
+ if (delta < fc_window) {
|
|
+ fc_store(clockid, res, base_mono + ((delta * mult) >> 32), real_offset);
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return fc_resync(clockid, res);
|
|
+}
|
|
+#endif
|
|
+
|
|
VISIBLE
|
|
int clock_gettime (int __clockid, struct timespec* __res)
|
|
{
|
|
int rv;
|
|
|
|
+#if defined(__x86_64__)
|
|
+ /* Exact and cheaper than the syscall; the vDSO answer comes from the same
|
|
+ * clocksource the syscall would read. */
|
|
+ rv = __darling_vdso_clock_gettime(__clockid, __res);
|
|
+ if (rv == 0)
|
|
+ return 0;
|
|
+
|
|
+ rv = __darling_fast_clock(__clockid, __res);
|
|
+ if (rv == 0)
|
|
+ return 0;
|
|
+ if (rv == 1)
|
|
+#endif
|
|
rv = LINUX_SYSCALL(__NR_clock_gettime, __clockid, __res);
|
|
if (rv < 0)
|
|
{
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/file_handle.c b/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/file_handle.c
|
|
index 08c05fd..b51d66d 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/file_handle.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/file_handle.c
|
|
@@ -1,7 +1,7 @@
|
|
#include <darling/emulation/linux_premigration/ext/file_handle.h>
|
|
|
|
#include <sys/errno.h>
|
|
-#include <os/lock.h>
|
|
+#include <darling/emulation/common/private_mutex.h>
|
|
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/conversion/fcntl/open.h>
|
|
@@ -27,10 +27,8 @@ struct SavedRef
|
|
#define MOUNT_ID_SAVED (-100)
|
|
static struct SavedRef g_savedRefs[200];
|
|
static int g_nextSavedRef = 0, g_nextGen = 0;
|
|
-static os_unfair_lock g_savedRefLock = OS_UNFAIR_LOCK_INIT;
|
|
+static emulation_mutex g_savedRefLock = EMULATION_MUTEX_INITIALIZER;
|
|
|
|
-void __attribute__((weak)) os_unfair_lock_unlock(os_unfair_lock_t lock) {}
|
|
-void __attribute__((weak)) os_unfair_lock_lock(os_unfair_lock_t lock) {}
|
|
|
|
VISIBLE
|
|
int sys_name_to_handle(const char* name, RefData* ref, int follow)
|
|
@@ -53,7 +51,7 @@ int sys_name_to_handle(const char* name, RefData* ref, int follow)
|
|
// This is unfortunately the case for overlayfs, which doesn't support "nfs_export" along with "metacopy"
|
|
if (ret == -LINUX_EOPNOTSUPP && sys_access(name, 0) == 0)
|
|
{
|
|
- os_unfair_lock_lock(&g_savedRefLock);
|
|
+ emulation_mutex_lock(&g_savedRefLock);
|
|
|
|
if (g_savedRefs[g_nextSavedRef].path)
|
|
free(g_savedRefs[g_nextSavedRef].path);
|
|
@@ -66,7 +64,7 @@ int sys_name_to_handle(const char* name, RefData* ref, int follow)
|
|
|
|
g_nextSavedRef = (g_nextSavedRef + 1) % (sizeof(g_savedRefs) / sizeof(g_savedRefs[0]));
|
|
|
|
- os_unfair_lock_unlock(&g_savedRefLock);
|
|
+ emulation_mutex_unlock(&g_savedRefLock);
|
|
|
|
ret = 0;
|
|
}
|
|
@@ -83,7 +81,7 @@ int sys_handle_to_name(RefData* ref, char name[4096])
|
|
if (ref->mount_id == MOUNT_ID_SAVED)
|
|
{
|
|
int ret = -ENOENT;
|
|
- os_unfair_lock_lock(&g_savedRefLock);
|
|
+ emulation_mutex_lock(&g_savedRefLock);
|
|
|
|
if (g_savedRefs[ref->index].gen == ref->gen)
|
|
{
|
|
@@ -91,7 +89,7 @@ int sys_handle_to_name(RefData* ref, char name[4096])
|
|
ret = sys_access(name, 0);
|
|
}
|
|
|
|
- os_unfair_lock_unlock(&g_savedRefLock);
|
|
+ emulation_mutex_unlock(&g_savedRefLock);
|
|
return ret;
|
|
}
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/sched_yield.c b/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/sched_yield.c
|
|
index dc8fbb8..171746a 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/sched_yield.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/linux_premigration/ext/sched_yield.c
|
|
@@ -1,7 +1,58 @@
|
|
#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
-void __linux_sched_yield(void)
|
|
+/* darling-lol M634: swtch/yield spin detector. The League launcher's
|
|
+ * dependency-resolution path pinned a core in a tight yield loop; per-thread
|
|
+ * burst counters with caller tags, return addresses and a bounded stack
|
|
+ * window identify the guest-side spinner without libc. */
|
|
+
|
|
+struct lolsys_yield_slot {
|
|
+ long tid;
|
|
+ unsigned long long n;
|
|
+ unsigned int marks;
|
|
+};
|
|
+
|
|
+/* darling-lol: no libc; slot table mirrors base.c's lolsys_slots pattern. */
|
|
+#define YIELD_SLOTS 64
|
|
+static struct lolsys_yield_slot yield_slots[YIELD_SLOTS];
|
|
+
|
|
+static int linux_gettid_raw(void)
|
|
{
|
|
+ return (int) LINUX_SYSCALL0(__NR_gettid);
|
|
+}
|
|
+
|
|
+static void yield_mark(const char* site, unsigned long long n)
|
|
+{
|
|
+ __simple_printf("LOLYLD site=%s tid=%d n=%llu\n", site, linux_gettid_raw(), n);
|
|
+
|
|
+}
|
|
+
|
|
+void __linux_sched_yield_site(const char* site)
|
|
+{
|
|
+ int tid = linux_gettid_raw();
|
|
+ struct lolsys_yield_slot* s = &yield_slots[((unsigned int) tid) % YIELD_SLOTS];
|
|
+ if (s->tid != tid) {
|
|
+ s->tid = tid;
|
|
+ s->n = 0;
|
|
+ s->marks = 0;
|
|
+ }
|
|
+ s->n++;
|
|
+ /* Thresholds: ~0.1s, ~1s, ~17s, ~4.5min at 15k/s. Cap: 5 dumps/thread. */
|
|
+ static const unsigned long long marks[5] = {1000, 16000, 262144, 4000000, 64000000};
|
|
+ if (s->marks < 5) {
|
|
+ for (int i = s->marks; i < 5; i++) {
|
|
+ if (s->n == marks[i]) {
|
|
+ s->marks++;
|
|
+ yield_mark(site, s->n);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
LINUX_SYSCALL0(__NR_sched_yield);
|
|
}
|
|
+
|
|
+void __linux_sched_yield(void)
|
|
+{
|
|
+ __linux_sched_yield_site("SWTYLD");
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c b/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c
|
|
index 206e92e..0646cb7 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c
|
|
@@ -151,8 +151,13 @@ void sigrt_handler(int signum, struct linux_siginfo* info, struct linux_ucontext
|
|
int status = dserver_rpc_interrupt_enter();
|
|
|
|
if (status != 0) {
|
|
- __simple_printf("*** dserver_rpc_interrupt_enter failed with code %d ***\n", status);
|
|
- __simple_abort();
|
|
+ /* darling-lol: same rationale as sigexc_handler -- do not abort
|
|
+ * inside the handler when darlingserver is unreachable. RT
|
|
+ * signals are control traffic; if we cannot service them, we
|
|
+ * simply return (the signal is consumed) rather than kill the
|
|
+ * process. */
|
|
+ __simple_printf("*** sigrt_handler: dserver_rpc_interrupt_enter failed with code %d (returning) ***\n", status);
|
|
+ return;
|
|
}
|
|
|
|
if (signum == SIGNAL_SIGEXC_SUSPEND) {
|
|
@@ -267,13 +272,150 @@ static void state_from_kernel(struct linux_ucontext* ctxt, const void* tstate, c
|
|
#endif
|
|
}
|
|
|
|
+/* Avoid recursive fault reporting; normal signal handling still runs. */
|
|
+static int _lol_depth = 0;
|
|
+
|
|
+#ifdef __x86_64__
|
|
+/* The crashing process's stdout is often not a log its owner can read (the League game's is not),
|
|
+ * so the same report is also appended to the host's /tmp/lolsig.log with the module map needed to
|
|
+ * resolve the addresses. Only return addresses and the map are written, never data. */
|
|
+static void _lol_report_line(int fd, const char* text)
|
|
+{
|
|
+ if (fd >= 0) {
|
|
+ unsigned long length = 0;
|
|
+ while (text[length]) ++length;
|
|
+ linux_syscall(fd, text, length, 0, 0, 0, __NR_write);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void _lol_report_maps(int fd)
|
|
+{
|
|
+ if (fd < 0) return;
|
|
+ int maps = (int) linux_syscall("/proc/self/maps", 0 /* O_RDONLY */, 0, 0, 0, 0, __NR_open);
|
|
+ if (maps < 0) return;
|
|
+ char chunk[2048];
|
|
+ long got;
|
|
+ while ((got = linux_syscall(maps, chunk, sizeof(chunk), 0, 0, 0, __NR_read)) > 0)
|
|
+ linux_syscall(fd, chunk, got, 0, 0, 0, __NR_write);
|
|
+ linux_syscall(maps, 0, 0, 0, 0, 0, __NR_close);
|
|
+}
|
|
+
|
|
+static void _lol_log_fault(int linux_signum, struct linux_siginfo* info, struct linux_ucontext* ctxt)
|
|
+{
|
|
+ char line[160];
|
|
+ /* O_WRONLY | O_CREAT | O_APPEND */
|
|
+ int report = (int) linux_syscall("/tmp/lolsig.log", 0x441, 0644, 0, 0, 0, __NR_open);
|
|
+ long self = linux_syscall(0, 0, 0, 0, 0, 0, __NR_getpid);
|
|
+
|
|
+ /* The interrupted stack and argument pointers may be invalid. Reading them
|
|
+ * here can cause another signal and replace a recoverable fault with a
|
|
+ * fatal crash in the reporter. Keep only code/fault addresses, never data. */
|
|
+ __simple_printf("LOLSIGFAIL pid=%ld sig=%d code=%d addr=0x%llx rip=0x%llx\n",
|
|
+ self, linux_signum, info->si_code,
|
|
+ (unsigned long long) info->si_addr,
|
|
+ (unsigned long long) ctxt->uc_mcontext.gregs.rip);
|
|
+ __simple_sprintf(line, "LOLSIGFAIL pid=%ld sig=%d code=%d addr=0x%llx rip=0x%llx\n",
|
|
+ self, linux_signum, info->si_code,
|
|
+ (unsigned long long) info->si_addr,
|
|
+ (unsigned long long) ctxt->uc_mcontext.gregs.rip);
|
|
+ _lol_report_line(report, line);
|
|
+
|
|
+ /* Kernel-mediated copies fail safely for inaccessible stacks. Print only
|
|
+ * return addresses, never stack contents, strings, or argument values. */
|
|
+ struct lol_iovec { void *base; unsigned long size; };
|
|
+ unsigned long long frame = ctxt->uc_mcontext.gregs.rbp;
|
|
+ for (unsigned i = 0; i < 40 && frame; ++i) {
|
|
+ unsigned long long pair[2];
|
|
+ struct lol_iovec local = { pair, sizeof(pair) };
|
|
+ struct lol_iovec remote = { (void *)frame, sizeof(pair) };
|
|
+ long copied = linux_syscall(self, &local, 1, &remote, 1, 0, __NR_process_vm_readv);
|
|
+ if (copied != sizeof(pair)) break;
|
|
+ __simple_printf("LOLSIGFRAME pid=%ld n=%u pc=0x%llx\n", self, i, pair[1]);
|
|
+ __simple_sprintf(line, "LOLSIGFRAME pid=%ld n=%u pc=0x%llx\n", self, i, pair[1]);
|
|
+ _lol_report_line(report, line);
|
|
+ if (pair[0] <= frame || pair[0] - frame > 1024 * 1024) break;
|
|
+ frame = pair[0];
|
|
+ }
|
|
+
|
|
+ _lol_report_line(report, "LOLMAPS\n");
|
|
+ _lol_report_maps(report);
|
|
+ _lol_report_line(report, "LOLEND\n");
|
|
+ if (report >= 0)
|
|
+ linux_syscall(report, 0, 0, 0, 0, 0, __NR_close);
|
|
+}
|
|
+
|
|
+#endif
|
|
+
|
|
void sigexc_handler(int linux_signum, struct linux_siginfo* info, struct linux_ucontext* ctxt)
|
|
{
|
|
int status = dserver_rpc_interrupt_enter();
|
|
+ int relaxed = 0;
|
|
+
|
|
+#ifdef __x86_64__
|
|
+ if (_lol_depth > 0) {
|
|
+ /* Nested fault inside the reporter above: do not recurse, fall
|
|
+ * through to normal handling with no further logging. */
|
|
+ goto normal;
|
|
+ }
|
|
+#endif
|
|
|
|
if (status != 0) {
|
|
+ /* darling-lol: previously this hard-aborted, which killed the
|
|
+ * process inside the signal handler and hid the original fault
|
|
+ * (e.g. a freshly-forked/spawned thread whose per-thread RPC
|
|
+ * socket is stale returns -111/ECONNREFUSED here). We cannot
|
|
+ * talk to darlingserver, but we can still do the one thing that
|
|
+ * matters most: report WHERE the fault happened, then apply the
|
|
+ * default signal effects so the process terminates for the
|
|
+ * right reason instead of via our abort(). */
|
|
__simple_printf("*** dserver_rpc_interrupt_enter failed with code %d ***\n", status);
|
|
- __simple_abort();
|
|
+#ifdef __x86_64__
|
|
+ _lol_depth = 1;
|
|
+ _lol_log_fault(linux_signum, info, ctxt);
|
|
+ _lol_depth = 0;
|
|
+#endif
|
|
+ relaxed = 1;
|
|
+ }
|
|
+
|
|
+#ifdef __x86_64__
|
|
+ /* darling-lol M38: also report fatal signals that DO reach
|
|
+ * darlingserver (the fail-fast branch above only fires when the RPC
|
|
+ * socket is dead). Log-and-continue: delivery semantics unchanged. */
|
|
+ /* Skip when relaxed: the fail-fast branch above already logged. */
|
|
+ if (!relaxed &&
|
|
+ (linux_signum == LINUX_SIGABRT || linux_signum == LINUX_SIGSEGV ||
|
|
+ linux_signum == LINUX_SIGILL || linux_signum == LINUX_SIGBUS ||
|
|
+ linux_signum == LINUX_SIGTRAP)) {
|
|
+ _lol_depth = 1;
|
|
+ _lol_log_fault(linux_signum, info, ctxt);
|
|
+ _lol_depth = 0;
|
|
+ }
|
|
+normal:
|
|
+ /* Self-heal: if a stack read above faulted, its nested entry jumped
|
|
+ * here with _lol_depth stuck at 1; reset so later faults still log. */
|
|
+ _lol_depth = 0;
|
|
+#endif
|
|
+
|
|
+ if (relaxed) {
|
|
+ /* Cannot reach darlingserver: reset the handler to SIG_DFL and
|
|
+ * re-raise so the kernel applies the normal signal disposition
|
|
+ * (and any core dump). Returning would loop forever on the same
|
|
+ * faulting instruction. */
|
|
+ struct linux_sigaction sa;
|
|
+ sa.sa_sigaction = (linux_sig_handler*) NULL; /* SIG_DFL */
|
|
+ sa.sa_mask = 0x7fffffff;
|
|
+ sa.sa_flags = LINUX_SA_RESTORER | LINUX_SA_SIGINFO | LINUX_SA_RESTART | LINUX_SA_ONSTACK;
|
|
+ sa.sa_restorer = sig_restorer;
|
|
+ LINUX_SYSCALL(__NR_rt_sigaction, linux_signum, &sa, NULL, sizeof(sa.sa_mask));
|
|
+ /* darling-lol: retarget to the faulting THREAD only. kill(0, sig)
|
|
+ * delivers to the whole Linux process group, so one dying guest
|
|
+ * process took down every sibling (launcher + UX + daemons all
|
|
+ * showed simultaneous SIGABRT/SIGILL). tgkill confines the
|
|
+ * re-raise to this thread; raw syscalls, no libc. */
|
|
+ LINUX_SYSCALL(__NR_tgkill,
|
|
+ LINUX_SYSCALL(__NR_getpid), LINUX_SYSCALL(__NR_gettid),
|
|
+ linux_signum);
|
|
+ return;
|
|
}
|
|
|
|
kern_printf("sigexc_handler(%d, %p, %p)\n", linux_signum, info, ctxt);
|
|
@@ -359,7 +501,13 @@ void sigexc_handler(int linux_signum, struct linux_siginfo* info, struct linux_u
|
|
sizeof(sa.sa_mask));
|
|
|
|
// Resend signal to self
|
|
- LINUX_SYSCALL(__NR_kill, 0, linux_signum);
|
|
+ // (darling-lol: tgkill, thread-only -- kill(0, sig) hit
|
|
+ // the whole process group and massacred siblings; the
|
|
+ // comment always said "self".)
|
|
+ LINUX_SYSCALL(__NR_tgkill,
|
|
+ LINUX_SYSCALL(__NR_getpid),
|
|
+ LINUX_SYSCALL(__NR_gettid),
|
|
+ linux_signum);
|
|
}
|
|
}
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/other/mach/lkm.c b/darling/src/libsystem_kernel/emulation/src/other/mach/lkm.c
|
|
index 8c6d5d9..6fd1acf 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/other/mach/lkm.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/other/mach/lkm.c
|
|
@@ -97,6 +97,15 @@ void mach_driver_init(const char** applep)
|
|
// we're being called in the child after a fork; let xtrace know about it
|
|
_xtrace_postfork_child();
|
|
}
|
|
+
|
|
+#if defined(__x86_64__)
|
|
+ /* darling-lol: resolve the host vDSO clock entry now, while the native
|
|
+ * loader is known to be usable; clock reads can then happen from signal
|
|
+ * handlers and RPC-guarded callers without running dlopen/dlsym. The vDSO
|
|
+ * mapping survives fork(), so the cached pointer stays valid in the child. */
|
|
+ extern void __darling_vdso_clock_init(void);
|
|
+ __darling_vdso_clock_init();
|
|
+#endif
|
|
}
|
|
|
|
__attribute__((visibility("default")))
|
|
@@ -128,12 +137,20 @@ uint64_t mach_absolute_time(void)
|
|
{
|
|
struct timespec ts;
|
|
uint64_t out;
|
|
-
|
|
+
|
|
+ /* darling-lol: the vDSO copy of the clock is exact and avoids a host
|
|
+ * syscall; `clock_gettime` here is the Linux wrapper, which repeats the
|
|
+ * vDSO check and otherwise falls back to the opt-in TSC clock and the
|
|
+ * syscall. */
|
|
+#if defined(__x86_64__)
|
|
+ extern int __darling_vdso_clock_gettime(int clockid, struct timespec* res);
|
|
+ if (__darling_vdso_clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
|
|
+#endif
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
-
|
|
+
|
|
out = ts.tv_nsec;
|
|
out += ts.tv_sec * NSEC_PER_SEC;
|
|
-
|
|
+
|
|
return out;
|
|
}
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/bsd_syscall_table.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/bsd_syscall_table.c
|
|
index 7d5ef80..46debb8 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/bsd_syscall_table.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/bsd_syscall_table.c
|
|
@@ -24,6 +24,7 @@
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/fcntl/openat.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/guarded/guarded_close_np.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/guarded/guarded_kqueue_np.h>
|
|
+#include <darling/emulation/xnu_syscall/bsd/impl/guarded/change_fdguard_np.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/guarded/guarded_open_np.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/hfs/stub.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/ioctl/ioctl.h>
|
|
@@ -44,6 +45,7 @@
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/getrlimit.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/getrusage.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/iopolicysys.h>
|
|
+#include <darling/emulation/xnu_syscall/bsd/impl/misc/mac_syscall.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/proc_info.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/ptrace.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/reboot.h>
|
|
@@ -427,6 +429,7 @@ void* __bsd_syscall_table[600] = {
|
|
[369] = sys_kevent64,
|
|
[372] = sys_thread_selfid,
|
|
[374] = sys_kevent_qos,
|
|
+ [381] = sys___mac_syscall,
|
|
[394] = sys_pselect,
|
|
[395] = sys_pselect_nocancel,
|
|
[396] = sys_read_nocancel,
|
|
@@ -462,6 +465,7 @@ void* __bsd_syscall_table[600] = {
|
|
[441] = sys_guarded_open_np,
|
|
[442] = sys_guarded_close_np,
|
|
[443] = sys_guarded_kqueue_np,
|
|
+ [444] = sys_change_fdguard_np,
|
|
[461] = sys_getattrlistbulk,
|
|
[462] = sys_clonefileat,
|
|
[463] = sys_openat,
|
|
@@ -483,6 +487,7 @@ void* __bsd_syscall_table[600] = {
|
|
[500] = sys_getentropy,
|
|
[515] = sys_ulock_wait,
|
|
[516] = sys_ulock_wake,
|
|
+ [544] = sys_ulock_wait2,
|
|
[517] = sys_fclonefileat,
|
|
[521] = sys_abort_with_payload,
|
|
[524] = sys_setattrlistat,
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_hw.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_hw.c
|
|
index f273989..1093a3a 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_hw.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_hw.c
|
|
@@ -9,6 +9,7 @@
|
|
#include <darling/emulation/xnu_syscall/bsd/helper/misc/sysctl_proc.h>
|
|
#include <darling/emulation/linux_premigration/ext/sys/utsname.h>
|
|
#include <darling/emulation/common/simple.h>
|
|
+#include <darling/emulation/common/cpu_topology.h>
|
|
#include <darling/emulation/common/string.h>
|
|
|
|
extern kern_return_t mach_port_deallocate(ipc_space_t task, mach_port_name_t name);
|
|
@@ -16,6 +17,7 @@ extern kern_return_t host_info(mach_port_name_t host, int itype, void* hinfo, ma
|
|
|
|
/* Darling specific */
|
|
enum {
|
|
+ _HW_ACTIVECPU = 26, // hw.activecpu's OID in XNU; unused in this table before
|
|
_HW_PHYSICAL_CPU = 1000,
|
|
_HW_PHYSICAL_CPU_MAX,
|
|
_HW_LOGICAL_CPU,
|
|
@@ -25,9 +27,12 @@ enum {
|
|
_HW_CPUTHREADTYPE,
|
|
_HW_64BITCAPABLE,
|
|
_HW_CPUFREQUENCY = 15,
|
|
+ _HW_CACHECONFIG = 1200,
|
|
+ _HW_CACHESIZE,
|
|
};
|
|
|
|
static sysctl_handler(handle_availcpu);
|
|
+static sysctl_handler(handle_ncpu);
|
|
static sysctl_handler(handle_physicalcpu);
|
|
static sysctl_handler(handle_physicalcpu_max);
|
|
static sysctl_handler(handle_logicalcpu);
|
|
@@ -39,11 +44,77 @@ static sysctl_handler(handle_cpusubtype);
|
|
static sysctl_handler(handle_cputhreadtype);
|
|
static sysctl_handler(handle_cpu64bitcapable);
|
|
static sysctl_handler(handle_machine);
|
|
+static sysctl_handler(handle_model);
|
|
static sysctl_handler(handle_cpufrequency);
|
|
+static sysctl_handler(handle_cacheconfig);
|
|
+static sysctl_handler(handle_cachesize);
|
|
+
|
|
+/* hw.optional: CPUID leaf, output register (0-3 = eax-edx) and bit */
|
|
+static long optional_feature(void* old, unsigned long* oldlen, unsigned int leaf, int reg, int bit)
|
|
+{
|
|
+ int value = 0;
|
|
+
|
|
+ sysctl_handle_size(sizeof(int));
|
|
+#if defined(__i386__) || defined(__x86_64__)
|
|
+ unsigned int regs[4];
|
|
+ __asm__("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3]) : "0" (leaf), "2" (0));
|
|
+ value = (regs[reg] >> bit) & 1;
|
|
+#endif
|
|
+ *((int*) old) = value;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define optional_handler(_name, _leaf, _reg, _bit) \
|
|
+ static sysctl_handler(handle_optional_##_name) { return optional_feature(old, oldlen, _leaf, _reg, _bit); }
|
|
+
|
|
+optional_handler(floatingpoint, 1, 3, 0)
|
|
+optional_handler(mmx, 1, 3, 23)
|
|
+optional_handler(sse, 1, 3, 25)
|
|
+optional_handler(sse2, 1, 3, 26)
|
|
+optional_handler(sse3, 1, 2, 0)
|
|
+optional_handler(supplementalsse3, 1, 2, 9)
|
|
+optional_handler(sse4_1, 1, 2, 19)
|
|
+optional_handler(sse4_2, 1, 2, 20)
|
|
+optional_handler(aes, 1, 2, 25)
|
|
+optional_handler(avx1_0, 1, 2, 28)
|
|
+optional_handler(rdrand, 1, 2, 30)
|
|
+optional_handler(f16c, 1, 2, 29)
|
|
+optional_handler(fma, 1, 2, 12)
|
|
+optional_handler(avx2_0, 7, 1, 5)
|
|
+optional_handler(bmi1, 7, 1, 3)
|
|
+optional_handler(bmi2, 7, 1, 8)
|
|
+optional_handler(avx512f, 7, 1, 16)
|
|
+
|
|
+enum {
|
|
+ _HW_OPTIONAL = 1100,
|
|
+};
|
|
+
|
|
+static const struct known_sysctl sysctls_hw_optional[] = {
|
|
+ { .oid = 1, .type = CTLTYPE_INT, .exttype = "I", .name = "floatingpoint", .handler = handle_optional_floatingpoint },
|
|
+ { .oid = 2, .type = CTLTYPE_INT, .exttype = "I", .name = "mmx", .handler = handle_optional_mmx },
|
|
+ { .oid = 3, .type = CTLTYPE_INT, .exttype = "I", .name = "sse", .handler = handle_optional_sse },
|
|
+ { .oid = 4, .type = CTLTYPE_INT, .exttype = "I", .name = "sse2", .handler = handle_optional_sse2 },
|
|
+ { .oid = 5, .type = CTLTYPE_INT, .exttype = "I", .name = "sse3", .handler = handle_optional_sse3 },
|
|
+ { .oid = 6, .type = CTLTYPE_INT, .exttype = "I", .name = "supplementalsse3", .handler = handle_optional_supplementalsse3 },
|
|
+ { .oid = 7, .type = CTLTYPE_INT, .exttype = "I", .name = "sse4_1", .handler = handle_optional_sse4_1 },
|
|
+ { .oid = 8, .type = CTLTYPE_INT, .exttype = "I", .name = "sse4_2", .handler = handle_optional_sse4_2 },
|
|
+ { .oid = 9, .type = CTLTYPE_INT, .exttype = "I", .name = "aes", .handler = handle_optional_aes },
|
|
+ { .oid = 10, .type = CTLTYPE_INT, .exttype = "I", .name = "avx1_0", .handler = handle_optional_avx1_0 },
|
|
+ { .oid = 11, .type = CTLTYPE_INT, .exttype = "I", .name = "rdrand", .handler = handle_optional_rdrand },
|
|
+ { .oid = 12, .type = CTLTYPE_INT, .exttype = "I", .name = "f16c", .handler = handle_optional_f16c },
|
|
+ { .oid = 13, .type = CTLTYPE_INT, .exttype = "I", .name = "fma", .handler = handle_optional_fma },
|
|
+ { .oid = 14, .type = CTLTYPE_INT, .exttype = "I", .name = "avx2_0", .handler = handle_optional_avx2_0 },
|
|
+ { .oid = 15, .type = CTLTYPE_INT, .exttype = "I", .name = "bmi1", .handler = handle_optional_bmi1 },
|
|
+ { .oid = 16, .type = CTLTYPE_INT, .exttype = "I", .name = "bmi2", .handler = handle_optional_bmi2 },
|
|
+ { .oid = 17, .type = CTLTYPE_INT, .exttype = "I", .name = "avx512f", .handler = handle_optional_avx512f },
|
|
+ { .oid = 18, .type = CTLTYPE_INT, .exttype = "I", .name = "x86_64", .handler = handle_cpu64bitcapable },
|
|
+ { .oid = -1 }
|
|
+};
|
|
|
|
const struct known_sysctl sysctls_hw[] = {
|
|
{ .oid = HW_AVAILCPU, .type = CTLTYPE_INT, .exttype = "", .name = "availcpu", .handler = handle_availcpu },
|
|
- { .oid = HW_NCPU, .type = CTLTYPE_INT, .exttype = "", .name = "ncpu", .handler = handle_availcpu },
|
|
+ { .oid = _HW_ACTIVECPU, .type = CTLTYPE_INT, .exttype = "", .name = "activecpu", .handler = handle_availcpu },
|
|
+ { .oid = HW_NCPU, .type = CTLTYPE_INT, .exttype = "", .name = "ncpu", .handler = handle_ncpu },
|
|
{ .oid = _HW_PHYSICAL_CPU, .type = CTLTYPE_INT, .exttype = "", .name = "physicalcpu", .handler = handle_physicalcpu },
|
|
{ .oid = _HW_PHYSICAL_CPU_MAX, .type = CTLTYPE_INT, .exttype = "", .name = "physicalcpu_max", .handler = handle_physicalcpu_max },
|
|
{ .oid = _HW_LOGICAL_CPU, .type = CTLTYPE_INT, .exttype = "", .name = "logicalcpu", .handler = handle_logicalcpu },
|
|
@@ -55,7 +126,11 @@ const struct known_sysctl sysctls_hw[] = {
|
|
{ .oid = _HW_CPUTHREADTYPE, .type = CTLTYPE_INT, .exttype = "", .name = "cputhreadtype", .handler = handle_cputhreadtype },
|
|
{ .oid = _HW_64BITCAPABLE, .type = CTLTYPE_INT, .exttype = "", .name = "cpu64bit_capable", .handler = handle_cpu64bitcapable },
|
|
{ .oid = HW_MACHINE, .type = CTLTYPE_STRING, .exttype = "S", .name = "machine", .handler = handle_machine },
|
|
+ { .oid = _HW_OPTIONAL, .type = CTLTYPE_NODE, .exttype = "", .name = "optional", .subctls = sysctls_hw_optional },
|
|
+ { .oid = HW_MODEL, .type = CTLTYPE_STRING, .exttype = "S", .name = "model", .handler = handle_model },
|
|
{ .oid = _HW_CPUFREQUENCY, .type = CTLTYPE_INT, .exttype = "", .name = "cpufrequency", .handler = handle_cpufrequency },
|
|
+ { .oid = _HW_CACHECONFIG, .type = CTLTYPE_OPAQUE, .exttype = "Q", .name = "cacheconfig", .handler = handle_cacheconfig },
|
|
+ { .oid = _HW_CACHESIZE, .type = CTLTYPE_OPAQUE, .exttype = "Q", .name = "cachesize", .handler = handle_cachesize },
|
|
{ .oid = -1 }
|
|
};
|
|
|
|
@@ -71,6 +146,32 @@ static const struct host_basic_info* gethostinfo(void)
|
|
hself = mach_host_self();
|
|
host_info(hself, HOST_BASIC_INFO, &hinfo, &hcount);
|
|
mach_port_deallocate(mach_task_self(), hself);
|
|
+
|
|
+ // Keep the emulated MacBookPro16,1 identity (hw.model, brand string,
|
|
+ // memory size), but report the host's real CPU counts. The game and the
|
|
+ // clients size their worker pools from these values; the old fixed
|
|
+ // 8 logical / 8 physical configuration made the game create 7 workers
|
|
+ // on a 12-thread host. Note it did not even match the emulated machine:
|
|
+ // a MacBookPro16,1 with the i9-9980HK is 8-core/16-thread.
|
|
+ // /tmp/rclol-fixed-cpu-count restores the old values for A/B runs.
|
|
+ const struct darling_cpu_topology* topo = darling_host_cpu_topology();
|
|
+
|
|
+ if (topo->detected && !topo->fixed)
|
|
+ {
|
|
+ hinfo.avail_cpus = topo->available;
|
|
+ hinfo.max_cpus = topo->online;
|
|
+ hinfo.physical_cpu = topo->physical_available;
|
|
+ hinfo.physical_cpu_max = topo->physical_online;
|
|
+ hinfo.logical_cpu = topo->available;
|
|
+ hinfo.logical_cpu_max = topo->online;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ hinfo.avail_cpus = hinfo.max_cpus = DARLING_CPU_FIXED_COUNT;
|
|
+ hinfo.physical_cpu = hinfo.physical_cpu_max = DARLING_CPU_FIXED_COUNT;
|
|
+ hinfo.logical_cpu = hinfo.logical_cpu_max = DARLING_CPU_FIXED_COUNT;
|
|
+ }
|
|
+ hinfo.max_mem = 17179869184ULL; // 16 GB, matching the emulated machine
|
|
}
|
|
|
|
return &hinfo;
|
|
@@ -83,6 +184,13 @@ sysctl_handler(handle_availcpu)
|
|
return 0;
|
|
}
|
|
|
|
+sysctl_handler(handle_ncpu)
|
|
+{
|
|
+ sysctl_handle_size(sizeof(int));
|
|
+ *((int*) old) = gethostinfo()->max_cpus;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
sysctl_handler(handle_physicalcpu)
|
|
{
|
|
sysctl_handle_size(sizeof(int));
|
|
@@ -174,6 +282,8 @@ sysctl_handler(handle_cpufrequency)
|
|
int freq = 2400000;
|
|
char buf[16];
|
|
|
|
+ sysctl_handle_size(sizeof(int));
|
|
+
|
|
if (read_string("/sys/bus/cpu/devices/cpu0/cpufreq/cpuinfo_max_freq", buf, sizeof(buf)))
|
|
freq = __simple_atoi(buf, NULL);
|
|
*((int*) old) = freq;
|
|
@@ -193,3 +303,104 @@ sysctl_handler(handle_machine)
|
|
copyout_string(need_uname()->machine, (char*) old, oldlen);
|
|
return 0;
|
|
}
|
|
+
|
|
+sysctl_handler(handle_model)
|
|
+{
|
|
+ // an Intel Mac model identifier, matching the x86_64 machine we emulate
|
|
+ copyout_string("MacBookPro16,1", (char*) old, oldlen);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define CACHE_LEVELS 10
|
|
+// The guest has no /sys of its own; the host's is reachable through the SystemRoot volume
|
|
+#define HOST_CACHE_DIR "/Volumes/SystemRoot/sys/devices/system/cpu/cpu0/cache/"
|
|
+
|
|
+static unsigned long long parse_ull(const char* s, const char** end)
|
|
+{
|
|
+ unsigned long long v = 0;
|
|
+ while (*s >= '0' && *s <= '9')
|
|
+ v = v * 10 + (*s++ - '0');
|
|
+ if (end)
|
|
+ *end = s;
|
|
+ return v;
|
|
+}
|
|
+
|
|
+// Number of CPUs in a sysfs list such as "0-1,4-5" (darling_cpu_count_list, shared with the topology probe)
|
|
+
|
|
+// Describes the data/unified cache of one level (1 = L1D) of the host's CPU 0.
|
|
+// Instruction caches are skipped, as in XNU. Returns false if the host doesn't report the level.
|
|
+static bool host_cache_level(unsigned int level, unsigned long long* size, unsigned int* sharing)
|
|
+{
|
|
+ char path[96], buf[64];
|
|
+
|
|
+ for (int index = 0; index < 10; index++)
|
|
+ {
|
|
+ const char* end;
|
|
+ unsigned long long value;
|
|
+
|
|
+ __simple_sprintf(path, HOST_CACHE_DIR "index%d/level", index);
|
|
+ if (!read_string(path, buf, sizeof(buf)))
|
|
+ break;
|
|
+ if (parse_ull(buf, NULL) != level)
|
|
+ continue;
|
|
+
|
|
+ __simple_sprintf(path, HOST_CACHE_DIR "index%d/type", index);
|
|
+ if (!read_string(path, buf, sizeof(buf)) || buf[0] == 'I')
|
|
+ continue;
|
|
+
|
|
+ __simple_sprintf(path, HOST_CACHE_DIR "index%d/size", index);
|
|
+ if (!read_string(path, buf, sizeof(buf)))
|
|
+ continue;
|
|
+ value = parse_ull(buf, &end);
|
|
+ if (*end == 'K')
|
|
+ value <<= 10;
|
|
+ else if (*end == 'M')
|
|
+ value <<= 20;
|
|
+ *size = value;
|
|
+
|
|
+ __simple_sprintf(path, HOST_CACHE_DIR "index%d/shared_cpu_list", index);
|
|
+ *sharing = read_string(path, buf, sizeof(buf)) ? darling_cpu_count_list(buf) : 1;
|
|
+ if (*sharing == 0)
|
|
+ *sharing = 1;
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
+// hw.cacheconfig[0] is the number of logical CPUs, [n] the number of CPUs sharing a level n cache
|
|
+sysctl_handler(handle_cacheconfig)
|
|
+{
|
|
+ unsigned long long* config = (unsigned long long*) old;
|
|
+
|
|
+ sysctl_handle_size(CACHE_LEVELS * sizeof(*config));
|
|
+ memset(config, 0, CACHE_LEVELS * sizeof(*config));
|
|
+ config[0] = gethostinfo()->logical_cpu_max;
|
|
+ for (unsigned int level = 1; level < 4; level++)
|
|
+ {
|
|
+ unsigned long long size;
|
|
+ unsigned int sharing;
|
|
+
|
|
+ if (host_cache_level(level, &size, &sharing))
|
|
+ config[level] = sharing;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+// hw.cachesize[0] is the memory size, [n] the size in bytes of the level n cache
|
|
+sysctl_handler(handle_cachesize)
|
|
+{
|
|
+ unsigned long long* sizes = (unsigned long long*) old;
|
|
+
|
|
+ sysctl_handle_size(CACHE_LEVELS * sizeof(*sizes));
|
|
+ memset(sizes, 0, CACHE_LEVELS * sizeof(*sizes));
|
|
+ sizes[0] = gethostinfo()->max_mem;
|
|
+ for (unsigned int level = 1; level < 4; level++)
|
|
+ {
|
|
+ unsigned long long size;
|
|
+ unsigned int sharing;
|
|
+
|
|
+ if (host_cache_level(level, &size, &sharing))
|
|
+ sizes[level] = size;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_kern.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_kern.c
|
|
index bd9cdc2..83ad15c 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_kern.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_kern.c
|
|
@@ -139,18 +139,23 @@ sysctl_handler(handle_boottime)
|
|
// Fill in struct timeval { tv_sec, tv_usec }
|
|
struct sysinfo info;
|
|
struct bsd_timeval* tv;
|
|
-
|
|
- if (__linux_sysinfo(&info) == -1)
|
|
- return -errno;
|
|
+ /* darling-lol: the boot time does not change, and libc's clock_gettime(CLOCK_MONOTONIC) asks for it twice per
|
|
+ * call (two sysinfo + two gettimeofday syscalls). Computed once per process. */
|
|
+ static volatile long boot_sec;
|
|
|
|
tv = (struct bsd_timeval*) old;
|
|
if (tv != NULL)
|
|
{
|
|
if (*oldlen < sizeof(*tv))
|
|
return -EINVAL;
|
|
- sys_gettimeofday(tv, NULL);
|
|
-
|
|
- tv->tv_sec -= info.uptime;
|
|
+ if (boot_sec == 0)
|
|
+ {
|
|
+ if (__linux_sysinfo(&info) == -1)
|
|
+ return -errno;
|
|
+ sys_gettimeofday(tv, NULL);
|
|
+ boot_sec = tv->tv_sec - info.uptime;
|
|
+ }
|
|
+ tv->tv_sec = boot_sec;
|
|
|
|
// we don't want the microseconds to be constantly jumping around
|
|
// (that actually causes an infinite loop in `_mach_boottime_usec` in libc)
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_machdep.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_machdep.c
|
|
index 4319355..122ad3a 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_machdep.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_machdep.c
|
|
@@ -4,6 +4,7 @@
|
|
#include <alloca.h>
|
|
|
|
#include <darling/emulation/common/simple.h>
|
|
+#include <darling/emulation/common/cpu_topology.h>
|
|
#include <darling/emulation/conversion/fcntl/open.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/fcntl/open.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/helper/misc/readline.h>
|
|
@@ -25,6 +26,7 @@ enum {
|
|
_CPU_BRAND_STRING,
|
|
_CPU_FEATURES,
|
|
_CPU_CORE_COUNT,
|
|
+ _CPU_THREAD_COUNT,
|
|
};
|
|
|
|
static sysctl_handler(handle_vendor);
|
|
@@ -32,9 +34,15 @@ static sysctl_handler(handle_max_basic);
|
|
static sysctl_handler(handle_family);
|
|
static sysctl_handler(handle_model);
|
|
static sysctl_handler(handle_stepping);
|
|
-static sysctl_handler(handle_brand_string);
|
|
-static sysctl_handler(handle_features);
|
|
+static sysctl_handler(handle_brand_string)
|
|
+{
|
|
+ copyout_string("Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz", (char*) old, oldlen);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+sysctl_handler(handle_features);
|
|
static sysctl_handler(handle_core_count);
|
|
+static sysctl_handler(handle_thread_count);
|
|
|
|
const struct known_sysctl sysctls_machdep_cpu[] = {
|
|
{ .oid = _CPU_MAX_BASIC, .type = CTLTYPE_INT, .exttype = "I", .name = "max_basic", .handler = handle_max_basic },
|
|
@@ -42,9 +50,10 @@ const struct known_sysctl sysctls_machdep_cpu[] = {
|
|
{ .oid = _CPU_FAMILY, .type = CTLTYPE_INT, .exttype = "I", .name = "family", .handler = handle_family },
|
|
{ .oid = _CPU_MODEL, .type = CTLTYPE_INT, .exttype = "I", .name = "model", .handler = handle_model },
|
|
{ .oid = _CPU_STEPPING, .type = CTLTYPE_INT, .exttype = "I", .name = "stepping", .handler = handle_stepping },
|
|
- { .oid = _CPU_BRAND_STRING, .type = CTLTYPE_STRING, .exttype = "S", .name = "vendor", .handler = handle_brand_string },
|
|
+ { .oid = _CPU_BRAND_STRING, .type = CTLTYPE_STRING, .exttype = "S", .name = "brand_string", .handler = handle_brand_string },
|
|
{ .oid = _CPU_FEATURES, .type = CTLTYPE_STRING, .exttype = "S", .name = "features", .handler = handle_features },
|
|
{ .oid = _CPU_CORE_COUNT, .type = CTLTYPE_INT, .exttype = "I", .name = "core_count", .handler = handle_core_count },
|
|
+ { .oid = _CPU_THREAD_COUNT, .type = CTLTYPE_INT, .exttype = "I", .name = "thread_count", .handler = handle_thread_count },
|
|
{ .oid = -1 }
|
|
};
|
|
|
|
@@ -154,38 +163,37 @@ sysctl_handler(handle_stepping)
|
|
return 0;
|
|
}
|
|
|
|
+// Original cpuid-based implementation, replaced by the fixed brand string defined above
|
|
+#if 0
|
|
sysctl_handler(handle_brand_string)
|
|
{
|
|
- setup(0x80000000);
|
|
-
|
|
- __cpuid(level,eax,ebx, ecx, edx);
|
|
-
|
|
- if (eax < 0x80000004) // the information is not implemented
|
|
- return 2;
|
|
-
|
|
+ union
|
|
+ {
|
|
+ unsigned int brand[12];
|
|
+ char name[49];
|
|
+ } v;
|
|
+ unsigned int eax, ebx, ecx, edx;
|
|
|
|
+ __asm__("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "0" (0x80000000u), "2" (0));
|
|
|
|
- union
|
|
- {
|
|
- unsigned int brand[12];
|
|
- char name[49];
|
|
- } v;
|
|
+ if (eax < 0x80000004) // the information is not implemented
|
|
+ return -ENOENT;
|
|
|
|
- for (int i = 1; i < 4; i++)
|
|
- {
|
|
- eax = 0x80000000+i;
|
|
- __cpuid(level, eax, ebx, ecx, edx);
|
|
- v.brand[0x0+(i-1)*4] = eax;
|
|
- v.brand[0x1+(i-1)*4] = ebx;
|
|
- v.brand[0x2+(i-1)*4] = ecx;
|
|
- v.brand[0x3+(i-1)*4] = edx;
|
|
- }
|
|
+ for (unsigned int i = 0; i < 3; i++)
|
|
+ {
|
|
+ __asm__("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "0" (0x80000002u + i), "2" (0));
|
|
+ v.brand[i * 4 + 0] = eax;
|
|
+ v.brand[i * 4 + 1] = ebx;
|
|
+ v.brand[i * 4 + 2] = ecx;
|
|
+ v.brand[i * 4 + 3] = edx;
|
|
+ }
|
|
|
|
- v.name[48] = 0;
|
|
- copyout_string(v.name, (char*) old, oldlen);
|
|
+ v.name[48] = 0;
|
|
+ copyout_string(v.name, (char*) old, oldlen);
|
|
|
|
- return 0;
|
|
+ return 0;
|
|
}
|
|
+#endif
|
|
|
|
sysctl_handler(handle_features)
|
|
{
|
|
@@ -269,45 +277,54 @@ sysctl_handler(handle_features)
|
|
// TODO: i doubt core count is ever going to change.
|
|
// this is something that could be cached if we had some way of doing something only once in libsystem_kernel (i.e. a simple version of dispatch_once).
|
|
sysctl_handler(handle_core_count) {
|
|
- // it's easier to use /proc/cpuinfo for this.
|
|
- // using cpuid directly is more difficult because AMD processors don't use the same method as Intel processors.
|
|
+ // the topology probe (shared with the hw.* sysctls) reads the host's
|
|
+ // package/core ids and caches the answer; fall back to /proc/cpuinfo when
|
|
+ // the host doesn't expose /sys.
|
|
+ const struct darling_cpu_topology* topo = darling_host_cpu_topology();
|
|
+ size_t core_count = (topo->detected && !topo->fixed) ? topo->physical_online : 0;
|
|
+ int infofd = -1;
|
|
|
|
- int infofd = sys_open_nocancel("/proc/cpuinfo", BSD_O_RDONLY, 0);
|
|
- struct rdline_buffer rbuf;
|
|
- const char* line;
|
|
+ if (core_count == 0) {
|
|
+ // it's easier to use /proc/cpuinfo for this.
|
|
+ // using cpuid directly is more difficult because AMD processors don't use the same method as Intel processors.
|
|
|
|
- // a reasonable default
|
|
- size_t core_count = 1;
|
|
+ infofd = sys_open_nocancel("/proc/cpuinfo", BSD_O_RDONLY, 0);
|
|
+ struct rdline_buffer rbuf;
|
|
+ const char* line;
|
|
|
|
- if (infofd < 0)
|
|
- goto out;
|
|
+ // a reasonable default
|
|
+ core_count = 1;
|
|
|
|
- _readline_init(&rbuf);
|
|
+ if (infofd < 0)
|
|
+ goto out;
|
|
|
|
- while ((line = _readline(infofd, &rbuf)) != NULL) {
|
|
- if (strncmp(line, "cpu cores", 9) != 0) {
|
|
- continue;
|
|
- }
|
|
+ _readline_init(&rbuf);
|
|
|
|
- // great, we've got "cpu cores"
|
|
- // now find the actual number
|
|
- while (*line != ':' && *line != '\0')
|
|
- ++line;
|
|
+ while ((line = _readline(infofd, &rbuf)) != NULL) {
|
|
+ if (strncmp(line, "cpu cores", 9) != 0) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- // shouldn't happen, but just in case
|
|
- if (*line == '\0')
|
|
- continue;
|
|
+ // great, we've got "cpu cores"
|
|
+ // now find the actual number
|
|
+ while (*line != ':' && *line != '\0')
|
|
+ ++line;
|
|
|
|
- ++line; // skip the colon
|
|
- if (*line == '\0')
|
|
- continue;
|
|
+ // shouldn't happen, but just in case
|
|
+ if (*line == '\0')
|
|
+ continue;
|
|
|
|
- ++line; // skip the space
|
|
- if (*line == '\0')
|
|
- continue;
|
|
+ ++line; // skip the colon
|
|
+ if (*line == '\0')
|
|
+ continue;
|
|
|
|
- core_count = __simple_atoi(line, NULL);
|
|
- break;
|
|
+ ++line; // skip the space
|
|
+ if (*line == '\0')
|
|
+ continue;
|
|
+
|
|
+ core_count = __simple_atoi(line, NULL);
|
|
+ break;
|
|
+ }
|
|
}
|
|
|
|
out:
|
|
@@ -319,3 +336,21 @@ out:
|
|
*oldlen = sizeof(uint32_t);
|
|
return 0;
|
|
}
|
|
+
|
|
+// The host's online logical CPU count (machdep.cpu.thread_count on macOS).
|
|
+sysctl_handler(handle_thread_count) {
|
|
+ const struct darling_cpu_topology* topo = darling_host_cpu_topology();
|
|
+ size_t thread_count = (topo->detected && !topo->fixed) ? topo->online : 0;
|
|
+
|
|
+ if (thread_count == 0) {
|
|
+ // Detection failed or the opt-out is active: fall back to the same
|
|
+ // fixed value the hw.* handlers use.
|
|
+ thread_count = DARLING_CPU_FIXED_COUNT;
|
|
+ }
|
|
+
|
|
+ if (old != NULL && oldlen && *oldlen >= sizeof(uint32_t))
|
|
+ *(uint32_t*)old = thread_count;
|
|
+ if (oldlen)
|
|
+ *oldlen = sizeof(uint32_t);
|
|
+ return 0;
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_net.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_net.c
|
|
index 8fabdda..6cc204e 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_net.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_net.c
|
|
@@ -22,6 +22,7 @@ static sysctl_handler(handle_route);
|
|
static int do_iflist(void* old, unsigned long* oldlen);
|
|
|
|
#define LINUX_SIOCGIFCONF 0x8912
|
|
+#define LINUX_SIOCGIFFLAGS 0x8913
|
|
#define LINUX_SIOCGIFHWADDR 0x8927
|
|
#define LINUX_SIOCGIFBRDADDR 0x8919
|
|
#define LINUX_SIOCGIFNETMASK 0x891b
|
|
@@ -144,6 +145,16 @@ static int do_iflist(void* old, unsigned long* oldlen) {
|
|
|
|
memcpy(dl->sdl_data, curr->lifr_name, name_len);
|
|
|
|
+ /* Interface-list consumers (including Chromium) discard adapters
|
|
+ * without IFF_UP. Populate actual flags instead of leaving zero.
|
|
+ * The low ten flags share values; Linux multicast differs. */
|
|
+ memcpy(tmp.lifr_name, curr->lifr_name, sizeof(curr->lifr_name));
|
|
+ if ((status = __real_ioctl(tmp_sock, LINUX_SIOCGIFFLAGS, &tmp)) < 0)
|
|
+ goto out;
|
|
+ unsigned short linux_flags = (unsigned short)tmp.lifr_flags;
|
|
+ ifm->ifm_flags = linux_flags & 0x03ff;
|
|
+ if (linux_flags & 0x1000) ifm->ifm_flags |= IFF_MULTICAST;
|
|
+
|
|
// grab the MAC
|
|
memcpy(tmp.lifr_name, curr->lifr_name, sizeof(curr->lifr_name));
|
|
if ((status = __real_ioctl(tmp_sock, LINUX_SIOCGIFHWADDR, &tmp)) < 0)
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_sysctl.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_sysctl.c
|
|
index 5a49882..5dc5fa7 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_sysctl.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/helper/misc/sysctl_sysctl.c
|
|
@@ -7,12 +7,15 @@
|
|
|
|
enum {
|
|
_PROC_CPUTYPE = 1000,
|
|
+ _PROC_TRANSLATED,
|
|
};
|
|
|
|
static sysctl_handler(handle_proc_cputype);
|
|
+static sysctl_handler(handle_proc_translated);
|
|
|
|
const struct known_sysctl sysctls_sysctl[] = {
|
|
{ .oid = _PROC_CPUTYPE, .type = CTLTYPE_INT, .exttype = "I", .name = "proc_cputype", .handler = handle_proc_cputype },
|
|
+ { .oid = _PROC_TRANSLATED, .type = CTLTYPE_INT, .exttype = "I", .name = "proc_translated", .handler = handle_proc_translated },
|
|
{ .oid = -1 }
|
|
};
|
|
|
|
@@ -44,3 +47,11 @@ sysctl_handler(handle_proc_cputype)
|
|
|
|
return 0;
|
|
}
|
|
+
|
|
+sysctl_handler(handle_proc_translated)
|
|
+{
|
|
+ // guest code runs natively on the host CPU, never under binary translation
|
|
+ sysctl_handle_size(sizeof(int));
|
|
+ *((int*) old) = 0;
|
|
+ return 0;
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/audit/audit_addr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/audit/audit_addr.c
|
|
index 304dadd..1320eda 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/audit/audit_addr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/audit/audit_addr.c
|
|
@@ -1,6 +1,6 @@
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/audit/audit_addr.h>
|
|
|
|
-#include <os/lock.h>
|
|
+#include <darling/emulation/common/private_mutex.h>
|
|
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/unistd/geteuid.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/misc/getentropy.h>
|
|
@@ -18,19 +18,19 @@ static auditinfo_addr_t info = {
|
|
.ai_flags = 0,
|
|
};
|
|
// should be a rw lock but *shrug*
|
|
-static os_unfair_lock info_lock = OS_UNFAIR_LOCK_INIT;
|
|
+static emulation_mutex info_lock = EMULATION_MUTEX_INITIALIZER;
|
|
|
|
long sys_getaudit_addr(struct auditinfo_addr* auditinfo_addr, int length) {
|
|
- os_unfair_lock_lock(&info_lock);
|
|
+ emulation_mutex_lock(&info_lock);
|
|
|
|
memcpy(auditinfo_addr, &info, min(length, sizeof(auditinfo_addr_t)));
|
|
|
|
- os_unfair_lock_unlock(&info_lock);
|
|
+ emulation_mutex_unlock(&info_lock);
|
|
return 0;
|
|
};
|
|
|
|
long sys_setaudit_addr(struct auditinfo_addr* auditinfo_addr, int length) {
|
|
- os_unfair_lock_lock(&info_lock);
|
|
+ emulation_mutex_lock(&info_lock);
|
|
|
|
memcpy(&info, auditinfo_addr, min(length, sizeof(auditinfo_addr_t)));
|
|
|
|
@@ -39,6 +39,6 @@ long sys_setaudit_addr(struct auditinfo_addr* auditinfo_addr, int length) {
|
|
sys_getentropy(&info.ai_asid, sizeof(info.ai_asid));
|
|
}
|
|
|
|
- os_unfair_lock_unlock(&info_lock);
|
|
+ emulation_mutex_unlock(&info_lock);
|
|
return 0;
|
|
};
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_create.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_create.c
|
|
index 6f8cf43..c5f6d08 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_create.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_create.c
|
|
@@ -33,6 +33,7 @@ static bool _uses_threads = false;
|
|
|
|
// http://www.tldp.org/FAQ/Threads-FAQ/clone.c
|
|
|
|
+
|
|
static void rpc_guard(int fd) {
|
|
guard_entry_options_t options;
|
|
options.close = __dserver_close_socket;
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c
|
|
index 919205b..5c678ab 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c
|
|
@@ -34,6 +34,29 @@ struct _pthread_registration_data {
|
|
|
|
static struct _pthread_registration_data g_pth_regdata;
|
|
|
|
+extern int __darling_is_league_game(void);
|
|
+extern void __darling_core_isolate_main_thread(void);
|
|
+extern void __darling_core_isolate_new_thread(void);
|
|
+/* Modern libpthread registration has stack_addr_hint at byte 36, followed
|
|
+ * by mutex_default_policy at byte 44. Keep the old prefix ABI unchanged. */
|
|
+/* darling-lol: the League game process takes FIRSTFIT mutexes, whose contention the process handles itself
|
|
+ * (see psynch_mutexwait.c) and whose condition variables then use the ulock path. This is decided once per
|
|
+ * process: on for the game unless /tmp/rclol-no-local-condvars exists, and /tmp/rclol-local-condvars-all turns
|
|
+ * it on for every process (probe tests). /tmp/rclol-local-condvars used to be the opt-in switch; the file may
|
|
+ * still exist and is now ignored. */
|
|
+static void enable_local_conditions(const struct _pthread_registration_data* data,
|
|
+ unsigned long length)
|
|
+{
|
|
+ if (length < 48 || data->version < 48) return;
|
|
+ if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-local-condvars-all", 0) != 0 &&
|
|
+ (LINUX_SYSCALL(__NR_access, "/tmp/rclol-no-local-condvars", 0) == 0 ||
|
|
+ !__darling_is_league_game())) return;
|
|
+ /* FIRSTFIT (3) | USE_ULOCK (0x100). Recursive/error-check/shared mutexes
|
|
+ * still take libpthread's psynch path. This field is a kernel copyout. */
|
|
+ uint32_t policy = 3u | 0x100u;
|
|
+ __builtin_memcpy((char*)(uintptr_t)data + 44, &policy, sizeof(policy));
|
|
+}
|
|
+
|
|
long sys_bsdthread_register(void* thread_start, void* wqthread, int pthsize,
|
|
const struct _pthread_registration_data* pth_regdata,
|
|
unsigned long pth_regdata_len, unsigned long long dpq_offset)
|
|
@@ -42,6 +65,12 @@ long sys_bsdthread_register(void* thread_start, void* wqthread, int pthsize,
|
|
pthread_entry_point = (bsdthread_entry_t) thread_start;
|
|
wqueue_entry_point = (bsdwqthread_entry_t) wqthread;
|
|
g_pth_regdata = *pth_regdata;
|
|
+ enable_local_conditions(pth_regdata, pth_regdata_len);
|
|
+ /* darling-lol: libpthread initializes here, on the process' initial thread, before any thread can be
|
|
+ * created. That is the one place where the game process' main thread can be given its own core
|
|
+ * scheduling cookie (see mach_traps.c). The call decides for itself whether this is the game, whether
|
|
+ * the caller is the initial thread and whether the host supports the prctl. */
|
|
+ __darling_core_isolate_main_thread();
|
|
|
|
uintptr_t pthread_handle = _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
|
|
uintptr_t dispatch_qaddr = pthread_handle + g_pth_regdata.tsd_offset + g_pth_regdata.dispatch_queue_offset;
|
|
@@ -59,6 +88,10 @@ long sys_bsdthread_register(void* thread_start, void* wqthread, int pthsize,
|
|
void pthread_entry_point_wrapper(void* self, int thread_port, void* funptr,
|
|
void* funarg, unsigned long stack_addr, unsigned int flags)
|
|
{
|
|
+ /* darling-lol: every guest thread starts here; it must not keep the core scheduling cookie it
|
|
+ * inherited from its creator (a thread created by the main thread would carry the main cookie and
|
|
+ * could run on the main thread's core). See mach_traps.c. */
|
|
+ __darling_core_isolate_new_thread();
|
|
sigexc_thread_setup();
|
|
|
|
uintptr_t pthread_handle = (unsigned long) self;
|
|
@@ -122,6 +155,9 @@ void pthread_entry_point_wrapper(void* self, int thread_port, void* funptr,
|
|
void wqueue_entry_point_wrapper(void* self, int thread_port, void* stackaddr,
|
|
void* item, int reuse, int nevents)
|
|
{
|
|
+ /* darling-lol: workqueue threads start here too and must lose the inherited core scheduling
|
|
+ * cookie the same way normal pthreads do (see mach_traps.c). */
|
|
+ __darling_core_isolate_new_thread();
|
|
sigexc_thread_setup();
|
|
wqueue_entry_point_asm_jump(self, thread_port, stackaddr, item, reuse, nevents);
|
|
}
|
|
@@ -156,6 +192,11 @@ void wqueue_entry_point_asm_jump(void* self, int thread_port, void* stackaddr,
|
|
// `_start_wqthread` does not expect the stack to be aligned (using the
|
|
// call instruction will add the return address to the stack, causing
|
|
// it to be aligned).
|
|
+ // Workqueue entry is a fresh kernel-style jump, including on reuse.
|
|
+ // self sits immediately above the usable stack. Dropping the old
|
|
+ // syscall frames prevents every reuse from consuming another frame.
|
|
+ "movq %%rdi, %%rsp\n"
|
|
+ "xorq %%rbp, %%rbp\n"
|
|
"jmpq *%[wqueue_entry_point]\n"
|
|
::
|
|
// Arguments follow the usual x86_64 calling conventions.
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_terminate.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_terminate.c
|
|
index 14a8238..7c95cdf 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_terminate.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/bsdthread_terminate.c
|
|
@@ -1,3 +1,5 @@
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/bsdthread/bsdthread_terminate.h>
|
|
|
|
#include <sys/errno.h>
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/pthread_canceled.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/pthread_canceled.c
|
|
index 1e78300..a6acb59 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/pthread_canceled.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/pthread_canceled.c
|
|
@@ -12,12 +12,24 @@
|
|
|
|
long sys_pthread_canceled(int action)
|
|
{
|
|
+ static unsigned unsupported_actions;
|
|
+ unsigned action_bit = (unsigned)action <= 2 ? 1U << action : 0;
|
|
+
|
|
if (action == 0 && !uses_threads())
|
|
return -EINVAL;
|
|
|
|
+ /* Cache only a server-confirmed unsupported action. Cancellation state
|
|
+ * itself must never be cached: a server implementing it still receives
|
|
+ * every request. The server implementation is fixed for this process's
|
|
+ * lifetime. Atomic access keeps the shared capability cache race-free. */
|
|
+ if (__atomic_load_n(&unsupported_actions, __ATOMIC_RELAXED) & action_bit)
|
|
+ return -ENOSYS;
|
|
+
|
|
int ret = dserver_rpc_pthread_canceled(action);
|
|
if (ret < 0)
|
|
ret = errno_linux_to_bsd(ret);
|
|
+ if (ret == -ENOSYS && action_bit)
|
|
+ __atomic_fetch_or(&unsupported_actions, action_bit, __ATOMIC_RELAXED);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/workq_kernreturn.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/workq_kernreturn.c
|
|
index dbcdd98..0021dc2 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/workq_kernreturn.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/bsdthread/workq_kernreturn.c
|
|
@@ -3,7 +3,7 @@
|
|
#include <sys/errno.h>
|
|
#include <stddef.h>
|
|
#include <sys/queue.h>
|
|
-#include <os/lock.h>
|
|
+#include <darling/emulation/common/private_mutex.h>
|
|
#include <pthread/tsd_private.h>
|
|
|
|
#define __PTHREAD_EXPOSE_INTERNALS__ 1
|
|
@@ -61,7 +61,7 @@
|
|
#define WORKQ_EXIT_THREAD_NKEVENT (-1)
|
|
|
|
static int workq_sem = WQ_MAX_THREADS; // max 64 threads in use
|
|
-static os_unfair_lock workq_parked_lock = OS_UNFAIR_LOCK_INIT;
|
|
+static emulation_mutex workq_parked_lock = EMULATION_MUTEX_INITIALIZER;
|
|
// static int workq_parked_threads = 0; // num spawned, but unused threads
|
|
static int workq_parked[WQ_MAX_THREADS];
|
|
static int workq_parked_prio[WQ_MAX_THREADS];
|
|
@@ -78,14 +78,41 @@ struct parked_thread
|
|
static void list_add(struct parked_thread* head, struct parked_thread* item);
|
|
static void list_remove(struct parked_thread* head, struct parked_thread* item);
|
|
|
|
+// Parked-thread nodes live in a fixed pool, not on the parked thread's own stack. The list is shared by
|
|
+// all workers, and a node on a stack that goes away while a neighbour is still linked turns the next
|
|
+// removal into a write to unmapped memory. Callers hold workq_parked_lock.
|
|
+#define WQ_NODE_POOL_SIZE (WQ_MAX_THREADS * 2)
|
|
+static struct parked_thread workq_node_pool[WQ_NODE_POOL_SIZE];
|
|
+static unsigned char workq_node_used[WQ_NODE_POOL_SIZE];
|
|
+
|
|
+static struct parked_thread* workq_node_alloc(void)
|
|
+{
|
|
+ for (int i = 0; i < WQ_NODE_POOL_SIZE; i++)
|
|
+ {
|
|
+ if (!workq_node_used[i])
|
|
+ {
|
|
+ workq_node_used[i] = 1;
|
|
+ workq_node_pool[i].sem = 0;
|
|
+ workq_node_pool[i].flags = 0;
|
|
+ workq_node_pool[i].event = NULL;
|
|
+ return &workq_node_pool[i];
|
|
+ }
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void workq_node_free(struct parked_thread* node)
|
|
+{
|
|
+ if (node >= &workq_node_pool[0] && node < &workq_node_pool[WQ_NODE_POOL_SIZE])
|
|
+ workq_node_used[node - &workq_node_pool[0]] = 0;
|
|
+}
|
|
+
|
|
struct timespec
|
|
{
|
|
long tv_sec;
|
|
long tv_nsec;
|
|
};
|
|
|
|
-void __attribute__((weak)) os_unfair_lock_unlock(os_unfair_lock_t lock) {}
|
|
-void __attribute__((weak)) os_unfair_lock_lock(os_unfair_lock_t lock) {}
|
|
|
|
//void* __attribute__((weak)) __attribute__((visibility("default"))) pthread_getspecific(unsigned long key) { return NULL; }
|
|
//int __attribute__((weak)) __attribute__((visibility("default"))) pthread_setspecific(unsigned long key, const void* value) { return 1; }
|
|
@@ -95,6 +122,7 @@ static int priority_to_class(int prio);
|
|
// This is horrible, but it may work
|
|
static struct wq_kevent_data* wq_event_pending = NULL;
|
|
|
|
+
|
|
static void rpc_guard(int fd) {
|
|
guard_entry_options_t options;
|
|
options.close = __dserver_close_socket;
|
|
@@ -162,14 +190,24 @@ long sys_workq_kernreturn(int options, void* item, int affinity, int prio)
|
|
// and can be either recycled or terminated
|
|
|
|
int thread_self;
|
|
- struct parked_thread me;
|
|
+ struct parked_thread stack_me; // only used if the node pool is exhausted
|
|
+ struct parked_thread* me;
|
|
+ struct wq_kevent_data* resume_event = NULL;
|
|
+ int resume_flags;
|
|
dthread_t dthread;
|
|
bool terminating = false;
|
|
|
|
- os_unfair_lock_lock(&workq_parked_lock);
|
|
+ emulation_mutex_lock(&workq_parked_lock);
|
|
+
|
|
+ me = workq_node_alloc();
|
|
+ if (me == NULL)
|
|
+ {
|
|
+ me = &stack_me;
|
|
+ me->event = NULL;
|
|
+ }
|
|
|
|
// Semaphore locked state (wait for wakeup)
|
|
- me.sem = 0;
|
|
+ me->sem = 0;
|
|
|
|
// extract initial flags
|
|
// (in case we only get created and used once and then terminate; `_pthread_wqthread` requires a valid `flags` argument)
|
|
@@ -177,33 +215,37 @@ long sys_workq_kernreturn(int options, void* item, int affinity, int prio)
|
|
prio = _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_QOS_CLASS);
|
|
// doesn't extract `WQ_FLAG_THREAD_KEVENT` if we had it set, but that shouldn't matter
|
|
// like i said before, the only case where we actually need these flags to be set here is when the thread is going to die immediately after creation
|
|
- me.flags = extract_wq_flags(prio);
|
|
+ me->flags = extract_wq_flags(prio);
|
|
|
|
// Enqueue for future WQOPS_QUEUE_REQTHREADS
|
|
- TAILQ_INSERT_HEAD(&workq_parked_head, &me, entries);
|
|
+ TAILQ_INSERT_HEAD(&workq_parked_head, me, entries);
|
|
|
|
// Decrease the amount of running threads
|
|
sem_up(&workq_sem);
|
|
|
|
- os_unfair_lock_unlock(&workq_parked_lock);
|
|
+ emulation_mutex_unlock(&workq_parked_lock);
|
|
|
|
// Wait until someone calls WQOPS_QUEUE_REQTHREADS
|
|
// and wakes us up
|
|
- if (!sem_down(&me.sem, 15))
|
|
+ if (!sem_down(&me->sem, 15))
|
|
{
|
|
// Make sure we haven't just been woken up before locking the queue
|
|
// and remove us from the queue if not.
|
|
- os_unfair_lock_lock(&workq_parked_lock);
|
|
+ emulation_mutex_lock(&workq_parked_lock);
|
|
|
|
- if (me.sem > 0)
|
|
+ if (me->sem > 0)
|
|
{
|
|
- os_unfair_lock_unlock(&workq_parked_lock);
|
|
+ emulation_mutex_unlock(&workq_parked_lock);
|
|
goto wakeup;
|
|
}
|
|
|
|
- TAILQ_REMOVE(&workq_parked_head, &me, entries);
|
|
+ TAILQ_REMOVE(&workq_parked_head, me, entries);
|
|
|
|
- os_unfair_lock_unlock(&workq_parked_lock);
|
|
+ resume_event = NULL;
|
|
+ resume_flags = me->flags;
|
|
+ workq_node_free(me);
|
|
+
|
|
+ emulation_mutex_unlock(&workq_parked_lock);
|
|
|
|
terminating = true;
|
|
|
|
@@ -214,8 +256,15 @@ long sys_workq_kernreturn(int options, void* item, int affinity, int prio)
|
|
wakeup: // we actually want to go back into the thread to do work
|
|
// __simple_printf("Thread %d woken up, prio=%d\n", thread_self, me.flags & WQ_FLAG_THREAD_PRIOMASK);
|
|
|
|
- if (me.event)
|
|
- wq_event_pending = me.event;
|
|
+ // the requester already took us off the list; the node is ours to read, then to give back
|
|
+ emulation_mutex_lock(&workq_parked_lock);
|
|
+ resume_event = me->event;
|
|
+ resume_flags = me->flags;
|
|
+ workq_node_free(me);
|
|
+ emulation_mutex_unlock(&workq_parked_lock);
|
|
+
|
|
+ if (resume_event)
|
|
+ wq_event_pending = resume_event;
|
|
|
|
resume_thread: // we want the thread to resume, but it might be just to die
|
|
// reset stack and call entry point again with WQ_FLAG_THREAD_REUSE
|
|
@@ -223,9 +272,9 @@ resume_thread: // we want the thread to resume, but it might be just to die
|
|
dthread = _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
|
|
|
|
wqueue_entry_point_asm_jump(dthread, thread_self, dthread->stackbottom,
|
|
- (!terminating && me.event) ? me.event->events : NULL,
|
|
- me.flags | WQ_FLAG_THREAD_REUSE,
|
|
- terminating ? WORKQ_EXIT_THREAD_NKEVENT : (me.event ? me.event->nevents : 0)
|
|
+ (!terminating && resume_event) ? resume_event->events : NULL,
|
|
+ resume_flags | WQ_FLAG_THREAD_REUSE,
|
|
+ terminating ? WORKQ_EXIT_THREAD_NKEVENT : (resume_event ? resume_event->nevents : 0)
|
|
);
|
|
|
|
__builtin_unreachable();
|
|
@@ -257,7 +306,7 @@ resume_thread: // we want the thread to resume, but it might be just to die
|
|
// Increase the amount of running threads
|
|
sem_down(&workq_sem, -1);
|
|
|
|
- os_unfair_lock_lock(&workq_parked_lock);
|
|
+ emulation_mutex_lock(&workq_parked_lock);
|
|
|
|
if (workq_parked_head.tqh_first != NULL)
|
|
{
|
|
@@ -276,12 +325,12 @@ resume_thread: // we want the thread to resume, but it might be just to die
|
|
|
|
// Resume the thread
|
|
sem_up(&thread->sem);
|
|
- os_unfair_lock_unlock(&workq_parked_lock);
|
|
+ emulation_mutex_unlock(&workq_parked_lock);
|
|
|
|
continue;
|
|
}
|
|
|
|
- os_unfair_lock_unlock(&workq_parked_lock);
|
|
+ emulation_mutex_unlock(&workq_parked_lock);
|
|
|
|
// __simple_printf("Spawning a new thread, nevents=%d\n", (wq_event != NULL) ? wq_event->nevents : -1);
|
|
wq_event_pending = wq_event;
|
|
@@ -320,36 +369,52 @@ resume_thread: // we want the thread to resume, but it might be just to die
|
|
#endif
|
|
}
|
|
|
|
+/* Keep a nonnegative token count. The old decrement/negative-marker/plain
|
|
+ * store sequence could overwrite a concurrent post, losing a worker wakeup.
|
|
+ * EAGAIN and EINTR are retry conditions, not worker retirement requests. */
|
|
int sem_down(int* sem, int timeout)
|
|
{
|
|
- int result;
|
|
- struct timespec ts = { timeout, 0 };
|
|
-
|
|
-try_again:
|
|
- result = __sync_add_and_fetch(sem, -1);
|
|
- if (result < 0)
|
|
- {
|
|
- *sem = -1;
|
|
- result = __linux_futex(sem, FUTEX_WAIT, -1, (timeout != -1) ? &ts : NULL, 0, 0);
|
|
-
|
|
- if (result == -1)
|
|
- return 0;
|
|
-
|
|
- goto try_again;
|
|
- }
|
|
-
|
|
- return 1;
|
|
+ struct timespec deadline = {0, 0};
|
|
+ if (timeout >= 0) {
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1 /* CLOCK_MONOTONIC */, &deadline);
|
|
+ deadline.tv_sec += timeout;
|
|
+ }
|
|
+ for (;;) {
|
|
+ int value = __atomic_load_n(sem, __ATOMIC_ACQUIRE);
|
|
+ while (value > 0) {
|
|
+ if (__atomic_compare_exchange_n(sem, &value, value - 1, 0,
|
|
+ __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
|
+ return 1;
|
|
+ }
|
|
+ struct timespec remaining;
|
|
+ if (timeout >= 0) {
|
|
+ struct timespec now;
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1 /* CLOCK_MONOTONIC */, &now);
|
|
+ remaining.tv_sec = deadline.tv_sec - now.tv_sec;
|
|
+ remaining.tv_nsec = deadline.tv_nsec - now.tv_nsec;
|
|
+ if (remaining.tv_nsec < 0) {
|
|
+ --remaining.tv_sec;
|
|
+ remaining.tv_nsec += 1000000000;
|
|
+ }
|
|
+ if (remaining.tv_sec < 0 ||
|
|
+ (remaining.tv_sec == 0 && remaining.tv_nsec == 0))
|
|
+ return 0;
|
|
+ }
|
|
+ long result = LINUX_SYSCALL(__NR_futex, sem, FUTEX_WAIT, 0,
|
|
+ timeout >= 0 ? &remaining : NULL, NULL, 0);
|
|
+ if (result == -110 /* ETIMEDOUT */)
|
|
+ return 0;
|
|
+ if (result < 0 && result != -11 /* EAGAIN */ && result != -4 /* EINTR */)
|
|
+ return 0;
|
|
+ }
|
|
}
|
|
|
|
void sem_up(int* sem)
|
|
{
|
|
- int result;
|
|
- result = __sync_add_and_fetch(sem, 1);
|
|
- if (result == 0)
|
|
- {
|
|
- *sem = 1;
|
|
- __linux_futex(sem, FUTEX_WAKE, 1, NULL, 0, 0);
|
|
- }
|
|
+ __atomic_fetch_add(sem, 1, __ATOMIC_RELEASE);
|
|
+ /* Wake on every post: several waiters can be asleep while tokens are
|
|
+ * posted faster than the first awakened waiter can consume them. */
|
|
+ LINUX_SYSCALL(__NR_futex, sem, FUTEX_WAKE, 1, NULL, NULL, 0);
|
|
}
|
|
|
|
#define __PTHREAD_PRIORITY_CBIT_USER_INTERACTIVE 0x20
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/change_fdguard_np.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/change_fdguard_np.c
|
|
new file mode 100644
|
|
index 0000000..0bb5800
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/change_fdguard_np.c
|
|
@@ -0,0 +1,13 @@
|
|
+#include <darling/emulation/xnu_syscall/bsd/impl/guarded/change_fdguard_np.h>
|
|
+
|
|
+#include <darling/emulation/common/base.h>
|
|
+
|
|
+/* darling-lol M57: Chromium's UDP socket code calls change_fdguard_np
|
|
+ * (Darwin syscall 444). Darling's fdguard support is a no-op everywhere
|
|
+ * (guarded_open_np/guarded_close_np/guarded_kqueue_np all ignore it), so
|
|
+ * accept the request and report success. Without this the syscall was
|
|
+ * unhandled (ENOSYS), which made Chromium FATAL in udp_socket_posix.cc. */
|
|
+long sys_change_fdguard_np(int fd, uint64_t* guard, uint64_t* nguard, unsigned guardflags, int* fdflagsp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_close_np.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_close_np.c
|
|
index 8980d77..361efc4 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_close_np.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_close_np.c
|
|
@@ -7,6 +7,8 @@
|
|
|
|
long sys_guarded_close_np(int fd, uint64_t* guardid)
|
|
{
|
|
- *guardid = 0;
|
|
+ /* darling-lol M57: callers can pass a NULL or otherwise unusable guardid
|
|
+ * pointer; writing it crashed (SEGV on a read-only page). The guard id is
|
|
+ * not used by Darling's no-op fdguard implementation, so don't write it. */
|
|
return sys_close(fd);
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_kqueue_np.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_kqueue_np.c
|
|
index bdedda8..1992482 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_kqueue_np.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_kqueue_np.c
|
|
@@ -7,6 +7,6 @@
|
|
|
|
long sys_guarded_kqueue_np(uint64_t* guardid, unsigned guardflags)
|
|
{
|
|
- *guardid = 0;
|
|
+ /* darling-lol M57: do not write guardid (may be unusable). */
|
|
return sys_kqueue();
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_open_np.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_open_np.c
|
|
index 0fe8a0f..5ae6ad0 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_open_np.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/guarded/guarded_open_np.c
|
|
@@ -7,6 +7,6 @@
|
|
|
|
long sys_guarded_open_np(const char* filename, uint64_t* guardid, unsigned int guardflags, int flags, unsigned int mode)
|
|
{
|
|
- *guardid = 0;
|
|
+ /* darling-lol M57: do not write guardid (may be unusable). */
|
|
return sys_open(filename, flags, mode);
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent.c
|
|
index 15d7b43..dea29b8 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent.c
|
|
@@ -3,7 +3,8 @@
|
|
#include <stddef.h>
|
|
#include <sys/errno.h>
|
|
|
|
-#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#define LOLSYS(fmt, ...) do { } while (0)
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
@@ -13,6 +14,7 @@ long sys_kevent(int kq, const struct kevent *changelist, int nchanges,
|
|
struct kevent *eventlist, int nevents,
|
|
const struct timespec *timeout)
|
|
{
|
|
+ LOLSYS("sys_kevent entered");
|
|
int ret = kevent_impl(kq, changelist, nchanges, eventlist, nevents, timeout);
|
|
if (ret < 0)
|
|
ret = -errno;
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent64.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent64.c
|
|
index dd9bb0d..137f319 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent64.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent64.c
|
|
@@ -3,7 +3,8 @@
|
|
#include <stddef.h>
|
|
#include <sys/errno.h>
|
|
|
|
-#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#define LOLSYS(fmt, ...) do { } while (0)
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
@@ -13,6 +14,7 @@ long sys_kevent64(int kq, const struct kevent64_s *changelist, int nchanges,
|
|
struct kevent64_s *eventlist, int nevents, unsigned int flags,
|
|
const struct timespec *timeout)
|
|
{
|
|
+ LOLSYS("sys_kevent64 entered");
|
|
int ret = kevent64_impl(kq, changelist, nchanges, eventlist, nevents, flags, timeout);
|
|
if (ret < 0)
|
|
ret = -errno;
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent_qos.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent_qos.c
|
|
index 3f7cba6..bed91d7 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent_qos.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kevent_qos.c
|
|
@@ -4,7 +4,8 @@
|
|
#include <sys/errno.h>
|
|
#include <sys/fcntl.h>
|
|
|
|
-#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#define LOLSYS(fmt, ...) do { } while (0)
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/common/simple.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/fcntl/fcntl.h>
|
|
@@ -15,6 +16,9 @@
|
|
|
|
static int default_kq = -1;
|
|
|
|
+extern void* malloc(__SIZE_TYPE__ size);
|
|
+extern void free(void* ptr);
|
|
+
|
|
extern void* memmove(void* dst, const void* src, __SIZE_TYPE__ n);
|
|
|
|
static void kevent_qos_to_64(const struct kevent_qos_s *ev, struct kevent64_s* ev64);
|
|
@@ -29,6 +33,11 @@ long sys_kevent_qos(int kq, const struct kevent_qos_s *changelist, int nchanges,
|
|
struct kevent64_s* eventlist64 = NULL;
|
|
struct wq_kevent_data wq_kevent;
|
|
struct timespec polling_timeout = {};
|
|
+ struct kevent64_s small_changes[16], small_events[16];
|
|
+ struct kevent_qos_s workq_event;
|
|
+ void* allocated_changes = NULL;
|
|
+ void* allocated_events = NULL;
|
|
+ if(nchanges < 0 || nevents < 0) return -EINVAL;
|
|
|
|
if ((kq == -1) != !!(flags & KEVENT_FLAG_WORKQ))
|
|
return -EINVAL;
|
|
@@ -43,17 +52,28 @@ long sys_kevent_qos(int kq, const struct kevent_qos_s *changelist, int nchanges,
|
|
{
|
|
int i;
|
|
|
|
- changelist64 = (struct kevent64_s*) __builtin_alloca(nchanges * sizeof(struct kevent64_s));
|
|
+ if(nchanges <= 16) changelist64=small_changes;
|
|
+ else {
|
|
+ allocated_changes=malloc((size_t)nchanges*sizeof(struct kevent64_s));
|
|
+ if(!allocated_changes) return -ENOMEM;
|
|
+ changelist64=allocated_changes;
|
|
+ }
|
|
for (i = 0; i < nchanges; i++)
|
|
kevent_qos_to_64(&changelist[i], &changelist64[i]);
|
|
}
|
|
- if (eventlist != NULL && nevents > 0)
|
|
- eventlist64 = (struct kevent64_s*) __builtin_alloca(nevents * sizeof(struct kevent64_s));
|
|
+ if (eventlist != NULL && nevents > 0) {
|
|
+ if(nevents <= 16) eventlist64=small_events;
|
|
+ else {
|
|
+ allocated_events=malloc((size_t)nevents*sizeof(struct kevent64_s));
|
|
+ if(!allocated_events) { free(allocated_changes);return -ENOMEM; }
|
|
+ eventlist64=allocated_events;
|
|
+ }
|
|
+ }
|
|
if (nevents == 0 && (flags & KEVENT_FLAG_WORKQ))
|
|
{
|
|
- eventlist = (struct kevent_qos_s*) __builtin_alloca(nevents * sizeof(struct kevent_qos_s));
|
|
+ eventlist = &workq_event;
|
|
nevents = 1;
|
|
- eventlist64 = (struct kevent64_s*) __builtin_alloca(nevents * sizeof(struct kevent64_s));
|
|
+ eventlist64 = small_events;
|
|
}
|
|
|
|
rv = sys_kevent64(kq, changelist64, nchanges, eventlist64, nevents, flags, (flags & KEVENT_FLAG_IMMEDIATE) ? &polling_timeout : NULL);
|
|
@@ -83,6 +103,8 @@ long sys_kevent_qos(int kq, const struct kevent_qos_s *changelist, int nchanges,
|
|
// __simple_printf("workq done processing, returning %d to caller\n", rv);
|
|
}
|
|
|
|
+ free(allocated_events);
|
|
+ free(allocated_changes);
|
|
return rv;
|
|
}
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kqueue.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kqueue.c
|
|
index e808529..bd663f6 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kqueue.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/kqueue/kqueue.c
|
|
@@ -3,7 +3,8 @@
|
|
#include <stddef.h>
|
|
#include <sys/errno.h>
|
|
|
|
-#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#define LOLSYS(fmt, ...) do { } while (0)
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
#include <darling/emulation/common/simple.h>
|
|
@@ -16,6 +17,7 @@ int __attribute__((weak)) __attribute__((visibility("default"))) kqueue_impl(voi
|
|
|
|
long sys_kqueue(void)
|
|
{
|
|
+ LOLSYS("sys_kqueue entered");
|
|
int ret = kqueue_impl();
|
|
if (ret < 0)
|
|
ret = -errno;
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makefd.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makefd.c
|
|
index 1afb57e..d6896e6 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makefd.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makefd.c
|
|
@@ -3,14 +3,20 @@
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
-#include <darling/emulation/other/mach/lkm.h>
|
|
+#include <darlingserver/rpc.h>
|
|
+
|
|
+extern int dserver_rpc_fileport_makefd(unsigned int port_name, int* fd);
|
|
|
|
long sys_fileport_makefd(int port)
|
|
{
|
|
- int ret = lkm_call(NR_fileport_makefd, (void*)(unsigned long) port);
|
|
+ int fd = -1;
|
|
+ int ret;
|
|
|
|
- if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ /* darling-lol M54: see fileport_makeport.c. */
|
|
+ ret = dserver_rpc_fileport_makefd((unsigned int)port, &fd);
|
|
+ if (ret < 0) {
|
|
+ return -ret;
|
|
+ }
|
|
|
|
- return ret;
|
|
+ return fd;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makeport.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makeport.c
|
|
index be2926c..a25882e 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makeport.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/fileport_makeport.c
|
|
@@ -3,20 +3,22 @@
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
-#include <darling/emulation/other/mach/lkm.h>
|
|
+#include <darlingserver/rpc.h>
|
|
+
|
|
+extern int dserver_rpc_fileport_makeport(int fd, unsigned int* port_name);
|
|
|
|
long sys_fileport_makeport(int fd, int* port)
|
|
{
|
|
- struct fileport_makeport_args args;
|
|
-
|
|
- args.fd = fd;
|
|
- args.port_out = 0;
|
|
-
|
|
- int ret = lkm_call(NR_fileport_makeport, &args);
|
|
+ unsigned int port_name = 0;
|
|
+ int ret;
|
|
|
|
- if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ /* darling-lol M54: the old LKM API is gone (its stub UD2s). Route
|
|
+ * through darlingserver, which holds the FD in a fileport kobject. */
|
|
+ ret = dserver_rpc_fileport_makeport(fd, &port_name);
|
|
+ if (ret < 0) {
|
|
+ return -ret;
|
|
+ }
|
|
|
|
- *port = args.port_out;
|
|
+ *port = (int)port_name;
|
|
return 0;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/mac_syscall.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/mac_syscall.c
|
|
new file mode 100644
|
|
index 0000000..479294a
|
|
--- /dev/null
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/mac_syscall.c
|
|
@@ -0,0 +1,53 @@
|
|
+#include <darling/emulation/xnu_syscall/bsd/impl/misc/mac_syscall.h>
|
|
+
|
|
+#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
+#include <darling/emulation/conversion/duct_errno.h>
|
|
+
|
|
+extern int memcmp(const void* s1, const void* s2, __SIZE_TYPE__ n);
|
|
+extern __SIZE_TYPE__ strlen(const char* s);
|
|
+
|
|
+long sys___mac_syscall(const char* policy, int call, void* arg)
|
|
+{
|
|
+ char name[32];
|
|
+ __SIZE_TYPE__ i;
|
|
+
|
|
+ /* darling-lol: syscall 381 was unmapped (ENOSYS), and Chromium's
|
|
+ * seatbelt client CHECKs it, killing every helper/GPU process with a
|
|
+ * silent UD2. There is no MAC framework to back this; for the
|
|
+ * "Seatbelt" policy we fail soft with success (consistent with the
|
|
+ * sandbox_init stubs that report success without enforcement).
|
|
+ * Anything else keeps ENOSYS. Guest memory is directly readable here;
|
|
+ * bound the copy. */
|
|
+
|
|
+ if (policy == NULL) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < sizeof(name) - 1; i++) {
|
|
+ char c = policy[i];
|
|
+ name[i] = c;
|
|
+ if (c == '\0') {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ name[sizeof(name) - 1] = '\0';
|
|
+
|
|
+ if (memcmp(name, "Seatbelt", 9) == 0) {
|
|
+ __simple_printf("mac_syscall: Seatbelt call %d (arg=%p), no enforcement, succeeding\n",
|
|
+ call, arg);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (memcmp(name, "AMFI", 5) == 0) {
|
|
+ /* darling-lol: same posture as Seatbelt (Chromium's sandbox code
|
|
+ * probes AMFI status via ___sandbox_ms; ENOSYS trips its CHECK).
|
|
+ * No code-signing enforcement exists to report on. */
|
|
+ __simple_printf("mac_syscall: AMFI call %d (arg=%p), no enforcement, succeeding\n",
|
|
+ call, arg);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ __simple_printf("mac_syscall: unsupported policy \"%s\" call %d\n", name, call);
|
|
+ return -ENOSYS;
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c
|
|
index c2f70fb..af904ed 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c
|
|
@@ -83,6 +83,38 @@ long sys_proc_info(uint32_t callnum, int32_t pid, uint32_t flavor,
|
|
case 2: // proc_pidinfo
|
|
return _proc_pidinfo(pid, flavor, arg, buffer, bufsize);
|
|
case 3: // proc_pidfdinfo
|
|
+ {
|
|
+ if (flavor == PROC_PIDFDVNODEPATHINFO)
|
|
+ {
|
|
+ struct vnode_fdinfowithpath* info = (struct vnode_fdinfowithpath*) buffer;
|
|
+ struct vchroot_unexpand_args uargs;
|
|
+ char procpath[64];
|
|
+ long len;
|
|
+
|
|
+ if (!buffer)
|
|
+ return -EFAULT;
|
|
+ if (bufsize < (int32_t)sizeof(*info))
|
|
+ return -ENOSPC;
|
|
+
|
|
+ memset(buffer, 0, bufsize);
|
|
+
|
|
+ /* Resolve /proc/<pid>/fd/<fd> to the emulated path. */
|
|
+ __simple_sprintf(procpath, "/proc/%d/fd/%d", pid, (int)arg);
|
|
+ len = sys_readlink(procpath, uargs.path, sizeof(uargs.path) - 1);
|
|
+ if (len < 0)
|
|
+ return errno_linux_to_bsd(len);
|
|
+ uargs.path[len] = 0;
|
|
+
|
|
+ if (vchroot_unexpand(&uargs) != 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ strncpy(info->pvip.vip_path, uargs.path, sizeof(info->pvip.vip_path) - 1);
|
|
+ return sizeof(*info);
|
|
+ }
|
|
+ __simple_printf("sys_proc_info(): Unsupported pidfdinfo flavor: %d\n",
|
|
+ flavor);
|
|
+ return -ENOTSUP;
|
|
+ }
|
|
case 1: // proc_listpids
|
|
default:
|
|
__simple_printf("sys_proc_info(): Unsupported callnum: %d\n",
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c
|
|
index 0a8f1e4..55aadbe 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c
|
|
@@ -171,6 +171,17 @@ long sys_ptrace(int request, int pid, void* addr, int data)
|
|
// return ret;
|
|
cmd = "PT_THUPDATE"; break;
|
|
}
|
|
+ case XNU_PT_DENY_ATTACH:
|
|
+ {
|
|
+ // Apple semantics: this process can no longer be traced or
|
|
+ // debugged; always succeeds. Darling has no kernel debugger
|
|
+ // support to disable, but the call itself must report success:
|
|
+ // anti-tamper code treats any other return as failure.
|
|
+ // (darling-lol: LeagueClient dies right after this call when
|
|
+ // it returns stack garbage instead of 0.)
|
|
+ ret = 0;
|
|
+ cmd = "PT_DENY_ATTACH"; break;
|
|
+ }
|
|
}
|
|
|
|
if (cmd != NULL)
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/sysctl.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/sysctl.c
|
|
index 10f39a5..29747d4 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/sysctl.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/sysctl.c
|
|
@@ -42,11 +42,15 @@ const struct known_sysctl sysctls_root = {
|
|
|
|
void copyout_string(const char* str, char* out, unsigned long* out_len)
|
|
{
|
|
- unsigned long len;
|
|
+ // as on XNU, the reported length includes the terminating NUL
|
|
+ unsigned long len = strlen(str) + 1;
|
|
+
|
|
if (out != NULL)
|
|
- len = strlcpy(out, str, *out_len);
|
|
- else
|
|
- len = strlen(str);
|
|
+ {
|
|
+ strlcpy(out, str, *out_len);
|
|
+ if (len > *out_len)
|
|
+ len = *out_len;
|
|
+ }
|
|
|
|
if (out_len != NULL)
|
|
*out_len = len;
|
|
@@ -130,7 +134,10 @@ long sys_sysctlbyname(const char* name, unsigned long namelen, void* old, unsign
|
|
}
|
|
|
|
if (next == NULL)
|
|
+ {
|
|
+ __simple_kprintf("sysctlbyname: unknown name %s", name);
|
|
return -ENOENT;
|
|
+ }
|
|
|
|
current = next;
|
|
token = strtok_r(NULL, ".", &saveptr);
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c
|
|
index 10c412b..adb7e24 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c
|
|
@@ -4,23 +4,97 @@
|
|
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/common/base.h>
|
|
+#include <darling/emulation/common/simple.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
+#include <darling/emulation/xnu_syscall/bsd/helper/misc/sysctl_proc.h>
|
|
|
|
#define MADV_FREE 5
|
|
#define MADV_FREE_REUSABLE 7
|
|
#define MADV_FREE_REUSE 8
|
|
+#define LINUX_MADV_FREE 8
|
|
+#define LINUX_MADV_POPULATE_WRITE 23
|
|
+
|
|
+/* darling-lol: Linux splits a transparent huge page when MADV_FREE covers only
|
|
+ * part of it. libmalloc hands the free hint out per 4 KiB page of its tiny,
|
|
+ * small and medium regions, i.e. for the mappings that otherwise get 2 MiB
|
|
+ * huge pages (measured on the target host: one 4 KiB hint turned 2 MiB of
|
|
+ * AnonHugePages into 512 small pages). Forwarding the hint only for whole huge
|
|
+ * pages stays inside the Darwin contract (a hint may cover less than the
|
|
+ * requested range) and keeps the huge pages.
|
|
+ *
|
|
+ * The host policy is read once per process: with THP "always" every anonymous
|
|
+ * mapping gets huge pages, so partial hints are skipped; with "never" or
|
|
+ * "madvise" there are no huge pages for this layer to split (the emulation
|
|
+ * never requests MADV_HUGEPAGE), so the full range is forwarded as before.
|
|
+ * An unreadable policy keeps the old full-range behaviour. */
|
|
+static unsigned long thp_huge_page_size; /* written before thp_mode */
|
|
+static int thp_mode = -1; /* -1 not probed, 0 full range, 1 whole huge pages only */
|
|
+
|
|
+static int host_thp_mode(void)
|
|
+{
|
|
+ int mode = __atomic_load_n(&thp_mode, __ATOMIC_ACQUIRE);
|
|
+ char buffer[64];
|
|
+
|
|
+ if (mode >= 0)
|
|
+ return mode;
|
|
+
|
|
+ mode = 0;
|
|
+ if (read_string("/sys/kernel/mm/transparent_hugepage/enabled", buffer, sizeof(buffer)))
|
|
+ {
|
|
+ const char* selected;
|
|
+
|
|
+ /* the selected policy is the one in brackets */
|
|
+ for (selected = buffer; *selected != '\0' && *selected != '['; ++selected)
|
|
+ ;
|
|
+ if (selected[0] == '[' && selected[1] == 'a') /* [always] */
|
|
+ mode = 1;
|
|
+ }
|
|
+ if (mode == 1 && read_string("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", buffer, sizeof(buffer)))
|
|
+ {
|
|
+ unsigned long size = __simple_atoi(buffer, 0);
|
|
+ if (size >= 4096 && (size & (size - 1)) == 0)
|
|
+ thp_huge_page_size = size;
|
|
+ }
|
|
+ __atomic_store_n(&thp_mode, mode, __ATOMIC_RELEASE);
|
|
+ return mode;
|
|
+}
|
|
|
|
long sys_madvise(void* addr, unsigned long len, int advice)
|
|
{
|
|
int ret;
|
|
|
|
- if (advice == MADV_FREE_REUSABLE || advice == MADV_FREE_REUSE)
|
|
- advice = MADV_FREE;
|
|
+ if (advice == MADV_FREE || advice == MADV_FREE_REUSABLE) {
|
|
+ if (host_thp_mode() == 1) {
|
|
+ unsigned long page = thp_huge_page_size;
|
|
+ unsigned long lo, hi;
|
|
|
|
- // advice < 0 are identical between OS X and Linux
|
|
- // advice >= 5 are specific to OS X/BSD
|
|
- if (advice >= 5)
|
|
+ if (page == 0)
|
|
+ page = 2 * 1024 * 1024;
|
|
+ /* round the range inward to whole huge pages */
|
|
+ lo = ((unsigned long) addr + page - 1) & ~(page - 1);
|
|
+ hi = ((unsigned long) addr + len) & ~(page - 1);
|
|
+ if (hi <= lo)
|
|
+ return 0; /* no whole huge page: nothing to split */
|
|
+ addr = (void*) lo;
|
|
+ len = hi - lo;
|
|
+ }
|
|
+ // The allocator no longer needs these private anonymous pages. Linux
|
|
+ // may reclaim them without swapping; subsequent writes cancel reclaim.
|
|
+ advice = LINUX_MADV_FREE;
|
|
+ } else if (advice == MADV_FREE_REUSE) {
|
|
+ // Reused pages must stop being lazily discardable. A write-prefault
|
|
+ // cancels MADV_FREE without changing the surviving page contents.
|
|
+ // This operation is available on Linux 5.14 and later.
|
|
+ //
|
|
+ // The full range is forwarded on purpose: Darwin promises that the
|
|
+ // range is no longer lazily reclaimable when REUSE returns, and a
|
|
+ // caller may reuse a page range that an earlier free hint marked.
|
|
+ // Only a write-prefault of that particular page cancels its laziness.
|
|
+ advice = LINUX_MADV_POPULATE_WRITE;
|
|
+ } else if (advice >= 5) {
|
|
+ // Other OS X/BSD-specific hints do not share Linux's numeric values.
|
|
return 0;
|
|
+ }
|
|
|
|
ret = LINUX_SYSCALL(__NR_madvise, addr, len, advice);
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/accept.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/accept.c
|
|
index 4daa631..5aeedd0 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/accept.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/accept.c
|
|
@@ -34,9 +34,16 @@ long sys_accept_nocancel(int fd, void* from, int* socklen)
|
|
ret = errno_linux_to_bsd(ret);
|
|
else
|
|
{
|
|
- fixed = (struct sockaddr_fixup*) from;
|
|
- fixed->bsd_family = sfamily_linux_to_bsd(fixed->linux_family);
|
|
- fixed->bsd_length = *socklen;
|
|
+ /* darling-lol M57: accept(fd, NULL, NULL) is legal on macOS (the
|
|
+ * caller isn't interested in the peer address). Chromium's socket
|
|
+ * code does this; dereferencing NULL crashed in
|
|
+ * sys_accept_nocancel. */
|
|
+ if (from != 0 && socklen != 0)
|
|
+ {
|
|
+ fixed = (struct sockaddr_fixup*) from;
|
|
+ fixed->bsd_family = sfamily_linux_to_bsd(fixed->linux_family);
|
|
+ fixed->bsd_length = *socklen;
|
|
+ }
|
|
|
|
// on macOS (and BSD), accept-ed sockets inherit O_NONBLOCK
|
|
int parent_flags = sys_fcntl_nocancel(fd, F_GETFL, 0);
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvfrom.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvfrom.c
|
|
index 1419bed..ea83e75 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvfrom.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvfrom.c
|
|
@@ -11,6 +11,8 @@
|
|
#include <darling/emulation/xnu_syscall/bsd/helper/bsdthread/cancelable.h>
|
|
|
|
extern void *memcpy(void *dest, const void *src, __SIZE_TYPE__ n);
|
|
+extern void rclol_trace_io(const char* op, int fd, long ret);
|
|
+
|
|
|
|
long sys_recvfrom(int fd, void* buf, unsigned long len,
|
|
int flags, void* from, int* socklen)
|
|
@@ -31,8 +33,11 @@ long sys_recvfrom_nocancel(int fd, void* buf, unsigned long len,
|
|
ret = LINUX_SYSCALL(__NR_socketcall, LINUX_SYS_RECVFROM, ((long[6]) { fd, buf, len,
|
|
flags, from, socklen }));
|
|
#else
|
|
- ret = LINUX_SYSCALL(__NR_recvfrom, fd, buf, len, flags, from, socklen);
|
|
+ /* this passed the unconverted BSD flags: BSD MSG_DONTWAIT (0x80) is Linux MSG_EOR and BSD MSG_WAITALL (0x40) is
|
|
+ * Linux MSG_DONTWAIT. sendto/sendmsg/recvmsg already used the converted value. */
|
|
+ ret = LINUX_SYSCALL(__NR_recvfrom, fd, buf, len, linux_flags, from, socklen);
|
|
#endif
|
|
+ rclol_trace_io("recvfrom", fd, ret);
|
|
|
|
if (ret < 0)
|
|
ret = errno_linux_to_bsd(ret);
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvmsg.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvmsg.c
|
|
index 927ccbb..ae353d3 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvmsg.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/network/recvmsg.c
|
|
@@ -17,6 +17,8 @@ extern void* memcpy(void* dest, const void* src, __SIZE_TYPE__ len);
|
|
|
|
#define ALIGN(what, how) ( ((what) + (how) - 1) & ~((how) - 1) )
|
|
|
|
+extern void rclol_trace_io(const char* op, int fd, long ret);
|
|
+
|
|
long sys_recvmsg(int socket, struct bsd_msghdr* msg, int flags)
|
|
{
|
|
CANCELATION_POINT();
|
|
@@ -62,6 +64,7 @@ long sys_recvmsg_nocancel(int socket, struct bsd_msghdr* msg, int flags)
|
|
#else
|
|
ret = LINUX_SYSCALL(__NR_recvmsg, socket, &lmsg, linux_flags);
|
|
#endif
|
|
+ rclol_trace_io("recvmsg", socket, ret);
|
|
|
|
if (ret < 0)
|
|
ret = errno_linux_to_bsd(ret);
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/process/fork.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/process/fork.c
|
|
index 76b6f53..a6c24ff 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/process/fork.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/process/fork.c
|
|
@@ -4,6 +4,7 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/duct_errno.h>
|
|
#include <darling/emulation/conversion/signal/duct_signals.h>
|
|
#include <darling/emulation/common/bsdthread/per_thread_wd.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/impl/unistd/fchdir.h>
|
|
@@ -18,18 +19,21 @@
|
|
|
|
extern _libkernel_functions_t _libkernel_functions;
|
|
|
|
-long sys_fork(void)
|
|
+/* The guest syscall trampoline does not promise ELF stack alignment. */
|
|
+__attribute__((force_align_arg_pointer)) long sys_fork(void)
|
|
{
|
|
int ret;
|
|
int wdfd = get_perthread_wd();
|
|
|
|
-#ifdef SYS_fork
|
|
- ret = LINUX_SYSCALL(__NR_fork);
|
|
-#else
|
|
- ret = LINUX_SYSCALL(__NR_clone, LINUX_SIGCHLD, 0);
|
|
-#endif
|
|
- if (ret < 0)
|
|
- ret = errno_linux_to_bsd(ret);
|
|
+ /* The child immediately uses host malloc while refreshing RPC sockets.
|
|
+ * A raw fork inherits locked glibc arenas from vanished host threads.
|
|
+ * Use the ELF fork, whose atfork machinery prepares those arenas and
|
|
+ * the loader's socket bitmap. Guest libsystem callbacks remain separate. */
|
|
+ extern struct elf_calls* elfcalls(void);
|
|
+ ret = elfcalls()->fork();
|
|
+ if (ret < 0) {
|
|
+ ret = errno_linux_to_bsd(-elfcalls()->get_errno());
|
|
+ }
|
|
else if (ret == 0)
|
|
{
|
|
// in the child
|
|
@@ -39,6 +43,11 @@ long sys_fork(void)
|
|
// that should also take care of closing descriptors for any other threads.
|
|
guard_table_postfork_child();
|
|
|
|
+ /* darling-lol: the child's IPC space is fresh (duct-tape ipc_task_init creates an empty one),
|
|
+ * so local Mach semaphores inherited from the parent must not be served in the child. */
|
|
+ extern void __darling_local_semaphores_after_fork(void);
|
|
+ __darling_local_semaphores_after_fork();
|
|
+
|
|
// create a new dserver RPC socket
|
|
__dserver_per_thread_socket_refresh();
|
|
int newReadFd = __dserver_process_lifetime_pipe_refresh();
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_cvwait.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_cvwait.c
|
|
index d093b73..b5d0ab1 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_cvwait.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_cvwait.c
|
|
@@ -6,12 +6,28 @@
|
|
#include <darling/emulation/conversion/duct_errno.h>
|
|
|
|
#include <darlingserver/rpc.h>
|
|
+#include <darling/emulation/common/slowwait.h>
|
|
|
|
long sys_psynch_cvwait(void* cv, uint64_t cvlsgen, uint32_t cvugen, void * mutex, uint64_t mugen,
|
|
uint32_t flags, int64_t sec, uint32_t nsec)
|
|
{
|
|
uint32_t retval;
|
|
+ {
|
|
+ /* darling-lol: the kernel drops the mutex for the caller before it waits (kern_synch.c _psynch_cvwait). A mutex
|
|
+ * whose waiters are queued in this process (psynch_mutexwait.c) has to be dropped here; the server knows
|
|
+ * nothing about it. */
|
|
+ extern int __darling_local_mutex_usable(uint32_t flags);
|
|
+ extern uint32_t __darling_local_mutexdrop(void* mutex, uint32_t mgen, uint32_t ugen, uint32_t flags);
|
|
+ if (mutex != 0 && __darling_local_mutex_usable(flags)) {
|
|
+ __darling_local_mutexdrop(mutex, (uint32_t) (mugen & 0xffffffffu), (uint32_t) (mugen >> 32), flags);
|
|
+ mutex = 0;
|
|
+ }
|
|
+ }
|
|
+ unsigned long long slowStart = slowwait_enabled() ? slowwait_now() : 0;
|
|
int ret = dserver_rpc_psynch_cvwait(cv, cvlsgen, cvugen, mutex, mugen, flags, sec, nsec, &retval);
|
|
+ if (slowStart) {
|
|
+ slowwait_log("psynch_cvwait", (unsigned int)(unsigned long) cv, slowStart, (void**) __builtin_frame_address(0), flags, (unsigned int) sec);
|
|
+ }
|
|
|
|
if (ret < 0) {
|
|
if (ret == -LINUX_EINTR) {
|
|
@@ -21,5 +37,8 @@ long sys_psynch_cvwait(void* cv, uint64_t cvlsgen, uint32_t cvugen, void * mutex
|
|
__simple_abort();
|
|
}
|
|
|
|
- return (ret) ? ret : retval;
|
|
+ /* RPC ret is a Darwin errno (possibly carrying condition-variable
|
|
+ * cleanup flags), not successful sequence bits. The syscall trampoline
|
|
+ * recognizes negative errors and sets errno for libpthread's -1 path. */
|
|
+ return (ret) ? -(long)ret : (long)retval;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexdrop.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexdrop.c
|
|
index 1799f60..d6f4879 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexdrop.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexdrop.c
|
|
@@ -8,6 +8,11 @@
|
|
|
|
long sys_psynch_mutexdrop(void* mutex, uint32_t mgen, uint32_t ugen, uint64_t tid, uint32_t flags)
|
|
{
|
|
+ extern int __darling_local_mutex_usable(uint32_t flags);
|
|
+ extern uint32_t __darling_local_mutexdrop(void* mutex, uint32_t mgen, uint32_t ugen, uint32_t flags);
|
|
+ if (__darling_local_mutex_usable(flags))
|
|
+ return __darling_local_mutexdrop(mutex, mgen, ugen, flags);
|
|
+
|
|
uint32_t retval;
|
|
int ret = dserver_rpc_psynch_mutexdrop(mutex, mgen, ugen, tid, flags, &retval);
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexwait.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexwait.c
|
|
index 2a05251..205eaa6 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexwait.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/psynch_mutexwait.c
|
|
@@ -7,11 +7,274 @@
|
|
#include <darling/emulation/conversion/duct_errno.h>
|
|
|
|
#include <darlingserver/rpc.h>
|
|
+#include <darling/emulation/common/slowwait.h>
|
|
+
|
|
+/* darling-lol: contended first-fit pthread mutexes handled inside the process.
|
|
+ *
|
|
+ * libpthread locks and unlocks an uncontended mutex in user space. When threads collide, the loser calls
|
|
+ * psynch_mutexwait to sleep and the unlocker calls psynch_mutexdrop to hand the lock on; both were round trips to
|
|
+ * darlingserver (~45 us each, with the caller asleep). In League the hot one is the game's own allocator mutex, taken
|
|
+ * by the main thread and the workers all the time.
|
|
+ *
|
|
+ * This is the kernel's algorithm for the FIRSTFIT policy (libpthread/kern/kern_synch.c: _psynch_mutexwait,
|
|
+ * _psynch_mutexdrop_internal, ksyn_queue_insert/remove_item, update_low_high) with the same sequence bookkeeping and the
|
|
+ * same return values, minus what only matters inside a kernel (owner tracking for priority inheritance, interruptible
|
|
+ * waits). A waiter sleeps on a futex. Fair-share and process-shared mutexes, condition variables and rwlocks still go
|
|
+ * to the server; psynch_cvwait performs the mutex drop here first when the mutex is a local one.
|
|
+ *
|
|
+ * Decided once per process: in the League game process this is on unless the host file /tmp/rclol-no-local-mutex
|
|
+ * exists. (/tmp/rclol-local-mutex used to be the opt-in switch and is now a no-op.) /tmp/rclol-local-mutex-all
|
|
+ * still enables it in every process (probe tests). */
|
|
+#define LMTX_COUNT_MASK 0xffffff00u
|
|
+#define LMTX_KBIT 0x01u
|
|
+#define LMTX_EBIT 0x02u
|
|
+#define LMTX_MTX_WAIT 0x20u
|
|
+#define LMTX_POLICY_MASK 0x1c0u
|
|
+#define LMTX_POLICY_FIRSTFIT 0x80u
|
|
+#define LMTX_PSHARED_MASK 0x30u
|
|
+#define LMTX_PSHARED 0x10u
|
|
+#define LMTX_BUCKETS 256u
|
|
+#define LMTX_POOL 4096u
|
|
+
|
|
+struct lmtx_waiter {
|
|
+ struct lmtx_waiter* next;
|
|
+ uint32_t lockseq;
|
|
+ uint32_t retval;
|
|
+ volatile int woken;
|
|
+};
|
|
+
|
|
+struct lmtx {
|
|
+ struct lmtx* next; /* bucket chain / free list */
|
|
+ unsigned long addr;
|
|
+ struct lmtx_waiter* head;
|
|
+ struct lmtx_waiter* tail;
|
|
+ uint32_t inqueue;
|
|
+ uint32_t prepost;
|
|
+ uint32_t firstnum, lastnum; /* sequence of the first and of the last queued waiter, as the kernel keeps them */
|
|
+ uint32_t lowseq, highseq;
|
|
+};
|
|
+
|
|
+static struct lmtx lmtx_pool[LMTX_POOL];
|
|
+static struct lmtx* lmtx_free;
|
|
+static unsigned int lmtx_pool_used;
|
|
+static struct lmtx* lmtx_buckets[LMTX_BUCKETS];
|
|
+static volatile int lmtx_locks[LMTX_BUCKETS];
|
|
+static volatile int lmtx_pool_lock;
|
|
+static volatile int lmtx_state; /* 0 undecided, 1 on, -1 off */
|
|
+static volatile int lmtx_pid;
|
|
+
|
|
+extern int __darling_is_league_game(void);
|
|
+
|
|
+static inline int lmtx_seq_lower(uint32_t x, uint32_t y)
|
|
+{
|
|
+ x &= LMTX_COUNT_MASK; y &= LMTX_COUNT_MASK;
|
|
+ return x < y ? (y - x) < (LMTX_COUNT_MASK / 2) : (x - y) > (LMTX_COUNT_MASK / 2);
|
|
+}
|
|
+
|
|
+static inline int lmtx_seq_higher(uint32_t x, uint32_t y)
|
|
+{
|
|
+ x &= LMTX_COUNT_MASK; y &= LMTX_COUNT_MASK;
|
|
+ return x > y ? (x - y) < (LMTX_COUNT_MASK / 2) : (y - x) > (LMTX_COUNT_MASK / 2);
|
|
+}
|
|
+
|
|
+/* Three-state lock (0 free, 1 held, 2 held with sleepers). A thread that has slept must take the lock as "2": it
|
|
+ * cannot know whether others are still asleep, and taking it as "1" made the next unlock skip the wake-up and left
|
|
+ * them sleeping forever (the first version of this did exactly that and hung the stress test). */
|
|
+static void lmtx_lock(volatile int* lock)
|
|
+{
|
|
+ int c = 0;
|
|
+ for (int spin = 0; spin < 64; ++spin) {
|
|
+ c = 0;
|
|
+ if (__atomic_compare_exchange_n(lock, &c, 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
|
+ return;
|
|
+ __asm__ volatile("pause");
|
|
+ }
|
|
+ if (c != 2)
|
|
+ c = __atomic_exchange_n(lock, 2, __ATOMIC_ACQUIRE);
|
|
+ while (c != 0) {
|
|
+ LINUX_SYSCALL(__NR_futex, lock, 0 /* FUTEX_WAIT */, 2, 0UL, 0UL, 0);
|
|
+ c = __atomic_exchange_n(lock, 2, __ATOMIC_ACQUIRE);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void lmtx_unlock(volatile int* lock)
|
|
+{
|
|
+ if (__atomic_exchange_n(lock, 0, __ATOMIC_RELEASE) == 2)
|
|
+ LINUX_SYSCALL(__NR_futex, lock, 1 /* FUTEX_WAKE */, 1, 0UL, 0UL, 0);
|
|
+}
|
|
+
|
|
+static inline unsigned int lmtx_bucket(unsigned long addr)
|
|
+{
|
|
+ return (unsigned int) ((addr >> 4) * 2654435761u) >> 24;
|
|
+}
|
|
+
|
|
+__attribute__((visibility("hidden")))
|
|
+int __darling_local_mutex_usable(uint32_t flags)
|
|
+{
|
|
+ int state = __atomic_load_n(&lmtx_state, __ATOMIC_ACQUIRE);
|
|
+ if (state == 0) {
|
|
+ if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-local-mutex-all", 0) == 0)
|
|
+ state = 1;
|
|
+ else if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-no-local-mutex", 0) != 0 && __darling_is_league_game())
|
|
+ state = 1;
|
|
+ else
|
|
+ state = -1;
|
|
+ __atomic_store_n(&lmtx_pid, (int) LINUX_SYSCALL(__NR_getpid), __ATOMIC_RELAXED);
|
|
+ __atomic_store_n(&lmtx_state, state, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ if (state != 1)
|
|
+ return 0;
|
|
+ if ((flags & LMTX_POLICY_MASK) != LMTX_POLICY_FIRSTFIT || (flags & LMTX_PSHARED_MASK) == LMTX_PSHARED)
|
|
+ return 0;
|
|
+ /* a forked child starts with empty queues: the threads recorded as waiting only exist in the parent */
|
|
+ int pid = (int) LINUX_SYSCALL(__NR_getpid);
|
|
+ if (__atomic_load_n(&lmtx_pid, __ATOMIC_RELAXED) != pid) {
|
|
+ for (unsigned int i = 0; i < LMTX_BUCKETS; ++i) { lmtx_buckets[i] = 0; lmtx_locks[i] = 0; }
|
|
+ lmtx_free = 0; lmtx_pool_used = 0; lmtx_pool_lock = 0;
|
|
+ __atomic_store_n(&lmtx_pid, pid, __ATOMIC_RELAXED);
|
|
+ }
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/* caller holds the bucket lock */
|
|
+static struct lmtx* lmtx_find(unsigned int bucket, unsigned long addr, int create)
|
|
+{
|
|
+ struct lmtx* m;
|
|
+ for (m = lmtx_buckets[bucket]; m; m = m->next)
|
|
+ if (m->addr == addr)
|
|
+ return m;
|
|
+ if (!create)
|
|
+ return 0;
|
|
+ lmtx_lock(&lmtx_pool_lock);
|
|
+ m = lmtx_free;
|
|
+ if (m)
|
|
+ lmtx_free = m->next;
|
|
+ else if (lmtx_pool_used < LMTX_POOL)
|
|
+ m = &lmtx_pool[lmtx_pool_used++];
|
|
+ lmtx_unlock(&lmtx_pool_lock);
|
|
+ if (!m) {
|
|
+ __simple_printf("darling-lol: local mutex table exhausted (%u entries)\n", LMTX_POOL);
|
|
+ __simple_abort();
|
|
+ }
|
|
+ m->addr = addr;
|
|
+ m->head = m->tail = 0;
|
|
+ m->inqueue = m->prepost = 0;
|
|
+ m->firstnum = m->lastnum = m->lowseq = m->highseq = 0;
|
|
+ m->next = lmtx_buckets[bucket];
|
|
+ lmtx_buckets[bucket] = m;
|
|
+ return m;
|
|
+}
|
|
+
|
|
+/* caller holds the bucket lock; an entry with no waiters and no pending hand-off is back in its initial state */
|
|
+static void lmtx_release_if_unused(unsigned int bucket, struct lmtx* m)
|
|
+{
|
|
+ if (m->inqueue != 0 || m->prepost != 0)
|
|
+ return;
|
|
+ struct lmtx** link = &lmtx_buckets[bucket];
|
|
+ while (*link && *link != m)
|
|
+ link = &(*link)->next;
|
|
+ if (*link)
|
|
+ *link = m->next;
|
|
+ m->addr = 0;
|
|
+ lmtx_lock(&lmtx_pool_lock);
|
|
+ m->next = lmtx_free;
|
|
+ lmtx_free = m;
|
|
+ lmtx_unlock(&lmtx_pool_lock);
|
|
+}
|
|
+
|
|
+__attribute__((visibility("hidden")))
|
|
+uint32_t __darling_local_mutexdrop(void* mutex, uint32_t mgen, uint32_t ugen, uint32_t flags)
|
|
+{
|
|
+ const unsigned long addr = (unsigned long) mutex;
|
|
+ const unsigned int bucket = lmtx_bucket(addr);
|
|
+ (void) mgen; (void) ugen; (void) flags;
|
|
+ lmtx_lock(&lmtx_locks[bucket]);
|
|
+ struct lmtx* m = lmtx_find(bucket, addr, 1);
|
|
+ if (m->inqueue == 0) {
|
|
+ /* nobody is queued yet: the contender that made the unlocker come here is still on its way */
|
|
+ m->prepost++;
|
|
+ lmtx_unlock(&lmtx_locks[bucket]);
|
|
+ return 0;
|
|
+ }
|
|
+ const uint32_t updatebits = (m->highseq & LMTX_COUNT_MASK) | (LMTX_EBIT | LMTX_KBIT);
|
|
+ struct lmtx_waiter* w = m->head;
|
|
+ m->head = w->next;
|
|
+ if (!m->head)
|
|
+ m->tail = 0;
|
|
+ if (--m->inqueue > 0) {
|
|
+ /* ksyn_queue_remove_item: first/last sequence from the queue ends, then low/high */
|
|
+ m->firstnum = m->head->lockseq;
|
|
+ m->lastnum = m->tail->lockseq;
|
|
+ if (m->lowseq == w->lockseq) m->lowseq = m->firstnum;
|
|
+ if (m->highseq == w->lockseq) m->highseq = m->lastnum;
|
|
+ } else {
|
|
+ m->firstnum = m->lastnum = m->lowseq = m->highseq = 0;
|
|
+ }
|
|
+ w->retval = updatebits;
|
|
+ volatile int* wake = &w->woken;
|
|
+ lmtx_release_if_unused(bucket, m);
|
|
+ __atomic_store_n(wake, 1, __ATOMIC_RELEASE); /* the waiter's stack frame may be gone right after this */
|
|
+ lmtx_unlock(&lmtx_locks[bucket]);
|
|
+ LINUX_SYSCALL(__NR_futex, wake, 1 /* FUTEX_WAKE */, 1, 0UL, 0UL, 0);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+__attribute__((visibility("hidden")))
|
|
+uint32_t __darling_local_mutexwait(void* mutex, uint32_t mgen, uint32_t ugen, uint32_t flags)
|
|
+{
|
|
+ const unsigned long addr = (unsigned long) mutex;
|
|
+ const unsigned int bucket = lmtx_bucket(addr);
|
|
+ const uint32_t lseq = mgen & LMTX_COUNT_MASK;
|
|
+ (void) ugen; (void) flags;
|
|
+ lmtx_lock(&lmtx_locks[bucket]);
|
|
+ struct lmtx* m = lmtx_find(bucket, addr, 1);
|
|
+ if (m->prepost) {
|
|
+ /* an unlock already arrived for a contender: take it */
|
|
+ m->prepost--;
|
|
+ uint32_t updatebits = (m->inqueue == 0 ? lseq : (m->highseq & LMTX_COUNT_MASK)) | (LMTX_KBIT | LMTX_EBIT);
|
|
+ updatebits &= ~LMTX_MTX_WAIT;
|
|
+ lmtx_release_if_unused(bucket, m);
|
|
+ lmtx_unlock(&lmtx_locks[bucket]);
|
|
+ return updatebits;
|
|
+ }
|
|
+ struct lmtx_waiter self;
|
|
+ self.next = 0;
|
|
+ self.lockseq = lseq;
|
|
+ self.retval = 0;
|
|
+ self.woken = 0;
|
|
+ /* ksyn_queue_insert, FIRSTFIT: arrival order */
|
|
+ if (m->inqueue == 0) {
|
|
+ m->head = m->tail = &self;
|
|
+ m->firstnum = m->lastnum = lseq;
|
|
+ } else {
|
|
+ m->tail->next = &self;
|
|
+ m->tail = &self;
|
|
+ if (lmtx_seq_lower(lseq, m->firstnum)) m->firstnum = lseq;
|
|
+ if (lmtx_seq_higher(lseq, m->lastnum)) m->lastnum = lseq;
|
|
+ }
|
|
+ if (++m->inqueue == 1) {
|
|
+ m->lowseq = m->highseq = lseq;
|
|
+ } else {
|
|
+ if (lmtx_seq_lower(lseq, m->lowseq)) m->lowseq = lseq;
|
|
+ if (lmtx_seq_higher(lseq, m->highseq)) m->highseq = lseq;
|
|
+ }
|
|
+ lmtx_unlock(&lmtx_locks[bucket]);
|
|
+ while (!__atomic_load_n(&self.woken, __ATOMIC_ACQUIRE))
|
|
+ LINUX_SYSCALL(__NR_futex, &self.woken, 0 /* FUTEX_WAIT */, 0, 0UL, 0UL, 0);
|
|
+ return self.retval & ~LMTX_MTX_WAIT;
|
|
+}
|
|
|
|
long sys_psynch_mutexwait(void* mutex, uint32_t mgen, uint32_t ugen, uint64_t tid, uint32_t flags)
|
|
{
|
|
+ if (__darling_local_mutex_usable(flags))
|
|
+ return __darling_local_mutexwait(mutex, mgen, ugen, flags);
|
|
+
|
|
uint32_t retval;
|
|
+ unsigned long long slowStart = slowwait_enabled() ? slowwait_now() : 0;
|
|
int ret = dserver_rpc_psynch_mutexwait(mutex, mgen, ugen, tid, flags, &retval);
|
|
+ if (slowStart) {
|
|
+ slowwait_log("psynch_mutexwait", (unsigned int)(unsigned long) mutex, slowStart, (void**) __builtin_frame_address(0), flags, 0);
|
|
+ }
|
|
|
|
if (ret < 0) {
|
|
if (ret == -LINUX_EINTR) {
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/ulock_wait.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/ulock_wait.c
|
|
index f26ec28..2f31473 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/ulock_wait.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/psynch/ulock_wait.c
|
|
@@ -3,6 +3,7 @@
|
|
#include <sys/errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
+#include <darling/emulation/xnu_syscall/bsd/impl/bsdthread/pthread_canceled.h>
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
@@ -60,3 +61,42 @@ long sys_ulock_wait(uint32_t operation, void* addr, uint64_t value, uint32_t tim
|
|
|
|
return ret;
|
|
}
|
|
+
|
|
+/* Nanosecond variant used by libpthread's private condition variables. The
|
|
+ * sequence check and sleep are atomic in FUTEX_WAIT; a signal between the
|
|
+ * userspace unlock and this call therefore cannot be lost. */
|
|
+long sys_ulock_wait2(uint32_t operation, void* addr, uint64_t value,
|
|
+ uint64_t timeout, uint64_t value2)
|
|
+{
|
|
+ const uint32_t allowed = XNU_UL_OPCODE_MASK | XNU_ULF_NO_ERRNO |
|
|
+ 0x00010000u /* WORKQ_DATA_CONTENTION */ | 0x00020000u /* CANCEL_POINT */ |
|
|
+ 0x00040000u /* ADAPTIVE_SPIN */;
|
|
+ int op = operation & XNU_UL_OPCODE_MASK;
|
|
+ int ret;
|
|
+ if ((operation & ~allowed) || value2 ||
|
|
+ (op != XNU_UL_COMPARE_AND_WAIT && op != XNU_UL_UNFAIR_LOCK) ||
|
|
+ ((uintptr_t)addr & 3)) {
|
|
+ ret = -EINVAL;
|
|
+ } else {
|
|
+ int cancellation = (operation & 0x00020000u) ? sys_pthread_canceled(0) : -EINVAL;
|
|
+ if (cancellation == 0) {
|
|
+ ret = -EINTR;
|
|
+ } else {
|
|
+ /* Darling's server does not implement markcancel/canceled yet.
|
|
+ * Bound cancelable sleeps in that case so libpthread can observe
|
|
+ * its pending cancellation bit. EINTR is a permitted spurious
|
|
+ * condition wake, not a premature timed-wait timeout. */
|
|
+ bool cancellation_poll = cancellation == -ENOSYS &&
|
|
+ (!timeout || timeout > 50000000ull);
|
|
+ uint64_t sleep_ns = cancellation_poll ? 50000000ull : timeout;
|
|
+ struct timespec ts = { sleep_ns / 1000000000ull, sleep_ns % 1000000000ull };
|
|
+ ret = LINUX_SYSCALL(__NR_futex, addr, FUTEX_WAIT | FUTEX_PRIVATE_FLAG,
|
|
+ (uint32_t)value, sleep_ns ? &ts : NULL, 0UL, 0);
|
|
+ if (ret == -LINUX_ETIMEDOUT && cancellation_poll) ret = -LINUX_EINTR;
|
|
+ if (ret == 0 || ret == -LINUX_EAGAIN) ret = 1;
|
|
+ else if (ret < 0) ret = errno_linux_to_bsd(ret);
|
|
+ }
|
|
+ }
|
|
+ if (ret < 0 && (operation & XNU_ULF_NO_ERRNO)) ret &= ~0x800;
|
|
+ return ret;
|
|
+}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/select/select.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/select/select.c
|
|
index c3c33f9..831e8cb 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/select/select.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/select/select.c
|
|
@@ -7,6 +7,168 @@
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
#include <darling/emulation/xnu_syscall/bsd/helper/bsdthread/cancelable.h>
|
|
|
|
+#include <darling/emulation/common/simple.h>
|
|
+
|
|
+/* darling-lol: opt-in trace for select() busy loops (a thread whose select keeps returning "ready" without
|
|
+ * anything consuming the descriptor). Enabled when the host file /tmp/rclol-select-trace exists at the first select
|
|
+ * of a process; output is appended to the host /tmp/rclol-select-trace.log. After 5000 consecutive "ready" returns it
|
|
+ * logs which descriptors were ready and marks the first readable one; read/recv/recvmsg/close on that descriptor are
|
|
+ * then logged too (descriptor numbers and return values only, never data). */
|
|
+int rclol_trace_fd = -1;
|
|
+static int rclol_trace_state = -1; /* -1 unknown, 0 off, 1 on */
|
|
+static unsigned rclol_trace_lines = 0;
|
|
+
|
|
+static int rclol_trace_enabled(void)
|
|
+{
|
|
+ if (rclol_trace_state < 0)
|
|
+ rclol_trace_state = LINUX_SYSCALL(__NR_access, "/tmp/rclol-select-trace", 0) == 0;
|
|
+ return rclol_trace_state;
|
|
+}
|
|
+
|
|
+static void rclol_trace_write(const char* line)
|
|
+{
|
|
+ unsigned long length = 0;
|
|
+ int fd;
|
|
+ if (rclol_trace_lines >= 400)
|
|
+ return;
|
|
+ ++rclol_trace_lines;
|
|
+ fd = LINUX_SYSCALL(__NR_open, "/tmp/rclol-select-trace.log", 0x441 /* O_WRONLY | O_CREAT | O_APPEND */, 0644);
|
|
+ if (fd < 0)
|
|
+ return;
|
|
+ while (line[length]) ++length;
|
|
+ LINUX_SYSCALL(__NR_write, fd, line, length);
|
|
+ LINUX_SYSCALL1(__NR_close, fd);
|
|
+}
|
|
+
|
|
+void rclol_trace_io(const char* op, int fd, long ret)
|
|
+{
|
|
+ char line[128];
|
|
+ if (rclol_trace_state != 1 || fd != rclol_trace_fd || fd < 0)
|
|
+ return;
|
|
+ __simple_sprintf(line, "io pid=%d tid=%d %s fd=%d ret=%ld\n", (int) LINUX_SYSCALL0(__NR_getpid),
|
|
+ (int) LINUX_SYSCALL0(__NR_gettid), op, fd, ret);
|
|
+ rclol_trace_write(line);
|
|
+}
|
|
+
|
|
+static void rclol_trace_select(int nfds, const unsigned* rfds, const unsigned* wfds, const unsigned* efds,
|
|
+ struct bsd_timeval* timeout, int ret)
|
|
+{
|
|
+ static unsigned streak = 0;
|
|
+ char line[512];
|
|
+ int used, fd, listed = 0, first_readable = -1;
|
|
+
|
|
+ if (ret <= 0) {
|
|
+ streak = 0;
|
|
+ return;
|
|
+ }
|
|
+ if (++streak != 5000 && streak % 100000 != 0)
|
|
+ return;
|
|
+
|
|
+ used = __simple_sprintf(line, "select pid=%d tid=%d streak=%u nfds=%d ret=%d timeout=%lds+%dus ready:",
|
|
+ (int) LINUX_SYSCALL0(__NR_getpid), (int) LINUX_SYSCALL0(__NR_gettid), streak, nfds, ret,
|
|
+ timeout ? (long) timeout->tv_sec : -1L, timeout ? (int) timeout->tv_usec : 0);
|
|
+ for (fd = 0; fd < nfds && fd < 1024 && listed < 12; ++fd) {
|
|
+ int r = rfds && (rfds[fd / 32] >> (fd % 32) & 1);
|
|
+ int w = wfds && (wfds[fd / 32] >> (fd % 32) & 1);
|
|
+ int e = efds && (efds[fd / 32] >> (fd % 32) & 1);
|
|
+ if (!r && !w && !e)
|
|
+ continue;
|
|
+ used += __simple_sprintf(line + used, " %d%s%s%s", fd, r ? "r" : "", w ? "w" : "", e ? "e" : "");
|
|
+ ++listed;
|
|
+ if (r && first_readable < 0)
|
|
+ first_readable = fd;
|
|
+ }
|
|
+ line[used++] = '\n';
|
|
+ line[used] = 0;
|
|
+ rclol_trace_write(line);
|
|
+ if (first_readable >= 0)
|
|
+ rclol_trace_fd = first_readable;
|
|
+}
|
|
+
|
|
+/* darling-lol: idle-poll backoff.
|
|
+ * The League client's HTTP/TLS reader (LeagueClient thread "ExternalClient") and a second client process call
|
|
+ * select() with a zero timeout in a tight loop, 20-30 thousand times a second, finding nothing (measured: 8 million
|
|
+ * calls, 99.95% empty, always the same descriptor). That burns ~80% of a core, i.e. several watts of a power-limited
|
|
+ * APU that the game's main thread would otherwise turn into clock speed. Limit this workaround to those client
|
|
+ * executables: the game also does repeated empty polls on its frame thread, where sleeping delays every frame.
|
|
+ * A client thread that has polled with a zero timeout
|
|
+ * and found nothing thousands of times in a row is idle-polling, so sleep briefly between its polls: the first
|
|
+ * poll that finds something resets the streak, and the added latency is at most 0.5 ms. Streaks are tracked per
|
|
+ * thread in a small table. Disable with the host file /tmp/rclol-no-select-backoff. */
|
|
+struct rclol_poll_slot { int tid; unsigned streak; };
|
|
+static struct rclol_poll_slot rclol_poll_slots[256];
|
|
+static int rclol_backoff_state = -1; /* -1 unknown, 0 off, 1 on */
|
|
+
|
|
+static int rclol_client_poll_backoff_enabled(void)
|
|
+{
|
|
+ char argv0[512];
|
|
+ const char* basename = argv0;
|
|
+ const char* clients[] = { "LeagueClient", "RiotClientServices" };
|
|
+ long fd, length;
|
|
+ unsigned i, j;
|
|
+ if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-no-select-backoff", 0) == 0)
|
|
+ return 0;
|
|
+ /* Native /proc identifies argv[0] without a server RPC or guest path translation.
|
|
+ * Fail closed on a read error or a truncated executable name; never log arguments. */
|
|
+ fd = LINUX_SYSCALL(__NR_open, "/proc/self/cmdline", 0, 0);
|
|
+ if (fd < 0)
|
|
+ return 0;
|
|
+ length = LINUX_SYSCALL(__NR_read, fd, argv0, sizeof(argv0));
|
|
+ LINUX_SYSCALL1(__NR_close, fd);
|
|
+ if (length <= 0)
|
|
+ return 0;
|
|
+ for (i = 0; i < (unsigned) length && argv0[i]; ++i)
|
|
+ if (argv0[i] == '/') basename = argv0 + i + 1;
|
|
+ if (i == (unsigned) length)
|
|
+ return 0;
|
|
+ for (i = 0; i < sizeof(clients) / sizeof(clients[0]); ++i) {
|
|
+ for (j = 0; basename[j] && basename[j] == clients[i][j]; ++j) {}
|
|
+ if (!basename[j] && !clients[i][j])
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void rclol_select_backoff_before(struct bsd_timeval* timeout)
|
|
+{
|
|
+ struct rclol_poll_slot* slot;
|
|
+ unsigned streak;
|
|
+ int tid;
|
|
+ if (rclol_backoff_state < 0)
|
|
+ rclol_backoff_state = rclol_client_poll_backoff_enabled();
|
|
+ if (!rclol_backoff_state || timeout == NULL || timeout->tv_sec != 0 || timeout->tv_usec != 0)
|
|
+ return;
|
|
+ tid = (int) LINUX_SYSCALL0(__NR_gettid);
|
|
+ slot = &rclol_poll_slots[(unsigned) tid & 255];
|
|
+ if (__atomic_load_n(&slot->tid, __ATOMIC_RELAXED) != tid)
|
|
+ return;
|
|
+ streak = __atomic_load_n(&slot->streak, __ATOMIC_RELAXED);
|
|
+ if (streak >= 2000) {
|
|
+ struct { long tv_sec; long tv_nsec; } pause = { 0, streak >= 20000 ? 500000 : 100000 };
|
|
+ LINUX_SYSCALL(__NR_nanosleep, &pause, 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void rclol_select_backoff_after(struct bsd_timeval* timeout, int ret)
|
|
+{
|
|
+ struct rclol_poll_slot* slot;
|
|
+ int tid;
|
|
+ if (!rclol_backoff_state)
|
|
+ return;
|
|
+ tid = (int) LINUX_SYSCALL0(__NR_gettid);
|
|
+ slot = &rclol_poll_slots[(unsigned) tid & 255];
|
|
+ if (timeout != NULL && timeout->tv_sec == 0 && timeout->tv_usec == 0 && ret == 0) {
|
|
+ if (__atomic_load_n(&slot->tid, __ATOMIC_RELAXED) != tid) {
|
|
+ __atomic_store_n(&slot->tid, tid, __ATOMIC_RELAXED);
|
|
+ __atomic_store_n(&slot->streak, 1, __ATOMIC_RELAXED);
|
|
+ } else if (__atomic_load_n(&slot->streak, __ATOMIC_RELAXED) < 1000000) {
|
|
+ __atomic_fetch_add(&slot->streak, 1, __ATOMIC_RELAXED);
|
|
+ }
|
|
+ } else {
|
|
+ __atomic_store_n(&slot->streak, 0, __ATOMIC_RELAXED);
|
|
+ }
|
|
+}
|
|
+
|
|
long sys_select(int nfds, void* rfds, void* wfds, void* efds, struct bsd_timeval* timeout)
|
|
{
|
|
CANCELATION_POINT();
|
|
@@ -25,6 +187,28 @@ long sys_select_nocancel(int nfds, void* rfds, void* wfds, void* efds, struct bs
|
|
ltv.tv_usec = timeout->tv_usec;
|
|
}
|
|
|
|
+ /* every 20000th call of the process: what was asked (taken before the kernel rewrites the sets) */
|
|
+ static unsigned rclol_calls = 0, rclol_pos = 0, rclol_zero = 0, rclol_neg = 0;
|
|
+ char rclol_line[400];
|
|
+ int rclol_used = 0;
|
|
+ if (rclol_trace_enabled() && ++rclol_calls % 20000 == 0) {
|
|
+ int fd, listed = 0;
|
|
+ rclol_used = __simple_sprintf(rclol_line, "sample pid=%d tid=%d calls=%u pos=%u zero=%u neg=%u nfds=%d timeout=%lds+%dus asked:",
|
|
+ (int) LINUX_SYSCALL0(__NR_getpid), (int) LINUX_SYSCALL0(__NR_gettid), rclol_calls, rclol_pos, rclol_zero,
|
|
+ rclol_neg, nfds, timeout ? (long) timeout->tv_sec : -1L, timeout ? (int) timeout->tv_usec : 0);
|
|
+ for (fd = 0; fd < nfds && fd < 1024 && listed < 12; ++fd) {
|
|
+ int r = rfds && (((const unsigned*) rfds)[fd / 32] >> (fd % 32) & 1);
|
|
+ int w = wfds && (((const unsigned*) wfds)[fd / 32] >> (fd % 32) & 1);
|
|
+ int e = efds && (((const unsigned*) efds)[fd / 32] >> (fd % 32) & 1);
|
|
+ if (!r && !w && !e)
|
|
+ continue;
|
|
+ rclol_used += __simple_sprintf(rclol_line + rclol_used, " %d%s%s%s", fd, r ? "r" : "", w ? "w" : "", e ? "e" : "");
|
|
+ ++listed;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ rclol_select_backoff_before(timeout);
|
|
+
|
|
#if defined(__NR__newselect)
|
|
ret = LINUX_SYSCALL(__NR__newselect, nfds, rfds, wfds, efds,
|
|
(timeout != NULL) ? <v : NULL);
|
|
@@ -32,6 +216,15 @@ long sys_select_nocancel(int nfds, void* rfds, void* wfds, void* efds, struct bs
|
|
ret = LINUX_SYSCALL(__NR_select, nfds, rfds, wfds, efds,
|
|
(timeout != NULL) ? <v : NULL);
|
|
#endif
|
|
+ rclol_select_backoff_after(timeout, ret);
|
|
+ if (rclol_trace_enabled()) {
|
|
+ if (ret > 0) ++rclol_pos; else if (ret == 0) ++rclol_zero; else ++rclol_neg;
|
|
+ if (rclol_used) {
|
|
+ __simple_sprintf(rclol_line + rclol_used, " -> ret=%d\n", ret);
|
|
+ rclol_trace_write(rclol_line);
|
|
+ }
|
|
+ rclol_trace_select(nfds, rfds, wfds, efds, timeout, ret);
|
|
+ }
|
|
if (ret < 0)
|
|
ret = errno_linux_to_bsd(ret);
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/fstatfs.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/fstatfs.c
|
|
index 1098510..1741d5a 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/fstatfs.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/fstatfs.c
|
|
@@ -19,6 +19,9 @@ extern __SIZE_TYPE__ strlen(const char* str);
|
|
extern int strncmp(const char* s1, const char* s2, __SIZE_TYPE__ len);
|
|
extern int sprintf(char *str, const char *format, ...);
|
|
|
|
+/* darling-lol M58F140: shared cached /proc/self/mounts lookup (see statfs.c) */
|
|
+VISIBLE void lolsys_fill_mount_names(const char* path, char* mnton, __SIZE_TYPE__ mnton_sz, char* fstype, __SIZE_TYPE__ fstype_sz, char* mntfrom, __SIZE_TYPE__ mntfrom_sz);
|
|
+
|
|
long sys_fstatfs(int fd, struct bsd_statfs* buf)
|
|
{
|
|
#ifdef __x86_64__
|
|
@@ -31,12 +34,9 @@ long sys_fstatfs(int fd, struct bsd_statfs* buf)
|
|
|
|
long sys_fstatfs64(int fd, struct bsd_statfs64* buf)
|
|
{
|
|
- int fd_m, ret;
|
|
- struct simple_readline_buf rbuf;
|
|
+ int ret;
|
|
struct linux_statfs64 lbuf;
|
|
- char line[512];
|
|
char path[1024];
|
|
- int max_len = 0;
|
|
|
|
#ifdef __NR_fstatfs64
|
|
ret = LINUX_SYSCALL3(__NR_fstatfs64, fd, sizeof(lbuf), &lbuf);
|
|
@@ -54,48 +54,11 @@ long sys_fstatfs64(int fd, struct bsd_statfs64* buf)
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
- fd_m = sys_open("/proc/self/mounts", O_RDONLY, 0);
|
|
- if (fd_m < 0)
|
|
- return fd_m;
|
|
-
|
|
- __simple_readline_init(&rbuf);
|
|
-
|
|
- buf->f_mntonname[0] = 0;
|
|
- buf->f_fstypename[0] = 0;
|
|
- buf->f_mntfromname[0] = 0;
|
|
buf->f_iosize = 512;
|
|
|
|
- while (__simple_readline(fd_m, &rbuf, line, sizeof(line)))
|
|
- {
|
|
- char* p;
|
|
- char* saveptr;
|
|
- char* mntfrom;
|
|
- int len;
|
|
-
|
|
- p = strtok_r(line, " ", &saveptr);
|
|
- if (p == NULL)
|
|
- continue;
|
|
-
|
|
- mntfrom = p;
|
|
- p = strtok_r(NULL, " ", &saveptr);
|
|
- if (p == NULL)
|
|
- continue;
|
|
-
|
|
- len = strlen(p);
|
|
- if (strncmp(p, path, len) != 0 || len < max_len)
|
|
- continue;
|
|
-
|
|
- max_len = len;
|
|
- strlcpy(buf->f_mntonname, p, sizeof(buf->f_mntonname));
|
|
-
|
|
- p = strtok_r(NULL, " ", &saveptr);
|
|
- if (p == NULL)
|
|
- continue;
|
|
-
|
|
- strlcpy(buf->f_fstypename, p, sizeof(buf->f_fstypename));
|
|
- strlcpy(buf->f_mntfromname, mntfrom, sizeof(buf->f_mntfromname));
|
|
- }
|
|
-
|
|
- close_internal(fd_m);
|
|
+ lolsys_fill_mount_names(path,
|
|
+ buf->f_mntonname, sizeof(buf->f_mntonname),
|
|
+ buf->f_fstypename, sizeof(buf->f_fstypename),
|
|
+ buf->f_mntfromname, sizeof(buf->f_mntfromname));
|
|
return 0;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/statfs.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/statfs.c
|
|
index a6a5162..2155dc4 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/statfs.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/stat/statfs.c
|
|
@@ -20,6 +20,92 @@ extern __SIZE_TYPE__ strlcpy(char* dst, const char* src, __SIZE_TYPE__ size);
|
|
extern __SIZE_TYPE__ strlen(const char* str);
|
|
extern int strncmp(const char* s1, const char* s2, __SIZE_TYPE__ len);
|
|
|
|
+/* darling-lol M58F140: /proc/self/mounts changes slowly, but statfs/fstatfs
|
|
+ * used to re-open and re-parse it on every call. The Riot launcher calls
|
|
+ * statfs hundreds of times while scanning its install; the per-call parse was a
|
|
+ * large part of its slow start (which tripped the patcher's "no progress"
|
|
+ * timeout). Cache the parsed table with a 1-second TTL. */
|
|
+#define LOLSYS_MAX_MOUNTS 512
|
|
+struct lolsys_mount_entry {
|
|
+ char mnton[1024];
|
|
+ char fstype[128];
|
|
+ char mntfrom[256];
|
|
+};
|
|
+static struct lolsys_mount_entry lolsys_mount_table[LOLSYS_MAX_MOUNTS];
|
|
+static int lolsys_mount_count = 0;
|
|
+static long lolsys_mount_sec = -1;
|
|
+
|
|
+static void lolsys_refresh_mounts(long now_sec)
|
|
+{
|
|
+ int fd;
|
|
+ struct simple_readline_buf rbuf;
|
|
+ char line[512];
|
|
+
|
|
+ if (lolsys_mount_count > 0 && now_sec == lolsys_mount_sec)
|
|
+ return;
|
|
+
|
|
+ lolsys_mount_count = 0;
|
|
+ fd = sys_open("/proc/self/mounts", O_RDONLY, 0);
|
|
+ if (fd < 0)
|
|
+ return;
|
|
+
|
|
+ __simple_readline_init(&rbuf);
|
|
+ while (__simple_readline(fd, &rbuf, line, sizeof(line))) {
|
|
+ char* p;
|
|
+ char* saveptr;
|
|
+ char* mntfrom;
|
|
+ struct lolsys_mount_entry* e;
|
|
+ if (lolsys_mount_count >= LOLSYS_MAX_MOUNTS)
|
|
+ break;
|
|
+ p = strtok_r(line, " ", &saveptr);
|
|
+ if (p == NULL)
|
|
+ continue;
|
|
+ mntfrom = p;
|
|
+ p = strtok_r(NULL, " ", &saveptr);
|
|
+ if (p == NULL)
|
|
+ continue;
|
|
+ e = &lolsys_mount_table[lolsys_mount_count];
|
|
+ strlcpy(e->mnton, p, sizeof(e->mnton));
|
|
+ p = strtok_r(NULL, " ", &saveptr);
|
|
+ if (p == NULL)
|
|
+ continue;
|
|
+ strlcpy(e->fstype, p, sizeof(e->fstype));
|
|
+ strlcpy(e->mntfrom, mntfrom, sizeof(e->mntfrom));
|
|
+ lolsys_mount_count++;
|
|
+ }
|
|
+ close_internal(fd);
|
|
+ lolsys_mount_sec = now_sec;
|
|
+}
|
|
+
|
|
+VISIBLE
|
|
+void lolsys_fill_mount_names(const char* path, char* mnton, __SIZE_TYPE__ mnton_sz, char* fstype, __SIZE_TYPE__ fstype_sz, char* mntfrom, __SIZE_TYPE__ mntfrom_sz)
|
|
+{
|
|
+ int i, max_len = 0;
|
|
+ struct { long s; long us; } tv;
|
|
+
|
|
+ tv.s = 0;
|
|
+ tv.us = 0;
|
|
+ LINUX_SYSCALL2(__NR_gettimeofday, &tv, 0);
|
|
+ lolsys_refresh_mounts(tv.s);
|
|
+
|
|
+ mnton[0] = 0;
|
|
+ fstype[0] = 0;
|
|
+ mntfrom[0] = 0;
|
|
+
|
|
+ for (i = 0; i < lolsys_mount_count; i++) {
|
|
+ struct lolsys_mount_entry* e = &lolsys_mount_table[i];
|
|
+ int len = (int) strlen(e->mnton);
|
|
+ if (len == 0)
|
|
+ continue;
|
|
+ if (strncmp(e->mnton, path, len) != 0 || len < max_len)
|
|
+ continue;
|
|
+ max_len = len;
|
|
+ strlcpy(mnton, e->mnton, mnton_sz);
|
|
+ strlcpy(fstype, e->fstype, fstype_sz);
|
|
+ strlcpy(mntfrom, e->mntfrom, mntfrom_sz);
|
|
+ }
|
|
+}
|
|
+
|
|
long sys_statfs(const char* path, struct bsd_statfs* buf)
|
|
{
|
|
#ifdef __x86_64__
|
|
@@ -32,11 +118,8 @@ long sys_statfs(const char* path, struct bsd_statfs* buf)
|
|
|
|
long sys_statfs64(const char* path, struct bsd_statfs64* buf)
|
|
{
|
|
- int fd, ret;
|
|
- struct simple_readline_buf rbuf;
|
|
+ int ret;
|
|
struct linux_statfs64 lbuf;
|
|
- char line[512];
|
|
- int max_len = 0;
|
|
|
|
struct vchroot_expand_args vc;
|
|
vc.flags = 0;
|
|
@@ -58,47 +141,9 @@ long sys_statfs64(const char* path, struct bsd_statfs64* buf)
|
|
|
|
statfs_linux_to_bsd64(&lbuf, buf);
|
|
|
|
- fd = sys_open("/proc/self/mounts", O_RDONLY, 0);
|
|
- if (fd < 0)
|
|
- return fd;
|
|
-
|
|
- __simple_readline_init(&rbuf);
|
|
-
|
|
- buf->f_mntonname[0] = 0;
|
|
- buf->f_fstypename[0] = 0;
|
|
- buf->f_mntfromname[0] = 0;
|
|
-
|
|
- while (__simple_readline(fd, &rbuf, line, sizeof(line)))
|
|
- {
|
|
- char* p;
|
|
- char* saveptr;
|
|
- char* mntfrom;
|
|
- int len;
|
|
-
|
|
- p = strtok_r(line, " ", &saveptr);
|
|
- if (p == NULL)
|
|
- continue;
|
|
-
|
|
- mntfrom = p;
|
|
- p = strtok_r(NULL, " ", &saveptr);
|
|
- if (p == NULL)
|
|
- continue;
|
|
-
|
|
- len = strlen(p);
|
|
- if (strncmp(p, vc.path, len) != 0 || len < max_len)
|
|
- continue;
|
|
-
|
|
- max_len = len;
|
|
- strlcpy(buf->f_mntonname, p, sizeof(buf->f_mntonname));
|
|
-
|
|
- p = strtok_r(NULL, " ", &saveptr);
|
|
- if (p == NULL)
|
|
- continue;
|
|
-
|
|
- strlcpy(buf->f_fstypename, p, sizeof(buf->f_fstypename));
|
|
- strlcpy(buf->f_mntfromname, mntfrom, sizeof(buf->f_mntfromname));
|
|
- }
|
|
-
|
|
- close_internal(fd);
|
|
+ lolsys_fill_mount_names(vc.path,
|
|
+ buf->f_mntonname, sizeof(buf->f_mntonname),
|
|
+ buf->f_fstypename, sizeof(buf->f_fstypename),
|
|
+ buf->f_mntfromname, sizeof(buf->f_mntfromname));
|
|
return 0;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/futimes.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/futimes.c
|
|
index 687a1b5..83b905a 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/futimes.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/futimes.c
|
|
@@ -4,20 +4,25 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/duct_errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
|
|
long sys_futimes(int fd, struct bsd_timeval* tv)
|
|
{
|
|
int ret;
|
|
- struct linux_timeval ltv[2];
|
|
+ struct { long tv_sec; long tv_nsec; } times[2];
|
|
+ if (tv) {
|
|
+ for (int i = 0; i < 2; ++i) {
|
|
+ if (tv[i].tv_usec < 0 || tv[i].tv_usec >= 1000000)
|
|
+ return errno_linux_to_bsd(-LINUX_EINVAL);
|
|
+ times[i].tv_sec = tv[i].tv_sec;
|
|
+ times[i].tv_nsec = (long)tv[i].tv_usec * 1000;
|
|
+ }
|
|
+ }
|
|
+ /* NULL requests current timestamps; utimensat uses nanoseconds. */
|
|
+ ret = LINUX_SYSCALL(__NR_utimensat, fd, NULL, tv ? times : NULL, 0);
|
|
|
|
- ltv[0].tv_sec = tv[0].tv_sec;
|
|
- ltv[0].tv_usec = tv[0].tv_usec;
|
|
- ltv[1].tv_sec = tv[1].tv_sec;
|
|
- ltv[1].tv_usec = tv[1].tv_usec;
|
|
-
|
|
- ret = LINUX_SYSCALL(__NR_utimensat, fd, NULL, ltv, 0);
|
|
if (ret < 0)
|
|
ret = errno_linux_to_bsd(ret);
|
|
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/gettimeofday.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/gettimeofday.c
|
|
index be89afd..96bc339 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/gettimeofday.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/time/gettimeofday.c
|
|
@@ -3,12 +3,32 @@
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
+#include <darling/emulation/linux_premigration/ext/sys/linux_time.h>
|
|
|
|
long sys_gettimeofday(struct bsd_timeval* tv, struct timezone* tz)
|
|
{
|
|
int ret;
|
|
struct linux_timeval ltv;
|
|
|
|
+#if defined(__x86_64__)
|
|
+ /* darling-lol: user-space clock, see linux_premigration/ext/clock_gettime.c */
|
|
+ if (tz == 0)
|
|
+ {
|
|
+ extern int __darling_vdso_clock_gettime(int clockid, struct timespec* res);
|
|
+ extern int __darling_fast_clock(int clockid, struct timespec* res);
|
|
+ struct timespec ts;
|
|
+ /* The vDSO's CLOCK_REALTIME is the same value the syscall would
|
|
+ * return; the TSC path stays the fallback for a host whose vDSO
|
|
+ * cannot serve the clock. */
|
|
+ if (__darling_vdso_clock_gettime(0 /* CLOCK_REALTIME */, &ts) == 0
|
|
+ || __darling_fast_clock(0 /* CLOCK_REALTIME */, &ts) == 0)
|
|
+ {
|
|
+ tv->tv_sec = ts.tv_sec;
|
|
+ tv->tv_usec = ts.tv_nsec / 1000;
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
ret = LINUX_SYSCALL(__NR_gettimeofday, <v, tz);
|
|
if (ret < 0)
|
|
{
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/close.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/close.c
|
|
index 6fad87f..e77e786 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/close.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/close.c
|
|
@@ -16,6 +16,8 @@ __attribute__((weak))
|
|
__attribute__((visibility("default")))
|
|
void kqueue_closed_fd(int fd) {}
|
|
|
|
+extern void rclol_trace_io(const char* op, int fd, long ret);
|
|
+
|
|
long sys_close(int fd)
|
|
{
|
|
CANCELATION_POINT();
|
|
@@ -39,6 +41,7 @@ long sys_close_nocancel(int fd)
|
|
}
|
|
|
|
ret = LINUX_SYSCALL1(__NR_close, fd);
|
|
+ rclol_trace_io("close", fd, ret);
|
|
if (ret < 0)
|
|
ret = errno_linux_to_bsd(ret);
|
|
else
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/read.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/read.c
|
|
index bafb315..1e8dd67 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/read.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/unistd/read.c
|
|
@@ -9,6 +9,9 @@
|
|
|
|
#define LINUX_TCGETA 0x5405
|
|
|
|
+extern void rclol_trace_io(const char* op, int fd, long ret);
|
|
+
|
|
+
|
|
long sys_read(int fd, void* mem, int len)
|
|
{
|
|
CANCELATION_POINT();
|
|
@@ -20,6 +23,7 @@ long sys_read_nocancel(int fd, void* mem, int len)
|
|
int ret;
|
|
|
|
ret = LINUX_SYSCALL3(__NR_read, fd, mem, len);
|
|
+ rclol_trace_io("read", fd, ret);
|
|
if (ret < 0)
|
|
{
|
|
// When the slave side of a PTY is closed, Linux produces -EIO on the master side.
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fgetxattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fgetxattr.c
|
|
index 6dca738..3d864d2 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fgetxattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fgetxattr.c
|
|
@@ -4,20 +4,23 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
long sys_fgetxattr(int fd, const char* name, char* value,
|
|
unsigned long size, unsigned int pos, int options)
|
|
{
|
|
int ret;
|
|
+ char linux_name[1024];
|
|
|
|
if (pos != 0)
|
|
return -ERANGE;
|
|
|
|
+ name = xattr_name_to_linux(name, linux_name, sizeof(linux_name));
|
|
ret = LINUX_SYSCALL(__NR_fgetxattr, fd, name, value, size);
|
|
|
|
if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ return xattr_errno_to_bsd(ret);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fremovexattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fremovexattr.c
|
|
index adaff44..eb445fe 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fremovexattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fremovexattr.c
|
|
@@ -2,16 +2,19 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
long sys_fremovexattr(int fd, const char* name, int options)
|
|
{
|
|
int ret;
|
|
+ char linux_name[1024];
|
|
|
|
+ name = xattr_name_to_linux(name, linux_name, sizeof(linux_name));
|
|
ret = LINUX_SYSCALL(__NR_fremovexattr, fd, name);
|
|
|
|
if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ return xattr_errno_to_bsd(ret);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fsetxattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fsetxattr.c
|
|
index 9aaef2c..1770c47 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fsetxattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/fsetxattr.c
|
|
@@ -4,20 +4,23 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
long sys_fsetxattr(int fd, const char* name, const void* value,
|
|
unsigned long size, unsigned int pos, int options)
|
|
{
|
|
int ret;
|
|
+ char linux_name[1024];
|
|
|
|
if (pos != 0)
|
|
return -ERANGE;
|
|
|
|
+ name = xattr_name_to_linux(name, linux_name, sizeof(linux_name));
|
|
ret = LINUX_SYSCALL(__NR_fsetxattr, fd, name, value, size);
|
|
|
|
if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ return xattr_errno_to_bsd(ret);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/getxattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/getxattr.c
|
|
index daca861..4b53ad2 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/getxattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/getxattr.c
|
|
@@ -7,6 +7,7 @@ extern char* strcpy(char* dst, const char* src);
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
#include <darling/emulation/conversion/xattr/getxattr.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
#include <darling/emulation/linux_premigration/vchroot_expand.h>
|
|
#include <darling/emulation/common/bsdthread/per_thread_wd.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
@@ -15,6 +16,7 @@ long sys_getxattr(const char* path, const char* name, char* value,
|
|
unsigned long size, unsigned int pos, int options)
|
|
{
|
|
int ret;
|
|
+ char linux_name[1024];
|
|
|
|
if (pos != 0)
|
|
return -ERANGE;
|
|
@@ -29,10 +31,16 @@ long sys_getxattr(const char* path, const char* name, char* value,
|
|
if (ret < 0)
|
|
return errno_linux_to_bsd(ret);
|
|
|
|
+ name = xattr_name_to_linux(name, linux_name, sizeof(linux_name));
|
|
+
|
|
ret = LINUX_SYSCALL(__NR_getxattr, vc.path, name, value, size);
|
|
|
|
if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ return xattr_errno_to_bsd(ret);
|
|
|
|
- return 0;
|
|
+ /* Darling returns the number of bytes copied (POSIX semantics).
|
|
+ * Returning 0 here made callers (e.g. Crashpad's
|
|
+ * crashpad.database.initialized marker) treat a present attribute as
|
|
+ * an empty value, so the crash database never initialised. */
|
|
+ return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/listxattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/listxattr.c
|
|
index f4740ec..bc88090 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/listxattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/listxattr.c
|
|
@@ -2,12 +2,38 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
#include <darling/emulation/linux_premigration/vchroot_expand.h>
|
|
#include <darling/emulation/common/bsdthread/per_thread_wd.h>
|
|
|
|
#define XATTR_NOFOLLOW 1
|
|
|
|
+/* Strip the "user." prefix from each NUL-separated name in the buffer so
|
|
+ * Darwin callers see namespace-less names, and compact the buffer. */
|
|
+static unsigned long xattr_strip_user_prefix(char* namebuf, unsigned long len)
|
|
+{
|
|
+ char* in = namebuf;
|
|
+ char* out = namebuf;
|
|
+ char* end = namebuf + len;
|
|
+
|
|
+ while (in < end) {
|
|
+ unsigned long n = 0;
|
|
+ const char* s = in;
|
|
+ char* dst = out;
|
|
+ if (__builtin_strncmp(s, "user.", 5) == 0)
|
|
+ s += 5;
|
|
+ while (s + n < end && s[n] != '\0') {
|
|
+ dst[n] = s[n];
|
|
+ n++;
|
|
+ }
|
|
+ dst[n] = '\0';
|
|
+ out += n + 1;
|
|
+ in += (s - in) + n + 1;
|
|
+ }
|
|
+ return (unsigned long)(out - namebuf);
|
|
+}
|
|
+
|
|
long sys_listxattr(const char* path, char* namebuf, unsigned long size, int options)
|
|
{
|
|
int ret;
|
|
@@ -23,12 +49,15 @@ long sys_listxattr(const char* path, char* namebuf, unsigned long size, int opti
|
|
return errno_linux_to_bsd(ret);
|
|
|
|
if (options & XATTR_NOFOLLOW)
|
|
- ret = LINUX_SYSCALL(__NR_llistxattr, path, namebuf, size);
|
|
+ ret = LINUX_SYSCALL(__NR_llistxattr, vc.path, namebuf, size);
|
|
else
|
|
- ret = LINUX_SYSCALL(__NR_listxattr, path, namebuf, size);
|
|
+ ret = LINUX_SYSCALL(__NR_listxattr, vc.path, namebuf, size);
|
|
|
|
if (ret < 0)
|
|
return errno_linux_to_bsd(ret);
|
|
|
|
+ if (namebuf != 0 && ret > 0)
|
|
+ ret = (int) xattr_strip_user_prefix(namebuf, (unsigned long) ret);
|
|
+
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/removexattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/removexattr.c
|
|
index 628a4f7..705b845 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/removexattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/removexattr.c
|
|
@@ -2,6 +2,9 @@
|
|
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
+#include <darling/emulation/linux_premigration/vchroot_expand.h>
|
|
+#include <darling/emulation/common/bsdthread/per_thread_wd.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
#define XATTR_NOFOLLOW 1
|
|
@@ -9,14 +12,26 @@
|
|
long sys_removexattr(const char* path, const char* name, int options)
|
|
{
|
|
int ret;
|
|
+ char linux_name[1024];
|
|
+
|
|
+ struct vchroot_expand_args vc;
|
|
+ vc.flags = (options & XATTR_NOFOLLOW) ? 0 : VCHROOT_FOLLOW;
|
|
+ vc.dfd = get_perthread_wd();
|
|
+
|
|
+ strcpy(vc.path, path);
|
|
+ ret = vchroot_expand(&vc);
|
|
+ if (ret < 0)
|
|
+ return errno_linux_to_bsd(ret);
|
|
+
|
|
+ name = xattr_name_to_linux(name, linux_name, sizeof(linux_name));
|
|
|
|
if (options & XATTR_NOFOLLOW)
|
|
- ret = LINUX_SYSCALL(__NR_lremovexattr, path, name);
|
|
+ ret = LINUX_SYSCALL(__NR_lremovexattr, vc.path, name);
|
|
else
|
|
- ret = LINUX_SYSCALL(__NR_removexattr, path, name);
|
|
+ ret = LINUX_SYSCALL(__NR_removexattr, vc.path, name);
|
|
|
|
if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ return xattr_errno_to_bsd(ret);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/setxattr.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/setxattr.c
|
|
index c1b2d50..4e19963 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/setxattr.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/xattr/setxattr.c
|
|
@@ -2,8 +2,13 @@
|
|
|
|
#include <sys/errno.h>
|
|
|
|
+extern char* strcpy(char* dst, const char* src);
|
|
+
|
|
#include <darling/emulation/common/base.h>
|
|
#include <darling/emulation/conversion/errno.h>
|
|
+#include <darling/emulation/conversion/xattr/name.h>
|
|
+#include <darling/emulation/linux_premigration/vchroot_expand.h>
|
|
+#include <darling/emulation/common/bsdthread/per_thread_wd.h>
|
|
#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
#define XATTR_NOFOLLOW 1
|
|
@@ -12,21 +17,39 @@ long sys_setxattr(const char* path, const char* name, const void* value,
|
|
unsigned long size, unsigned int pos, int options)
|
|
{
|
|
int ret;
|
|
+ char linux_name[1024];
|
|
|
|
if (pos != 0)
|
|
return -ERANGE;
|
|
|
|
+ /* darling-lol: vchroot-expand the path like getxattr does. Without
|
|
+ * this, a guest path (e.g. /Users/.../Crashes/Riot Client) was passed
|
|
+ * straight to the host setxattr and failed ENOENT -- Crashpad could
|
|
+ * never write its crashpad.database.initialized marker. */
|
|
+ struct vchroot_expand_args vc;
|
|
+ vc.flags = (options & XATTR_NOFOLLOW) ? 0 : VCHROOT_FOLLOW;
|
|
+ vc.dfd = get_perthread_wd();
|
|
+
|
|
+ strcpy(vc.path, path);
|
|
+ ret = vchroot_expand(&vc);
|
|
+ if (ret < 0)
|
|
+ return errno_linux_to_bsd(ret);
|
|
+
|
|
+ /* darling-lol: Darwin names are namespace-less; Linux needs a
|
|
+ * namespace prefix or returns ENOTSUP. */
|
|
+ name = xattr_name_to_linux(name, linux_name, sizeof(linux_name));
|
|
+
|
|
if (options & XATTR_NOFOLLOW)
|
|
{
|
|
- ret = LINUX_SYSCALL(__NR_lsetxattr, path, name, value, size);
|
|
+ ret = LINUX_SYSCALL(__NR_lsetxattr, vc.path, name, value, size);
|
|
}
|
|
else
|
|
{
|
|
- ret = LINUX_SYSCALL(__NR_setxattr, path, name, value, size);
|
|
+ ret = LINUX_SYSCALL(__NR_setxattr, vc.path, name, value, size);
|
|
}
|
|
|
|
if (ret < 0)
|
|
- return errno_linux_to_bsd(ret);
|
|
+ return xattr_errno_to_bsd(ret);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
|
index b998456..c6281e6 100644
|
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
|
@@ -12,9 +12,28 @@
|
|
#include <darling/emulation/linux_premigration/ext/mremap.h>
|
|
#include <darling/emulation/common/simple.h>
|
|
#include <darling/emulation/conversion/duct_errno.h>
|
|
+#include <darling/emulation/linux_premigration/linux-syscalls/linux.h>
|
|
|
|
#include <darlingserver/rpc.h>
|
|
|
|
+/* darling-lol: transient darlingserver RPC failures (notably -111/
|
|
+ * ECONNREFUSED when many threads check in at once, e.g. Electron
|
|
+ * helper bursts) must not kill the process outright. Bounded retry
|
|
+ * with backoff; still aborts if the failure persists. */
|
|
+struct dserver_retry_ts {
|
|
+ long tv_sec;
|
|
+ long tv_nsec;
|
|
+};
|
|
+
|
|
+static void dserver_retry_sleep(int attempt)
|
|
+{
|
|
+ struct dserver_retry_ts ts;
|
|
+ long ms = 1L << (attempt > 6 ? 6 : attempt); /* 2..128ms */
|
|
+ ts.tv_sec = 0;
|
|
+ ts.tv_nsec = ms * 1000L * 1000L;
|
|
+ LINUX_SYSCALL(__NR_nanosleep, &ts, 0UL);
|
|
+}
|
|
+
|
|
#define UNIMPLEMENTED_TRAP() { char msg[] = "Called unimplemented Mach trap: "; write(2, msg, sizeof(msg)-1); write(2, __FUNCTION__, sizeof(__FUNCTION__)-1); write(2, "\n", 1); }
|
|
|
|
mach_port_name_t mach_reply_port_impl(void)
|
|
@@ -44,6 +63,8 @@ mach_port_name_t host_self_trap_impl(void)
|
|
return port_name;
|
|
}
|
|
|
|
+#include <darling/emulation/common/slowwait.h>
|
|
+
|
|
mach_msg_return_t mach_msg_trap_impl(
|
|
mach_msg_header_t *msg,
|
|
mach_msg_option_t option,
|
|
@@ -72,8 +93,12 @@ mach_msg_return_t mach_msg_overwrite_trap_impl(
|
|
{
|
|
int code;
|
|
|
|
-retry:
|
|
+retry:;
|
|
+ unsigned long long slowStart = slowwait_enabled() ? slowwait_now() : 0;
|
|
code = dserver_rpc_mach_msg_overwrite(msg, option, send_size, rcv_size, rcv_name, timeout, notify, rcv_msg);
|
|
+ if (slowStart) {
|
|
+ slowwait_log("mach_msg", rcv_name, slowStart, (void**) __builtin_frame_address(0), option, timeout);
|
|
+ }
|
|
|
|
if (code < 0) {
|
|
if (code == -LINUX_EINTR) {
|
|
@@ -92,33 +117,860 @@ retry:
|
|
}
|
|
}
|
|
__simple_printf("mach_msg_overwrite failed (internally): %d\n", code);
|
|
+ {
|
|
+ /* darling-lol diagnostic for the LeagueClient crash (2026-10-10): is this thread's RPC descriptor still
|
|
+ * open, and what is it? Written to the host file /tmp/rclol-rpc-fail.log as well. */
|
|
+ extern int __dserver_per_thread_socket(void);
|
|
+ int rpc_fd = __dserver_per_thread_socket();
|
|
+ long getfd = LINUX_SYSCALL(__NR_fcntl, rpc_fd, 1 /* F_GETFD */);
|
|
+ char link[64], target[128], line[320];
|
|
+ __simple_sprintf(link, "/proc/self/fd/%d", rpc_fd);
|
|
+ long tl = LINUX_SYSCALL(__NR_readlink, link, target, sizeof(target) - 1);
|
|
+ target[tl > 0 ? tl : 0] = 0;
|
|
+ int len = __simple_sprintf(line, "LOLRPCFAIL pid=%d tid=%d code=%d rpc_fd=%d getfd=%ld target=%s option=0x%x\n",
|
|
+ (int) LINUX_SYSCALL(__NR_getpid), (int) LINUX_SYSCALL(__NR_gettid), code, rpc_fd, getfd, target, (unsigned) option);
|
|
+ __simple_printf("%s", line);
|
|
+ long log = LINUX_SYSCALL(__NR_open, "/tmp/rclol-rpc-fail.log", 02101, 0666);
|
|
+ if (log >= 0) { LINUX_SYSCALL(__NR_write, log, line, len); LINUX_SYSCALL(__NR_close, log); }
|
|
+ }
|
|
__simple_abort();
|
|
}
|
|
+ /* A receive can be interrupted after a combined send has completed.
|
|
+ * Without MACH_RCV_INTERRUPT, callers expect the receive to resume.
|
|
+ * Retrying the send would duplicate the request. */
|
|
+ if ((option & MACH_RCV_MSG) != 0 &&
|
|
+ code == MACH_RCV_INTERRUPTED &&
|
|
+ (option & (MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT)) == 0) {
|
|
+ option &= ~MACH_SEND_MSG;
|
|
+ send_size = 0;
|
|
+ goto retry;
|
|
+ }
|
|
+ #if 0 /* M58 diagnostics: keep guest Mach failure traces off by default. */
|
|
+ if (code == MACH_SEND_INVALID_DEST && msg != 0 &&
|
|
+ (option & MACH_SEND_MSG) != 0) {
|
|
+ /* Only failed sends are traced; this is never on the Mach hot path. */
|
|
+ __simple_printf("M58GP pid=%d tid=%d send-invalid dest=%u local=%u bits=0x%x id=%d opt=0x%x size=%u\n",
|
|
+ (int) LINUX_SYSCALL(__NR_getpid), (int) LINUX_SYSCALL(__NR_gettid),
|
|
+ (unsigned) msg->msgh_remote_port,
|
|
+ (unsigned) msg->msgh_local_port,
|
|
+ (unsigned) msg->msgh_bits,
|
|
+ (int) msg->msgh_id,
|
|
+ (unsigned) option,
|
|
+ (unsigned) send_size);
|
|
+ }
|
|
+ if (code != MACH_MSG_SUCCESS &&
|
|
+ !(code == MACH_RCV_TIMED_OUT && (option & MACH_RCV_TIMEOUT) != 0) &&
|
|
+ code != MACH_RCV_TOO_LARGE && (option & MACH_RCV_MSG) != 0) {
|
|
+ static unsigned int receive_failures;
|
|
+ if (__sync_fetch_and_add(&receive_failures, 1) < 128) {
|
|
+ __simple_printf("M58GR pid=%d tid=%d recv-fail code=0x%x rcv=%u opt=0x%x notify=%u send=%u\n",
|
|
+ (int) LINUX_SYSCALL(__NR_getpid), (int) LINUX_SYSCALL(__NR_gettid),
|
|
+ (unsigned) code, (unsigned) rcv_name,
|
|
+ (unsigned) option, (unsigned) notify,
|
|
+ (unsigned) send_size);
|
|
+ }
|
|
+ }
|
|
+ #endif
|
|
|
|
return code;
|
|
}
|
|
|
|
+/* darling-lol: process-local Mach semaphores.
|
|
+ *
|
|
+ * Every semaphore_wait/semaphore_signal used to be a round trip to darlingserver: the caller sends a message and sleeps
|
|
+ * until the server thread has run and answered. League's job system and libdispatch use Mach semaphores to hand work to
|
|
+ * threads of the same process (about 19 such round trips per frame on the main thread alone, 2026-10-10), so each
|
|
+ * wake-up paid two extra context switches and the caller of semaphore_signal blocked for the server's turnaround.
|
|
+ *
|
|
+ * A semaphore created by this task (semaphore_create on mach_task_self) is now also kept here as a futex counter and
|
|
+ * all traps on it are served locally. The server-side object still exists, so the port name stays valid, but it is
|
|
+ * never signalled. Anything the table did not insert itself -- a name it has never seen, a semaphore created by or
|
|
+ * for another task, an inherited name in a fork child -- goes to the server path unchanged; the two are never mixed
|
|
+ * for one name.
|
|
+ *
|
|
+ * A slot is (name, generation, value). The generation changes on every assignment and on every destroy, and value+gen
|
|
+ * share one atomic word, so a waiter takes a token with a compare-exchange on that word: it can only ever consume a
|
|
+ * token of the generation it resolved, and a slot reused after a destroy -- even under the same port name -- is a
|
|
+ * different generation, which fails the compare-exchange or one of the generation/name checks every operation makes.
|
|
+ * KERN_TERMINATED on a slot that no longer names the resolved object is what Darwin returns for a semaphore that died
|
|
+ * while the call was in flight. A fork tombstones the child's copy of the table (see
|
|
+ * __darling_local_semaphores_after_fork): the child's IPC space is fresh, no inherited name is valid there.
|
|
+ *
|
|
+ * LIMITS: a semaphore whose right is sent to another process is signalled through the server on the other side and
|
|
+ * never reaches this counter (nothing in the game does this; the counters in build/perf-research/blocking are meant
|
|
+ * to prove it before this becomes default); a local wait cannot be interrupted by thread_abort/pthread_cancel (the
|
|
+ * server does not know about it), so only a signal, a destroy or its timeout ends one; semaphore_signal_thread with
|
|
+ * a specific thread answers KERN_NOT_WAITING. build/perf-research/lsem-fix/REPORT.md has the full list.
|
|
+ *
|
|
+ * On by default in the League game process only, unless the host file /tmp/rclol-no-local-semaphores exists
|
|
+ * (/tmp/rclol-local-semaphores, the former opt-in switch, is now a no-op). While it was on by default in every
|
|
+ * process (2026-10-10 22:22-23:50) LeagueClient died ~85 s after start in 3 of 4 checked launches
|
|
+ * ("mach_msg_overwrite failed -9", then dserver_rpc_interrupt_enter -9: its RPC descriptor had become invalid) and
|
|
+ * the user saw Vanguard errors; that mechanism is not understood, so the clients keep the server path. With the
|
|
+ * lifetime fixes above the game ran two full matches with it (2026-10-14: the main thread's server waits fell from
|
|
+ * 1.36 to 0.10 ms per frame, 39 to 15 blocks per frame). /tmp/rclol-local-semaphores-all turns it on for every
|
|
+ * process (probe tests). Decided once per process and logged
|
|
+ * once per game process to /tmp/rclol-game-decisions.log as local_semaphores=0|1. */
|
|
+#define LSEM_SLOTS 8192u
|
|
+#define LSEM_TOMB 0xffffffffu
|
|
+/* What a destroy leaves in the token word: a negative count, so no waiter and no signal can take it for a live
|
|
+ * token even if it resolved the slot while the destroy was still in flight (the generation and the name are only
|
|
+ * two more loads away from being visible; the value is the one the token compare-exchange goes through). */
|
|
+#define LSEM_TOMB_VALUE (-1)
|
|
+#define LSEM_FUTEX_WAIT 0
|
|
+#define LSEM_FUTEX_WAKE 1
|
|
+#define LSEM_FUTEX_PRIVATE 0x80u /* FUTEX_PRIVATE_FLAG: the word is always this process's own memory */
|
|
+/* The token count and the generation are one atomic word: lsem_wait takes a token with a compare-exchange on the
|
|
+ * pair, so the token can only come from the generation it checked. The low half is the futex word the syscalls
|
|
+ * address; every value write is a generation-checked compare-exchange, never a plain add. */
|
|
+union lsem_pv {
|
|
+ unsigned long long pair;
|
|
+ struct {
|
|
+ volatile int value;
|
|
+ volatile unsigned int gen;
|
|
+ } w;
|
|
+};
|
|
+_Static_assert(sizeof(union lsem_pv) == 8 && _Alignof(union lsem_pv) == 8,
|
|
+ "the token and the generation must share one naturally aligned atomic word");
|
|
+struct lsem {
|
|
+ union lsem_pv pv;
|
|
+ volatile unsigned int name;
|
|
+ volatile int waiters; /* a hint for the signal wake filter and signal_all's count; the futex protocol carries correctness */
|
|
+};
|
|
+/* The second semaphore of a combined wait_signal/timedwait_signal trap. XNU registers the wait on the wait semaphore
|
|
+ * and only then signals the signal semaphore (even when the wait did not block); a local wait signals it from inside
|
|
+ * the loop, at that same point (see lsem_post_signal). */
|
|
+struct lsem_post {
|
|
+ struct lsem* s; /* 0: not local, signal the name through the server */
|
|
+ unsigned int name;
|
|
+ unsigned int gen;
|
|
+ volatile int done;
|
|
+};
|
|
+static struct lsem lsems[LSEM_SLOTS];
|
|
+static volatile int lsem_state; /* 0 undecided, 1 on, -1 off */
|
|
+static volatile int lsem_lock;
|
|
+
|
|
+extern kern_return_t __darling_rpc_semaphore_create(mach_port_name_t task, mach_port_name_t* semaphore, int policy, int value);
|
|
+extern kern_return_t __darling_rpc_semaphore_destroy(mach_port_name_t task, mach_port_name_t semaphore);
|
|
+
|
|
+static int darling_is_league_game_uncached(void);
|
|
+
|
|
+/* Read once per process: the answer cannot change, and other processes must not keep opening files for it. */
|
|
+static int darling_is_league_game(void)
|
|
+{
|
|
+ static volatile int cached; /* 0 unknown, 1 yes, -1 no */
|
|
+ int value = __atomic_load_n(&cached, __ATOMIC_ACQUIRE);
|
|
+ if (value == 0) {
|
|
+ value = darling_is_league_game_uncached() ? 1 : -1;
|
|
+ __atomic_store_n(&cached, value, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ return value == 1;
|
|
+}
|
|
+
|
|
+static int darling_is_league_game_uncached(void)
|
|
+{
|
|
+ /* Only the executable path (argv[0], the first NUL-terminated string) counts. The whole command line was searched
|
|
+ * before, so any process that merely had the game's path among its arguments would have been treated as the game. */
|
|
+ char buf[512];
|
|
+ static const char mark[] = "/Game/LeagueOfLegends.app/Contents/MacOS/";
|
|
+ int found = 0;
|
|
+ long n = 0;
|
|
+ int fd = LINUX_SYSCALL(__NR_open, "/proc/self/cmdline", 0 /* O_RDONLY */, 0);
|
|
+ if (fd >= 0) {
|
|
+ n = LINUX_SYSCALL(__NR_read, fd, buf, sizeof(buf) - 1);
|
|
+ LINUX_SYSCALL(__NR_close, fd);
|
|
+ if (n < 0) n = 0;
|
|
+ buf[n] = 0;
|
|
+ long end = 0;
|
|
+ while (end < n && buf[end]) ++end; /* argv[0] */
|
|
+ for (long i = 0; i + (long) sizeof(mark) - 1 <= end && !found; ++i) {
|
|
+ unsigned long j = 0;
|
|
+ while (j < sizeof(mark) - 1 && buf[i + j] == mark[j]) ++j;
|
|
+ if (j == sizeof(mark) - 1) found = 1;
|
|
+ }
|
|
+ /* one line per process in the host file /tmp/rclol-game-decisions.log: which processes asked, and the answer */
|
|
+ long log = LINUX_SYSCALL(__NR_open, "/tmp/rclol-game-decisions.log", 02101 /* O_WRONLY|O_CREAT|O_APPEND */, 0666);
|
|
+ if (log >= 0) {
|
|
+ char line[160];
|
|
+ long tail = end > 60 ? end - 60 : 0;
|
|
+ int len = __simple_sprintf(line, "pid=%d game=%d argv0=...%s\n", (int) LINUX_SYSCALL(__NR_getpid), found, buf + tail);
|
|
+ LINUX_SYSCALL(__NR_write, log, line, len);
|
|
+ LINUX_SYSCALL(__NR_close, log);
|
|
+ }
|
|
+ }
|
|
+ return found;
|
|
+}
|
|
+
|
|
+/* for the other emulation files (psynch) */
|
|
+__attribute__((visibility("hidden")))
|
|
+int __darling_is_league_game(void)
|
|
+{
|
|
+ return darling_is_league_game();
|
|
+}
|
|
+
|
|
+/* darling-lol: core isolation for the game's initial thread.
|
|
+ *
|
|
+ * The game's main thread is the bottleneck (~80% of one core) and in the last matches 16% of its running time (up
|
|
+ * to 36% in fights) another thread of ours ran on its SMT sibling. Linux core scheduling (CONFIG_SCHED_CORE,
|
|
+ * prctl PR_SCHED_CORE, 5.13+) lets a task carry a cookie: tasks with different cookies (an untagged task has
|
|
+ * cookie 0) are never scheduled on the same physical core at the same time, the losing sibling is forced idle.
|
|
+ * The main thread creates a cookie of its own, so no other thread of the process, no darlingserver thread and no
|
|
+ * other process can share its core; the kernel keeps the rest of the system's SMT pairing as it was. On this host
|
|
+ * (first run 2026-10-14, build/perf-research/core-isolation/) two threads pinned to the two SMT siblings of one
|
|
+ * core did one thread's work when one was tagged and the other was not, and twice that when both were untagged or
|
|
+ * shared a cookie.
|
|
+ *
|
|
+ * A new thread inherits the cookie of the thread that created it (kernel core_sched.c: sched_core_fork()), so a
|
|
+ * thread created by the tagged main thread would carry the main cookie and could run on its core again. Every
|
|
+ * other thread of the process therefore shares one *other* cookie instead: the first one to start creates it and
|
|
+ * remembers its tid, later threads take it over with PR_SCHED_CORE_SHARE_FROM. They stay in one group (they may
|
|
+ * still share cores with each other, as before) and only the main thread becomes incompatible with everything
|
|
+ * else. Thread pools retire their threads, so if the recorded owner is gone (SHARE_FROM fails) the caller creates
|
|
+ * a fresh cookie and takes the role over. A forked child inherits the record as well, and the tid in it can then
|
|
+ * name a task of the parent (or of an unrelated process), so the record is only used while it belongs to the
|
|
+ * process reading it; a child's first thread takes the worker role over with a cookie of its own.
|
|
+ *
|
|
+ * Best effort throughout: if the prctl is unavailable (EINVAL/EPERM/ENODEV - host without CONFIG_SCHED_CORE, SMT
|
|
+ * off, or a kernel without the feature) nothing changes.
|
|
+ *
|
|
+ * Opt-in, only with the host file /tmp/rclol-core-isolation: in the first match with it on (2026-10-14, Hexgate,
|
|
+ * same minutes compared) the game fell from 118 to 103 fps. The main thread waited for a CPU 12.6% of the time
|
|
+ * (1% without) and the other game threads queued as well: forced idle siblings cost more on this busy 6-core host
|
|
+ * than the sharing they prevent.
|
|
+ * The decision is appended once per process to /tmp/rclol-game-decisions.log as
|
|
+ * "core_isolation=0|1 errno=<prctl result, -1 off, -2 registration was not on the initial thread> cookie=<hash>". */
|
|
+#define LCORE_PRCTL_SCHED_CORE 62 /* include/uapi/linux/prctl.h */
|
|
+#define LCORE_GET 0
|
|
+#define LCORE_CREATE 1 /* give the calling thread a cookie of its own */
|
|
+#define LCORE_SHARE_FROM 3 /* the calling thread takes over the cookie of the thread at arg3 */
|
|
+#define LCORE_SCOPE_THREAD 0
|
|
+
|
|
+static volatile int lcore_state; /* 0 undecided, 1 on, -1 off */
|
|
+static volatile int lcore_main; /* 0 not tried, 1 the initial thread has its own cookie, -1 it has none */
|
|
+static volatile int lcore_worker_tid; /* tid whose cookie the other threads share, 0 = none yet */
|
|
+static volatile int lcore_worker_pid; /* process that tid belongs to; a forked child must not use it */
|
|
+
|
|
+static void lcore_log(int on, long err, unsigned long long cookie)
|
|
+{
|
|
+ char line[128];
|
|
+ long log = LINUX_SYSCALL(__NR_open, "/tmp/rclol-game-decisions.log", 02101 /* O_WRONLY|O_CREAT|O_APPEND */, 0666);
|
|
+ if (log < 0)
|
|
+ return;
|
|
+ int len = __simple_sprintf(line, "pid=%d core_isolation=%d errno=%ld cookie=%llu\n",
|
|
+ (int) LINUX_SYSCALL(__NR_getpid), on, err, cookie);
|
|
+ LINUX_SYSCALL(__NR_write, log, line, len);
|
|
+ LINUX_SYSCALL(__NR_close, log);
|
|
+}
|
|
+
|
|
+static int lcore_decide(void)
|
|
+{
|
|
+ int state = __atomic_load_n(&lcore_state, __ATOMIC_ACQUIRE);
|
|
+ if (state != 0)
|
|
+ return state;
|
|
+ if (__darling_is_league_game() && LINUX_SYSCALL(__NR_access, "/tmp/rclol-core-isolation", 0) == 0)
|
|
+ state = 1;
|
|
+ else
|
|
+ state = -1;
|
|
+ __atomic_store_n(&lcore_state, state, __ATOMIC_RELEASE);
|
|
+ return state;
|
|
+}
|
|
+
|
|
+/* Called from sys_bsdthread_register (libpthread's per-process initialization). */
|
|
+__attribute__((visibility("hidden")))
|
|
+void __darling_core_isolate_main_thread(void)
|
|
+{
|
|
+ unsigned long long cookie = 0;
|
|
+ int expected = 0;
|
|
+ long err;
|
|
+
|
|
+ /* once per process: a forked child keeps (inherits) whatever the parent's initial thread had */
|
|
+ if (!__atomic_compare_exchange_n(&lcore_main, &expected, -1, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE))
|
|
+ return;
|
|
+
|
|
+ if (lcore_decide() != 1) {
|
|
+ /* only the game logs its decision; every other Darling process would add a line otherwise */
|
|
+ if (__darling_is_league_game())
|
|
+ lcore_log(0, -1, 0); /* /tmp/rclol-core-isolation absent */
|
|
+ return;
|
|
+ }
|
|
+ /* Only the process' initial thread may take this cookie: its threads are created with clone() from it and
|
|
+ * the thread entry clears the inherited cookie again. gettid() == getpid() is that initial thread. If
|
|
+ * registration ever ran on another thread, leave everything untagged rather than tag the wrong thread. */
|
|
+ if (LINUX_SYSCALL(__NR_gettid) != LINUX_SYSCALL(__NR_getpid)) {
|
|
+ lcore_log(0, -2, 0);
|
|
+ return;
|
|
+ }
|
|
+ err = LINUX_SYSCALL5(__NR_prctl, LCORE_PRCTL_SCHED_CORE, LCORE_CREATE, 0, LCORE_SCOPE_THREAD, 0);
|
|
+ if (err == 0) {
|
|
+ long got = LINUX_SYSCALL5(__NR_prctl, LCORE_PRCTL_SCHED_CORE, LCORE_GET, 0, LCORE_SCOPE_THREAD, &cookie);
|
|
+ if (got != 0)
|
|
+ cookie = 0;
|
|
+ __atomic_store_n(&lcore_main, 1, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ lcore_log(err == 0, err, cookie);
|
|
+}
|
|
+
|
|
+/* Called from the entry of every new guest thread (pthread_entry_point_wrapper and
|
|
+ * wqueue_entry_point_wrapper). */
|
|
+__attribute__((visibility("hidden")))
|
|
+void __darling_core_isolate_new_thread(void)
|
|
+{
|
|
+ int pid, me, owner = 0;
|
|
+ int expected;
|
|
+
|
|
+ /* the initial thread was not tagged (not the game, opted out, or the prctl failed): nothing to avoid */
|
|
+ if (__atomic_load_n(&lcore_main, __ATOMIC_ACQUIRE) != 1)
|
|
+ return;
|
|
+
|
|
+ pid = (int) LINUX_SYSCALL(__NR_getpid);
|
|
+ me = (int) LINUX_SYSCALL(__NR_gettid);
|
|
+ if (me == pid)
|
|
+ return; /* the initial thread keeps its own cookie */
|
|
+
|
|
+ if (__atomic_load_n(&lcore_worker_pid, __ATOMIC_ACQUIRE) == pid)
|
|
+ owner = __atomic_load_n(&lcore_worker_tid, __ATOMIC_ACQUIRE);
|
|
+
|
|
+ if (owner == 0) {
|
|
+ if (LINUX_SYSCALL5(__NR_prctl, LCORE_PRCTL_SCHED_CORE, LCORE_CREATE, 0, LCORE_SCOPE_THREAD, 0) != 0)
|
|
+ return; /* no cookie for this thread; it keeps the one it inherited */
|
|
+ expected = 0;
|
|
+ if (__atomic_compare_exchange_n(&lcore_worker_tid, &expected, me, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
|
+ __atomic_store_n(&lcore_worker_pid, pid, __ATOMIC_RELEASE);
|
|
+ return; /* this thread owns the shared worker cookie from now on */
|
|
+ }
|
|
+ /* the published owner belongs to a process that no longer is this one (a forked child saw the
|
|
+ * parent's record): it must not be used, the cookie created above becomes the shared one */
|
|
+ if (__atomic_load_n(&lcore_worker_pid, __ATOMIC_ACQUIRE) != pid) {
|
|
+ __atomic_store_n(&lcore_worker_tid, me, __ATOMIC_RELEASE);
|
|
+ __atomic_store_n(&lcore_worker_pid, pid, __ATOMIC_RELEASE);
|
|
+ return;
|
|
+ }
|
|
+ owner = expected; /* another thread of this process published its cookie first: join it */
|
|
+ }
|
|
+
|
|
+ if (owner == me)
|
|
+ return;
|
|
+ if (LINUX_SYSCALL5(__NR_prctl, LCORE_PRCTL_SCHED_CORE, LCORE_SHARE_FROM, owner, LCORE_SCOPE_THREAD, 0) == 0)
|
|
+ return;
|
|
+ /* the recorded owner is gone (retired worker, or a parent process after fork): take the role over */
|
|
+ if (LINUX_SYSCALL5(__NR_prctl, LCORE_PRCTL_SCHED_CORE, LCORE_CREATE, 0, LCORE_SCOPE_THREAD, 0) != 0)
|
|
+ return;
|
|
+ expected = owner;
|
|
+ if (__atomic_compare_exchange_n(&lcore_worker_tid, &expected, me, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
|
+ __atomic_store_n(&lcore_worker_pid, pid, __ATOMIC_RELEASE);
|
|
+ return;
|
|
+ }
|
|
+ if (expected != 0 && expected != me)
|
|
+ LINUX_SYSCALL5(__NR_prctl, LCORE_PRCTL_SCHED_CORE, LCORE_SHARE_FROM, expected, LCORE_SCOPE_THREAD, 0);
|
|
+}
|
|
+
|
|
+static void lsem_log(int on)
|
|
+{
|
|
+ char line[96];
|
|
+ long log = LINUX_SYSCALL(__NR_open, "/tmp/rclol-game-decisions.log", 02101 /* O_WRONLY|O_CREAT|O_APPEND */, 0666);
|
|
+ if (log < 0)
|
|
+ return;
|
|
+ int len = __simple_sprintf(line, "pid=%d local_semaphores=%d\n", (int) LINUX_SYSCALL(__NR_getpid), on);
|
|
+ LINUX_SYSCALL(__NR_write, log, line, len);
|
|
+ LINUX_SYSCALL(__NR_close, log);
|
|
+}
|
|
+
|
|
+static int lsem_decide(void)
|
|
+{
|
|
+ int state = __atomic_load_n(&lsem_state, __ATOMIC_ACQUIRE);
|
|
+ int on = 0;
|
|
+ int expected = 0;
|
|
+ if (state != 0)
|
|
+ return state;
|
|
+ if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-local-semaphores-all", 0) == 0)
|
|
+ on = 1;
|
|
+ else if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-no-local-semaphores", 0) != 0)
|
|
+ on = darling_is_league_game();
|
|
+ if (__atomic_compare_exchange_n(&lsem_state, &expected, on ? 1 : -1, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
|
+ /* the one line per game process in the host file, next to core_isolation (lcore_log) */
|
|
+ if (darling_is_league_game())
|
|
+ lsem_log(on);
|
|
+ return on ? 1 : -1;
|
|
+ }
|
|
+ return __atomic_load_n(&lsem_state, __ATOMIC_ACQUIRE); /* another thread decided first */
|
|
+}
|
|
+
|
|
+static inline unsigned int lsem_hash(unsigned int name)
|
|
+{
|
|
+ return (name * 2654435761u) >> 19; /* 13 bits */
|
|
+}
|
|
+
|
|
+/* The generation the caller's lsem_find resolved. Every operation re-checks it, so work that started before a
|
|
+ * destroy can never land on the object that took the slot afterwards. */
|
|
+static inline unsigned int lsem_capture(struct lsem* s)
|
|
+{
|
|
+ return __atomic_load_n(&s->pv.w.gen, __ATOMIC_ACQUIRE);
|
|
+}
|
|
+
|
|
+static struct lsem* lsem_find(unsigned int name)
|
|
+{
|
|
+ if (__atomic_load_n(&lsem_state, __ATOMIC_ACQUIRE) != 1 || name == 0 || name == LSEM_TOMB)
|
|
+ return 0;
|
|
+ unsigned int i = lsem_hash(name);
|
|
+ for (unsigned int n = 0; n < LSEM_SLOTS; ++n, i = (i + 1) & (LSEM_SLOTS - 1)) {
|
|
+ unsigned int have = __atomic_load_n(&lsems[i].name, __ATOMIC_ACQUIRE);
|
|
+ if (have == name)
|
|
+ return &lsems[i];
|
|
+ if (have == 0)
|
|
+ return 0;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void lsem_table_lock(void)
|
|
+{
|
|
+ int expected = 0;
|
|
+ while (!__atomic_compare_exchange_n(&lsem_lock, &expected, 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
|
|
+ LINUX_SYSCALL(__NR_futex, &lsem_lock, LSEM_FUTEX_WAIT | LSEM_FUTEX_PRIVATE, 1, 0UL, 0UL, 0);
|
|
+ expected = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void lsem_table_unlock(void)
|
|
+{
|
|
+ __atomic_store_n(&lsem_lock, 0, __ATOMIC_RELEASE);
|
|
+ LINUX_SYSCALL(__NR_futex, &lsem_lock, LSEM_FUTEX_WAKE | LSEM_FUTEX_PRIVATE, 1, 0UL, 0UL, 0);
|
|
+}
|
|
+
|
|
+static void lsem_insert(unsigned int name, int value)
|
|
+{
|
|
+ if (name == 0 || name == LSEM_TOMB)
|
|
+ return;
|
|
+ lsem_table_lock();
|
|
+ struct lsem* free_slot = 0;
|
|
+ struct lsem* found = 0;
|
|
+ unsigned int i = lsem_hash(name);
|
|
+ for (unsigned int n = 0; n < LSEM_SLOTS; ++n, i = (i + 1) & (LSEM_SLOTS - 1)) {
|
|
+ unsigned int have = lsems[i].name;
|
|
+ if (have == name) { found = &lsems[i]; break; }
|
|
+ if (have == LSEM_TOMB && !free_slot) free_slot = &lsems[i];
|
|
+ if (have == 0) { if (!free_slot) free_slot = &lsems[i]; break; }
|
|
+ }
|
|
+ if (!found) found = free_slot;
|
|
+ if (found) { /* a full table leaves the semaphore to the server */
|
|
+ /* The new generation and the initial value are one atomic word: a waiter that looked up the old
|
|
+ * generation can never observe a half-updated slot, and no generation is ever handed out twice. */
|
|
+ unsigned long long old = __atomic_load_n(&found->pv.pair, __ATOMIC_RELAXED);
|
|
+ unsigned long long fresh = (((old >> 32) + 1) << 32) | (unsigned int) (value < 0 ? 0 : value);
|
|
+ __atomic_store_n(&found->pv.pair, fresh, __ATOMIC_RELEASE);
|
|
+ __atomic_store_n(&found->waiters, 0, __ATOMIC_RELAXED);
|
|
+ __atomic_store_n(&found->name, name, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ lsem_table_unlock();
|
|
+}
|
|
+
|
|
+static void lsem_remove(unsigned int name)
|
|
+{
|
|
+ struct lsem* s = lsem_find(name);
|
|
+ if (!s)
|
|
+ return;
|
|
+ lsem_table_lock();
|
|
+ /* Between the find and the lock another thread may have destroyed (and re-created) the name: the slot then
|
|
+ * holds an object this destroy was not called on, so leave it alone. */
|
|
+ if (__atomic_load_n(&s->name, __ATOMIC_ACQUIRE) != name) {
|
|
+ lsem_table_unlock();
|
|
+ return;
|
|
+ }
|
|
+ /* gen+1 and the tombstone value in one word: a waiter either took its token before this compare-exchange or it
|
|
+ * sees the new generation (or the negative value) and returns KERN_TERMINATED; a waiter of a later object in
|
|
+ * the slot can never see a generation it did not resolve. */
|
|
+ for (;;) {
|
|
+ unsigned long long old = __atomic_load_n(&s->pv.pair, __ATOMIC_SEQ_CST);
|
|
+ unsigned long long want = (((old >> 32) + 1) << 32) | (unsigned int) LSEM_TOMB_VALUE;
|
|
+ if (__atomic_compare_exchange_n(&s->pv.pair, &old, want, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
|
|
+ break;
|
|
+ }
|
|
+ __atomic_store_n(&s->name, LSEM_TOMB, __ATOMIC_RELEASE);
|
|
+ __atomic_store_n(&s->waiters, 0, __ATOMIC_RELAXED);
|
|
+ LINUX_SYSCALL(__NR_futex, &s->pv.w.value, LSEM_FUTEX_WAKE | LSEM_FUTEX_PRIVATE, 0x7fffffff, 0UL, 0UL, 0);
|
|
+ lsem_table_unlock();
|
|
+}
|
|
+
|
|
+/* Returns KERN_TERMINATED when the slot no longer names the object the caller resolved: the check and the token are
|
|
+ * one compare-exchange on the generation+value word, so no token is ever added to the object that took the slot. */
|
|
+static kern_return_t lsem_signal(struct lsem* s, unsigned int name, unsigned int gen)
|
|
+{
|
|
+ for (;;) {
|
|
+ unsigned long long old = __atomic_load_n(&s->pv.pair, __ATOMIC_SEQ_CST);
|
|
+ if ((unsigned int) (old >> 32) != gen || __atomic_load_n(&s->name, __ATOMIC_ACQUIRE) != name)
|
|
+ return KERN_TERMINATED;
|
|
+ if ((int) (unsigned int) old < 0)
|
|
+ return KERN_TERMINATED; /* the tombstone value: the object is being destroyed */
|
|
+ if (__atomic_compare_exchange_n(&s->pv.pair, &old, old + 1, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
|
|
+ break;
|
|
+ }
|
|
+ if (__atomic_load_n(&s->waiters, __ATOMIC_SEQ_CST) > 0)
|
|
+ LINUX_SYSCALL(__NR_futex, &s->pv.w.value, LSEM_FUTEX_WAKE | LSEM_FUTEX_PRIVATE, 1, 0UL, 0UL, 0);
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
+
|
|
+static kern_return_t lsem_signal_all(struct lsem* s, unsigned int name, unsigned int gen)
|
|
+{
|
|
+ for (;;) {
|
|
+ unsigned long long old = __atomic_load_n(&s->pv.pair, __ATOMIC_SEQ_CST);
|
|
+ int n;
|
|
+ if ((unsigned int) (old >> 32) != gen || __atomic_load_n(&s->name, __ATOMIC_ACQUIRE) != name)
|
|
+ return KERN_TERMINATED;
|
|
+ if ((int) (unsigned int) old < 0)
|
|
+ return KERN_TERMINATED; /* the tombstone value: the object is being destroyed */
|
|
+ /* wake everybody who is waiting now; nothing is left over for later waiters (XNU resets the count, and
|
|
+ * the trap version banks no count when nobody waits) */
|
|
+ n = __atomic_load_n(&s->waiters, __ATOMIC_SEQ_CST);
|
|
+ if (n <= 0)
|
|
+ return KERN_SUCCESS;
|
|
+ if (__atomic_compare_exchange_n(&s->pv.pair, &old, old + (unsigned int) n, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
|
|
+ LINUX_SYSCALL(__NR_futex, &s->pv.w.value, LSEM_FUTEX_WAKE | LSEM_FUTEX_PRIVATE, n, 0UL, 0UL, 0);
|
|
+ return KERN_SUCCESS;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static kern_return_t lsem_signal_thread(struct lsem* s, unsigned int name, unsigned int gen, unsigned int thread_name)
|
|
+{
|
|
+ unsigned long long old = __atomic_load_n(&s->pv.pair, __ATOMIC_SEQ_CST);
|
|
+ if ((unsigned int) (old >> 32) != gen || __atomic_load_n(&s->name, __ATOMIC_ACQUIRE) != name ||
|
|
+ (int) (unsigned int) old < 0)
|
|
+ return KERN_TERMINATED;
|
|
+ if (thread_name != 0)
|
|
+ return KERN_NOT_WAITING; /* a specific thread is not addressable here; see the header comment */
|
|
+ if (__atomic_load_n(&s->waiters, __ATOMIC_SEQ_CST) <= 0)
|
|
+ return KERN_NOT_WAITING; /* nobody waiting: XNU banks no count here either */
|
|
+ return lsem_signal(s, name, gen);
|
|
+}
|
|
+
|
|
+/* Signal the signal semaphore of a combined trap, once, at the point XNU would (after the wait on the other
|
|
+ * semaphore registered itself, or after it already succeeded). */
|
|
+static kern_return_t lsem_post_signal(struct lsem_post* post)
|
|
+{
|
|
+ if (!post || __atomic_exchange_n(&post->done, 1, __ATOMIC_ACQ_REL))
|
|
+ return KERN_SUCCESS;
|
|
+ if (post->s)
|
|
+ return lsem_signal(post->s, post->name, post->gen);
|
|
+ return semaphore_signal_trap_impl(post->name); /* not local: signal it through the server */
|
|
+}
|
|
+
|
|
+/* timeout: nanoseconds, or -1 to wait forever. (name, gen) is what the caller's lsem_find resolved. */
|
|
+static kern_return_t lsem_wait(struct lsem* s, unsigned int name, unsigned int gen, long long timeout_ns, struct lsem_post* post)
|
|
+{
|
|
+ struct { long tv_sec; long tv_nsec; } now, rel;
|
|
+ long long deadline = 0;
|
|
+ if (timeout_ns >= 0) {
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1 /* CLOCK_MONOTONIC */, &now);
|
|
+ deadline = now.tv_sec * 1000000000ll + now.tv_nsec + timeout_ns;
|
|
+ }
|
|
+ for (;;) {
|
|
+ /* both the generation and the name have to match: a slot reused for a different object, even one that
|
|
+ * got the same port name again, must not be waited on (Darwin terminated the object we resolved) */
|
|
+ if ((unsigned int) __atomic_load_n(&s->pv.w.gen, __ATOMIC_ACQUIRE) != gen ||
|
|
+ __atomic_load_n(&s->name, __ATOMIC_ACQUIRE) != name) {
|
|
+ (void) lsem_post_signal(post); /* XNU signals the signal semaphore even when the wait fails */
|
|
+ return KERN_TERMINATED;
|
|
+ }
|
|
+ unsigned long long old = __atomic_load_n(&s->pv.pair, __ATOMIC_SEQ_CST);
|
|
+ if ((unsigned int) (old >> 32) != gen)
|
|
+ continue; /* the slot changed while we looked at it: the top of the loop reports it */
|
|
+ int v = (int) (unsigned int) old;
|
|
+ if (v < 0) {
|
|
+ /* the destroy tombstone: a trap that resolved the slot while the destroy was in flight must
|
|
+ * report the death, never take the tombstone for a token */
|
|
+ (void) lsem_post_signal(post);
|
|
+ return KERN_TERMINATED;
|
|
+ }
|
|
+ if (v > 0) {
|
|
+ /* take the token only if value and generation still belong to this object: the compare-exchange
|
|
+ * is on the whole pair, so a reused slot cannot hand its token to this waiter */
|
|
+ if (__atomic_compare_exchange_n(&s->pv.pair, &old, old - 1, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
|
|
+ kern_return_t kr = lsem_post_signal(post);
|
|
+ /* the wait succeeded; XNU swallows a dead signal semaphore in this case */
|
|
+ return (kr == KERN_SUCCESS || kr == KERN_TERMINATED) ? KERN_SUCCESS : kr;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ void* ts = 0;
|
|
+ if (timeout_ns >= 0) {
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1, &now);
|
|
+ long long left = deadline - (now.tv_sec * 1000000000ll + now.tv_nsec);
|
|
+ if (left <= 0) {
|
|
+ kern_return_t kr = lsem_post_signal(post);
|
|
+ return (kr == KERN_SUCCESS || kr == KERN_TERMINATED) ? KERN_OPERATION_TIMED_OUT : kr;
|
|
+ }
|
|
+ rel.tv_sec = left / 1000000000ll;
|
|
+ rel.tv_nsec = left % 1000000000ll;
|
|
+ ts = &rel;
|
|
+ }
|
|
+ /* register (signal_all and signal_thread see us) and only then release the signal semaphore, as XNU does */
|
|
+ __atomic_add_fetch(&s->waiters, 1, __ATOMIC_SEQ_CST);
|
|
+ if (post) {
|
|
+ kern_return_t kr = lsem_post_signal(post);
|
|
+ if (kr != KERN_SUCCESS) {
|
|
+ __atomic_sub_fetch(&s->waiters, 1, __ATOMIC_SEQ_CST);
|
|
+ return kr;
|
|
+ }
|
|
+ }
|
|
+ LINUX_SYSCALL(__NR_futex, &s->pv.w.value, LSEM_FUTEX_WAIT | LSEM_FUTEX_PRIVATE, 0, ts, 0UL, 0);
|
|
+ __atomic_sub_fetch(&s->waiters, 1, __ATOMIC_SEQ_CST);
|
|
+ }
|
|
+}
|
|
+
|
|
+VISIBLE
|
|
+kern_return_t semaphore_create(mach_port_name_t task, mach_port_name_t* semaphore, int policy, int value)
|
|
+{
|
|
+ kern_return_t kr = __darling_rpc_semaphore_create(task, semaphore, policy, value);
|
|
+ if (kr == KERN_SUCCESS && task == mach_task_self()) {
|
|
+ int state = __atomic_load_n(&lsem_state, __ATOMIC_ACQUIRE);
|
|
+ if (state == 0)
|
|
+ state = lsem_decide();
|
|
+ if (state == 1)
|
|
+ lsem_insert(*semaphore, value);
|
|
+ }
|
|
+ return kr;
|
|
+}
|
|
+
|
|
+VISIBLE
|
|
+kern_return_t semaphore_destroy(mach_port_name_t task, mach_port_name_t semaphore)
|
|
+{
|
|
+ if (task == mach_task_self())
|
|
+ lsem_remove(semaphore);
|
|
+ return __darling_rpc_semaphore_destroy(task, semaphore);
|
|
+}
|
|
+
|
|
+/* darling-lol: the child of fork() has a fresh IPC space (duct-tape ipc_task_init creates an empty one), so a
|
|
+ * semaphore name inherited from the parent is not valid in the child. Without this reset the inherited table would
|
|
+ * serve the child's traps for those names locally: a wait would succeed without any object behind it (or block where
|
|
+ * the server would answer KERN_INVALID_NAME), and a signal would be a no-op the parent (a different address space
|
|
+ * after fork) never sees. Runs in the forking thread right after the host fork, before any other thread of the child
|
|
+ * exists: the table has no waiters to wake, and the lock (possibly held by a parent thread that does not exist in the
|
|
+ * child) can simply be dropped. */
|
|
+__attribute__((visibility("hidden")))
|
|
+void __darling_local_semaphores_after_fork(void)
|
|
+{
|
|
+ if (__atomic_load_n(&lsem_state, __ATOMIC_ACQUIRE) != 1)
|
|
+ return;
|
|
+ for (unsigned int i = 0; i < LSEM_SLOTS; ++i) {
|
|
+ unsigned int have = lsems[i].name;
|
|
+ if (have != 0 && have != LSEM_TOMB) {
|
|
+ unsigned long long old = __atomic_load_n(&lsems[i].pv.pair, __ATOMIC_RELAXED);
|
|
+ __atomic_store_n(&lsems[i].pv.pair, (((old >> 32) + 1) << 32) | 0, __ATOMIC_RELEASE);
|
|
+ __atomic_store_n(&lsems[i].name, LSEM_TOMB, __ATOMIC_RELEASE);
|
|
+ __atomic_store_n(&lsems[i].waiters, 0, __ATOMIC_RELAXED);
|
|
+ }
|
|
+ }
|
|
+ lsem_lock = 0;
|
|
+}
|
|
+
|
|
+/* darling-lol: mach_vm_region_recurse on the caller's own task, answered in the process.
|
|
+ *
|
|
+ * darlingserver answers this call from the kernel's PROCMAP_QUERY ioctl on /proc/<pid>/maps (duct-tape memory.c
|
|
+ * mach_vm_region_recurse + Process::memoryRegionInfo/getNextRegion). In a League match one thread asks ~9,000 times a
|
|
+ * second, which kept two server threads at 60% each and made every other RPC of the game queue behind it (server
|
|
+ * 120% CPU in a fresh match, 2026-10-10). The same ioctl on /proc/self/maps returns the same record without the round
|
|
+ * trip; the reply is built exactly as the server builds it. Other tasks and failures of the ioctl still go to the server.
|
|
+ * Game process only; host file /tmp/rclol-no-local-region turns it off, /tmp/rclol-local-region-all enables it for
|
|
+ * every process (probe tests). Refreshed every 1024 queries for live A/B. */
|
|
+struct lregion_query {
|
|
+ unsigned long long size, query_flags, query_addr, vma_start, vma_end, vma_flags, vma_page_size, vma_offset, inode;
|
|
+ unsigned int dev_major, dev_minor, vma_name_size, build_id_size;
|
|
+ unsigned long long vma_name_addr, build_id_addr;
|
|
+};
|
|
+#define LREGION_IOCTL 0xC0686611ul /* PROCMAP_QUERY = _IOWR('f', 17, struct procmap_query), 104 bytes */
|
|
+#define LREGION_SHORT_INFO_COUNT 12 /* VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 */
|
|
+struct lregion_short_info {
|
|
+ int protection, max_protection;
|
|
+ unsigned int inheritance;
|
|
+ unsigned long long offset;
|
|
+ unsigned int user_tag, ref_count;
|
|
+ unsigned short shadow_depth;
|
|
+ unsigned char external_pager, share_mode;
|
|
+ int is_submap, behavior;
|
|
+ unsigned int object_id;
|
|
+ unsigned short user_wired_count;
|
|
+} __attribute__((packed, aligned(4)));
|
|
+static volatile int lregion_state; /* 0 undecided, 1 on, -1 off */
|
|
+static unsigned int lregion_calls;
|
|
+static volatile int lregion_refresh;
|
|
+static volatile int lregion_maps_fd = -1;
|
|
+static volatile int lregion_maps_pid;
|
|
+_Static_assert(sizeof(struct lregion_query) == 104, "PROCMAP_QUERY ABI");
|
|
+_Static_assert(sizeof(struct lregion_short_info) == 48, "short region reply ABI");
|
|
+
|
|
+extern kern_return_t __darling_rpc_mach_vm_region_recurse(mach_port_name_t target, unsigned long long* address, unsigned long long* size,
|
|
+ unsigned int* depth, int* info, unsigned int* infoCnt);
|
|
+
|
|
+/* Pacing. The one heavy caller is a loop that asks again as soon as it has an answer: through the server it made
|
|
+ * ~9,000 calls a second using 17% of a core; answered locally without a pause it made several times more, took 85% of a
|
|
+ * core, and fights got slower (61 vs 68 fps, 2026-10-10) although the server load halved. A call that follows the
|
|
+ * previous one within the pace interval waits out the rest of it, so the call rate stays what it was through the
|
|
+ * server while neither the server nor a core is spent on it. Isolated calls are not delayed. Host file
|
|
+ * /tmp/rclol-region-pace-us overrides the interval (0 = no pacing). */
|
|
+#define LREGION_DEFAULT_PACE_US 50 /* plus ~50 us of timer slack: ~105 us per call, the rate measured through the server */
|
|
+static volatile long lregion_pace_us = LREGION_DEFAULT_PACE_US;
|
|
+static volatile unsigned long long lregion_last_ns;
|
|
+
|
|
+static void lregion_pace(void)
|
|
+{
|
|
+ long pace = __atomic_load_n(&lregion_pace_us, __ATOMIC_RELAXED);
|
|
+ if (pace <= 0)
|
|
+ return;
|
|
+ struct { long tv_sec; long tv_nsec; } now, rest;
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1 /* CLOCK_MONOTONIC */, &now);
|
|
+ unsigned long long t = now.tv_sec * 1000000000ull + now.tv_nsec;
|
|
+ unsigned long long last = __atomic_exchange_n(&lregion_last_ns, t, __ATOMIC_RELAXED);
|
|
+ unsigned long long interval = (unsigned long long) pace * 1000ull;
|
|
+ if (t >= last && t - last < interval) {
|
|
+ rest.tv_sec = 0;
|
|
+ rest.tv_nsec = (long) (interval - (t - last));
|
|
+ LINUX_SYSCALL(__NR_nanosleep, &rest, 0UL);
|
|
+ LINUX_SYSCALL(__NR_clock_gettime, 1, &now);
|
|
+ __atomic_store_n(&lregion_last_ns, now.tv_sec * 1000000000ull + now.tv_nsec, __ATOMIC_RELAXED);
|
|
+ }
|
|
+}
|
|
+
|
|
+VISIBLE
|
|
+kern_return_t mach_vm_region_recurse(mach_port_name_t target, unsigned long long* address, unsigned long long* size,
|
|
+ unsigned int* depth, int* info, unsigned int* infoCnt)
|
|
+{
|
|
+ int state = __atomic_load_n(&lregion_state, __ATOMIC_ACQUIRE);
|
|
+ /* The switches are re-read a few times per second only in a process where the feature can be on (same-match A/B).
|
|
+ * Everywhere else the first decision is final and this function makes no further system calls of its own:
|
|
+ * LeagueClient runs the same scanner, and periodic file opens there were the one new behaviour in that process
|
|
+ * when it died twice with an invalid RPC descriptor (2026-10-10). */
|
|
+ if (state == 0 || (lregion_refresh && (__atomic_fetch_add(&lregion_calls, 1, __ATOMIC_RELAXED) & 1023) == 0)) {
|
|
+ if (state == 0)
|
|
+ lregion_refresh = LINUX_SYSCALL(__NR_access, "/tmp/rclol-local-region-all", 0) == 0 || darling_is_league_game();
|
|
+ if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-local-region-all", 0) == 0)
|
|
+ state = 1;
|
|
+ else if (LINUX_SYSCALL(__NR_access, "/tmp/rclol-no-local-region", 0) != 0 && darling_is_league_game())
|
|
+ state = 1;
|
|
+ else
|
|
+ state = -1;
|
|
+ __atomic_store_n(&lregion_state, state, __ATOMIC_RELEASE);
|
|
+ /* pacing in microseconds (host file, optional): see below */
|
|
+ long pfd = LINUX_SYSCALL(__NR_open, "/tmp/rclol-region-pace-us", 0, 0);
|
|
+ if (pfd >= 0) {
|
|
+ char digits[16];
|
|
+ long n = LINUX_SYSCALL(__NR_read, pfd, digits, sizeof(digits) - 1);
|
|
+ LINUX_SYSCALL(__NR_close, pfd);
|
|
+ long value = 0;
|
|
+ for (long i = 0; i < n && digits[i] >= '0' && digits[i] <= '9'; ++i) value = value * 10 + (digits[i] - '0');
|
|
+ if (n > 0 && value <= 100000) __atomic_store_n(&lregion_pace_us, value, __ATOMIC_RELAXED);
|
|
+ } else {
|
|
+ __atomic_store_n(&lregion_pace_us, LREGION_DEFAULT_PACE_US, __ATOMIC_RELAXED);
|
|
+ }
|
|
+ }
|
|
+ /* Any count up to the long structure (19 words): the server only fills the short one (12) and returns the others
|
|
+ * zeroed, with the region's address and size (checked with tools/riot-region-count19-check.c). The scanner in
|
|
+ * League asks with the long count, so the short-only version of this path never ran in a match. */
|
|
+ if (state == 1 && target == mach_task_self() && address && size && info && infoCnt &&
|
|
+ *infoCnt >= 1 && *infoCnt <= 19) {
|
|
+ /* One descriptor, opened once and kept at a high number. It was opened and closed for every call before
|
|
+ * (~9,000 times a second), which churned the low descriptor numbers the rest of the process hands out.
|
|
+ * After a fork the inherited descriptor still names the parent's address space, so it is tied to the pid;
|
|
+ * if the application closed or replaced it, the ioctl fails and the call goes to the server. */
|
|
+ lregion_pace();
|
|
+ int fd = __atomic_load_n(&lregion_maps_fd, __ATOMIC_ACQUIRE);
|
|
+ int self = (int) LINUX_SYSCALL(__NR_getpid);
|
|
+ if (fd < 0 || __atomic_load_n(&lregion_maps_pid, __ATOMIC_ACQUIRE) != self) {
|
|
+ int low = LINUX_SYSCALL(__NR_open, "/proc/self/maps", 02000000 /* O_RDONLY | O_CLOEXEC */, 0);
|
|
+ fd = -1;
|
|
+ if (low >= 0) {
|
|
+ fd = LINUX_SYSCALL(__NR_fcntl, low, 1030 /* F_DUPFD_CLOEXEC */, 900);
|
|
+ if (fd >= 0)
|
|
+ LINUX_SYSCALL(__NR_close, low);
|
|
+ else
|
|
+ fd = low;
|
|
+ __atomic_store_n(&lregion_maps_pid, self, __ATOMIC_RELEASE);
|
|
+ __atomic_store_n(&lregion_maps_fd, fd, __ATOMIC_RELEASE);
|
|
+ }
|
|
+ }
|
|
+ if (fd >= 0) {
|
|
+ struct lregion_query q;
|
|
+ for (unsigned long i = 0; i < sizeof(q); ++i) ((char*) &q)[i] = 0;
|
|
+ q.size = sizeof(q);
|
|
+ q.query_flags = 0x10; /* PROCMAP_QUERY_COVERING_OR_NEXT_VMA */
|
|
+ q.query_addr = *address;
|
|
+ long rv = LINUX_SYSCALL(__NR_ioctl, fd, LREGION_IOCTL, &q);
|
|
+ if (rv < 0 && rv != -LINUX_ENOENT)
|
|
+ __atomic_store_n(&lregion_maps_fd, -1, __ATOMIC_RELEASE); /* not ours any more: reopen on the next call */
|
|
+ if (rv == -LINUX_ENOENT)
|
|
+ return KERN_NO_SPACE; /* MIG leaves all caller outputs untouched on failure. */
|
|
+ if (rv == 0 && *infoCnt != LREGION_SHORT_INFO_COUNT) {
|
|
+ for (unsigned int i = 0; i < *infoCnt; ++i) info[i] = 0;
|
|
+ *address = q.vma_start;
|
|
+ *size = q.vma_end - q.vma_start;
|
|
+ if (depth) *depth = 0;
|
|
+ return KERN_SUCCESS;
|
|
+ }
|
|
+ if (rv == 0) {
|
|
+ struct lregion_short_info* out = (struct lregion_short_info*) info;
|
|
+ for (unsigned long i = 0; i < sizeof(*out); ++i) ((char*) out)[i] = 0;
|
|
+ out->protection = q.vma_flags & 7;
|
|
+ out->offset = q.vma_offset;
|
|
+ /* Compatibility with duct-tape memory.c: the server fills the LONG structure,
|
|
+ * then MIG copies only its first 48 bytes for a short request. Its share_mode
|
|
+ * lands at byte 47 (short-structure trailing padding), not the short share_mode. */
|
|
+ ((unsigned char*)out)[47] = (q.vma_flags & 8) ? 4 : 2;
|
|
+ out->max_protection = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE;
|
|
+ *address = q.vma_start;
|
|
+ *size = q.vma_end - q.vma_start;
|
|
+ if (depth) *depth = 0;
|
|
+ return KERN_SUCCESS;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return __darling_rpc_mach_vm_region_recurse(target, address, size, depth, info, infoCnt);
|
|
+}
|
|
+
|
|
kern_return_t semaphore_signal_trap_impl(
|
|
mach_port_name_t signal_name)
|
|
{
|
|
+ struct lsem* local = lsem_find(signal_name);
|
|
+ if (local)
|
|
+ return lsem_signal(local, signal_name, lsem_capture(local));
|
|
int code = dserver_rpc_semaphore_signal(signal_name);
|
|
+ int attempt;
|
|
+
|
|
+ for (attempt = 0; code == -111 && attempt < 8; attempt++) {
|
|
+ dserver_retry_sleep(attempt);
|
|
+ code = dserver_rpc_semaphore_signal(signal_name);
|
|
+ }
|
|
|
|
if (code < 0) {
|
|
+ /* darling-lol: fail SOFT, never abort. semaphore_signal is issued
|
|
+ * on every pthread exit (sys_bsdthread_terminate posts join_sem,
|
|
+ * 0 when no joiner); if the per-thread darlingserver RPC channel
|
|
+ * is already stale the RPC fails with -111, and aborting the
|
|
+ * whole process for a dying thread's goodbye-signal killed e.g.
|
|
+ * Chromium/Electron renderers (SIGILL in __simple_abort, then
|
|
+ * SIGABRT). The caller ignores the result; return the error. */
|
|
__simple_printf("semaphore_signal failed (internally): %d\n", code);
|
|
- __simple_abort();
|
|
}
|
|
|
|
return code;
|
|
}
|
|
-
|
|
+
|
|
kern_return_t semaphore_signal_all_trap_impl(
|
|
mach_port_name_t signal_name)
|
|
{
|
|
+ struct lsem* local = lsem_find(signal_name);
|
|
+ if (local)
|
|
+ return lsem_signal_all(local, signal_name, lsem_capture(local));
|
|
int code = dserver_rpc_semaphore_signal_all(signal_name);
|
|
+ int attempt;
|
|
+
|
|
+ for (attempt = 0; code == -111 && attempt < 8; attempt++) {
|
|
+ dserver_retry_sleep(attempt);
|
|
+ code = dserver_rpc_semaphore_signal_all(signal_name);
|
|
+ }
|
|
|
|
if (code < 0) {
|
|
+ /* darling-lol: fail soft, see semaphore_signal_trap_impl. */
|
|
__simple_printf("semaphore_signal_all failed (internally): %d\n", code);
|
|
- __simple_abort();
|
|
}
|
|
|
|
return code;
|
|
@@ -128,21 +980,33 @@ kern_return_t semaphore_signal_thread_trap_impl(
|
|
mach_port_name_t signal_name,
|
|
mach_port_name_t thread_name)
|
|
{
|
|
+ struct lsem* local = lsem_find(signal_name);
|
|
+ if (local)
|
|
+ return lsem_signal_thread(local, signal_name, lsem_capture(local), thread_name);
|
|
UNIMPLEMENTED_TRAP();
|
|
return KERN_FAILURE;
|
|
}
|
|
|
|
+
|
|
kern_return_t semaphore_wait_trap_impl(
|
|
mach_port_name_t wait_name)
|
|
{
|
|
+ struct lsem* local = lsem_find(wait_name);
|
|
+ if (local)
|
|
+ return lsem_wait(local, wait_name, lsem_capture(local), -1, 0);
|
|
+ unsigned long long slowStart = slowwait_enabled() ? slowwait_now() : 0;
|
|
int code = dserver_rpc_semaphore_wait(wait_name);
|
|
+ if (slowStart) {
|
|
+ slowwait_log("semaphore_wait", wait_name, slowStart, (void**) __builtin_frame_address(0), 0, 0);
|
|
+ }
|
|
|
|
if (code < 0) {
|
|
if (code == -LINUX_EINTR) {
|
|
return KERN_ABORTED;
|
|
}
|
|
+ /* darling-lol: fail soft (a waiter whose RPC channel died must
|
|
+ * get an error, not kill the process); see above. */
|
|
__simple_printf("semaphore_wait failed (internally): %d\n", code);
|
|
- __simple_abort();
|
|
}
|
|
|
|
return code;
|
|
@@ -152,14 +1016,33 @@ kern_return_t semaphore_wait_signal_trap_impl(
|
|
mach_port_name_t wait_name,
|
|
mach_port_name_t signal_name)
|
|
{
|
|
+ struct lsem* localWait = lsem_find(wait_name);
|
|
+ struct lsem* localSignal = lsem_find(signal_name);
|
|
+ if (localWait || localSignal) {
|
|
+ /* The wait goes first: XNU registers it on wait_name and only then signals signal_name (even when the
|
|
+ * wait did not block). A local wait can do that from inside its loop; with a server-side wait the
|
|
+ * signal has to be issued first, the only order the server can be given. */
|
|
+ if (localWait) {
|
|
+ struct lsem_post post;
|
|
+ post.s = localSignal;
|
|
+ post.name = signal_name;
|
|
+ post.gen = localSignal ? lsem_capture(localSignal) : 0;
|
|
+ post.done = 0;
|
|
+ return lsem_wait(localWait, wait_name, lsem_capture(localWait), -1, &post);
|
|
+ }
|
|
+ kern_return_t kr = semaphore_signal_trap_impl(signal_name);
|
|
+ if (kr != KERN_SUCCESS)
|
|
+ return kr;
|
|
+ return semaphore_wait_trap_impl(wait_name);
|
|
+ }
|
|
int code = dserver_rpc_semaphore_wait_signal(wait_name, signal_name);
|
|
|
|
if (code < 0) {
|
|
if (code == -LINUX_EINTR) {
|
|
return KERN_ABORTED;
|
|
}
|
|
+ /* darling-lol: fail soft; see above. */
|
|
__simple_printf("semaphore_wait_signal failed (internally): %d\n", code);
|
|
- __simple_abort();
|
|
}
|
|
|
|
return code;
|
|
@@ -170,14 +1053,24 @@ kern_return_t semaphore_timedwait_trap_impl(
|
|
unsigned int sec,
|
|
clock_res_t nsec)
|
|
{
|
|
+ struct lsem* local = lsem_find(wait_name);
|
|
+ if (local) {
|
|
+ if (nsec < 0 || nsec >= 1000000000)
|
|
+ return KERN_INVALID_VALUE; /* what the server's BAD_MACH_TIMESPEC check answers */
|
|
+ return lsem_wait(local, wait_name, lsem_capture(local), (long long) sec * 1000000000ll + nsec, 0);
|
|
+ }
|
|
+ unsigned long long slowStart = slowwait_enabled() ? slowwait_now() : 0;
|
|
int code = dserver_rpc_semaphore_timedwait(wait_name, sec, nsec);
|
|
+ if (slowStart) {
|
|
+ slowwait_log("semaphore_timedwait", wait_name, slowStart, (void**) __builtin_frame_address(0), 0, sec);
|
|
+ }
|
|
|
|
if (code < 0) {
|
|
if (code == -LINUX_EINTR) {
|
|
return KERN_ABORTED;
|
|
}
|
|
+ /* darling-lol: fail soft; see above. */
|
|
__simple_printf("semaphore_timedwait failed (internally): %d\n", code);
|
|
- __simple_abort();
|
|
}
|
|
|
|
return code;
|
|
@@ -189,14 +1082,32 @@ kern_return_t semaphore_timedwait_signal_trap_impl(
|
|
unsigned int sec,
|
|
clock_res_t nsec)
|
|
{
|
|
+ struct lsem* localWait = lsem_find(wait_name);
|
|
+ struct lsem* localSignal = lsem_find(signal_name);
|
|
+ if (localWait || localSignal) {
|
|
+ if (localWait) {
|
|
+ struct lsem_post post;
|
|
+ if (nsec < 0 || nsec >= 1000000000)
|
|
+ return KERN_INVALID_VALUE; /* what the server's BAD_MACH_TIMESPEC check answers */
|
|
+ post.s = localSignal;
|
|
+ post.name = signal_name;
|
|
+ post.gen = localSignal ? lsem_capture(localSignal) : 0;
|
|
+ post.done = 0;
|
|
+ return lsem_wait(localWait, wait_name, lsem_capture(localWait), (long long) sec * 1000000000ll + nsec, &post);
|
|
+ }
|
|
+ kern_return_t kr = semaphore_signal_trap_impl(signal_name);
|
|
+ if (kr != KERN_SUCCESS)
|
|
+ return kr;
|
|
+ return semaphore_timedwait_trap_impl(wait_name, sec, nsec);
|
|
+ }
|
|
int code = dserver_rpc_semaphore_timedwait_signal(wait_name, signal_name, sec, nsec);
|
|
|
|
if (code < 0) {
|
|
if (code == -LINUX_EINTR) {
|
|
return KERN_ABORTED;
|
|
}
|
|
+ /* darling-lol: fail soft; see above. */
|
|
__simple_printf("semaphore_timedwait_signal failed (internally): %d\n", code);
|
|
- __simple_abort();
|
|
}
|
|
|
|
return code;
|
|
@@ -514,6 +1425,12 @@ kern_return_t _kernelrpc_mach_port_construct_trap_impl(
|
|
__simple_printf("mach_port_construct failed (internally): %d\n", code);
|
|
__simple_abort();
|
|
}
|
|
+ #if 0 /* M58 diagnostic. */
|
|
+ if (code == KERN_SUCCESS && name != 0 && *name == MACH_PORT_NULL) {
|
|
+ __simple_printf("M58PC construct-success-null target=%u flags=0x%x\n",
|
|
+ (unsigned) target, (unsigned) options->flags);
|
|
+ }
|
|
+ #endif
|
|
|
|
return code;
|
|
}
|
|
@@ -673,16 +1590,16 @@ kern_return_t macx_backing_store_recovery_impl(
|
|
return KERN_FAILURE;
|
|
}
|
|
|
|
-extern void __linux_sched_yield();
|
|
+extern void __linux_sched_yield_site(const char* site);
|
|
boolean_t swtch_pri_impl(int pri)
|
|
{
|
|
- __linux_sched_yield();
|
|
+ __linux_sched_yield_site("SWTPRI");
|
|
return 0;
|
|
}
|
|
|
|
boolean_t swtch_impl(void)
|
|
{
|
|
- __linux_sched_yield();
|
|
+ __linux_sched_yield_site("SWTCH");
|
|
return 0;
|
|
}
|
|
|
|
@@ -692,12 +1609,53 @@ kern_return_t syscall_thread_switch_impl(
|
|
int option,
|
|
mach_msg_timeout_t option_time)
|
|
{
|
|
+ /* option_time is in milliseconds (mach_msg_timeout_t). */
|
|
struct timespec tv = {
|
|
- .tv_sec = 0,
|
|
- .tv_nsec = 1000000
|
|
+ .tv_sec = option_time / 1000,
|
|
+ .tv_nsec = (long) (option_time % 1000) * 1000000
|
|
};
|
|
- // Sleep for 1ms
|
|
- __linux_nanosleep(&tv, &tv);
|
|
+
|
|
+ /* darling-lol: opt-in trace (host file /tmp/rclol-select-trace) of who calls thread_switch and with which option:
|
|
+ * every 50th call writes thread id, option, time and up to 6 caller addresses (frame-pointer walk) to
|
|
+ * /tmp/rclol-select-trace.log. Addresses are resolved offline with the process's /proc/<pid>/maps. */
|
|
+ {
|
|
+ static int trace_enabled = -1;
|
|
+ static unsigned long calls;
|
|
+ if (trace_enabled < 0)
|
|
+ trace_enabled = LINUX_SYSCALL(__NR_access, "/tmp/rclol-select-trace", 0) == 0;
|
|
+ if (trace_enabled && (__atomic_add_fetch(&calls, 1, __ATOMIC_RELAXED) % 50) == 1) {
|
|
+ char line[320];
|
|
+ int used;
|
|
+ void** fp = (void**) __builtin_frame_address(0);
|
|
+ void* prev = (void*) fp;
|
|
+ void* frames[6] = {0};
|
|
+ int count = 0;
|
|
+ while (count < 6 && fp && ((unsigned long) fp & 7) == 0 && (void*) fp >= prev && (unsigned long) fp - (unsigned long) prev < (1UL << 20)) {
|
|
+ frames[count++] = fp[1];
|
|
+ prev = (void*) fp;
|
|
+ fp = (void**) fp[0];
|
|
+ }
|
|
+ used = __simple_sprintf(line, "thread_switch pid=%d tid=%d n=%lu option=%d time=%u frames:",
|
|
+ (int) LINUX_SYSCALL0(__NR_getpid), (int) LINUX_SYSCALL0(__NR_gettid), calls, option, option_time);
|
|
+ for (int i = 0; i < count; ++i)
|
|
+ used += __simple_sprintf(line + used, " %lx", (unsigned long) frames[i]);
|
|
+ line[used++] = '\n';
|
|
+ line[used] = 0;
|
|
+ int fd = LINUX_SYSCALL(__NR_open, "/tmp/rclol-select-trace.log", 0x441, 0644);
|
|
+ if (fd >= 0) {
|
|
+ LINUX_SYSCALL(__NR_write, fd, line, used);
|
|
+ LINUX_SYSCALL1(__NR_close, fd);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* darling-lol perf: a plain yield (option_time == 0) only gives up the CPU; sleeping a
|
|
+ * full millisecond there is longer than a frame budget at high refresh rates. Timed
|
|
+ * switches (spin locks pass 1 ms) keep sleeping for the requested time. */
|
|
+ if (option_time == 0)
|
|
+ __linux_sched_yield_site("THRSW");
|
|
+ else
|
|
+ __linux_nanosleep(&tv, &tv);
|
|
|
|
// TODO: we could implement this with yield_to() in LKM
|
|
return KERN_SUCCESS;
|
|
diff --git a/libsyscall/CMakeLists.txt b/libsyscall/CMakeLists.txt
|
|
index 57cebad..3014255 100755
|
|
--- a/libsyscall/CMakeLists.txt
|
|
+++ b/libsyscall/CMakeLists.txt
|
|
@@ -104,6 +104,18 @@ foreach(MIG_FILENAME IN LISTS LIBSYSCALL_MIGS)
|
|
)
|
|
endforeach()
|
|
|
|
+# emulation/src/xnu_syscall/mach/impl/mach_traps.c wraps semaphore_create/semaphore_destroy
|
|
+# (process-local semaphores) and mach_vm_region_recurse (own task). The MIG stubs get private
|
|
+# names, which the wrappers call.
|
|
+foreach(MIG_ARCH i386 x86_64)
|
|
+ set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/mach/task-${MIG_ARCH}-User.c" PROPERTIES
|
|
+ COMPILE_DEFINITIONS "semaphore_create=__darling_rpc_semaphore_create;semaphore_destroy=__darling_rpc_semaphore_destroy"
|
|
+ )
|
|
+ set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/mach/mach_vm-${MIG_ARCH}-User.c" PROPERTIES
|
|
+ COMPILE_DEFINITIONS "mach_vm_region_recurse=__darling_rpc_mach_vm_region_recurse"
|
|
+ )
|
|
+endforeach()
|
|
+
|
|
# mach/exc also needs the server component
|
|
list(APPEND syscall_mig_sources_32 "${CMAKE_CURRENT_BINARY_DIR}/mach/exc-i386-Server.c")
|
|
list(APPEND syscall_mig_sources_64 "${CMAKE_CURRENT_BINARY_DIR}/mach/exc-x86_64-Server.c")
|
|
@@ -179,6 +191,7 @@ set(syscall_sources
|
|
wrappers/csr.c
|
|
wrappers/fs_snapshot.c
|
|
wrappers/__get_cpu_capabilities.S
|
|
+ wrappers/__sysctlbyname.S
|
|
wrappers/gethostuuid.c
|
|
wrappers/getiopolicy_np.c
|
|
wrappers/guarded_open_dprotected_np.c
|
|
diff --git a/libsyscall/custom/errno.c b/libsyscall/custom/errno.c
|
|
index 88658cb..83ce61c 100644
|
|
--- a/libsyscall/custom/errno.c
|
|
+++ b/libsyscall/custom/errno.c
|
|
@@ -62,6 +62,12 @@ __attribute__((noinline))
|
|
cerror_return_t
|
|
cerror_nocancel(int err)
|
|
{
|
|
+#ifdef DARLING
|
|
+ /* ulock's ULF_NO_ERRNO path marks its error with bit 0x800. Its
|
|
+ * callers need -errno as the result, with thread errno untouched. */
|
|
+ if (err & 0x800)
|
|
+ return (cerror_return_t)(int64_t)(-(err & ~0x800));
|
|
+#endif
|
|
errno = err;
|
|
int *tsderrno = (int*)_os_tsd_get_direct(__TSD_ERRNO);
|
|
if (tsderrno) {
|
|
diff --git a/libsyscall/os/tsd.h b/libsyscall/os/tsd.h
|
|
index 6619381..78d90df 100644
|
|
--- a/libsyscall/os/tsd.h
|
|
+++ b/libsyscall/os/tsd.h
|
|
@@ -63,6 +63,22 @@ static __inline__ unsigned int
|
|
_os_cpu_number(void)
|
|
{
|
|
#ifdef DARLING
|
|
+#if defined(__x86_64__)
|
|
+ /* Linux keeps (node << 12) | cpu in IA32_TSC_AUX, which RDTSCP returns without entering the kernel: this is
|
|
+ * what the kernel's own vDSO getcpu does. The syscall below costs ~470 ns and libmalloc asks for the CPU
|
|
+ * number ~30,000 times a second on the game's main thread. Verified against sched_getcpu on every core. */
|
|
+ static int __darling_has_rdtscp = -1;
|
|
+ if (__builtin_expect(__darling_has_rdtscp < 0, 0)) {
|
|
+ unsigned int a, b, c, d;
|
|
+ __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(0x80000001), "c"(0));
|
|
+ __darling_has_rdtscp = (d >> 27) & 1;
|
|
+ }
|
|
+ if (__builtin_expect(__darling_has_rdtscp, 1)) {
|
|
+ unsigned int lo, hi, aux;
|
|
+ __asm__ volatile("rdtscp" : "=a"(lo), "=d"(hi), "=c"(aux));
|
|
+ return aux & 0xfff;
|
|
+ }
|
|
+#endif
|
|
extern int __linux_syscall(int nr, ...);
|
|
|
|
unsigned int cpu_num = 0;
|
|
diff --git a/libsyscall/wrappers/__commpage_gettimeofday.c b/libsyscall/wrappers/__commpage_gettimeofday.c
|
|
index ccd2b33..1a1a7c6 100644
|
|
--- a/libsyscall/wrappers/__commpage_gettimeofday.c
|
|
+++ b/libsyscall/wrappers/__commpage_gettimeofday.c
|
|
@@ -66,6 +66,12 @@ __commpage_gettimeofday_internal(struct timeval *tp, uint64_t *tbr_out)
|
|
|
|
do {
|
|
TimeStamp_tick = *gtod_TimeStamp_tick_p;
|
|
+ /* An unpublished timestamp requires the syscall fallback. Avoid
|
|
+ * reading absolute time first, especially on syscall-only clocks.
|
|
+ */
|
|
+ if (TimeStamp_tick == 0) {
|
|
+ return 1;
|
|
+ }
|
|
/*
|
|
* This call contains an instruction barrier which will ensure that the
|
|
* second read of the abs time isn't speculated above the reads of the
|
|
diff --git a/libsyscall/wrappers/__sysctlbyname.S b/libsyscall/wrappers/__sysctlbyname.S
|
|
new file mode 100644
|
|
index 0000000..f185f6c
|
|
--- /dev/null
|
|
+++ b/libsyscall/wrappers/__sysctlbyname.S
|
|
@@ -0,0 +1,12 @@
|
|
+/*
|
|
+ * __sysctlbyname wrapper
|
|
+ * Bridges libc's sysctlbyname to the syscall stub ___sysctlbyname
|
|
+ */
|
|
+
|
|
+.text
|
|
+.globl __sysctlbyname
|
|
+.globl ___sysctlbyname
|
|
+
|
|
+__sysctlbyname:
|
|
+ /* Just jump to the syscall stub */
|
|
+ jmp ___sysctlbyname
|