mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
2886 lines
108 KiB
Diff
2886 lines
108 KiB
Diff
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
index 4920879..012235e 100644
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -133,6 +133,13 @@ target_compile_options(darlingserver PRIVATE
|
|
-pthread
|
|
-std=c++17
|
|
)
|
|
+# The parent project commonly leaves CMAKE_BUILD_TYPE empty. An unoptimized
|
|
+# RPC server spends its event-loop budget in C++ ownership/container helpers.
|
|
+# Preserve explicit debug and sanitizer builds; optimize the default runtime.
|
|
+if (NOT CMAKE_BUILD_TYPE AND NOT DSERVER_ASAN AND NOT DSERVER_UBSAN)
|
|
+ target_compile_options(darlingserver PRIVATE -O2)
|
|
+endif()
|
|
+
|
|
target_link_options(darlingserver PRIVATE
|
|
-pthread
|
|
-Wl,--gc-sections
|
|
diff --git a/duct-tape/CMakeLists.txt b/duct-tape/CMakeLists.txt
|
|
index 5950c7f..cf4f1ba 100644
|
|
--- a/duct-tape/CMakeLists.txt
|
|
+++ b/duct-tape/CMakeLists.txt
|
|
@@ -268,6 +268,7 @@ add_library(darlingserver_duct_tape STATIC
|
|
src/kqchan.c
|
|
src/semaphore.c
|
|
src/psynch.c
|
|
+ src/fileport.c
|
|
src/condvar.c
|
|
src/debug.c
|
|
|
|
@@ -380,3 +381,9 @@ add_dependencies(darlingserver_duct_tape
|
|
target_link_libraries(darlingserver_duct_tape PUBLIC
|
|
libsimple_darlingserver
|
|
)
|
|
+
|
|
+# Match the host server's default optimization while retaining the kernel's
|
|
+# pointer-aliasing semantics and explicit debug/sanitizer build policies.
|
|
+if (NOT CMAKE_BUILD_TYPE AND NOT DSERVER_ASAN AND NOT DSERVER_UBSAN)
|
|
+ target_compile_options(darlingserver_duct_tape PRIVATE -O2 -fno-strict-aliasing)
|
|
+endif()
|
|
diff --git a/duct-tape/include/darlingserver/duct-tape.h b/duct-tape/include/darlingserver/duct-tape.h
|
|
index a5ddfc7..607cf17 100644
|
|
--- a/duct-tape/include/darlingserver/duct-tape.h
|
|
+++ b/duct-tape/include/darlingserver/duct-tape.h
|
|
@@ -56,6 +56,13 @@ dtape_thread_t* dtape_thread_create(dtape_task_t* task, uint64_t nsid, void* con
|
|
dtape_kqchan_mach_port_t* dtape_kqchan_mach_port_create(dtape_task_t* owning_task, uint32_t port, uint64_t receive_buffer, uint64_t receive_buffer_size, uint64_t saved_filter_flags, dtape_kqchan_mach_port_notification_callback_f notification_callback, void* context);
|
|
dtape_semaphore_t* dtape_semaphore_create(dtape_task_t* owning_task, int initial_value);
|
|
|
|
+/* darling-lol M54: fileports (Mojo FD-over-Mach transport). Names and
|
|
+ * kern_return_t cross as plain ints to keep this header light. */
|
|
+int dtape_fileport_makeport(dtape_task_t* task, int fd, uint32_t* name_out);
|
|
+int dtape_fileport_makefd(dtape_task_t* task, uint32_t name, int* fd_out);
|
|
+struct ipc_port;
|
|
+void dtape_fileport_destroy(struct ipc_port* port);
|
|
+
|
|
void dtape_kqchan_mach_port_destroy(dtape_kqchan_mach_port_t* kqchan);
|
|
void dtape_semaphore_destroy(dtape_semaphore_t* semaphore);
|
|
|
|
diff --git a/duct-tape/include/darlingserver/duct-tape/hooks.h b/duct-tape/include/darlingserver/duct-tape/hooks.h
|
|
index 1dde8b7..f319f1f 100644
|
|
--- a/duct-tape/include/darlingserver/duct-tape/hooks.h
|
|
+++ b/duct-tape/include/darlingserver/duct-tape/hooks.h
|
|
@@ -28,6 +28,8 @@ typedef dtape_thread_t* (*dtape_hook_current_thread_f)(void);
|
|
typedef void (*dtape_hook_timer_arm_f)(uint64_t absolute_ns, bool override);
|
|
|
|
typedef void (*dtape_hook_log_f)(dtape_log_level_t level, const char* message);
|
|
+/* darling-lol: lets dtape_log skip formatting (a 4 KiB vsnprintf per call) when the level would be dropped anyway */
|
|
+typedef int (*dtape_hook_log_enabled_f)(dtape_log_level_t level);
|
|
typedef void (*dtape_hook_get_load_info_f)(dtape_load_info_t* load_info);
|
|
|
|
typedef void (*dtape_hook_thread_suspend_f)(void* thread_context, dtape_thread_continuation_callback_f continuation_callback, void* continuation_contex, libsimple_lock_t* unlock_me);
|
|
@@ -122,6 +124,8 @@ typedef struct dtape_hooks {
|
|
dtape_hook_task_remove_port_set_member_f task_remove_port_set_member;
|
|
dtape_hook_task_clear_port_set_f task_clear_port_set;
|
|
#endif
|
|
+
|
|
+ dtape_hook_log_enabled_f log_enabled;
|
|
} dtape_hooks_t;
|
|
|
|
#ifdef __cplusplus
|
|
diff --git a/duct-tape/internal-include/darlingserver/duct-tape/stubs.h b/duct-tape/internal-include/darlingserver/duct-tape/stubs.h
|
|
index 32ebd76..688015e 100644
|
|
--- a/duct-tape/internal-include/darlingserver/duct-tape/stubs.h
|
|
+++ b/duct-tape/internal-include/darlingserver/duct-tape/stubs.h
|
|
@@ -12,9 +12,13 @@ void dtape_stub_log(const char* function_name, int safety, const char* subsectio
|
|
#define dtape_stub_safe(...) (dtape_stub_log(__FUNCTION__, 1, "" __VA_ARGS__))
|
|
|
|
// for functions that have been confirmed to require an actual implementation (rather than a simple stub)
|
|
-#define dtape_stub_unsafe(...) ({ \
|
|
- dtape_stub_log(__FUNCTION__, -1, "" __VA_ARGS__); \
|
|
- __builtin_unreachable(); \
|
|
- }) \
|
|
+//
|
|
+// darling-lol: this used to end in __builtin_unreachable(), which is only
|
|
+// valid when dtape_stub_log() is guaranteed to abort. With the optional
|
|
+// DTAPE_NONFATAL_UNSAFE escape hatch it may return, so we must not tell the
|
|
+// compiler the path is dead (that miscompiles the function's fall-through).
|
|
+// dtape_stub_log() raises SIGABRT itself in the default (fatal) case, so
|
|
+// omitting __builtin_unreachable() preserves the original behavior.
|
|
+#define dtape_stub_unsafe(...) (dtape_stub_log(__FUNCTION__, -1, "" __VA_ARGS__))
|
|
|
|
#endif // _DARLINGSERVER_DUCT_TAPE_STUBS_H_
|
|
diff --git a/duct-tape/internal-include/darlingserver/duct-tape/thread.h b/duct-tape/internal-include/darlingserver/duct-tape/thread.h
|
|
index 633d729..75a3965 100644
|
|
--- a/duct-tape/internal-include/darlingserver/duct-tape/thread.h
|
|
+++ b/duct-tape/internal-include/darlingserver/duct-tape/thread.h
|
|
@@ -38,6 +38,8 @@ struct dtape_thread {
|
|
bool processing_signal;
|
|
|
|
bool waiting_suspended;
|
|
+ /* Protect the wait check through publication of microthread suspension. */
|
|
+ libsimple_lock_t block_suspend_gate;
|
|
dtape_mutex_t suspension_mutex;
|
|
dtape_condvar_t suspension_condvar;
|
|
|
|
diff --git a/duct-tape/pthread/kern_synch.c b/duct-tape/pthread/kern_synch.c
|
|
index aa2b6d3..ab7cdb7 100644
|
|
--- a/duct-tape/pthread/kern_synch.c
|
|
+++ b/duct-tape/pthread/kern_synch.c
|
|
@@ -42,6 +42,7 @@
|
|
#include <sys/param.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/resourcevar.h>
|
|
+#include <stdint.h> /* darling-lol M58f3: UINT64_MAX */
|
|
//#include <sys/proc_internal.h>
|
|
#include <sys/kauth.h>
|
|
#include <sys/systm.h>
|
|
@@ -1236,8 +1237,22 @@ _psynch_cvwait(__unused proc_t p, user_addr_t cv, uint64_t cvlsgen,
|
|
struct timespec ts;
|
|
ts.tv_sec = (__darwin_time_t)sec;
|
|
ts.tv_nsec = (nsec & 0x3fffffff);
|
|
- nanoseconds_to_absolutetime(
|
|
- (uint64_t)ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec, &abstime);
|
|
+ /* darling-lol M58f3: saturate the seconds->nanoseconds product.
|
|
+ * Callers legitimately pass near-infinite deadlines (e.g.
|
|
+ * Chromium's base::WaitableEvent uses TimeDelta::Max(), so
|
|
+ * tv_sec can be ~9.2e12); the product overflows uint64 and
|
|
+ * wraps to an arbitrary (sometimes past!) deadline, making
|
|
+ * the wait return ETIMEDOUT instantly or hang indefinitely.
|
|
+ * Clamp to a huge-but-safe interval (UINT64_MAX/2 ns ~ 292
|
|
+ * years) which still cannot wrap after the deadline add. */
|
|
+ uint64_t nsecs;
|
|
+ uint64_t secs_u = (ts.tv_sec < 0) ? 0 : (uint64_t)ts.tv_sec;
|
|
+ if (secs_u > (UINT64_MAX / 2 - (uint64_t)ts.tv_nsec) / NSEC_PER_SEC) {
|
|
+ nsecs = UINT64_MAX / 2;
|
|
+ } else {
|
|
+ nsecs = secs_u * NSEC_PER_SEC + (uint64_t)ts.tv_nsec;
|
|
+ }
|
|
+ nanoseconds_to_absolutetime(nsecs, &abstime);
|
|
clock_absolutetime_interval_to_deadline(abstime, &abstime);
|
|
}
|
|
|
|
diff --git a/duct-tape/src/fileport.c b/duct-tape/src/fileport.c
|
|
new file mode 100644
|
|
index 0000000..1741d59
|
|
--- /dev/null
|
|
+++ b/duct-tape/src/fileport.c
|
|
@@ -0,0 +1,154 @@
|
|
+/*
|
|
+ * darling-lol M54: fileport support (fileport_makeport/fileport_makefd).
|
|
+ *
|
|
+ * Chromium's Mojo-on-Mac transports Unix FDs over Mach messages by wrapping
|
|
+ * them in fileports (mojo/core/channel_mac.cc, mojo/core/ipcz_driver/
|
|
+ * wrapped_platform_handle.cc). Without this, every FD-carrying Mojo message
|
|
+ * (e.g. NetworkService startup) dies in the old-LKM-API UD2 trap.
|
|
+ *
|
|
+ * A fileport is a Mach port whose kobject holds a host FD owned by
|
|
+ * darlingserver. The kobject rides the port across IPC transfer
|
|
+ * automatically, and ipc_kobject_destroy closes the FD when the port dies,
|
|
+ * so no transfer hooks or task-death cleanup are needed. fileport_makefd
|
|
+ * dups the held FD back to the caller (independent lifecycle, like XNU).
|
|
+ */
|
|
+
|
|
+#include <darlingserver/duct-tape.h>
|
|
+
|
|
+#include <kern/debug.h>
|
|
+#include <stdlib.h>
|
|
+#include <ipc/ipc_object.h>
|
|
+#include <ipc/ipc_port.h>
|
|
+#include <ipc/ipc_space.h>
|
|
+#include <ipc/ipc_entry.h>
|
|
+#include <kern/ipc_kobject.h>
|
|
+#include <mach/mach_port.h>
|
|
+#include <mach/message.h>
|
|
+
|
|
+/* duct-tape convention (see memory.c): declare libc bits manually rather
|
|
+ * than including host <unistd.h>, whose types collide with XNU headers. */
|
|
+int close(int fd);
|
|
+int dup(int fd);
|
|
+
|
|
+#include <darlingserver/duct-tape/task.h>
|
|
+#include <darlingserver/duct-tape/log.h>
|
|
+
|
|
+struct dtape_fileport {
|
|
+ int fd;
|
|
+};
|
|
+
|
|
+int dtape_fileport_makeport(dtape_task_t* task, int fd, uint32_t* name_out)
|
|
+{
|
|
+ struct dtape_fileport* fp;
|
|
+ ipc_port_t port;
|
|
+ mach_port_name_t name;
|
|
+
|
|
+ if (task == NULL || fd < 0 || name_out == NULL) {
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+ if (task->xnu_task.itk_space == IS_NULL) {
|
|
+ return KERN_INVALID_TASK;
|
|
+ }
|
|
+
|
|
+ fp = malloc(sizeof(*fp));
|
|
+ if (fp == NULL) {
|
|
+ dtape_log_error("fileport_makeport: malloc failed");
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+ fp->fd = fd;
|
|
+
|
|
+ /* Allocate a kernel-owned port holding the fd. As in XNU's
|
|
+ * fileport_alloc(), the port comes back with a send right. */
|
|
+ port = ipc_kobject_alloc_port((ipc_kobject_t)fp, IKOT_FILEPORT,
|
|
+ IPC_KOBJECT_ALLOC_MAKE_SEND | IPC_KOBJECT_ALLOC_NSREQUEST);
|
|
+ if (!IP_VALID(port)) {
|
|
+ dtape_log_error("fileport_makeport: ipc_kobject_alloc_port failed fd=%d", fd);
|
|
+ free(fp);
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+
|
|
+ /* Install the send right into the task's space and get a name.
|
|
+ * ipc_port_copyout_send() is the XNU entry point for exactly this
|
|
+ * (see sys_fileport_makeport); it consumes the send right, so we
|
|
+ * must not release it afterwards. Do NOT go through
|
|
+ * mach_port_insert_right(): its MACH_MSG_TYPE_PORT_ANY_RIGHT() guard
|
|
+ * rejects MACH_MSG_TYPE_MAKE_SEND, which made every fileport fail
|
|
+ * with KERN_INVALID_VALUE and broke all FD-carrying Mojo messages. */
|
|
+ name = ipc_port_copyout_send(port, task->xnu_task.itk_space);
|
|
+ if (!MACH_PORT_VALID(name)) {
|
|
+ dtape_log_error("fileport_makeport: ipc_port_copyout_send failed fd=%d", fd);
|
|
+ /* port consumed by copyout; its kobject destroy closes fp->fd */
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+
|
|
+ *name_out = (uint32_t)name;
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
+
|
|
+int dtape_fileport_makefd(dtape_task_t* task, uint32_t uname, int* fd_out)
|
|
+{
|
|
+ ipc_object_t object = IO_NULL;
|
|
+ mach_msg_type_name_t dtype = 0;
|
|
+ mach_port_name_t name = (mach_port_name_t)uname;
|
|
+ ipc_port_t port;
|
|
+ struct dtape_fileport* fp;
|
|
+ int fd;
|
|
+
|
|
+ if (task == NULL || fd_out == NULL) {
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+ if (task->xnu_task.itk_space == IS_NULL) {
|
|
+ return KERN_INVALID_TASK;
|
|
+ }
|
|
+
|
|
+ /* Copyin a send right: validates the name, checks aliveness, and
|
|
+ * enforces that the right is a send right (receive/dead names fail).
|
|
+ * Does not consume the caller's right. */
|
|
+ if (ipc_object_copyin(task->xnu_task.itk_space, name,
|
|
+ MACH_MSG_TYPE_COPY_SEND, &object, 0, NULL,
|
|
+ IPC_OBJECT_COPYIN_FLAGS_ALLOW_IMMOVABLE_SEND) != KERN_SUCCESS) {
|
|
+ return KERN_INVALID_RIGHT;
|
|
+ }
|
|
+ dtype = ipc_object_copyin_type(MACH_MSG_TYPE_COPY_SEND);
|
|
+
|
|
+ port = ip_object_to_port(object);
|
|
+ if (!IP_VALID(port) || ip_kotype(port) != IKOT_FILEPORT) {
|
|
+ ipc_object_destroy(object, dtype);
|
|
+ return KERN_INVALID_RIGHT;
|
|
+ }
|
|
+ fp = (struct dtape_fileport*)ip_get_kobject(port);
|
|
+ if (fp == NULL || fp->fd < 0) {
|
|
+ ipc_object_destroy(object, dtype);
|
|
+ return KERN_INVALID_RIGHT;
|
|
+ }
|
|
+
|
|
+ /* Independent lifecycle: the receiver owns the dup, the kobject
|
|
+ * keeps its own. */
|
|
+ fd = dup(fp->fd);
|
|
+ ipc_object_destroy(object, dtype);
|
|
+ if (fd < 0) {
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+
|
|
+ *fd_out = fd;
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
+
|
|
+void dtape_fileport_destroy(ipc_port_t port)
|
|
+{
|
|
+ struct dtape_fileport* fp;
|
|
+
|
|
+ if (!IP_VALID(port)) {
|
|
+ return;
|
|
+ }
|
|
+ fp = (struct dtape_fileport*)ip_get_kobject(port);
|
|
+ if (fp == NULL) {
|
|
+ return;
|
|
+ }
|
|
+ /* Port is dead; just release our resources like
|
|
+ * mach_destroy_memory_entry does. */
|
|
+ if (fp->fd >= 0) {
|
|
+ close(fp->fd);
|
|
+ }
|
|
+ free(fp);
|
|
+}
|
|
diff --git a/duct-tape/src/host.c b/duct-tape/src/host.c
|
|
index a841dfd..9fe8bb4 100644
|
|
--- a/duct-tape/src/host.c
|
|
+++ b/duct-tape/src/host.c
|
|
@@ -28,6 +28,64 @@ long sysconf(int name);
|
|
#define _SC_NPROCESSORS_CONF 83
|
|
#define _SC_NPROCESSORS_ONLN 84
|
|
|
|
+// Linux file access for the SMT topology read (manual prototypes, as for sysconf above)
|
|
+int open(const char* pathname, int flags, ...);
|
|
+long read(int fd, void* buf, unsigned long count);
|
|
+int close(int fd);
|
|
+
|
|
+// darling-lol: host_info used to report physical_cpu == logical_cpu, which
|
|
+// tells the guest there is no SMT. Read the sibling count of cpu0 from sysfs
|
|
+// and divide; on any failure keep the logical count. The per-CPU package/core
|
|
+// scan in the guest-side sysctl_hw.c is more precise, but host_info has no
|
|
+// other source for the physical count.
|
|
+static uint32_t host_smt_siblings(void) {
|
|
+ static uint32_t cached;
|
|
+ char buf[64];
|
|
+ uint32_t count = 0;
|
|
+ long n;
|
|
+
|
|
+ if (cached != 0) {
|
|
+ return cached;
|
|
+ }
|
|
+
|
|
+ int fd = open("/sys/devices/system/cpu/cpu0/topology/thread_siblings_list", 0 /* O_RDONLY */);
|
|
+ if (fd < 0) {
|
|
+ return 1;
|
|
+ }
|
|
+ n = read(fd, buf, sizeof(buf) - 1);
|
|
+ close(fd);
|
|
+ if (n <= 0) {
|
|
+ return 1;
|
|
+ }
|
|
+ buf[n] = 0;
|
|
+
|
|
+ // a list like "0,6" or "0-3": count one CPU per element, ranges as ranges
|
|
+ const char* s = buf;
|
|
+ while (*s >= '0' && *s <= '9') {
|
|
+ unsigned long first = 0, last;
|
|
+ while (*s >= '0' && *s <= '9') {
|
|
+ first = first * 10 + (unsigned long)(*s++ - '0');
|
|
+ }
|
|
+ last = first;
|
|
+ if (*s == '-') {
|
|
+ ++s;
|
|
+ last = 0;
|
|
+ while (*s >= '0' && *s <= '9') {
|
|
+ last = last * 10 + (unsigned long)(*s++ - '0');
|
|
+ }
|
|
+ }
|
|
+ if (last >= first && last - first < 4096) {
|
|
+ count += (uint32_t)(last - first + 1);
|
|
+ }
|
|
+ if (*s == ',') {
|
|
+ ++s;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ cached = (count != 0) ? count : 1;
|
|
+ return cached;
|
|
+}
|
|
+
|
|
static void cache_sysinfo(void* context) {
|
|
struct sysinfo* cached_sysinfo = context;
|
|
|
|
@@ -73,11 +131,16 @@ kern_return_t host_info(host_t host, host_flavor_t flavor, host_info_t info, mac
|
|
|
|
// if there's room for the modern structure, fill in some additional info
|
|
if (*count >= HOST_BASIC_INFO_COUNT) {
|
|
- // TODO: properly differentiate physical vs. logical cores
|
|
+ uint32_t siblings = host_smt_siblings();
|
|
dtape_stub_safe("modern HOST_BASIC_INFO");
|
|
basic_info->cpu_threadtype = CPU_THREADTYPE_NONE;
|
|
- basic_info->physical_cpu = basic_info->avail_cpus;
|
|
- basic_info->physical_cpu_max = basic_info->max_cpus;
|
|
+ // darling-lol: physical cores, not logical CPUs (the guest
|
|
+ // sizes worker pools off physical_cpu and libmalloc derives
|
|
+ // its SMT sharing from the commpage's physical/logical pair).
|
|
+ basic_info->physical_cpu = (siblings != 0 && basic_info->avail_cpus % siblings == 0)
|
|
+ ? basic_info->avail_cpus / siblings : basic_info->avail_cpus;
|
|
+ basic_info->physical_cpu_max = (siblings != 0 && basic_info->max_cpus % siblings == 0)
|
|
+ ? basic_info->max_cpus / siblings : basic_info->max_cpus;
|
|
basic_info->logical_cpu = basic_info->avail_cpus;
|
|
basic_info->logical_cpu_max = basic_info->max_cpus;
|
|
|
|
diff --git a/duct-tape/src/kqchan.c b/duct-tape/src/kqchan.c
|
|
index 458e659..2e33f20 100644
|
|
--- a/duct-tape/src/kqchan.c
|
|
+++ b/duct-tape/src/kqchan.c
|
|
@@ -138,7 +138,16 @@ static void knote_post(struct knote* kn, long hint) {
|
|
|
|
dtape_log_debug("%s: kn=%p, kqchan=%p, kqchan->callback=%p", __FUNCTION__, kn, kqchan, kqchan->callback);
|
|
|
|
- if (!kqchan->callback) {
|
|
+ /* darling-lol: snapshot callback+context with a SINGLE load.
|
|
+ * The check-then-call used to load kqchan->callback twice; with -O0
|
|
+ * builds there is no CSE, so dtape_kqchan_mach_port_disable_notifications
|
|
+ * (or destroy on another worker) could NULL it between the guard and
|
|
+ * the call, producing call-0x0 -> SIGSEGV in darlingserver whenever a
|
|
+ * port with knotes was torn down (e.g. GPU helpers exiting). A single
|
|
+ * snapshot cannot call NULL. */
|
|
+ dtape_kqchan_mach_port_notification_callback_f cb = kqchan->callback;
|
|
+ void* ctx = kqchan->context;
|
|
+ if (!cb) {
|
|
return;
|
|
}
|
|
|
|
@@ -150,7 +159,7 @@ static void knote_post(struct knote* kn, long hint) {
|
|
|
|
dtape_log_debug("%s: kn=%p, kqchan=%p; invoking callback...", __FUNCTION__, kn, kqchan);
|
|
|
|
- kqchan->callback(kqchan->context);
|
|
+ cb(ctx);
|
|
};
|
|
|
|
void knote(struct klist* list, long hint) {
|
|
@@ -193,7 +202,12 @@ static void kqchan_waitq_waiter_entry(void* context, wait_result_t wait_result)
|
|
break;
|
|
} else {
|
|
if (filt_machportpeek(&kqchan->knote) & FILTER_ACTIVE) {
|
|
- kqchan->callback(kqchan->context);
|
|
+ /* darling-lol: single-load snapshot, see knote_post. */
|
|
+ dtape_kqchan_mach_port_notification_callback_f wcb = kqchan->callback;
|
|
+ void* wctx = kqchan->context;
|
|
+ if (wcb) {
|
|
+ wcb(wctx);
|
|
+ }
|
|
}
|
|
// wait until it's read
|
|
if (!dtape_semaphore_down_simple(kqchan->waiter_read_semaphore)) {
|
|
diff --git a/duct-tape/src/memory.c b/duct-tape/src/memory.c
|
|
index 3114947..e2a8d5e 100644
|
|
--- a/duct-tape/src/memory.c
|
|
+++ b/duct-tape/src/memory.c
|
|
@@ -11,6 +11,7 @@
|
|
|
|
#include <stdlib.h>
|
|
|
|
+
|
|
#include <mach_debug/mach_debug.h>
|
|
|
|
struct zone {
|
|
@@ -341,7 +342,14 @@ kern_return_t copyoutmap(vm_map_t map, void* fromdata, vm_map_address_t toaddr,
|
|
memmove((void*)toaddr, fromdata, length);
|
|
return KERN_SUCCESS;
|
|
} else {
|
|
- return dtape_hooks->task_write_memory(map->dtape_task->context, toaddr, fromdata, length) ? KERN_SUCCESS : KERN_FAILURE;
|
|
+ /* darling-lol M58dq: name the failing user-copy target behind the M58G
|
|
+ * ipc_kmsg_put "copyout failed" drops. */
|
|
+ if (!dtape_hooks->task_write_memory(map->dtape_task->context, toaddr, fromdata, length)) {
|
|
+ dtape_log_error("M58DQ copyoutmap FAIL toaddr=0x%llx len=%lu map=%p task=%p",
|
|
+ (unsigned long long) toaddr, (unsigned long) length, (void*) map, (void*) map->dtape_task);
|
|
+ return KERN_FAILURE;
|
|
+ }
|
|
+ return KERN_SUCCESS;
|
|
}
|
|
};
|
|
|
|
@@ -526,20 +534,212 @@ kern_return_t mach_vm_allocate_kernel(vm_map_t map, mach_vm_offset_t* addr, mach
|
|
}
|
|
};
|
|
|
|
+/*
|
|
+ * darling-lol: functional `_mach_make_memory_entry`.
|
|
+ *
|
|
+ * This used to be `dtape_stub_unsafe()` (abort), so any guest call killed
|
|
+ * darlingserver outright. Entries are backed by a SHARED memfd held in a
|
|
+ * refcounted descriptor on the entry: every task mapping the entry observes
|
|
+ * the same pages (required e.g. for Chromium field-trial shared memory,
|
|
+ * where the browser writes after creating and helpers map later).
|
|
+ */
|
|
+kern_return_t mach_memory_entry_allocate(vm_named_entry_t* user_entry_p, ipc_port_t* user_handle_p) {
|
|
+ vm_named_entry_t user_entry;
|
|
+ ipc_port_t user_handle;
|
|
+
|
|
+ user_entry = (vm_named_entry_t) kalloc(sizeof(*user_entry));
|
|
+ if (user_entry == NULL) {
|
|
+ return KERN_FAILURE;
|
|
+ }
|
|
+ bzero(user_entry, sizeof(*user_entry));
|
|
+
|
|
+ named_entry_lock_init(user_entry);
|
|
+ user_entry->backing.copy = NULL;
|
|
+ user_entry->is_object = FALSE;
|
|
+ user_entry->is_sub_map = FALSE;
|
|
+ user_entry->is_copy = FALSE;
|
|
+ user_entry->internal = FALSE;
|
|
+ user_entry->size = 0;
|
|
+ user_entry->offset = 0;
|
|
+ user_entry->data_offset = 0;
|
|
+ user_entry->protection = VM_PROT_NONE;
|
|
+ user_entry->ref_count = 1;
|
|
+
|
|
+ user_handle = ipc_kobject_alloc_port((ipc_kobject_t)user_entry,
|
|
+ IKOT_NAMED_ENTRY,
|
|
+ IPC_KOBJECT_ALLOC_MAKE_SEND | IPC_KOBJECT_ALLOC_NSREQUEST);
|
|
+ if (user_handle == IP_NULL) {
|
|
+ kfree(user_entry, sizeof(*user_entry));
|
|
+ return KERN_FAILURE;
|
|
+ }
|
|
+
|
|
+ *user_entry_p = user_entry;
|
|
+ *user_handle_p = user_handle;
|
|
+ return KERN_SUCCESS;
|
|
+};
|
|
+
|
|
kern_return_t _mach_make_memory_entry(vm_map_t target_map, memory_object_size_t* size, memory_object_offset_t offset, vm_prot_t permission, ipc_port_t* object_handle, ipc_port_t parent_entry) {
|
|
- dtape_stub_unsafe();
|
|
+ vm_map_size_t map_size = (vm_map_size_t)*size;
|
|
+ vm_map_offset_t map_start, map_end;
|
|
+ vm_map_copy_t copy = NULL;
|
|
+ vm_named_entry_t user_entry = NULL;
|
|
+ ipc_port_t user_handle = IP_NULL;
|
|
+ kern_return_t kr;
|
|
+
|
|
+ if (object_handle == NULL) {
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+ *object_handle = IP_NULL;
|
|
+
|
|
+ if (target_map == VM_MAP_NULL) {
|
|
+ target_map = current_map();
|
|
+ }
|
|
+
|
|
+ /* round the request out to whole pages, like XNU does */
|
|
+ map_start = vm_map_trunc_page(offset, VM_MAP_PAGE_MASK(target_map));
|
|
+ map_end = vm_map_round_page(offset + map_size, VM_MAP_PAGE_MASK(target_map));
|
|
+ map_size = map_end - map_start;
|
|
+
|
|
+ if (map_size == 0) {
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Backing strategy (darling-lol): a SHARED memfd, so every task that
|
|
+ * maps the entry sees the same pages. The previous snapshot-copy
|
|
+ * approach handed each mapper a private copy of the bytes as of
|
|
+ * creation time; a producer (e.g. Chromium's browser writing field
|
|
+ * trial state) then wrote into pages no consumer ever saw, and
|
|
+ * consumers parsed stale zeros -> garbage strings -> crashes
|
|
+ * (char_traits overlap assert) in every helper.
|
|
+ *
|
|
+ * Two cases, matching XNU:
|
|
+ *
|
|
+ * - MAP_MEM_NAMED_CREATE: fresh anonymous entry of *size bytes (what
|
|
+ * Chromium's PlatformSharedMemoryRegion allocation path wants).
|
|
+ * memfds start zero-filled. A null parent alone does NOT imply this:
|
|
+ * ordinary extraction calls also pass a null parent.
|
|
+ *
|
|
+ * - parent_entry != NULL: seed the memfd with a snapshot of the
|
|
+ * described region (copied once, at creation).
|
|
+ */
|
|
+ int memfd = -1;
|
|
+ dtape_map_shared_descriptor_t* desc = NULL;
|
|
+ if (permission & MAP_MEM_NAMED_CREATE) {
|
|
+ memfd = memfd_create("darling-mementry", MFD_CLOEXEC);
|
|
+ if (memfd < 0 || ftruncate(memfd, map_size) < 0) {
|
|
+ if (memfd >= 0) close(memfd);
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+ } else {
|
|
+ void* staged = NULL;
|
|
+ int source_prot = PROT_READ | PROT_WRITE;
|
|
+ uintptr_t remapped;
|
|
+ /* snapshot the region, then publish it into the shared object */
|
|
+ kr = vm_map_copyin_common(target_map, map_start, map_size,
|
|
+ FALSE /* src_destroy */, FALSE /* src_volatile */, ©,
|
|
+ FALSE /* use_maxprot */);
|
|
+ if (kr != KERN_SUCCESS) {
|
|
+ dtape_log_error("mach_make_memory_entry: copyin failed kr=%d map=%p start=%#llx size=%#llx perms=%#x",
|
|
+ kr, (void*)target_map, (unsigned long long)map_start, (unsigned long long)map_size, permission);
|
|
+ return kr;
|
|
+ }
|
|
+ memfd = memfd_create("darling-mementry", MFD_CLOEXEC);
|
|
+ if (memfd < 0 || ftruncate(memfd, map_size) < 0) {
|
|
+ if (memfd >= 0) close(memfd);
|
|
+ vm_map_copy_discard(copy);
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+ staged = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0);
|
|
+ if (staged == MAP_FAILED) {
|
|
+ close(memfd);
|
|
+ vm_map_copy_discard(copy);
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+ memcpy(staged, (char*)copy->cpy_kdata + copy->offset, map_size);
|
|
+ munmap(staged, map_size);
|
|
+ vm_map_copy_discard(copy);
|
|
+ copy = NULL;
|
|
+
|
|
+ /* Extraction must retain shared-object semantics, not snapshot
|
|
+ * semantics. Replace the source task's pages with a MAP_SHARED view
|
|
+ * of the populated memfd. Future writes by the creator are then
|
|
+ * visible to every process that maps the named entry. */
|
|
+ /* The requested permission constrains mappings made from the named
|
|
+ * entry; it does not downgrade the creator's existing mapping. The
|
|
+ * common Chromium extraction path exports a read-only handle from a
|
|
+ * writable region and continues writing through the original view. */
|
|
+ if (permission & VM_PROT_EXECUTE)
|
|
+ source_prot |= PROT_EXEC;
|
|
+ remapped = dtape_hooks->task_map_file(
|
|
+ target_map->dtape_task->context, memfd,
|
|
+ dtape_byte_count_to_page_count_round_up(map_size), source_prot,
|
|
+ map_start, 0,
|
|
+ dtape_memory_flag_fixed | dtape_memory_flag_overwrite);
|
|
+ if (remapped != map_start) {
|
|
+ close(memfd);
|
|
+ return KERN_FAILURE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ desc = dtape_map_shared_descriptor_create(memfd, map_size);
|
|
+ if (desc == NULL) {
|
|
+ close(memfd);
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+ memfd = -1; /* the descriptor now owns the memfd */
|
|
+
|
|
+ kr = mach_memory_entry_allocate(&user_entry, &user_handle);
|
|
+ if (kr != KERN_SUCCESS) {
|
|
+ dtape_map_shared_descriptor_release(desc);
|
|
+ dtape_log_error("mach_make_memory_entry: allocate failed kr=%d", kr);
|
|
+ return kr;
|
|
+ }
|
|
+
|
|
+ named_entry_lock(user_entry);
|
|
+ user_entry->backing.copy = NULL;
|
|
+ user_entry->is_copy = FALSE;
|
|
+ user_entry->dtape_descriptor = desc;
|
|
+ user_entry->size = map_size;
|
|
+ user_entry->offset = offset - map_start; /* logical offset, memfd base 0 */
|
|
+ user_entry->data_offset = 0;
|
|
+ user_entry->protection = permission;
|
|
+ named_entry_unlock(user_entry);
|
|
+
|
|
+ *size = (memory_object_size_t)map_size;
|
|
+ *object_handle = user_handle;
|
|
+ return KERN_SUCCESS;
|
|
+};
|
|
+
|
|
+kern_return_t mach_make_memory_entry_64(vm_map_t target_map, memory_object_size_t* size, memory_object_offset_t offset, vm_prot_t permission, ipc_port_t* object_handle, ipc_port_t parent_handle) {
|
|
+ return _mach_make_memory_entry(target_map, size, offset, permission, object_handle, parent_handle);
|
|
+};
|
|
+
|
|
+kern_return_t mach_make_memory_entry(vm_map_t target_map, vm_size_t* size, vm_offset_t offset, vm_prot_t permission, ipc_port_t* object_handle, ipc_port_t parent_entry) {
|
|
+ memory_object_size_t mo_size = (memory_object_size_t)*size;
|
|
+ kern_return_t kr = _mach_make_memory_entry(target_map, &mo_size, (memory_object_offset_t)offset, permission, object_handle, parent_entry);
|
|
+ *size = (vm_size_t)mo_size;
|
|
+ return kr;
|
|
};
|
|
|
|
kern_return_t mach_memory_entry_access_tracking(ipc_port_t entry_port, int* access_tracking, uint32_t* access_tracking_reads, uint32_t* access_tracking_writes) {
|
|
- dtape_stub_unsafe();
|
|
+ /* No access tracking in duct-tape; report "no tracking" rather than
|
|
+ * aborting the server. */
|
|
+ if (access_tracking) *access_tracking = 0;
|
|
+ if (access_tracking_reads) *access_tracking_reads = 0;
|
|
+ if (access_tracking_writes) *access_tracking_writes = 0;
|
|
+ return KERN_SUCCESS;
|
|
};
|
|
|
|
kern_return_t mach_memory_entry_ownership(ipc_port_t entry_port, task_t owner, int ledger_tag, int ledger_flags) {
|
|
- dtape_stub_unsafe();
|
|
+ /* Ledger ownership is not modeled; accept and ignore. */
|
|
+ return KERN_SUCCESS;
|
|
};
|
|
|
|
kern_return_t mach_memory_entry_purgable_control(ipc_port_t entry_port, vm_purgable_t control, int* state) {
|
|
- dtape_stub_unsafe();
|
|
+ /* Not purgable here. */
|
|
+ if (state) *state = VM_PURGABLE_NONVOLATILE;
|
|
+ return KERN_SUCCESS;
|
|
};
|
|
|
|
kern_return_t mach_memory_info(host_priv_t host, mach_zone_name_array_t* namesp, mach_msg_type_number_t* namesCntp, mach_zone_info_array_t* infop, mach_msg_type_number_t* infoCntp, mach_memory_info_array_t* memoryInfop, mach_msg_type_number_t* memoryInfoCntp) {
|
|
@@ -591,8 +791,115 @@ kern_return_t mach_vm_machine_attribute(vm_map_t map, mach_vm_address_t addr, ma
|
|
};
|
|
|
|
kern_return_t mach_vm_map_external(vm_map_t target_map, mach_vm_offset_t* address, mach_vm_size_t initial_size, mach_vm_offset_t mask, int flags, ipc_port_t port, vm_object_offset_t offset, boolean_t copy, vm_prot_t cur_protection, vm_prot_t max_protection, vm_inherit_t inheritance) {
|
|
- dtape_stub_safe();
|
|
- return KERN_SUCCESS;
|
|
+ /*
|
|
+ * darling-lol: real implementation. This used to be a "safe" stub
|
|
+ * returning KERN_SUCCESS without mapping anything or setting
|
|
+ * *address -- so every VM_FLAGS_ANYWHERE caller (notably libplatform's
|
|
+ * os_alloc heap via mach_vm_map, which backs ALL os_alloc_once
|
|
+ * singletons incl. libnotify globals) got success plus a stale address
|
|
+ * and crashed later on the unmapped pointer. Two cases:
|
|
+ *
|
|
+ * - MEMORY_OBJECT_NULL port: anonymous mapping. Allocate fresh pages
|
|
+ * in the target task (zero-filled by the host mmap underneath).
|
|
+ * - named-entry port (from _mach_make_memory_entry): map a duplicate
|
|
+ * of the entry's kernel-buffer copy at the requested offset. The
|
|
+ * entry keeps its own copy so it stays mappable (and destroyable)
|
|
+ * afterwards. NOTE: this gives copy semantics, not true cross-task
|
|
+ * sharing; sufficient to unblock, revisit if sharing artifacts show.
|
|
+ */
|
|
+ int prot = 0;
|
|
+ if (cur_protection & VM_PROT_READ)
|
|
+ prot |= PROT_READ;
|
|
+ if (cur_protection & VM_PROT_WRITE)
|
|
+ prot |= PROT_WRITE;
|
|
+ if (cur_protection & VM_PROT_EXECUTE)
|
|
+ prot |= PROT_EXEC;
|
|
+
|
|
+ if (target_map == VM_MAP_NULL) {
|
|
+ target_map = current_map();
|
|
+ }
|
|
+ if (target_map == VM_MAP_NULL || address == NULL) {
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+
|
|
+ if (port == MEMORY_OBJECT_NULL) {
|
|
+ uintptr_t hint = (flags & VM_FLAGS_ANYWHERE) ? 0 : (uintptr_t)*address;
|
|
+ uintptr_t addr = dtape_hooks->task_allocate_pages(
|
|
+ target_map->dtape_task->context,
|
|
+ dtape_byte_count_to_page_count_round_up(initial_size),
|
|
+ prot, hint, 0);
|
|
+ if (addr == 0) {
|
|
+ return KERN_RESOURCE_SHORTAGE;
|
|
+ }
|
|
+ *address = (mach_vm_offset_t)addr;
|
|
+ return KERN_SUCCESS;
|
|
+ }
|
|
+
|
|
+ if (IP_VALID(port) && ip_kotype(port) == IKOT_NAMED_ENTRY) {
|
|
+ vm_named_entry_t entry = (vm_named_entry_t)ip_get_kobject(port);
|
|
+ dtape_map_shared_descriptor_t* desc = NULL;
|
|
+ vm_map_offset_t entry_off = 0;
|
|
+ vm_map_size_t entry_size = 0;
|
|
+ vm_prot_t entry_prot = VM_PROT_NONE;
|
|
+ uintptr_t map_addr;
|
|
+ uint64_t page_off;
|
|
+
|
|
+ named_entry_lock(entry);
|
|
+ desc = (dtape_map_shared_descriptor_t*)entry->dtape_descriptor;
|
|
+ entry_off = entry->offset;
|
|
+ entry_size = entry->size;
|
|
+ entry_prot = entry->protection;
|
|
+ if (desc != NULL) {
|
|
+ dtape_map_shared_descriptor_retain(desc);
|
|
+ }
|
|
+ named_entry_unlock(entry);
|
|
+
|
|
+ if (desc == NULL) {
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+ /* darling-lol M53: enforce the entry's max protection. XNU
|
|
+ * refuses a writable mapping of a read-only entry with
|
|
+ * KERN_INVALID_RIGHT, and Chromium's shared-memory Take()
|
|
+ * depends on exactly that signal to detect read-only regions
|
|
+ * (without it, every readonly Mojo SHM probe "succeeds"
|
|
+ * writable and the GPU dies in a CHECK). Mask to VM_PROT bits:
|
|
+ * creation permission carries MAP_MEM_NAMED_CREATE etc. */
|
|
+ if ((cur_protection & (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE) &
|
|
+ ~(entry_prot & (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE))) != 0) {
|
|
+ dtape_map_shared_descriptor_release(desc);
|
|
+ return KERN_INVALID_RIGHT;
|
|
+ }
|
|
+ if ((vm_map_offset_t)offset >= entry_size) {
|
|
+ dtape_map_shared_descriptor_release(desc);
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
+ }
|
|
+ /* Map the shared object itself (MAP_SHARED in the target), so
|
|
+ * all tasks mapping the entry observe the same pages. */
|
|
+ page_off = ((uint64_t)entry_off + (uint64_t)offset) / 4096;
|
|
+ if ((flags & VM_FLAGS_ANYWHERE) != 0) {
|
|
+ map_addr = dtape_hooks->task_map_file(
|
|
+ target_map->dtape_task->context,
|
|
+ desc->memfd,
|
|
+ dtape_byte_count_to_page_count_round_up(initial_size),
|
|
+ prot, 0, page_off, dtape_memory_flag_none);
|
|
+ } else {
|
|
+ map_addr = dtape_hooks->task_map_file(
|
|
+ target_map->dtape_task->context,
|
|
+ desc->memfd,
|
|
+ dtape_byte_count_to_page_count_round_up(initial_size),
|
|
+ prot, (uintptr_t)*address, page_off,
|
|
+ dtape_memory_flag_fixed | dtape_memory_flag_overwrite);
|
|
+ }
|
|
+ dtape_map_shared_descriptor_release(desc);
|
|
+ if (map_addr == 0) {
|
|
+ return KERN_FAILURE;
|
|
+ }
|
|
+ *address = (mach_vm_offset_t)map_addr +
|
|
+ (((uint64_t)entry_off + (uint64_t)offset) % 4096);
|
|
+ return KERN_SUCCESS;
|
|
+ }
|
|
+
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
};
|
|
|
|
kern_return_t mach_vm_msync(vm_map_t map, mach_vm_offset_t address, mach_vm_size_t size, vm_sync_t sync_flags) {
|
|
@@ -620,15 +927,22 @@ kern_return_t mach_vm_msync(vm_map_t map, mach_vm_offset_t address, mach_vm_size
|
|
};
|
|
|
|
kern_return_t mach_vm_page_info(vm_map_t map, mach_vm_address_t address, vm_page_info_flavor_t flavor, vm_page_info_t info, mach_msg_type_number_t* count) {
|
|
- dtape_stub_unsafe();
|
|
+ // Page accounting is not implemented; let the caller handle unsupported queries.
|
|
+ if (count) *count = 0;
|
|
+ return KERN_NOT_SUPPORTED;
|
|
};
|
|
|
|
kern_return_t mach_vm_page_query(vm_map_t map, mach_vm_offset_t offset, int* disposition, int* ref_count) {
|
|
- dtape_stub_unsafe();
|
|
+ // Page accounting is not implemented; let the caller handle unsupported queries.
|
|
+ if (disposition) *disposition = 0;
|
|
+ if (ref_count) *ref_count = 0;
|
|
+ return KERN_NOT_SUPPORTED;
|
|
};
|
|
|
|
kern_return_t mach_vm_page_range_query(vm_map_t map, mach_vm_offset_t address, mach_vm_size_t size, mach_vm_address_t dispositions_addr, mach_vm_size_t* dispositions_count) {
|
|
- dtape_stub_unsafe();
|
|
+ // Page accounting is not implemented; let the caller handle unsupported queries.
|
|
+ if (dispositions_count) *dispositions_count = 0;
|
|
+ return KERN_NOT_SUPPORTED;
|
|
};
|
|
|
|
kern_return_t mach_vm_protect(vm_map_t map, mach_vm_offset_t start, mach_vm_size_t size, boolean_t set_maximum, vm_prot_t new_protection) {
|
|
@@ -756,7 +1070,8 @@ region_info_out:
|
|
return kr;
|
|
};
|
|
default:
|
|
- dtape_stub_unsafe("Unimplemented flavor");
|
|
+ if (object_name) *object_name = IP_NULL;
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
}
|
|
};
|
|
|
|
@@ -856,7 +1171,7 @@ static kern_return_t mach_vm_remap_external_shared(vm_map_t target_map, mach_vm_
|
|
|
|
src_existing_entry_count = dtape_map_find_shared_entries_locked(src_map, start_memaddr, map_size, src_existing_entries, sizeof(src_existing_entries) / sizeof(*src_existing_entries));
|
|
|
|
- if (src_existing_entry_count == 1 && src_existing_entries[0]->address == start_memaddr && src_existing_entries[0]->size >= map_size) {
|
|
+ if (src_existing_entry_count == 1 && src_existing_entries[0]->address == start_memaddr && src_existing_entries[0]->size == map_size) {
|
|
// special case for what LLDB does with dyld info
|
|
dtape_map_shared_descriptor_retain(src_existing_entries[0]->descriptor);
|
|
descriptor = src_existing_entries[0]->descriptor;
|
|
@@ -1441,6 +1756,10 @@ mach_destroy_memory_entry(
|
|
named_entry->ref_count -= 1;
|
|
|
|
if (named_entry->ref_count == 0) {
|
|
+ /* darling-lol: release duct-tape shared backing first. */
|
|
+ dtape_map_shared_descriptor_t* desc =
|
|
+ (dtape_map_shared_descriptor_t*)named_entry->dtape_descriptor;
|
|
+ named_entry->dtape_descriptor = NULL;
|
|
if (named_entry->is_sub_map) {
|
|
vm_map_deallocate(named_entry->backing.map);
|
|
} else if (named_entry->is_copy) {
|
|
@@ -1456,6 +1775,9 @@ mach_destroy_memory_entry(
|
|
|
|
named_entry_unlock(named_entry);
|
|
named_entry_lock_destroy(named_entry);
|
|
+ if (desc != NULL) {
|
|
+ dtape_map_shared_descriptor_release(desc);
|
|
+ }
|
|
|
|
#if VM_NAMED_ENTRY_LIST
|
|
lck_mtx_lock_spin(&vm_named_entry_list_lock_data);
|
|
diff --git a/duct-tape/src/misc.c b/duct-tape/src/misc.c
|
|
index afe60fc..02968b2 100644
|
|
--- a/duct-tape/src/misc.c
|
|
+++ b/duct-tape/src/misc.c
|
|
@@ -48,6 +48,9 @@ ssize_t getrandom(void* buf, size_t buflen, unsigned int flags);
|
|
|
|
|
|
void dtape_logv(dtape_log_level_t level, const char* format, va_list args) {
|
|
+ if (dtape_hooks->log_enabled && !dtape_hooks->log_enabled(level)) {
|
|
+ return;
|
|
+ }
|
|
char message[4096];
|
|
vsnprintf(message, sizeof(message), format, args);
|
|
dtape_hooks->log(level, message);
|
|
@@ -87,10 +90,28 @@ int scnprintf(char* buffer, size_t buffer_size, const char* format, ...) {
|
|
void (ipc_kmsg_trace_send)(ipc_kmsg_t kmsg, mach_msg_option_t option) {
|
|
pid_t dest_pid = -1;
|
|
ipc_port_t dest = kmsg->ikm_header->msgh_remote_port;
|
|
+ mach_msg_header_t* h = kmsg->ikm_header;
|
|
|
|
dest_pid = ipc_port_get_receiver_task(dest, NULL);
|
|
|
|
- dtape_log_debug("sending kmsg %p to pid %d", kmsg, dest_pid);
|
|
+ /* darling-lol M58 IPC probe: log enough of the Mach message to identify
|
|
+ * the Chromium/Mojo request or reply. Mojo's ChannelMac puts the Mojo
|
|
+ * message (with its 4-byte interface ordinal) in the Mach payload; the
|
|
+ * offset depends on the message layout, so dump a few words. */
|
|
+ {
|
|
+ unsigned char* p = (unsigned char*)h;
|
|
+ unsigned int size = h->msgh_size;
|
|
+ unsigned int w88 = 0, w92 = 0, w96 = 0, w100 = 0, w104 = 0, w108 = 0;
|
|
+ if (size >= 92) w88 = *(unsigned int*)(p + 88);
|
|
+ if (size >= 96) w92 = *(unsigned int*)(p + 92);
|
|
+ if (size >= 100) w96 = *(unsigned int*)(p + 96);
|
|
+ if (size >= 104) w100 = *(unsigned int*)(p + 100);
|
|
+ if (size >= 108) w104 = *(unsigned int*)(p + 104);
|
|
+ if (size >= 112) w108 = *(unsigned int*)(p + 108);
|
|
+ dtape_log_debug("sending kmsg to pid %d id=%#x size=%u bits=%#x b88=%08x b92=%08x b96=%08x b100=%08x b104=%08x b108=%08x",
|
|
+ dest_pid, h->msgh_id, size, h->msgh_bits,
|
|
+ w88, w92, w96, w100, w104, w108);
|
|
+ }
|
|
};
|
|
|
|
void Assert(const char* file, int line, const char* expression) {
|
|
diff --git a/duct-tape/src/stubs.c b/duct-tape/src/stubs.c
|
|
index dc1cfa5..39a8c8d 100644
|
|
--- a/duct-tape/src/stubs.c
|
|
+++ b/duct-tape/src/stubs.c
|
|
@@ -37,9 +37,18 @@ extern char* getenv(const char* name);
|
|
|
|
static bool log_safe_stubs = false;
|
|
|
|
+static bool nonfatal_unsafe_stubs = false;
|
|
+
|
|
__attribute__((constructor))
|
|
static void init_stubs() {
|
|
log_safe_stubs = getenv("DTAPE_LOG_SAFE_STUBS") != NULL;
|
|
+ /* darling-lol: optional escape hatch. When set, an "unsafe" (unimplemented,
|
|
+ * needs-real-impl) duct-tape stub logs but does NOT abort darlingserver.
|
|
+ * Aborting the server is catastrophic: it is a single process serving every
|
|
+ * guest process, so one unimplemented trap (e.g. a Chromium mach call) tears
|
|
+ * down the whole container's RPC, and every guest then fails with -111.
|
|
+ * Default remains fatal so genuine bugs stay loud during development. */
|
|
+ nonfatal_unsafe_stubs = getenv("DTAPE_NONFATAL_UNSAFE") != NULL;
|
|
};
|
|
|
|
void dtape_stub_log(const char* function_name, int safety, const char* subsection) {
|
|
@@ -57,7 +66,7 @@ void dtape_stub_log(const char* function_name, int safety, const char* subsectio
|
|
kind_info = "";
|
|
} else if (safety < 0) {
|
|
log_level = dtape_log_level_error;
|
|
- do_abort = true;
|
|
+ do_abort = !nonfatal_unsafe_stubs;
|
|
kind_info = " (unsafe)";
|
|
} else {
|
|
if (!log_safe_stubs) {
|
|
@@ -210,11 +219,15 @@ kern_return_t kmod_get_info(host_t host, kmod_info_array_t* kmod_list, mach_msg_
|
|
};
|
|
|
|
kern_return_t kext_request(host_priv_t hostPriv, uint32_t clientLogSpec, vm_offset_t requestIn, mach_msg_type_number_t requestLengthIn, vm_offset_t* responseOut, mach_msg_type_number_t* responseLengthOut, vm_offset_t* logDataOut, mach_msg_type_number_t* logDataLengthOut, kern_return_t* op_result) {
|
|
- dtape_stub_unsafe();
|
|
+ // there are no kernel extensions to query or load; fail the request
|
|
+ dtape_stub_safe();
|
|
+ return KERN_NOT_SUPPORTED;
|
|
};
|
|
|
|
uint32_t PE_i_can_has_debugger(uint32_t* something) {
|
|
- dtape_stub_unsafe();
|
|
+ // No kernel debugger capability is provided by this runtime.
|
|
+ if (something) *something = 0;
|
|
+ return 0;
|
|
};
|
|
|
|
bool work_interval_port_type_render_server(mach_port_name_t port_name) {
|
|
diff --git a/duct-tape/src/task.c b/duct-tape/src/task.c
|
|
index a9eb4bc..7ef2800 100644
|
|
--- a/duct-tape/src/task.c
|
|
+++ b/duct-tape/src/task.c
|
|
@@ -521,7 +521,9 @@ kern_return_t task_info(task_t xtask, task_flavor_t flavor, task_info_t task_inf
|
|
};
|
|
|
|
default:
|
|
- dtape_stub_unsafe("unimplemented flavor");
|
|
+ /* Unsupported task-info flavors are guest API errors, not fatal
|
|
+ * host errors. Chromium probes optional power/accounting data. */
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
}
|
|
};
|
|
|
|
@@ -585,7 +587,7 @@ kern_return_t task_restartable_ranges_synchronize(task_t task) {
|
|
};
|
|
|
|
kern_return_t task_set_exc_guard_behavior(task_t task, task_exc_guard_behavior_t behavior) {
|
|
- dtape_stub_unsafe();
|
|
+ return KERN_NOT_SUPPORTED;
|
|
};
|
|
|
|
kern_return_t task_set_info(task_t task, task_flavor_t flavor, task_info_t task_info_in, mach_msg_type_number_t task_info_count) {
|
|
@@ -593,7 +595,8 @@ kern_return_t task_set_info(task_t task, task_flavor_t flavor, task_info_t task_
|
|
};
|
|
|
|
kern_return_t task_set_phys_footprint_limit(task_t task, int new_limit_mb, int* old_limit_mb) {
|
|
- dtape_stub_unsafe();
|
|
+ if (old_limit_mb) *old_limit_mb = 0;
|
|
+ return KERN_NOT_SUPPORTED;
|
|
};
|
|
|
|
kern_return_t task_set_state(task_t task, int flavor, thread_state_t state, mach_msg_type_number_t state_count) {
|
|
diff --git a/duct-tape/src/thread.c b/duct-tape/src/thread.c
|
|
index f4ce909..a6cdb2c 100644
|
|
--- a/duct-tape/src/thread.c
|
|
+++ b/duct-tape/src/thread.c
|
|
@@ -53,6 +53,7 @@ dtape_thread_t* dtape_thread_create(dtape_task_t* task, uint64_t nsid, void* con
|
|
thread->processing_signal = false;
|
|
thread->name = NULL;
|
|
thread->waiting_suspended = false;
|
|
+ libsimple_lock_init(&thread->block_suspend_gate);
|
|
LIST_INIT(&thread->user_states);
|
|
dtape_mutex_init(&thread->suspension_mutex);
|
|
dtape_condvar_init(&thread->suspension_condvar);
|
|
@@ -521,11 +522,16 @@ wait_result_t thread_block_parameter(thread_continue_t continuation, void* param
|
|
thread->xnu_thread.parameter = parameter;
|
|
|
|
bool waiting = thread->xnu_thread.state & TH_WAIT;
|
|
+ /* A wakeup between this check and Thread::suspend used to be ignored
|
|
+ * because the microthread was not suspended yet. Keep the gate locked
|
|
+ * until the server has yielded and published the suspended state. */
|
|
+ if (waiting)
|
|
+ libsimple_lock_lock(&thread->block_suspend_gate);
|
|
|
|
thread_unlock(&thread->xnu_thread);
|
|
|
|
if (waiting) {
|
|
- dtape_hooks->thread_suspend(thread->context, continuation ? thread_continuation_callback : NULL, thread, NULL);
|
|
+ dtape_hooks->thread_suspend(thread->context, continuation ? thread_continuation_callback : NULL, thread, &thread->block_suspend_gate);
|
|
}
|
|
|
|
thread_lock(&thread->xnu_thread);
|
|
@@ -548,8 +554,20 @@ wait_result_t thread_block(thread_continue_t continuation) {
|
|
// thread locked
|
|
boolean_t thread_unblock(thread_t xthread, wait_result_t wresult) {
|
|
dtape_thread_t* thread = dtape_thread_for_xnu_thread(xthread);
|
|
+ libsimple_lock_lock(&thread->block_suspend_gate);
|
|
thread->xnu_thread.wait_result = wresult;
|
|
+ /* Match XNU's thread_unblock: a timer from a previous timed wait must
|
|
+ * not expire during the thread's next, untimed Mach receive. */
|
|
+ if (thread->xnu_thread.wait_timer_is_set) {
|
|
+ if (timer_call_cancel(&thread->xnu_thread.wait_timer)) {
|
|
+ thread->xnu_thread.wait_timer_active--;
|
|
+ }
|
|
+ thread->xnu_thread.wait_timer_is_set = FALSE;
|
|
+ }
|
|
+ thread->xnu_thread.state &= ~(TH_WAIT | TH_UNINT);
|
|
+ thread->xnu_thread.state |= TH_RUN;
|
|
dtape_hooks->thread_resume(thread->context);
|
|
+ libsimple_lock_unlock(&thread->block_suspend_gate);
|
|
return TRUE;
|
|
};
|
|
|
|
@@ -782,7 +800,7 @@ thread_set_state(
|
|
s64.dr6 = s->dr6;
|
|
s64.dr7 = s->dr7;
|
|
|
|
- return thread_set_state(thread, x86_DEBUG_STATE64, (thread_state_t) &s,
|
|
+ return thread_set_state(thread, x86_DEBUG_STATE64, (thread_state_t) &s64,
|
|
x86_DEBUG_STATE64_COUNT);
|
|
}
|
|
case x86_DEBUG_STATE64:
|
|
@@ -1293,7 +1311,7 @@ kern_return_t thread_info(thread_t xthread, thread_flavor_t flavor, thread_info_
|
|
};
|
|
|
|
default:
|
|
- dtape_stub_unsafe("Unimplemented flavor");
|
|
+ return KERN_INVALID_ARGUMENT;
|
|
}
|
|
};
|
|
|
|
@@ -1324,11 +1342,11 @@ kern_return_t thread_wire(host_priv_t host_priv, thread_t thread, boolean_t wire
|
|
};
|
|
|
|
kern_return_t thread_getstatus_to_user(thread_t thread, int flavor, thread_state_t tstate, mach_msg_type_number_t* count) {
|
|
- dtape_stub_unsafe();
|
|
+ return thread_get_state_internal(thread, flavor, tstate, count, TRUE);
|
|
};
|
|
|
|
kern_return_t thread_setstatus_from_user(thread_t thread, int flavor, thread_state_t tstate, mach_msg_type_number_t count) {
|
|
- dtape_stub_unsafe();
|
|
+ return thread_set_state_from_user(thread, flavor, tstate, count);
|
|
};
|
|
|
|
boolean_t thread_should_abort(thread_t thread) {
|
|
diff --git a/duct-tape/src/timer.c b/duct-tape/src/timer.c
|
|
index bbaab01..4f01234 100644
|
|
--- a/duct-tape/src/timer.c
|
|
+++ b/duct-tape/src/timer.c
|
|
@@ -53,8 +53,9 @@ uint64_t _rtc_nanotime_read(pal_rtc_nanotime_t* rntp) {
|
|
};
|
|
|
|
void dtape_timer_fired(void) {
|
|
- uint64_t next_deadline = timer_queue_expire(&timer_queue, mach_absolute_time());
|
|
- dtape_hooks->timer_arm(next_deadline, true);
|
|
+ /* Expiration rearms while holding the queue lock. Rearming here from
|
|
+ * its returned snapshot could overwrite a concurrent insertion. */
|
|
+ timer_queue_expire(&timer_queue, mach_absolute_time());
|
|
};
|
|
|
|
void timer_call_nosync_cpu(int cpu, void (*fn)(void* arg), void* arg) {
|
|
@@ -62,7 +63,8 @@ void timer_call_nosync_cpu(int cpu, void (*fn)(void* arg), void* arg) {
|
|
};
|
|
|
|
mpqueue_head_t* timer_queue_assign(uint64_t deadline) {
|
|
- dtape_hooks->timer_arm(deadline, false);
|
|
+ /* Arm only after insertion, under the queue lock. A short deadline
|
|
+ * can otherwise expire before its call has reached the queue. */
|
|
return &timer_queue;
|
|
};
|
|
|
|
diff --git a/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c b/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c
|
|
index 7c2559b..202d5dc 100644
|
|
--- a/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c
|
|
+++ b/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c
|
|
@@ -77,6 +77,7 @@
|
|
#include <mach/message.h>
|
|
#include <mach/port.h>
|
|
#include <mach/vm_map.h>
|
|
+#include <darlingserver/duct-tape/log.h> /* darling-lol M58g1: dtape_log_error */
|
|
#include <mach/mach_vm.h>
|
|
#include <mach/vm_statistics.h>
|
|
|
|
@@ -2497,6 +2498,10 @@ ipc_kmsg_put(
|
|
}
|
|
|
|
if (copyoutmsg((const char *) kmsg->ikm_header, rcv_addr, size)) {
|
|
+ dtape_log_error("M58G ipc_kmsg_put copyout failed size=%u rcv_size=%u rcv_addr=0x%llx option=0x%x bits=0x%x id=%d task=%p map=%p",
|
|
+ size, (unsigned)rcv_size, (unsigned long long) rcv_addr, (unsigned) option,
|
|
+ (unsigned) kmsg->ikm_header->msgh_bits, (int) kmsg->ikm_header->msgh_id,
|
|
+ (void*) current_task(), (void*) current_map());
|
|
mr = MACH_RCV_INVALID_DATA;
|
|
size = 0;
|
|
} else {
|
|
@@ -3819,7 +3824,9 @@ ipc_kmsg_copyin_body(
|
|
goto clean_message;
|
|
}
|
|
|
|
- if ((size >= MSG_OOL_SIZE_SMALL) &&
|
|
+ /* Darling has a zero small-copy threshold. Empty OOL regions
|
|
+ * require no allocation and must not trip the overflow check. */
|
|
+ if (size != 0 && (size >= MSG_OOL_SIZE_SMALL) &&
|
|
(daddr->out_of_line.copy == MACH_MSG_PHYSICAL_COPY) &&
|
|
!(daddr->out_of_line.deallocate)) {
|
|
/*
|
|
diff --git a/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c b/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c
|
|
index 98ad438..221a1be 100644
|
|
--- a/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c
|
|
+++ b/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c
|
|
@@ -87,6 +87,7 @@
|
|
#include <ipc/port.h>
|
|
#include <ipc/ipc_mqueue.h>
|
|
#include <ipc/ipc_kmsg.h>
|
|
+#include <darlingserver/duct-tape/log.h> /* darling-lol M58bc: dtape_log_error */
|
|
#include <ipc/ipc_port.h>
|
|
#include <ipc/ipc_pset.h>
|
|
#include <ipc/ipc_space.h>
|
|
@@ -757,6 +758,25 @@ ipc_mqueue_post(
|
|
|
|
ipc_kmsg_trace_send(kmsg, option);
|
|
|
|
+ {
|
|
+ /* darling-lol M58bc: mach-message delivery trace. Logs the receiver's
|
|
+ * pid, message id and size for each enqueue, so we can see whether a
|
|
+ * message addressed to the renderer's port is delivered. Capped. */
|
|
+ ipc_port_t __m58bc_port = ip_from_mq(mqueue);
|
|
+ if (__m58bc_port != IP_NULL && ip_active(__m58bc_port)) {
|
|
+ int __m58bc_rpid = ipc_port_get_receiver_task(__m58bc_port, NULL);
|
|
+ mach_msg_size_t __m58bc_sz = (kmsg->ikm_header != 0) ? kmsg->ikm_header->msgh_size : 0;
|
|
+ int __m58bc_id = (kmsg->ikm_header != 0) ? (int) kmsg->ikm_header->msgh_id : 0;
|
|
+#if 0 /* darling-lol M58F173: disabled again (per-message dserver log overhead) */
|
|
+ static unsigned long __m58bc_n = 0;
|
|
+ if (__m58bc_n++ < 200000) {
|
|
+ dtape_log_error("M58BC post rpid=%d id=%d size=%u port=%p",
|
|
+ __m58bc_rpid, __m58bc_id, (unsigned) __m58bc_sz, (void*)__m58bc_port);
|
|
+ }
|
|
+#endif
|
|
+ }
|
|
+ }
|
|
+
|
|
/*
|
|
* While the msg queue is locked, we have control of the
|
|
* kmsg, so the ref in it for the port is still good.
|
|
@@ -786,6 +806,24 @@ ipc_mqueue_post(
|
|
WAITQ_KEEP_LOCKED);
|
|
/* waitq still locked, thread locked */
|
|
|
|
+ /* darling-lol M58RD: record whether the post found a blocked receiver
|
|
+ * or had to queue. A queued message while a thread is parked in
|
|
+ * ipc_mqueue_receive on the SAME port means a lost wakeup. */
|
|
+#if 0 /* darling-lol M58F173: disabled again (per-message dserver log overhead) */
|
|
+ {
|
|
+ static unsigned long __m58rd_n = 0;
|
|
+ if (__m58rd_n++ < 200000) {
|
|
+ ipc_port_t __m58rd_port = ip_from_mq(mqueue);
|
|
+ int __m58rd_rpid = (__m58rd_port != IP_NULL && ip_active(__m58rd_port))
|
|
+ ? ipc_port_get_receiver_task(__m58rd_port, NULL) : -1;
|
|
+ int __m58rd_id = (kmsg->ikm_header != 0) ? (int)kmsg->ikm_header->msgh_id : 0;
|
|
+ dtape_log_error("M58RD %s rpid=%d id=%d port=%p rcv=%p",
|
|
+ (receiver == THREAD_NULL) ? "QUEUED" : "HANDOFF",
|
|
+ __m58rd_rpid, __m58rd_id, (void*)__m58rd_port, (void*)receiver);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
if (receiver == THREAD_NULL) {
|
|
/*
|
|
* no receivers; queue kmsg if space still reserved
|
|
@@ -1222,6 +1260,23 @@ ipc_mqueue_receive_on_thread(
|
|
TURNSTILE_DELAYED_UPDATE);
|
|
}
|
|
|
|
+ /* darling-lol M58RE: receive-side trace. When a thread is about to block
|
|
+ * on a mach-message receive, log the receiver pid and the port it waits on
|
|
+ * (pointer identity). Pair with M58BC posts (same port=) to see whether the
|
|
+ * expected message is enqueued on this exact port. Capped. */
|
|
+#if 0 /* darling-lol M58F151: disabled (per-receive dserver log overhead) */
|
|
+ {
|
|
+ static unsigned long __m58re_n = 0;
|
|
+ if (__m58re_n++ < 200000) {
|
|
+ ipc_port_t __m58re_port = ip_from_mq(mqueue);
|
|
+ int __m58re_rpid = (__m58re_port != IP_NULL && ip_active(__m58re_port))
|
|
+ ? ipc_port_get_receiver_task(__m58re_port, NULL) : -1;
|
|
+ dtape_log_error("M58RE recv rpid=%d tid=%p port=%p opt=0x%x",
|
|
+ __m58re_rpid, (void*)thread, (void*)__m58re_port, (unsigned)option);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
thread_set_pending_block_hint(thread, kThreadWaitPortReceive);
|
|
wresult = waitq_assert_wait64_locked(&mqueue->imq_wait_queue,
|
|
IPC_MQUEUE_RECEIVE,
|
|
diff --git a/duct-tape/xnu/osfmk/ipc/ipc_object.c b/duct-tape/xnu/osfmk/ipc/ipc_object.c
|
|
index 5e2fdb8..2352d43 100644
|
|
--- a/duct-tape/xnu/osfmk/ipc/ipc_object.c
|
|
+++ b/duct-tape/xnu/osfmk/ipc/ipc_object.c
|
|
@@ -89,6 +89,7 @@
|
|
#include <ipc/ipc_hash.h>
|
|
#include <ipc/ipc_right.h>
|
|
#include <ipc/ipc_notify.h>
|
|
+#include <darlingserver/duct-tape/log.h> /* darling-lol M58DD: copyout diagnostics */
|
|
#include <ipc/ipc_port.h>
|
|
#include <ipc/ipc_pset.h>
|
|
|
|
@@ -1016,6 +1017,13 @@ ipc_object_copyout(
|
|
is_write_unlock(space);
|
|
|
|
out:
|
|
+ /* Successful copyouts are a hot path; diagnostic logging here slows
|
|
+ * normal Mach traffic and fills dserver.log during Riot startup. */
|
|
+ if (kr != KERN_SUCCESS) {
|
|
+ dtape_log_error("M58DD copyout-fail msgt=0x%x kr=%d",
|
|
+ (unsigned) msgt_name, (int) kr);
|
|
+ }
|
|
+
|
|
if (kr == KERN_SUCCESS) {
|
|
*namep = name;
|
|
} else if (IO_VALID(object)) {
|
|
diff --git a/duct-tape/xnu/osfmk/ipc/mach_msg.c b/duct-tape/xnu/osfmk/ipc/mach_msg.c
|
|
index f073413..0dd19db 100644
|
|
--- a/duct-tape/xnu/osfmk/ipc/mach_msg.c
|
|
+++ b/duct-tape/xnu/osfmk/ipc/mach_msg.c
|
|
@@ -76,7 +76,7 @@
|
|
#include <mach/message.h>
|
|
#include <mach/mig_errors.h>
|
|
#include <mach/mach_traps.h>
|
|
-
|
|
+#include <darlingserver/duct-tape/log.h> /* darling-lol M58g1: dtape_log_error */
|
|
#include <kern/kern_types.h>
|
|
#include <kern/assert.h>
|
|
#include <kern/cpu_number.h>
|
|
@@ -397,6 +397,14 @@ mach_msg_receive_results(
|
|
|
|
mr = ipc_kmsg_copyout(kmsg, space, map, MACH_MSG_BODY_NULL, option);
|
|
|
|
+ /* darling-lol M58g1: log receive-side failures to find the
|
|
+ * waitable-event receive failure (MACH_RCV_INVALID_DATA 5/5 in the
|
|
+ * harness repro, tools/m58g-machmsg-repro.c). */
|
|
+ if (mr != MACH_MSG_SUCCESS) {
|
|
+ dtape_log_error("M58G recv-err mr=0x%08x msgh_size=%u rcv_size=%u",
|
|
+ mr, kmsg->ikm_header->msgh_size, (unsigned)rcv_size);
|
|
+ }
|
|
+
|
|
trailer_size = ipc_kmsg_trailer_size(option, self);
|
|
|
|
if (mr != MACH_MSG_SUCCESS) {
|
|
@@ -779,6 +787,9 @@ msg_receive_error(
|
|
sizeof(trailer_template));
|
|
|
|
trailer_size = ipc_kmsg_trailer_size(option, self);
|
|
+ /* darling-lol M58g1: track msg_receive_error's copyout inputs. */
|
|
+ dtape_log_error("M58G receive-error trailer_size=%u rcv_size=%u",
|
|
+ (unsigned)trailer_size, (unsigned)rcv_size);
|
|
ipc_kmsg_add_trailer(kmsg, space, option, self,
|
|
seqno, TRUE, context);
|
|
|
|
diff --git a/duct-tape/xnu/osfmk/ipc/mach_port.c b/duct-tape/xnu/osfmk/ipc/mach_port.c
|
|
index deaa574..3adbde2 100644
|
|
--- a/duct-tape/xnu/osfmk/ipc/mach_port.c
|
|
+++ b/duct-tape/xnu/osfmk/ipc/mach_port.c
|
|
@@ -101,6 +101,7 @@
|
|
|
|
#if IMPORTANCE_INHERITANCE
|
|
#include <ipc/ipc_importance.h>
|
|
+#include <darlingserver/duct-tape/log.h> /* darling-lol M58DB: mod_refs diagnostics */
|
|
#endif
|
|
|
|
kern_return_t mach_port_get_attributes(ipc_space_t space, mach_port_name_t name,
|
|
@@ -999,12 +1000,18 @@ mach_port_mod_refs(
|
|
kr = ipc_right_lookup_write(space, name, &entry);
|
|
if (kr != KERN_SUCCESS) {
|
|
mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_NAME);
|
|
+ dtape_log_error("M58DB mod_refs LOOKUP-FAIL name=%u right=%d delta=%d kr=%d",
|
|
+ (unsigned) name, (int) right, (int) delta, (int) kr);
|
|
return kr;
|
|
}
|
|
|
|
/* space is write-locked and active */
|
|
|
|
kr = ipc_right_delta(space, name, entry, right, delta); /* unlocks */
|
|
+ if (kr != KERN_SUCCESS) {
|
|
+ dtape_log_error("M58DB mod_refs DELTA-FAIL name=%u right=%d delta=%d kr=%d",
|
|
+ (unsigned) name, (int) right, (int) delta, (int) kr);
|
|
+ }
|
|
return kr;
|
|
}
|
|
|
|
diff --git a/duct-tape/xnu/osfmk/kern/ipc_kobject.c b/duct-tape/xnu/osfmk/kern/ipc_kobject.c
|
|
index 408cb36..bbddfca 100644
|
|
--- a/duct-tape/xnu/osfmk/kern/ipc_kobject.c
|
|
+++ b/duct-tape/xnu/osfmk/kern/ipc_kobject.c
|
|
@@ -148,6 +148,8 @@
|
|
#include <kern/sync_sema.h>
|
|
#include <kern/work_interval.h>
|
|
#include <kern/suid_cred.h>
|
|
+/* darling-lol M54: fileport kobject lives in duct-tape/src/fileport.c */
|
|
+extern void dtape_fileport_destroy(struct ipc_port* port);
|
|
#include <kern/task_ident.h>
|
|
|
|
#if HYPERVISOR
|
|
@@ -1045,6 +1047,11 @@ ipc_kobject_destroy(
|
|
suid_cred_destroy(port);
|
|
break;
|
|
|
|
+ /* darling-lol M54: fileports. */
|
|
+ case IKOT_FILEPORT:
|
|
+ dtape_fileport_destroy(port);
|
|
+ break;
|
|
+
|
|
default:
|
|
break;
|
|
}
|
|
diff --git a/duct-tape/xnu/osfmk/kern/ipc_misc.c b/duct-tape/xnu/osfmk/kern/ipc_misc.c
|
|
index 6e7638f..76f680e 100644
|
|
--- a/duct-tape/xnu/osfmk/kern/ipc_misc.c
|
|
+++ b/duct-tape/xnu/osfmk/kern/ipc_misc.c
|
|
@@ -127,7 +127,8 @@ fileport_notify(mach_msg_header_t *msg)
|
|
if (port->ip_srights == 0) {
|
|
ip_unlock(port);
|
|
|
|
- fileport_releasefg(fg);
|
|
+ /* Darling stores a host FD kobject, not a BSD fileglob.
|
|
+ * ipc_kobject_destroy performs its one close/free on destruction. */
|
|
ipc_port_dealloc_kernel(port);
|
|
} else {
|
|
ip_unlock(port);
|
|
diff --git a/duct-tape/xnu/osfmk/kern/timer_call.c b/duct-tape/xnu/osfmk/kern/timer_call.c
|
|
index 4983e26..fd37050 100644
|
|
--- a/duct-tape/xnu/osfmk/kern/timer_call.c
|
|
+++ b/duct-tape/xnu/osfmk/kern/timer_call.c
|
|
@@ -385,6 +385,13 @@ timer_call_entry_enqueue_deadline(
|
|
}
|
|
new_mpqueue->count++;
|
|
|
|
+#ifdef __DARLING__
|
|
+ /* Publish the actual queue head after insertion, while serialized with
|
|
+ * expiration and cancellation. The timerfd must never reflect a stale
|
|
+ * pre-insertion or post-expiration snapshot. */
|
|
+ timer_queue_cancel(new_mpqueue, deadline, thead->tc_pqlink.deadline);
|
|
+#endif
|
|
+
|
|
return old_mpqueue;
|
|
}
|
|
|
|
@@ -1003,6 +1010,9 @@ timer_queue_expire_with_options(
|
|
queue->earliest_soft_deadline = cur_deadline = UINT64_MAX;
|
|
}
|
|
|
|
+#ifdef __DARLING__
|
|
+ timer_queue_cancel(queue, deadline, cur_deadline);
|
|
+#endif
|
|
timer_queue_unlock(queue);
|
|
|
|
return cur_deadline;
|
|
diff --git a/duct-tape/xnu/osfmk/vm/vm_map.h b/duct-tape/xnu/osfmk/vm/vm_map.h
|
|
index 15fa8c8..4321a91 100644
|
|
--- a/duct-tape/xnu/osfmk/vm/vm_map.h
|
|
+++ b/duct-tape/xnu/osfmk/vm/vm_map.h
|
|
@@ -205,6 +205,12 @@ struct vm_named_entry {
|
|
#define NAMED_ENTRY_BT_DEPTH 16
|
|
void *named_entry_bt[NAMED_ENTRY_BT_DEPTH];
|
|
#endif /* VM_NAMED_ENTRY_LIST */
|
|
+ /* darling-lol: duct-tape shared backing. XNU's object model is not
|
|
+ * wired up here; instead an entry may carry a shared memfd descriptor
|
|
+ * (see dtape_map_shared_descriptor in duct-tape/src/memory.c) that is
|
|
+ * mapped into tasks on demand, giving true cross-task sharing.
|
|
+ * Opaque pointer to avoid header coupling; NULL when unused. */
|
|
+ void *dtape_descriptor;
|
|
};
|
|
|
|
/*
|
|
diff --git a/internal-include/darlingserver/logging.hpp b/internal-include/darlingserver/logging.hpp
|
|
index d46bdb4..9a58727 100644
|
|
--- a/internal-include/darlingserver/logging.hpp
|
|
+++ b/internal-include/darlingserver/logging.hpp
|
|
@@ -41,6 +41,7 @@ namespace DarlingServer {
|
|
Error = 4,
|
|
};
|
|
|
|
+ bool _enabled(Type type) const;
|
|
void _log(Type type, std::string message) const;
|
|
static std::string _typeToString(Type type);
|
|
|
|
@@ -57,6 +58,9 @@ namespace DarlingServer {
|
|
class Stream;
|
|
friend class Stream;
|
|
|
|
+ /// whether a message of this level (1 = debug .. 4 = error) would be written; cheap, for skipping formatting work
|
|
+ bool levelEnabled(int level) const { return _enabled(static_cast<Type>(level)); };
|
|
+
|
|
Stream debug() const;
|
|
Stream info() const;
|
|
Stream warning() const;
|
|
@@ -74,7 +78,7 @@ namespace DarlingServer {
|
|
private:
|
|
Type _type;
|
|
const Log& _log;
|
|
- std::ostringstream _buffer;
|
|
+ std::unique_ptr<std::ostringstream> _buffer;
|
|
|
|
Stream(Type type, const Log& log);
|
|
|
|
@@ -91,7 +95,9 @@ namespace DarlingServer {
|
|
|
|
template<class T>
|
|
std::enable_if_t<!std::is_base_of_v<Loggable, T>, Stream&> operator<<(const T& value) {
|
|
- _buffer << value;
|
|
+ if (_buffer) {
|
|
+ *_buffer << value;
|
|
+ }
|
|
return *this;
|
|
};
|
|
};
|
|
diff --git a/internal-include/darlingserver/message.hpp b/internal-include/darlingserver/message.hpp
|
|
index 708f3f3..fd46ab8 100644
|
|
--- a/internal-include/darlingserver/message.hpp
|
|
+++ b/internal-include/darlingserver/message.hpp
|
|
@@ -28,6 +28,7 @@
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <functional>
|
|
+#include <array>
|
|
|
|
namespace DarlingServer {
|
|
class Address {
|
|
@@ -71,6 +72,11 @@ namespace DarlingServer {
|
|
void _ensureDescriptorHeader(size_t descriptorSpace);
|
|
|
|
public:
|
|
+ /** Called once after the server's final fork/privilege drop, before workers.
|
|
+ * The server does not change its native identity afterwards. Generic Message
|
|
+ * users retain live credentials unless they explicitly initialize this cache.
|
|
+ */
|
|
+ static void initializeServerCredentials();
|
|
/**
|
|
* Default-initializes a Message.
|
|
*
|
|
@@ -161,13 +167,30 @@ namespace DarlingServer {
|
|
class MessageQueue {
|
|
private:
|
|
std::deque<Message> _messages;
|
|
+ // Retain unused receive storage; queued Messages own their buffers and FDs.
|
|
+ std::array<std::optional<Message>, 16> _receiveMessages;
|
|
mutable std::mutex _lock;
|
|
std::function<void()> _messageArrivalNotificationCallback = nullptr;
|
|
|
|
+ void _notifyMessageArrival();
|
|
+
|
|
public:
|
|
void setMessageArrivalNotificationCallback(std::function<void()> messageArrivalNotificationCallback);
|
|
|
|
void push(Message&& message);
|
|
+
|
|
+ /**
|
|
+ * Pushes a message and immediately tries to send as much of the queue as
|
|
+ * possible through the given socket from the calling thread.
|
|
+ *
|
|
+ * This avoids the event loop hop for messages that can be sent right away.
|
|
+ * Sending always starts at the head of the queue, so a direct send can
|
|
+ * never overtake messages queued by an earlier EAGAIN. When the socket
|
|
+ * would block, the message-arrival notification callback fires (once) so
|
|
+ * the receiving loop retries once the socket is writable again.
|
|
+ */
|
|
+ void pushAndSend(Message&& message, int socket);
|
|
+
|
|
std::optional<Message> pop();
|
|
|
|
bool sendMany(int socket);
|
|
diff --git a/internal-include/darlingserver/process.hpp b/internal-include/darlingserver/process.hpp
|
|
index c97be30..31772dc 100644
|
|
--- a/internal-include/darlingserver/process.hpp
|
|
+++ b/internal-include/darlingserver/process.hpp
|
|
@@ -94,6 +94,13 @@ namespace DarlingServer {
|
|
std::shared_ptr<FD> _vchrootDescriptor;
|
|
dtape_task_t* _dtapeTask;
|
|
std::weak_ptr<Process> _parentProcess;
|
|
+
|
|
+ // /proc/<pid>/maps, kept open for PROCMAP_QUERY region lookups (opening it per lookup cost more than the query).
|
|
+ // Guarded by its own mutex: a stale descriptor is closed and reopened, and a concurrent ioctl on a closed and
|
|
+ // reused descriptor number would hit an unrelated file.
|
|
+ mutable std::mutex _mapsMutex;
|
|
+ mutable int _mapsFd = -1;
|
|
+ int _procmapQuery(uint64_t address, uint64_t flags, void* query) const;
|
|
bool _startSuspended = false;
|
|
bool _pendingReplacement = false;
|
|
std::unordered_map<uintptr_t, std::shared_ptr<Kqchan>> _kqchannels;
|
|
diff --git a/internal-include/darlingserver/server.hpp b/internal-include/darlingserver/server.hpp
|
|
index b960f17..5f0e621 100644
|
|
--- a/internal-include/darlingserver/server.hpp
|
|
+++ b/internal-include/darlingserver/server.hpp
|
|
@@ -46,6 +46,9 @@ namespace DarlingServer {
|
|
WorkQueue<std::shared_ptr<Thread>> _workQueue;
|
|
bool _canRead = false;
|
|
bool _canWrite = true;
|
|
+ /* darling-lol: producers send their own replies (see sendMessage) unless
|
|
+ * DSERVER_DIRECT_REPLY=0 restored the old outbox wakeup hand-off. */
|
|
+ bool _directReply = true;
|
|
int _wakeupFD;
|
|
int _timerFD;
|
|
uint64_t _currentTimerDeadline = 0;
|
|
diff --git a/scripts/generate-rpc-wrappers.py b/scripts/generate-rpc-wrappers.py
|
|
index e35221c..1068426 100755
|
|
--- a/scripts/generate-rpc-wrappers.py
|
|
+++ b/scripts/generate-rpc-wrappers.py
|
|
@@ -268,6 +268,22 @@ calls = [
|
|
('socket', '@fd'),
|
|
]),
|
|
|
|
+ #
|
|
+ # darling-lol M54: fileports (Mojo FD-over-Mach transport)
|
|
+ #
|
|
+
|
|
+ ('fileport_makeport', [
|
|
+ ('fd', '@fd'),
|
|
+ ], [
|
|
+ ('port_name', 'uint32_t'),
|
|
+ ]),
|
|
+
|
|
+ ('fileport_makefd', [
|
|
+ ('port_name', 'uint32_t'),
|
|
+ ], [
|
|
+ ('fd', '@fd'),
|
|
+ ]),
|
|
+
|
|
#
|
|
# pthread cancelation
|
|
#
|
|
diff --git a/src/call.cpp b/src/call.cpp
|
|
index cc10f1d..7df0056 100644
|
|
--- a/src/call.cpp
|
|
+++ b/src/call.cpp
|
|
@@ -18,9 +18,20 @@
|
|
*/
|
|
|
|
#define _GNU_SOURCE 1
|
|
+#include <map>
|
|
+#include <vector>
|
|
+#include <algorithm>
|
|
+#include <chrono>
|
|
+#include <cstdio>
|
|
+#include <cstdlib>
|
|
#include <darlingserver/call.hpp>
|
|
#include <darlingserver/server.hpp>
|
|
#include <sys/uio.h>
|
|
+#include <cerrno>
|
|
+#include <unistd.h>
|
|
+#include <atomic>
|
|
+#include <cstdlib>
|
|
+#include <cstdio>
|
|
|
|
#include <darlingserver/logging.hpp>
|
|
#include <darlingserver/duct-tape.h>
|
|
@@ -29,16 +40,76 @@
|
|
#include <sys/syscall.h>
|
|
#include <darlingserver/kqchan.hpp>
|
|
|
|
+static bool m634TraceEnabled() {
|
|
+ static const bool enabled = [] {
|
|
+ const char* value = std::getenv("DARLING_M634_TRACE");
|
|
+ return value && value[0] == '1' && value[1] == '\0';
|
|
+ }();
|
|
+ return enabled;
|
|
+}
|
|
+
|
|
static DarlingServer::Log callLog("calls");
|
|
|
|
DarlingServer::Log DarlingServer::Call::rpcReplyLog("replies");
|
|
|
|
+// darling-lol: opt-in request statistics. With DSERVER_CALL_STATS=<file> set, every 5 s the server appends how many
|
|
+// requests of each call number arrived and which (pid, tid) pairs sent the most, then starts over. Costs one getenv
|
|
+// at startup and a branch per request when off. callFromMessage runs on the server's single message loop thread.
|
|
+static void callStatsRecord(unsigned number, int pid, int tid) {
|
|
+ static const char* path = getenv("DSERVER_CALL_STATS");
|
|
+ if (!path || !path[0]) {
|
|
+ return;
|
|
+ }
|
|
+ static std::map<unsigned, unsigned long> byNumber;
|
|
+ static std::map<std::pair<int, unsigned>, unsigned long> byThreadCall;
|
|
+ static std::map<std::pair<int, int>, unsigned long> byThread;
|
|
+ static unsigned long total = 0;
|
|
+ static auto last = std::chrono::steady_clock::now();
|
|
+
|
|
+ ++byNumber[number];
|
|
+ ++byThreadCall[{tid, number}];
|
|
+ ++byThread[{pid, tid}];
|
|
+ ++total;
|
|
+
|
|
+ auto now = std::chrono::steady_clock::now();
|
|
+ if (now - last < std::chrono::seconds(5)) {
|
|
+ return;
|
|
+ }
|
|
+ double seconds = std::chrono::duration<double>(now - last).count();
|
|
+ last = now;
|
|
+
|
|
+ if (FILE* file = fopen(path, "a")) {
|
|
+ fprintf(file, "calls: %lu in %.1fs (%.0f/s)\n by call number:", total, seconds, total / seconds);
|
|
+ std::vector<std::pair<unsigned long, unsigned>> numbers;
|
|
+ for (auto& [num, count]: byNumber) numbers.push_back({count, num});
|
|
+ std::sort(numbers.rbegin(), numbers.rend());
|
|
+ for (size_t i = 0; i < numbers.size() && i < 12; ++i) fprintf(file, " #%u=%lu", numbers[i].second, numbers[i].first);
|
|
+ fprintf(file, "\n by pid/tid:");
|
|
+ std::vector<std::pair<unsigned long, std::pair<int, int>>> threads;
|
|
+ for (auto& [id, count]: byThread) threads.push_back({count, id});
|
|
+ std::sort(threads.rbegin(), threads.rend());
|
|
+ for (size_t i = 0; i < threads.size() && i < 12; ++i) fprintf(file, " %d/%d=%lu", threads[i].second.first, threads[i].second.second, threads[i].first);
|
|
+ fprintf(file, "\n by tid/call:");
|
|
+ std::vector<std::pair<unsigned long, std::pair<int, unsigned>>> pairs;
|
|
+ for (auto& [id, count]: byThreadCall) pairs.push_back({count, id});
|
|
+ std::sort(pairs.rbegin(), pairs.rend());
|
|
+ for (size_t i = 0; i < pairs.size() && i < 24; ++i) fprintf(file, " %d:#%u=%lu", pairs[i].second.first, pairs[i].second.second, pairs[i].first);
|
|
+ fprintf(file, "\n");
|
|
+ fclose(file);
|
|
+ }
|
|
+ byThreadCall.clear();
|
|
+ byNumber.clear();
|
|
+ byThread.clear();
|
|
+ total = 0;
|
|
+};
|
|
+
|
|
std::shared_ptr<DarlingServer::Call> DarlingServer::Call::callFromMessage(Message&& requestMessage) {
|
|
if (requestMessage.data().size() < sizeof(dserver_rpc_callhdr_t)) {
|
|
throw std::invalid_argument("Message buffer was too small for call header");
|
|
}
|
|
|
|
dserver_rpc_callhdr_t* header = reinterpret_cast<dserver_rpc_callhdr_t*>(requestMessage.data().data());
|
|
+ callStatsRecord(header->number, header->pid, header->tid);
|
|
std::shared_ptr<Call> result = nullptr;
|
|
std::shared_ptr<Process> process = nullptr;
|
|
std::shared_ptr<Thread> thread = nullptr;
|
|
@@ -123,9 +194,43 @@ std::shared_ptr<DarlingServer::Call> DarlingServer::Call::callFromMessage(Messag
|
|
}
|
|
}
|
|
|
|
- auto pidString = (process) ? (std::to_string(process->id()) + " (" + std::to_string(process->nsid()) + ")") : (std::to_string(header->pid) + " (-1)");
|
|
- auto tidString = (thread) ? (std::to_string(thread->id()) + " (" + std::to_string(thread->nsid()) + ")") : (std::to_string(header->tid) + " (-1)");
|
|
- callLog.debug() << "Received call #" << header->number << " (" << dserver_callnum_to_string(header->number) << ") from PID " << pidString << ", TID " << tidString << callLog.endLog;
|
|
+ if (callLog.levelEnabled(1)) {
|
|
+ auto pidString = (process) ? (std::to_string(process->id()) + " (" + std::to_string(process->nsid()) + ")") : (std::to_string(header->pid) + " (-1)");
|
|
+ auto tidString = (thread) ? (std::to_string(thread->id()) + " (" + std::to_string(thread->nsid()) + ")") : (std::to_string(header->tid) + " (-1)");
|
|
+ callLog.debug() << "Received call #" << header->number << " (" << dserver_callnum_to_string(header->number) << ") from PID " << pidString << ", TID " << tidString << callLog.endLog;
|
|
+ }
|
|
+
|
|
+ /* darling-lol M634: rate-limited trace of EVERY call through dserver, to
|
|
+ * see which RPCs the launcher's blocked service threads are waiting on
|
|
+ * and whether an S2C/interrupt round-trip is wedged. */
|
|
+ if (m634TraceEnabled()) {
|
|
+ static std::atomic<unsigned> m634AllCount{0};
|
|
+ unsigned seen = m634AllCount.fetch_add(1);
|
|
+ if (seen < 120 || seen % 64 == 0) {
|
|
+ fprintf(stderr, "M634RPC line=%u nr=%d pid=%d ns=%d tid=%d nstid=%d\n",
|
|
+ seen, (int)header->number, requestMessage.pid(),
|
|
+ process ? (int)process->nsid() : -1,
|
|
+ header->tid, thread ? (int)thread->nsid() : -1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* darling-lol M634: bounded trace of the storm calls (semaphore_timedwait
|
|
+ * and pthread_canceled) to diagnose the League-launch wedge. */
|
|
+ if (m634TraceEnabled() && (header->number == dserver_callnum_semaphore_timedwait || header->number == dserver_callnum_pthread_canceled)) {
|
|
+ static std::atomic<unsigned> m634CallCount{0};
|
|
+ unsigned seen = m634CallCount.fetch_add(1);
|
|
+ if (seen < 200 || seen % 256 == 0) {
|
|
+ if (header->number == dserver_callnum_semaphore_timedwait) {
|
|
+ auto call64 = reinterpret_cast<const dserver_rpc_call_semaphore_timedwait_t*>(requestMessage.data().data());
|
|
+ fprintf(stderr, "M634CALL line=%u nr=64 pid=%d tid=%d name=%u sec=%u nsec=%u\n",
|
|
+ seen, requestMessage.pid(), header->tid,
|
|
+ call64->body.wait_name, call64->body.sec, call64->body.nsec);
|
|
+ } else {
|
|
+ fprintf(stderr, "M634CALL line=%u nr=33 pid=%d tid=%d\n",
|
|
+ seen, requestMessage.pid(), header->tid);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
|
|
if (header->number == dserver_callnum_s2c) {
|
|
// this is an S2C reply
|
|
@@ -694,6 +799,60 @@ void DarlingServer::Call::KqchanProcOpen::processCall() {
|
|
_sendReply(code, socket);
|
|
};
|
|
|
|
+void DarlingServer::Call::FileportMakeport::processCall() {
|
|
+ int code = 0;
|
|
+ uint32_t port_name = 0;
|
|
+
|
|
+ if (auto thread = _thread.lock()) {
|
|
+ if (auto process = thread->process()) {
|
|
+ // _body.fd arrives via SCM_RIGHTS; the framework closes it
|
|
+ // after this call, so dup for the kobject's own lifecycle.
|
|
+ int fd = dup(_body.fd);
|
|
+ if (fd < 0) {
|
|
+ code = -errno;
|
|
+ } else {
|
|
+ int kr = dtape_fileport_makeport(process->_dtapeTask, fd, &port_name);
|
|
+ if (kr != 0) {
|
|
+ code = -EINVAL;
|
|
+ ::close(fd);
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ code = -ESRCH;
|
|
+ }
|
|
+ } else {
|
|
+ code = -ESRCH;
|
|
+ }
|
|
+
|
|
+ _sendReply(code, port_name);
|
|
+};
|
|
+
|
|
+void DarlingServer::Call::FileportMakefd::processCall() {
|
|
+ int code = 0;
|
|
+ int fd = -1;
|
|
+
|
|
+ if (auto thread = _thread.lock()) {
|
|
+ if (auto process = thread->process()) {
|
|
+ int held = -1;
|
|
+ int kr = dtape_fileport_makefd(process->_dtapeTask, _body.port_name, &held);
|
|
+ if (kr != 0) {
|
|
+ code = -EINVAL;
|
|
+ } else {
|
|
+ // dtape_fileport_makefd already returns an owned duplicate.
|
|
+ // Transfer it to Message; its destructor closes the server
|
|
+ // copy after SCM_RIGHTS gives the receiver its own descriptor.
|
|
+ fd = held;
|
|
+ }
|
|
+ } else {
|
|
+ code = -ESRCH;
|
|
+ }
|
|
+ } else {
|
|
+ code = -ESRCH;
|
|
+ }
|
|
+
|
|
+ _sendReply(code, fd);
|
|
+};
|
|
+
|
|
void DarlingServer::Call::ForkWaitForChild::processCall() {
|
|
int code = 0;
|
|
|
|
@@ -714,6 +873,24 @@ void DarlingServer::Call::Sigprocess::processCall() {
|
|
int code = 0;
|
|
int newBSDSignal = 0;
|
|
|
|
+ // darling-lol M34 TMPLOG: every guest signal delivered through sigexc.
|
|
+ // For the RC UX SIGSEGV this is the reliable place to see the crash:
|
|
+ // signal numbers + si_code + si_addr. RIP is not read here yet, but
|
|
+ // si_addr + code identify the fault class. (Chromium/Electron
|
|
+ // sigexc-routes its fatal signals through this RPC.)
|
|
+ std::string lolExe = "?";
|
|
+ if (auto th = _thread.lock()) {
|
|
+ if (auto pr = th->process())
|
|
+ lolExe = pr->executablePath();
|
|
+ }
|
|
+ callLog.error() << "LOLSIG processSignal bsd=" << _body.bsd_signal_number
|
|
+ << " linux=" << _body.linux_signal_number
|
|
+ << " code=" << _body.code
|
|
+ << " addr=0x" << std::hex << _body.signal_address << std::dec
|
|
+ << " ts=0x" << std::hex << _body.thread_state << std::dec
|
|
+ << " exe=" << lolExe
|
|
+ << callLog.endLog;
|
|
+
|
|
if (auto thread = _thread.lock()) {
|
|
try {
|
|
thread->processSignal(_body.bsd_signal_number, _body.linux_signal_number, _body.code, _body.signal_address, _body.thread_state, _body.float_state);
|
|
diff --git a/src/darlingserver.cpp b/src/darlingserver.cpp
|
|
index f79f057..2bf9cfb 100644
|
|
--- a/src/darlingserver.cpp
|
|
+++ b/src/darlingserver.cpp
|
|
@@ -497,6 +497,45 @@ static void handle_sigusr1(int signum) {
|
|
};
|
|
#endif
|
|
|
|
+/* darling-lol M58f2b: darlingserver was observed exiting silently ~100s into
|
|
+ * direct-UX runs; the monitor caught exit_code=15 — a SIGTERM sent by an
|
|
+ * unknown host process. Log the sender (siginfo.si_pid + its comm/cmdline)
|
|
+ * instead of dying, so the culprit is identified. Not strictly
|
|
+ * async-signal-safe, but this is a diagnostic for a fatal path. */
|
|
+static void handle_sigterm_diagnostic(int signum, siginfo_t* info, void* uctx) {
|
|
+ int fd = open("/tmp/dserver-signal.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
|
|
+ if (fd >= 0) {
|
|
+ dprintf(fd, "darlingserver pid=%d got %s from sender pid=%ld uid=%ld\n",
|
|
+ getpid(),
|
|
+ signum == SIGTERM ? "SIGTERM" : (signum == SIGINT ? "SIGINT" : "signal"),
|
|
+ info ? (long) info->si_pid : -1L,
|
|
+ info ? (long) info->si_uid : -1L);
|
|
+ if (info && info->si_pid > 0) {
|
|
+ char path[64], comm[256], cmdline[1024];
|
|
+ snprintf(path, sizeof(path), "/proc/%ld/comm", (long) info->si_pid);
|
|
+ int cfd = open(path, O_RDONLY);
|
|
+ if (cfd >= 0) {
|
|
+ ssize_t n = read(cfd, comm, sizeof(comm) - 1);
|
|
+ if (n > 0) { comm[n - 1] = '\0'; dprintf(fd, " sender comm: %s\n", comm); }
|
|
+ close(cfd);
|
|
+ }
|
|
+ snprintf(path, sizeof(path), "/proc/%ld/cmdline", (long) info->si_pid);
|
|
+ cfd = open(path, O_RDONLY);
|
|
+ if (cfd >= 0) {
|
|
+ ssize_t n = read(cfd, cmdline, sizeof(cmdline) - 1);
|
|
+ if (n > 0) {
|
|
+ cmdline[n] = '\0';
|
|
+ for (ssize_t i = 0; i < n; ++i) { if (cmdline[i] == '\0') cmdline[i] = ' '; }
|
|
+ dprintf(fd, " sender cmdline: %s\n", cmdline);
|
|
+ }
|
|
+ close(cfd);
|
|
+ }
|
|
+ }
|
|
+ close(fd);
|
|
+ }
|
|
+ /* keep running: the goal is to survive teardown races and name the sender */
|
|
+};
|
|
+
|
|
int main(int argc, char** argv) {
|
|
const char* prefix = NULL;
|
|
uid_t originalUID = -1;
|
|
@@ -515,6 +554,30 @@ int main(int argc, char** argv) {
|
|
char putOld[4096];
|
|
char *p;
|
|
|
|
+ /* darling-lol M58f2: never die from SIGPIPE. The server writes to
|
|
+ * client sockets (MessageQueue::sendMany's sendmmsg, RPC replies,
|
|
+ * async-writer) whose peers are guest processes that can be torn
|
|
+ * down at any moment (Chromium helper churn, crashes). The default
|
|
+ * SIGPIPE disposition kills the whole server SILENTLY — no core,
|
|
+ * no log — which then wedges the entire container (every guest RPC
|
|
+ * fails with ECONNREFUSED). The write paths already handle EPIPE /
|
|
+ * ECONNREFUSED errors gracefully (e.g. message.cpp sendMany drops
|
|
+ * the dead peer's message); they just never got a chance to run.
|
|
+ * Ignoring SIGPIPE makes those handlers reachable. */
|
|
+ signal(SIGPIPE, SIG_IGN);
|
|
+
|
|
+ /* darling-lol M58f2b: catch SIGTERM/SIGINT and log the sender instead
|
|
+ * of dying (see handle_sigterm_diagnostic above). */
|
|
+ {
|
|
+ struct sigaction sa_diag;
|
|
+ memset(&sa_diag, 0, sizeof(sa_diag));
|
|
+ sa_diag.sa_sigaction = handle_sigterm_diagnostic;
|
|
+ sa_diag.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
+ sigemptyset(&sa_diag.sa_mask);
|
|
+ sigaction(SIGTERM, &sa_diag, NULL);
|
|
+ sigaction(SIGINT, &sa_diag, NULL);
|
|
+ }
|
|
+
|
|
if (argc < 6) {
|
|
fprintf(stderr, "darlingserver is not meant to be started manually\n");
|
|
exit(1);
|
|
@@ -745,6 +808,13 @@ int main(int argc, char** argv) {
|
|
#endif
|
|
|
|
// create the server
|
|
+ // All native forks and privilege changes are complete. Snapshot only our
|
|
+ // fixed transport identity; received/per-message credentials stay independent.
|
|
+ DarlingServer::Message::initializeServerCredentials();
|
|
+ // Explicit desktop-debugging opt-in, after dropping elevated privileges.
|
|
+ if (getenv("RCLOL_DEBUG_ATTACH")) {
|
|
+ prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
|
|
+ }
|
|
auto server = new DarlingServer::Server(prefix);
|
|
|
|
// tell the child to go ahead; the socket has been created
|
|
diff --git a/src/kqchan.cpp b/src/kqchan.cpp
|
|
index 7f766a2..1597a8c 100644
|
|
--- a/src/kqchan.cpp
|
|
+++ b/src/kqchan.cpp
|
|
@@ -13,6 +13,27 @@ static DarlingServer::Log kqchanMachPortLog("kqchan:mach_port");
|
|
static DarlingServer::Log kqchanProcLog("kqchan:proc");
|
|
static std::atomic_uint64_t kqchanDebugIDCounter = 0;
|
|
|
|
+// Opt-in bounded metadata trace; never records Mach message contents.
|
|
+#include <unistd.h>
|
|
+#include <cstdlib>
|
|
+#include <cstdio>
|
|
+#include <cstring>
|
|
+static void riotKqTrace(char stage, uint64_t channel, int socket, long value = 0) {
|
|
+ static int fd = [] {
|
|
+ if (!std::getenv("RCLOL_KQTRACE")) return -1;
|
|
+ char path[96]; std::snprintf(path, sizeof(path), "/tmp/riot-kqtrace-%d.log", getpid());
|
|
+ return open(path, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0600);
|
|
+ }();
|
|
+ if (fd < 0) return;
|
|
+ static std::atomic_uint64_t next{0};
|
|
+ uint64_t index = next.fetch_add(1);
|
|
+ char row[128]; std::memset(row, ' ', sizeof(row));
|
|
+ int n = std::snprintf(row, sizeof(row), "%llu %c channel=%llu fd=%d value=%ld", (unsigned long long)index, stage, (unsigned long long)channel, socket, value);
|
|
+ if (n >= 0 && n < 127) row[n] = ' ';
|
|
+ row[127] = '\n';
|
|
+ pwrite(fd, row, sizeof(row), (index % 8192) * sizeof(row));
|
|
+}
|
|
+
|
|
//
|
|
// base class
|
|
//
|
|
@@ -67,6 +88,7 @@ int DarlingServer::Kqchan::setup() {
|
|
throw std::system_error(ret, std::generic_category());
|
|
}
|
|
|
|
+ riotKqTrace('S', _debugID, _socket->fd());
|
|
std::weak_ptr<Kqchan> weakThis = sharedFromRoot();
|
|
|
|
_outbox.setMessageArrivalNotificationCallback([weakThis]() {
|
|
@@ -84,6 +106,7 @@ int DarlingServer::Kqchan::setup() {
|
|
// we can send all the messages in the same context that they were pushed
|
|
do {
|
|
self->_canSend = self->_outbox.sendMany(self->_socket->fd());
|
|
+ riotKqTrace('O', self->_debugID, self->_socket->fd(), self->_canSend);
|
|
} while (self->_canSend && !self->_outbox.empty());
|
|
});
|
|
|
|
@@ -119,6 +142,7 @@ int DarlingServer::Kqchan::setup() {
|
|
|
|
kqchanLog.debug() << *self << ": socket has pending incoming messages" << kqchanLog.endLog;
|
|
|
|
+ riotKqTrace('I', self->_debugID, self->_socket->fd());
|
|
// receive them all
|
|
while (self->_inbox.receiveMany(self->_socket->fd()));
|
|
|
|
@@ -135,6 +159,7 @@ int DarlingServer::Kqchan::setup() {
|
|
kqchanLog.debug() << *self << ": socket is now writable; sending all pending outgoing messages" << kqchanLog.endLog;
|
|
do {
|
|
self->_canSend = self->_outbox.sendMany(self->_socket->fd());
|
|
+ riotKqTrace('O', self->_debugID, self->_socket->fd(), self->_canSend);
|
|
} while (self->_canSend && !self->_outbox.empty());
|
|
}
|
|
});
|
|
@@ -343,6 +368,7 @@ void DarlingServer::Kqchan::MachPort::_modify(uint64_t receiveBuffer, uint64_t r
|
|
};
|
|
|
|
void DarlingServer::Kqchan::MachPort::_read(uint64_t defaultBuffer, uint64_t defaultBufferSize, pid_t nstid) {
|
|
+ riotKqTrace('R', _debugID, _socket->fd(), nstid);
|
|
kqchanMachPortLog.debug() << *this << ": received read request with {defaultBuffer=" << defaultBuffer << ",defaultBufferSize=" << defaultBufferSize << "}" << kqchanMachPortLog.endLog;
|
|
|
|
{
|
|
@@ -380,6 +406,7 @@ void DarlingServer::Kqchan::MachPort::_read(uint64_t defaultBuffer, uint64_t def
|
|
|
|
auto self = shared_from_this();
|
|
Thread::kernelAsync([self, thread, defaultBuffer, defaultBufferSize]() {
|
|
+ riotKqTrace('K', self->_debugID, self->_socket->fd());
|
|
Message msg(sizeof(dserver_kqchan_reply_mach_port_read_t), 0, self->_checkForEventsAsyncFactory());
|
|
|
|
kqchanMachPortLog.debug() << *self << ": handling read request in microthread" << kqchanMachPortLog.endLog;
|
|
@@ -395,8 +422,11 @@ void DarlingServer::Kqchan::MachPort::_read(uint64_t defaultBuffer, uint64_t def
|
|
reply->header.code = 0xdead;
|
|
}
|
|
Thread::currentThread()->impersonate(nullptr);
|
|
+ riotKqTrace('F', self->_debugID, self->_socket->fd(), reply->header.code);
|
|
|
|
+ riotKqTrace('P', self->_debugID, self->_socket->fd());
|
|
self->_outbox.push(std::move(msg));
|
|
+ riotKqTrace('D', self->_debugID, self->_socket->fd());
|
|
|
|
// now let's send any deferred notifications we might have
|
|
self->_sendDeferredNotification();
|
|
diff --git a/src/logging.cpp b/src/logging.cpp
|
|
index 7b8000f..dbaf832 100644
|
|
--- a/src/logging.cpp
|
|
+++ b/src/logging.cpp
|
|
@@ -22,6 +22,8 @@
|
|
#include <darlingserver/thread.hpp>
|
|
#include <darlingserver/process.hpp>
|
|
#include <filesystem>
|
|
+#include <cstring>
|
|
+#include <cstdlib>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
@@ -45,7 +47,8 @@ DarlingServer::Log::Log(std::string category):
|
|
|
|
DarlingServer::Log::Stream::Stream(Type type, const Log& log):
|
|
_type(type),
|
|
- _log(log)
|
|
+ _log(log),
|
|
+ _buffer(log._enabled(type) ? std::make_unique<std::ostringstream>() : nullptr)
|
|
{};
|
|
|
|
DarlingServer::Log::Stream::~Stream() {
|
|
@@ -53,17 +56,22 @@ DarlingServer::Log::Stream::~Stream() {
|
|
};
|
|
|
|
DarlingServer::Log::Stream& DarlingServer::Log::Stream::operator<<(EndLog value) {
|
|
- auto str = _buffer.str();
|
|
+ if (!_buffer) {
|
|
+ return *this;
|
|
+ }
|
|
+ auto str = _buffer->str();
|
|
if (!str.empty()) {
|
|
_log._log(_type, str);
|
|
- _buffer.str(std::string());
|
|
- _buffer.clear();
|
|
+ _buffer->str(std::string());
|
|
+ _buffer->clear();
|
|
}
|
|
return *this;
|
|
};
|
|
|
|
DarlingServer::Log::Stream& DarlingServer::Log::Stream::operator<<(const Loggable& loggable) {
|
|
- loggable.logToStream(*this);
|
|
+ if (_buffer) {
|
|
+ loggable.logToStream(*this);
|
|
+ }
|
|
return *this;
|
|
};
|
|
|
|
@@ -98,20 +106,7 @@ std::string DarlingServer::Log::_typeToString(Type type) {
|
|
}
|
|
};
|
|
|
|
-void DarlingServer::Log::_log(Type type, std::string message) const {
|
|
- // NOTE: we use POSIX file APIs because we want to append each message to the log file atomically,
|
|
- // and as far as i can tell, C++ fstreams provide no such guarantee (that they won't write in chunks).
|
|
- static int logFile = []() {
|
|
- std::filesystem::path path(Server::sharedInstance().prefix() + "/private/var/log/dserver.log");
|
|
- std::filesystem::create_directories(path.parent_path());
|
|
- return open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
- }();
|
|
-
|
|
- static bool logToStderr = []() {
|
|
- auto val = getenv("DSERVER_LOG_STDERR");
|
|
- return val && strlen(val) >= 1 && (val[0] == 't' || val[0] == 'T' || val[0] == '1');
|
|
- }();
|
|
-
|
|
+bool DarlingServer::Log::_enabled(Type type) const {
|
|
static Type logMinLevel = []() {
|
|
auto val = getenv("DSERVER_LOG_LEVEL");
|
|
Type level = DEFAULT_LOG_CUTOFF;
|
|
@@ -129,7 +124,24 @@ void DarlingServer::Log::_log(Type type, std::string message) const {
|
|
return level;
|
|
}();
|
|
|
|
- if ((type < logMinLevel && !_alwaysLog) || logFile < 0) {
|
|
+ return _alwaysLog || type >= logMinLevel;
|
|
+};
|
|
+
|
|
+void DarlingServer::Log::_log(Type type, std::string message) const {
|
|
+ // NOTE: we use POSIX file APIs because we want to append each message to the log file atomically,
|
|
+ // and as far as i can tell, C++ fstreams provide no such guarantee (that they won't write in chunks).
|
|
+ static int logFile = []() {
|
|
+ std::filesystem::path path(Server::sharedInstance().prefix() + "/private/var/log/dserver.log");
|
|
+ std::filesystem::create_directories(path.parent_path());
|
|
+ return open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
+ }();
|
|
+
|
|
+ static bool logToStderr = []() {
|
|
+ auto val = getenv("DSERVER_LOG_STDERR");
|
|
+ return val && strlen(val) >= 1 && (val[0] == 't' || val[0] == 'T' || val[0] == '1');
|
|
+ }();
|
|
+
|
|
+ if (!_enabled(type) || logFile < 0) {
|
|
return;
|
|
}
|
|
|
|
diff --git a/src/message.cpp b/src/message.cpp
|
|
index 414c1d6..a7f5a94 100644
|
|
--- a/src/message.cpp
|
|
+++ b/src/message.cpp
|
|
@@ -24,6 +24,25 @@
|
|
#include <new>
|
|
#include <mutex>
|
|
#include <system_error>
|
|
+#include <pthread.h>
|
|
+#include <cstdio>
|
|
+#include <cstring>
|
|
+
|
|
+namespace {
|
|
+ std::optional<struct ucred> serverCredentials;
|
|
+ struct ucred outgoingCredentials() {
|
|
+ if (serverCredentials) return *serverCredentials;
|
|
+ return {getpid(), getuid(), getgid()};
|
|
+ }
|
|
+}
|
|
+
|
|
+void DarlingServer::Message::initializeServerCredentials() {
|
|
+ if (serverCredentials) throw std::logic_error("Server credentials already initialized");
|
|
+ // A future fork must never send the parent's cached PID from its child.
|
|
+ int result = pthread_atfork(nullptr, nullptr, [] { serverCredentials.reset(); });
|
|
+ if (result) throw std::system_error(result, std::generic_category(), "pthread_atfork");
|
|
+ serverCredentials = outgoingCredentials();
|
|
+}
|
|
|
|
DarlingServer::Address::Address() {
|
|
_address.sun_family = AF_UNIX;
|
|
@@ -55,6 +74,8 @@ void DarlingServer::Address::setRawSize(size_t newRawSize) {
|
|
DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std::function<void()> sendNotificationCallback):
|
|
_sendNotificationCallback(sendNotificationCallback)
|
|
{
|
|
+ // Allocate the vector first: if it throws, there is no raw control buffer to leak.
|
|
+ _buffer.resize(bufferSpace);
|
|
size_t controlLen = CMSG_SPACE(sizeof(struct ucred)) + (descriptorSpace > 0 ? CMSG_SPACE(sizeof(int) * descriptorSpace) : 0);
|
|
_controlHeader = static_cast<decltype(_controlHeader)>(malloc(controlLen));
|
|
|
|
@@ -65,10 +86,7 @@ DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std:
|
|
_controlHeader->cmsg_len = CMSG_LEN(sizeof(struct ucred));
|
|
_controlHeader->cmsg_level = SOL_SOCKET;
|
|
_controlHeader->cmsg_type = SCM_CREDENTIALS;
|
|
- struct ucred ourCreds;
|
|
- ourCreds.pid = getpid();
|
|
- ourCreds.uid = getuid();
|
|
- ourCreds.gid = getgid();
|
|
+ struct ucred ourCreds = outgoingCredentials();
|
|
// the cmsg man page says memcpy should be used with CMSG_DATA instead of direct pointer access
|
|
memcpy(CMSG_DATA(_controlHeader), &ourCreds, sizeof(ourCreds));
|
|
|
|
@@ -83,8 +101,6 @@ DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std:
|
|
}
|
|
}
|
|
|
|
- _buffer.resize(bufferSpace);
|
|
-
|
|
_header.msg_name = &_socketAddress.raw();
|
|
_header.msg_namelen = _socketAddress.rawSize();
|
|
_header.msg_iov = &_dataDescriptor;
|
|
@@ -348,10 +364,7 @@ void DarlingServer::Message::_ensureCredentialsHeader() {
|
|
credHeader->cmsg_level = SOL_SOCKET;
|
|
credHeader->cmsg_type = SCM_CREDENTIALS;
|
|
|
|
- struct ucred creds;
|
|
- creds.pid = getpid();
|
|
- creds.uid = getuid();
|
|
- creds.gid = getgid();
|
|
+ struct ucred creds = outgoingCredentials();
|
|
memcpy(CMSG_DATA(credHeader), &creds, sizeof(struct ucred));
|
|
};
|
|
|
|
@@ -496,13 +509,52 @@ std::optional<DarlingServer::Message> DarlingServer::MessageQueue::pop() {
|
|
}
|
|
};
|
|
|
|
+// darling-lol M58ak: log small (control) messages to/from clients so the
|
|
+// dserver's request/reply routing can be correlated. Writes to a host-visible
|
|
+// path from inside the guest. Size-filtered (<= 64) to keep volume low.
|
|
+static void m58ak_log_message(const char* dir, int socket, const struct sockaddr_un* addr, const void* data, size_t size) {
|
|
+ /* darling-lol M58aw: log only pthread_canceled (call 33) messages, both
|
|
+ * directions, with the destination address, to see whether the dserver
|
|
+ * emits the replies the client is missing. */
|
|
+ if (size < sizeof(int) || data == 0) {
|
|
+ return;
|
|
+ }
|
|
+ if (*(const int*) data != 33) {
|
|
+ return;
|
|
+ }
|
|
+ /* darling-lol: replies from workers and receives on the main loop can now
|
|
+ * reach this logging helper concurrently; serialize the lazy open and the
|
|
+ * shared counter. */
|
|
+ static std::mutex m58akLogLock;
|
|
+ std::scoped_lock m58akLock(m58akLogLock);
|
|
+ static FILE* f = nullptr;
|
|
+ static unsigned long n = 0;
|
|
+ if (!f) {
|
|
+ f = fopen("/tmp/opencode/m58ak.log", "a");
|
|
+ if (!f) {
|
|
+ f = fopen("/Volumes/SystemRoot/tmp/opencode/m58ak.log", "a");
|
|
+ }
|
|
+ }
|
|
+ if (!f || n++ > 300000) {
|
|
+ return;
|
|
+ }
|
|
+ char hex[20];
|
|
+ hex[0] = '\0';
|
|
+ if (addr) {
|
|
+ for (int i = 0; i < 8; ++i) {
|
|
+ snprintf(hex + i * 2, 3, "%02x", (unsigned char) addr->sun_path[i]);
|
|
+ }
|
|
+ }
|
|
+ fprintf(f, "M58AK %s sock=%d addr=%s size=%zu\n", dir, socket, hex, size);
|
|
+ fflush(f);
|
|
+}
|
|
+
|
|
bool DarlingServer::MessageQueue::sendMany(int socket) {
|
|
bool canSendMore = true;
|
|
std::scoped_lock lock(_lock);
|
|
struct mmsghdr mmsgs[16];
|
|
size_t len = 0;
|
|
int ret = 0;
|
|
-
|
|
while (ret >= 0) {
|
|
len = 0;
|
|
for (size_t i = 0; i < _messages.size() && i < sizeof(mmsgs) / sizeof(*mmsgs); ++i) {
|
|
@@ -538,7 +590,7 @@ bool DarlingServer::MessageQueue::sendMany(int socket) {
|
|
break;
|
|
} else if (errno == EINTR) {
|
|
ret = 0;
|
|
- } else if (errno == EPIPE || errno == ECONNREFUSED) {
|
|
+ } else if (errno == EPIPE || errno == ECONNREFUSED || errno == ENOENT) {
|
|
// this means that peer of the first message we tried to send has died.
|
|
// we'll just drop that message and try the others.
|
|
_messages.pop_front();
|
|
@@ -549,6 +601,7 @@ bool DarlingServer::MessageQueue::sendMany(int socket) {
|
|
}
|
|
|
|
for (size_t i = 0; i < ret; ++i) {
|
|
+ m58ak_log_message("send", socket, &_messages.front().address().raw(), _messages.front().data().data(), _messages.front().data().size());
|
|
auto cb = _messages.front().sendNotificationCallback();
|
|
_messages.pop_front();
|
|
if (cb) {
|
|
@@ -567,10 +620,11 @@ bool DarlingServer::MessageQueue::receiveMany(int socket) {
|
|
int ret = 0;
|
|
|
|
while (ret >= 0) {
|
|
- std::array<Message, sizeof(mmsgs) / sizeof(*mmsgs)> messages;
|
|
-
|
|
- for (size_t i = 0; i < messages.size(); ++i) {
|
|
- mmsgs[i].msg_hdr = messages[i].rawHeader();
|
|
+ for (size_t i = 0; i < _receiveMessages.size(); ++i) {
|
|
+ if (!_receiveMessages[i]) {
|
|
+ _receiveMessages[i].emplace();
|
|
+ }
|
|
+ mmsgs[i].msg_hdr = _receiveMessages[i]->rawHeader();
|
|
mmsgs[i].msg_len = 0;
|
|
}
|
|
|
|
@@ -587,11 +641,25 @@ bool DarlingServer::MessageQueue::receiveMany(int socket) {
|
|
}
|
|
}
|
|
|
|
- for (size_t i = 0; i < ret; ++i) {
|
|
- messages[i].rawHeader() = mmsgs[i].msg_hdr;
|
|
- messages[i].data().resize(mmsgs[i].msg_len);
|
|
- messages[i].setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen));
|
|
- _messages.push_back(std::move(messages[i]));
|
|
+ // Adopt every returned control header before anything can throw, so all
|
|
+ // received descriptors have an owner even if deque allocation later fails.
|
|
+ for (size_t i = 0; i < static_cast<size_t>(ret); ++i) {
|
|
+ _receiveMessages[i]->rawHeader() = mmsgs[i].msg_hdr;
|
|
+ }
|
|
+ struct ReceivedMessages {
|
|
+ decltype(_receiveMessages)& slots;
|
|
+ size_t count;
|
|
+ ~ReceivedMessages() {
|
|
+ for (size_t i = 0; i < count; ++i) slots[i].reset();
|
|
+ }
|
|
+ } receivedMessages{_receiveMessages, static_cast<size_t>(ret)};
|
|
+
|
|
+ for (size_t i = 0; i < static_cast<size_t>(ret); ++i) {
|
|
+ auto& message = *_receiveMessages[i];
|
|
+ message.data().resize(mmsgs[i].msg_len);
|
|
+ message.setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen));
|
|
+ m58ak_log_message("recv", socket, &message.address().raw(), message.data().data(), message.data().size());
|
|
+ _messages.push_back(std::move(message));
|
|
}
|
|
}
|
|
|
|
@@ -615,3 +683,43 @@ bool DarlingServer::MessageQueue::empty() const {
|
|
std::unique_lock lock(_lock);
|
|
return _messages.empty();
|
|
};
|
|
+
|
|
+void DarlingServer::MessageQueue::_notifyMessageArrival() {
|
|
+ std::function<void()> callback;
|
|
+
|
|
+ {
|
|
+ std::scoped_lock lock(_lock);
|
|
+ if (_messages.empty()) {
|
|
+ return;
|
|
+ }
|
|
+ callback = _messageArrivalNotificationCallback;
|
|
+ }
|
|
+
|
|
+ if (callback) {
|
|
+ callback();
|
|
+ }
|
|
+};
|
|
+
|
|
+void DarlingServer::MessageQueue::pushAndSend(Message&& message, int socket) {
|
|
+ {
|
|
+ std::scoped_lock lock(_lock);
|
|
+ _messages.push_back(std::move(message));
|
|
+ }
|
|
+
|
|
+ bool canSendMore = true;
|
|
+
|
|
+ try {
|
|
+ // send from this thread, but only by draining the queue in order, so that
|
|
+ // a direct send can never overtake messages queued by an earlier EAGAIN
|
|
+ canSendMore = sendMany(socket);
|
|
+ } catch (...) {
|
|
+ // the messages remain queued; the receiving loop retries and reports the error
|
|
+ _notifyMessageArrival();
|
|
+ throw;
|
|
+ }
|
|
+
|
|
+ if (!canSendMore) {
|
|
+ // the socket is full; the receiving loop has to retry once it is writable
|
|
+ _notifyMessageArrival();
|
|
+ }
|
|
+};
|
|
diff --git a/src/process.cpp b/src/process.cpp
|
|
index 9cd7b81..180f372 100644
|
|
--- a/src/process.cpp
|
|
+++ b/src/process.cpp
|
|
@@ -25,9 +25,15 @@
|
|
#include <darlingserver/logging.hpp>
|
|
|
|
#include <fstream>
|
|
+#include <atomic>
|
|
#include <regex>
|
|
+#include <cstdio>
|
|
+#include <cstring>
|
|
|
|
#include <sys/mman.h>
|
|
+#include <signal.h> /* darling-lol M58DS: kill(0) liveness probe */
|
|
+#include <stdio.h> /* darling-lol M58DS: /proc state read */
|
|
+#include <string.h>
|
|
|
|
static DarlingServer::Log processLog("process");
|
|
|
|
@@ -109,6 +115,10 @@ DarlingServer::Process::Process(KernelProcessConstructorTag tag):
|
|
|
|
DarlingServer::Process::~Process() {
|
|
processLog.info() << *this << ": process being destroyed" << processLog.endLog;
|
|
+ if (_mapsFd >= 0) {
|
|
+ close(_mapsFd);
|
|
+ _mapsFd = -1;
|
|
+ }
|
|
};
|
|
|
|
DarlingServer::Process::ID DarlingServer::Process::id() const {
|
|
@@ -245,6 +255,44 @@ bool DarlingServer::Process::_readOrWriteMemory(bool isWrite, uintptr_t remoteAd
|
|
<< strerror(code)
|
|
<< ")"
|
|
<< processMemoryAccessLog.endLog;
|
|
+ /* darling-lol M58DS: log why the kernel disagrees with isDead(). */
|
|
+ {
|
|
+ char m58path[64];
|
|
+ char m58comm[64] = "?";
|
|
+ char m58state = '?';
|
|
+ int m58k = kill(id(), 0);
|
|
+ int m58kerr = errno;
|
|
+ FILE* m58f;
|
|
+ size_t m58n;
|
|
+ snprintf(m58path, sizeof(m58path), "/proc/%d/comm", (int) id());
|
|
+ m58f = fopen(m58path, "r");
|
|
+ if (m58f) {
|
|
+ if (fgets(m58comm, sizeof(m58comm), m58f)) {
|
|
+ m58n = strlen(m58comm);
|
|
+ if (m58n && m58comm[m58n - 1] == '\n') m58comm[m58n - 1] = 0;
|
|
+ }
|
|
+ fclose(m58f);
|
|
+ }
|
|
+ snprintf(m58path, sizeof(m58path), "/proc/%d/stat", (int) id());
|
|
+ m58f = fopen(m58path, "r");
|
|
+ if (m58f) {
|
|
+ char m58buf[512];
|
|
+ if (fgets(m58buf, sizeof(m58buf), m58f)) {
|
|
+ char* m58p = strrchr(m58buf, ')');
|
|
+ if (m58p && m58p[1] == ' ') m58state = m58p[2];
|
|
+ }
|
|
+ fclose(m58f);
|
|
+ }
|
|
+ processMemoryAccessLog.error()
|
|
+ << "M58DS target hostpid=" << id()
|
|
+ << " nsid=" << nsid()
|
|
+ << " isDead=" << (isDead() ? 1 : 0)
|
|
+ << " kill0=" << m58k
|
|
+ << " killerrno=" << m58kerr
|
|
+ << " state=" << m58state
|
|
+ << " comm=" << m58comm
|
|
+ << processMemoryAccessLog.endLog;
|
|
+ }
|
|
if (errorCode) {
|
|
*errorCode = code;
|
|
}
|
|
@@ -369,7 +417,18 @@ void DarlingServer::Process::notifyCheckin(Architecture architecture) {
|
|
} else {
|
|
// notify the parent process (if we have one) that we've arrived
|
|
if (auto parent = _parentProcess.lock()) {
|
|
- dtape_semaphore_up(parent->_dtapeForkWaitSemaphore);
|
|
+ /* darling-lol: the parent may already be tearing down (its
|
|
+ * fork-wait semaphore destroyed and nullified in
|
|
+ * notifyDead/kernelAsync, or across exec). A late checkin
|
|
+ * used to up the dead/NULL semaphore -> SIGSEGV *in
|
|
+ * darlingserver*, killing the whole container whenever a
|
|
+ * child (e.g. a Chromium helper) checked in after its
|
|
+ * parent died. Take the parent's lock and skip when gone. */
|
|
+ std::unique_lock parentLock(parent->_rwlock);
|
|
+ if (parent->_dtapeForkWaitSemaphore != nullptr) {
|
|
+ dtape_semaphore_up(parent->_dtapeForkWaitSemaphore);
|
|
+ }
|
|
+ parentLock.unlock();
|
|
parent->_notifyListeningKqchannels(NOTE_FORK, nsid());
|
|
}
|
|
}
|
|
@@ -465,7 +524,106 @@ DarlingServer::Process::MemoryInfo DarlingServer::Process::memoryInfo() const {
|
|
return info;
|
|
};
|
|
|
|
-static const std::regex memoryRegionEntryRegex("([0-9a-fA-F]+)\\-([0-9a-fA-F]+)\\s+((?:r|w|x|p|s|\\-)+)\\s+([0-9a-fA-F]+)");
|
|
+// parses "start-end perms offset ..." from a `/proc/<pid>/maps` line without std::regex (which is extremely slow here)
|
|
+static bool parseMapsLine(const std::string& line, uintptr_t& start, uintptr_t& end, char perms[8], uintptr_t* offset) {
|
|
+ unsigned long long s = 0, e = 0, o = 0;
|
|
+ char p[8] = {0};
|
|
+ if (sscanf(line.c_str(), "%llx-%llx %7s %llx", &s, &e, p, &o) != 4) {
|
|
+ return false;
|
|
+ }
|
|
+ start = s;
|
|
+ end = e;
|
|
+ memcpy(perms, p, 8);
|
|
+ if (offset) {
|
|
+ *offset = o;
|
|
+ }
|
|
+ return true;
|
|
+};
|
|
+
|
|
+// darling-lol: PROCMAP_QUERY (Linux 6.11+) answers "which VMA covers/follows this address" in O(log n)
|
|
+// without generating and re-parsing the whole /proc/<pid>/maps text; mach_msg with out-of-line
|
|
+// descriptors asked for that on every call and dominated darlingserver CPU. Returns 0 on success,
|
|
+// ENOENT when no VMA matches, and -1 when the ioctl is unavailable (callers fall back to the text parser).
|
|
+#include <sys/ioctl.h>
|
|
+#include <linux/fs.h>
|
|
+#include <fcntl.h>
|
|
+// Distribution headers older than Linux 6.11 (Ubuntu 24.04 ships 6.8) lack the ioctl; its ABI is
|
|
+// fixed, so define it here. On an older running kernel the ioctl fails and the text parser is used.
|
|
+#ifndef PROCMAP_QUERY
|
|
+#include <linux/types.h>
|
|
+enum procmap_query_flags {
|
|
+ PROCMAP_QUERY_VMA_READABLE = 0x01,
|
|
+ PROCMAP_QUERY_VMA_WRITABLE = 0x02,
|
|
+ PROCMAP_QUERY_VMA_EXECUTABLE = 0x04,
|
|
+ PROCMAP_QUERY_VMA_SHARED = 0x08,
|
|
+ PROCMAP_QUERY_COVERING_OR_NEXT_VMA = 0x10,
|
|
+ PROCMAP_QUERY_FILE_BACKED_VMA = 0x20,
|
|
+};
|
|
+struct procmap_query {
|
|
+ __u64 size;
|
|
+ __u64 query_flags;
|
|
+ __u64 query_addr;
|
|
+ __u64 vma_start;
|
|
+ __u64 vma_end;
|
|
+ __u64 vma_flags;
|
|
+ __u64 vma_page_size;
|
|
+ __u64 vma_offset;
|
|
+ __u64 inode;
|
|
+ __u32 dev_major;
|
|
+ __u32 dev_minor;
|
|
+ __u32 vma_name_size;
|
|
+ __u32 build_id_size;
|
|
+ __u64 vma_name_addr;
|
|
+ __u64 build_id_addr;
|
|
+};
|
|
+#define PROCMAP_QUERY _IOWR('f', 17, struct procmap_query)
|
|
+#endif
|
|
+static std::atomic<bool> procmapQueryUnsupported(false);
|
|
+
|
|
+// Returns 0 on success, ENOENT when no VMA matches, -1 when the ioctl cannot be used (callers fall back to parsing
|
|
+// /proc/<pid>/maps text). The descriptor stays open between lookups; any failure other than "no such mapping" drops it
|
|
+// and retries once with a fresh one (after an exec the old descriptor refers to a dead address space).
|
|
+int DarlingServer::Process::_procmapQuery(uint64_t address, uint64_t flags, void* result) const {
|
|
+ if (procmapQueryUnsupported.load(std::memory_order_relaxed)) {
|
|
+ return -1;
|
|
+ }
|
|
+ auto& query = *static_cast<procmap_query*>(result);
|
|
+ std::lock_guard<std::mutex> lock(_mapsMutex);
|
|
+ for (int attempt = 0; attempt < 2; ++attempt) {
|
|
+ bool fresh = false;
|
|
+ if (_mapsFd < 0) {
|
|
+ _mapsFd = open(("/proc/" + std::to_string(_pid) + "/maps").c_str(), O_RDONLY | O_CLOEXEC);
|
|
+ if (_mapsFd < 0) {
|
|
+ return -1;
|
|
+ }
|
|
+ fresh = true;
|
|
+ }
|
|
+ memset(&query, 0, sizeof(query));
|
|
+ query.size = sizeof(query);
|
|
+ query.query_flags = flags;
|
|
+ query.query_addr = address;
|
|
+ int status = ioctl(_mapsFd, PROCMAP_QUERY, &query);
|
|
+ int error = errno;
|
|
+ if (status == 0) {
|
|
+ return 0;
|
|
+ }
|
|
+ if (error == ENOENT) {
|
|
+ return ENOENT;
|
|
+ }
|
|
+ if (fresh && (error == ENOTTY || error == EINVAL)) {
|
|
+ procmapQueryUnsupported.store(true, std::memory_order_relaxed);
|
|
+ close(_mapsFd);
|
|
+ _mapsFd = -1;
|
|
+ return -1;
|
|
+ }
|
|
+ close(_mapsFd);
|
|
+ _mapsFd = -1;
|
|
+ if (fresh) {
|
|
+ return -1;
|
|
+ }
|
|
+ }
|
|
+ return -1;
|
|
+};
|
|
|
|
DarlingServer::Process::MemoryRegionInfo DarlingServer::Process::memoryRegionInfo(uintptr_t address) const {
|
|
if (isDead()) {
|
|
@@ -473,31 +631,55 @@ DarlingServer::Process::MemoryRegionInfo DarlingServer::Process::memoryRegionInf
|
|
}
|
|
|
|
MemoryRegionInfo info;
|
|
+
|
|
+ {
|
|
+ procmap_query query;
|
|
+ int status = _procmapQuery(address, 0, &query);
|
|
+ if (status == 0) {
|
|
+ info.startAddress = query.vma_start;
|
|
+ info.pageCount = (query.vma_end - query.vma_start) / sysconf(_SC_PAGESIZE);
|
|
+ info.mapOffset = query.vma_offset;
|
|
+ info.protection = 0;
|
|
+ if (query.vma_flags & PROCMAP_QUERY_VMA_READABLE) {
|
|
+ info.protection |= PROT_READ;
|
|
+ }
|
|
+ if (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) {
|
|
+ info.protection |= PROT_WRITE;
|
|
+ }
|
|
+ if (query.vma_flags & PROCMAP_QUERY_VMA_EXECUTABLE) {
|
|
+ info.protection |= PROT_EXEC;
|
|
+ }
|
|
+ info.shared = !!(query.vma_flags & PROCMAP_QUERY_VMA_SHARED);
|
|
+ return info;
|
|
+ } else if (status == ENOENT) {
|
|
+ processLog.warning() << *this << ": Address 0x" << std::hex << address << " not found in \"/proc/" << std::dec << _pid << "/maps\"" << processLog.endLog;
|
|
+ throw std::system_error(EFAULT, std::generic_category());
|
|
+ }
|
|
+ }
|
|
+
|
|
std::ifstream file("/proc/" + std::to_string(_pid) + "/maps");
|
|
std::string line;
|
|
|
|
uintptr_t endAddress;
|
|
|
|
while (std::getline(file, line)) {
|
|
- std::smatch match;
|
|
+ char permsBuffer[8];
|
|
+ uintptr_t mapOffset = 0;
|
|
|
|
- if (!std::regex_search(line, match, memoryRegionEntryRegex)) {
|
|
+ if (!parseMapsLine(line, info.startAddress, endAddress, permsBuffer, &mapOffset)) {
|
|
processLog.warning() << "Encountered malformed `/proc/<pid>/maps` entry? Definitely a bug (on our part)." << processLog.endLog;
|
|
continue;
|
|
}
|
|
|
|
- info.startAddress = std::stoul(match[1].str(), nullptr, 16);
|
|
- endAddress = std::stoul(match[2].str(), nullptr, 16);
|
|
-
|
|
if (endAddress <= address || info.startAddress > address) {
|
|
continue;
|
|
}
|
|
|
|
info.pageCount = (endAddress - info.startAddress) / sysconf(_SC_PAGESIZE);
|
|
|
|
- info.mapOffset = std::stoul(match[4].str(), nullptr, 16);
|
|
+ info.mapOffset = mapOffset;
|
|
|
|
- auto perms = match[3].str();
|
|
+ std::string perms = permsBuffer;
|
|
|
|
info.protection = 0;
|
|
|
|
@@ -657,27 +839,38 @@ void DarlingServer::Process::syncMemory(uintptr_t address, size_t size, int sync
|
|
return thread->syncMemory(address, size, sync_flags);
|
|
};
|
|
|
|
-static const std::regex memoryRegionEntryAddressRegex("([0-9a-fA-F]+)\\-([0-9a-fA-F]+)");
|
|
-
|
|
uintptr_t DarlingServer::Process::getNextRegion(uintptr_t address) const {
|
|
if (isDead()) {
|
|
throw std::system_error(ESRCH, std::generic_category(), "dead process; can't call getNextRegion");
|
|
}
|
|
|
|
+ {
|
|
+ // first region whose start is above `address`: the VMA covering-or-following address + 1, unless
|
|
+ // that one already started at or below `address`, in which case ask again from its end
|
|
+ procmap_query query;
|
|
+ int status = address == UINTPTR_MAX ? ENOENT : _procmapQuery(address + 1, PROCMAP_QUERY_COVERING_OR_NEXT_VMA, &query);
|
|
+ if (status == 0 && query.vma_start <= address) {
|
|
+ status = _procmapQuery(query.vma_end, PROCMAP_QUERY_COVERING_OR_NEXT_VMA, &query);
|
|
+ }
|
|
+ if (status == 0) {
|
|
+ return query.vma_start;
|
|
+ } else if (status == ENOENT) {
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
std::ifstream file("/proc/" + std::to_string(_pid) + "/maps");
|
|
std::string line;
|
|
|
|
while (std::getline(file, line)) {
|
|
- std::smatch match;
|
|
+ uintptr_t startAddress, endAddress;
|
|
+ char permsBuffer[8];
|
|
|
|
- if (!std::regex_search(line, match, memoryRegionEntryAddressRegex)) {
|
|
+ if (!parseMapsLine(line, startAddress, endAddress, permsBuffer, nullptr)) {
|
|
processLog.warning() << "Encountered malformed `/proc/<pid>/maps` entry? Definitely a bug (on our part)." << processLog.endLog;
|
|
continue;
|
|
}
|
|
|
|
- auto startAddress = std::stoul(match[1].str(), nullptr, 16);
|
|
- auto endAddress = std::stoul(match[2].str(), nullptr, 16);
|
|
-
|
|
if (startAddress <= address) {
|
|
continue;
|
|
}
|
|
diff --git a/src/server.cpp b/src/server.cpp
|
|
index e502e28..8d1cf2b 100644
|
|
--- a/src/server.cpp
|
|
+++ b/src/server.cpp
|
|
@@ -28,6 +28,8 @@
|
|
#include <sys/epoll.h>
|
|
#include <fcntl.h>
|
|
#include <system_error>
|
|
+#include <cstdio>
|
|
+#include <time.h>
|
|
#include <thread>
|
|
#include <array>
|
|
#include <darlingserver/registry.hpp>
|
|
@@ -40,6 +42,44 @@
|
|
|
|
#include <darlingserver/logging.hpp>
|
|
|
|
+/* darling-lol M58F129: bounded work-queue trace. With the single-threaded
|
|
+ * WorkQueue, this proves whether a process's RPCs wait behind another's: push
|
|
+ * (enqueue) and run (dequeue) timestamps per guest thread, in one monotonic
|
|
+ * clock, land in dserver.log as `M58WQ push|run pid=.. tid=.. t=<us>`. */
|
|
+#include <ctime>
|
|
+#include <atomic>
|
|
+#include <cstdlib>
|
|
+
|
|
+static bool m634TraceEnabled() {
|
|
+ static const bool enabled = [] {
|
|
+ const char* value = std::getenv("DARLING_M634_TRACE");
|
|
+ return value && value[0] == '1' && value[1] == '\0';
|
|
+ }();
|
|
+ return enabled;
|
|
+}
|
|
+
|
|
+static uint64_t m58wq_now(void) {
|
|
+ struct timespec ts;
|
|
+ clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
+ return (uint64_t)ts.tv_sec * 1000000ull + (uint64_t)(ts.tv_nsec / 1000);
|
|
+};
|
|
+
|
|
+static std::atomic<unsigned long> m58wq_count(0);
|
|
+
|
|
+static void m58wq_log(const char* what, std::shared_ptr<DarlingServer::Thread> thread) {
|
|
+ /* darling-lol M58F151: disabled (per-work-item dserver log overhead) */
|
|
+ (void) what;
|
|
+ (void) thread;
|
|
+#if 0
|
|
+ if (m58wq_count.fetch_add(1) >= 200000) return;
|
|
+ if (!thread) return;
|
|
+ auto proc = thread->process();
|
|
+ static const auto log = DarlingServer::Log("M58WQ");
|
|
+ log.error() << what << " pid=" << (proc ? proc->id() : -1)
|
|
+ << " tid=" << thread->id() << " t=" << m58wq_now() << log.endLog;
|
|
+#endif
|
|
+};
|
|
+
|
|
static DarlingServer::Server* sharedInstancePointer = nullptr;
|
|
|
|
struct DTapeHooks {
|
|
@@ -104,8 +144,44 @@ struct DTapeHooks {
|
|
|
|
std::unique_lock lock(server._timerLock);
|
|
|
|
- if (!override && server._currentTimerDeadline != 0 && deadline_ns >= server._currentTimerDeadline) {
|
|
- return;
|
|
+ /* darling-lol M634: bounded trace of timer arms to diagnose the
|
|
+ * League-launch wedge (instant semaphore timeouts + RPC storm).
|
|
+ * Rate-limited after the boot burst so launch-time behavior persists. */
|
|
+ if (m634TraceEnabled()) {
|
|
+ static std::atomic<unsigned> m634ArmCount{0};
|
|
+ unsigned seen = m634ArmCount.fetch_add(1);
|
|
+ if (seen < 120 || seen % 64 == 0) {
|
|
+ struct timespec nowTs = {0, 0};
|
|
+ clock_gettime(CLOCK_MONOTONIC, &nowTs);
|
|
+ uint64_t nowNs = (uint64_t)nowTs.tv_sec * 1000000000ull + nowTs.tv_nsec;
|
|
+ fprintf(stderr, "M634ARM line=%u deadline=%llu override=%d prev=%llu now=%llu delta=%lld\n",
|
|
+ seen,
|
|
+ (unsigned long long)deadline_ns, (int)override,
|
|
+ (unsigned long long)server._currentTimerDeadline,
|
|
+ (unsigned long long)nowNs,
|
|
+ (long long)((int64_t)deadline_ns - (int64_t)nowNs));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!override) {
|
|
+ /* darling-lol M634: the software marker races with kernel expiry
|
|
+ * (an arm landing between kernel expiry and the fire read used to
|
|
+ * be skipped, losing that timer forever). Query the kernel's own
|
|
+ * remaining time as the source of truth instead: a zero remaining
|
|
+ * value means the timer is disarmed/consumed and must be armed. */
|
|
+ struct itimerspec armedSpec;
|
|
+ if (timerfd_gettime(server._timerFD, &armedSpec) == 0
|
|
+ && (armedSpec.it_value.tv_sec != 0 || armedSpec.it_value.tv_nsec != 0)) {
|
|
+ struct timespec nowTs = {0, 0};
|
|
+ clock_gettime(CLOCK_MONOTONIC, &nowTs);
|
|
+ uint64_t armedRemaining = (uint64_t)armedSpec.it_value.tv_sec * 1000000000ull
|
|
+ + (uint64_t)armedSpec.it_value.tv_nsec;
|
|
+ uint64_t nowNs = (uint64_t)nowTs.tv_sec * 1000000000ull + nowTs.tv_nsec;
|
|
+ uint64_t armedDeadline = nowNs + armedRemaining;
|
|
+ if (deadline_ns >= armedDeadline) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
server._currentTimerDeadline = deadline_ns;
|
|
@@ -115,6 +191,11 @@ struct DTapeHooks {
|
|
}
|
|
};
|
|
|
|
+ static int dtape_hook_log_enabled(dtape_log_level_t level) {
|
|
+ static const auto log = DarlingServer::Log("dtape");
|
|
+ return log.levelEnabled(static_cast<int>(level) + 1) ? 1 : 0;
|
|
+ };
|
|
+
|
|
static void dtape_hook_log(dtape_log_level_t level, const char* message) {
|
|
static const auto log = DarlingServer::Log("dtape");
|
|
auto process = DarlingServer::Process::currentProcess();
|
|
@@ -271,11 +352,23 @@ struct DTapeHooks {
|
|
};
|
|
|
|
static void dtape_hook_task_get_memory_info(void* task_context, dtape_memory_info_t* memory_info) {
|
|
- auto info = static_cast<DarlingServer::Process*>(task_context)->memoryInfo();
|
|
- memory_info->virtual_size = info.virtualSize;
|
|
- memory_info->resident_size = info.residentSize;
|
|
- memory_info->page_size = info.pageSize;
|
|
- memory_info->region_count = info.regionCount;
|
|
+ /* darling-lol M57: a task_info(TASK_BASIC_INFO) for a process that has
|
|
+ * already exited made memoryInfo() throw std::system_error, which was
|
|
+ * uncaught here and aborted darlingserver, killing the whole session
|
|
+ * (Chromium's renderer/GPU probing dies with "dead process; can't call
|
|
+ * memoryInfo"). Fail soft with zeroes instead. */
|
|
+ try {
|
|
+ auto info = static_cast<DarlingServer::Process*>(task_context)->memoryInfo();
|
|
+ memory_info->virtual_size = info.virtualSize;
|
|
+ memory_info->resident_size = info.residentSize;
|
|
+ memory_info->page_size = info.pageSize;
|
|
+ memory_info->region_count = info.regionCount;
|
|
+ } catch (...) {
|
|
+ memory_info->virtual_size = 0;
|
|
+ memory_info->resident_size = 0;
|
|
+ memory_info->page_size = 0;
|
|
+ memory_info->region_count = 0;
|
|
+ }
|
|
};
|
|
|
|
static bool dtape_hook_task_get_memory_region_info(void* task_context, uintptr_t address, dtape_memory_region_info_t* memory_region_info) {
|
|
@@ -392,6 +485,7 @@ struct DTapeHooks {
|
|
.timer_arm = dtape_hook_timer_arm,
|
|
|
|
.log = dtape_hook_log,
|
|
+ .log_enabled = dtape_hook_log_enabled,
|
|
.get_load_info = dtape_hook_get_load_info,
|
|
|
|
.thread_suspend = dtape_hook_thread_suspend,
|
|
@@ -445,6 +539,13 @@ DarlingServer::Server::Server(std::string prefix):
|
|
{
|
|
sharedInstancePointer = this;
|
|
|
|
+ /* darling-lol: producers send their own replies by default; DSERVER_DIRECT_REPLY=0
|
|
+ * restores the old path (every reply wakes the main loop out of epoll_wait). */
|
|
+ _directReply = []() {
|
|
+ const char* value = std::getenv("DSERVER_DIRECT_REPLY");
|
|
+ return !(value && value[0] == '0' && value[1] == '\0');
|
|
+ }();
|
|
+
|
|
// remove the old socket (if it exists)
|
|
unlink(_socketPath.c_str());
|
|
|
|
@@ -540,6 +641,7 @@ void DarlingServer::Server::start() {
|
|
// TODO: this could be done concurrently
|
|
auto call = DarlingServer::Call::callFromMessage(std::move(*msg));
|
|
if (call) {
|
|
+ m58wq_log("push", call->thread());
|
|
_workQueue.push(call->thread());
|
|
}
|
|
}
|
|
@@ -549,13 +651,15 @@ void DarlingServer::Server::start() {
|
|
eventfd_t value;
|
|
eventfd_read(_wakeupFD, &value);
|
|
|
|
- if (_canWrite) {
|
|
+ if (_canWrite || !_outbox.empty()) {
|
|
_canWrite = _outbox.sendMany(_listenerSocket);
|
|
}
|
|
|
|
struct epoll_event settings;
|
|
settings.data.ptr = &_wakeupFD;
|
|
- settings.events = (_canWrite) ? (EPOLLIN | EPOLLONESHOT) : 0;
|
|
+ // keep the wakeup armed while replies may still be waiting for the socket
|
|
+ // to become writable again, so a producer's failed direct send is retried
|
|
+ settings.events = (_canWrite || !_outbox.empty()) ? (EPOLLIN | EPOLLONESHOT) : 0;
|
|
|
|
if (epoll_ctl(_epollFD, EPOLL_CTL_MOD, _wakeupFD, &settings) < 0) {
|
|
throw std::system_error(errno, std::generic_category(), "Failed to modify eventfd in epoll context");
|
|
@@ -604,6 +708,25 @@ void DarlingServer::Server::start() {
|
|
continue;
|
|
}
|
|
|
|
+ /* darling-lol M634: the timer is now consumed; drop the stale
|
|
+ * armed-deadline marker so future arms are not skipped. Also
|
|
+ * trace the fire (rate-limited) to diagnose the launch wedge. */
|
|
+ if (m634TraceEnabled()) {
|
|
+ static std::atomic<unsigned> m634FireCount{0};
|
|
+ unsigned seen = m634FireCount.fetch_add(1);
|
|
+ if (seen < 80 || seen % 64 == 0) {
|
|
+ struct timespec nowTs = {0, 0};
|
|
+ clock_gettime(CLOCK_MONOTONIC, &nowTs);
|
|
+ uint64_t nowNs = (uint64_t)nowTs.tv_sec * 1000000000ull + nowTs.tv_nsec;
|
|
+ fprintf(stderr, "M634FIRE line=%u exp=%llu prevDeadline=%llu now=%llu\n",
|
|
+ seen,
|
|
+ (unsigned long long)expirations,
|
|
+ (unsigned long long)_currentTimerDeadline,
|
|
+ (unsigned long long)nowNs);
|
|
+ }
|
|
+ }
|
|
+ _currentTimerDeadline = 0;
|
|
+
|
|
// we're done handling the timerfd;
|
|
// we don't need to lock anymore (and the following call might need to arm the timer again)
|
|
lock.unlock();
|
|
@@ -681,6 +804,7 @@ std::string DarlingServer::Server::prefix() const {
|
|
};
|
|
|
|
void DarlingServer::Server::_worker(std::shared_ptr<Thread> thread) {
|
|
+ m58wq_log("run", thread);
|
|
thread->doWork();
|
|
};
|
|
|
|
@@ -815,5 +939,20 @@ std::shared_ptr<DarlingServer::FD> DarlingServer::Monitor::fd() const {
|
|
};
|
|
|
|
void DarlingServer::Server::sendMessage(Message&& message) {
|
|
- _outbox.push(std::move(message));
|
|
+ if (!_directReply) {
|
|
+ _outbox.push(std::move(message));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* darling-lol: the thread that produced the message usually sends it itself,
|
|
+ * instead of waking the main loop for a second pass through its event loop.
|
|
+ * pushAndSend only ever sends from the head of the outbox, so ordering is the
|
|
+ * same as the main loop's, and it wakes the main loop when the socket is full. */
|
|
+ try {
|
|
+ _outbox.pushAndSend(std::move(message), _listenerSocket);
|
|
+ } catch (const std::system_error&) {
|
|
+ /* pushAndSend has already left the message queued and woken the main loop,
|
|
+ * where unexpected send errors have always been reported; a worker
|
|
+ * microthread must not throw. */
|
|
+ }
|
|
};
|