mirror of
https://github.com/Sikarugir-App/rosettax87_jit.git
synced 2026-11-05 14:50:25 -08:00
loader: select detach path by XNU build version
detach_golden_gate() (a 1:1 port of debugserver's MachProcess::Detach) is correct only on new-enough XNU; on older kernels it leaves the tracee P_LTRACED, forcing SIG_DFL on all signals and freezing signal-driven GUI apps, so those need the classic detach(). Parse the xnu build number from kern.version and route to detach_golden_gate() at >= 13432.0.94.501.4, otherwise detach(). Comparison is component-wise numeric (a string compare mis-orders "9" vs "13432" and the dotted tail); falls back to the classic path on any sysctl/parse failure. This is a temporary workaround: the threshold is empirical and the divergence is not yet understood. Once Apple releases the corresponding XNU sources we can properly investigate the root cause and, ideally, converge on a single detach path that works across kernels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
cd411f741b
commit
92107acde0
1 file changed
+98
-1
@@ -7,6 +7,7 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sched.h>
|
||||
|
||||
@@ -33,6 +34,55 @@ const char* logsEnabled = nullptr;
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Returns true when the running XNU build is >= the given threshold build
|
||||
// number, compared component-wise (numeric), parsed from kern.version's
|
||||
// "root:xnu-<A.B.C.D.E>~<F>" token. On any parse/sysctl failure, returns
|
||||
// `fallback` (default false => keep the conservative detach() path).
|
||||
//
|
||||
// A string compare would be wrong here ("9" > "13432" lexically, and the dotted
|
||||
// tail needs numeric ordering), so we compare each dotted component as an int.
|
||||
// The trailing "~N" revision suffix is ignored. Missing trailing components in
|
||||
// the running build are treated as 0, so an exact prefix match counts as "at
|
||||
// least" (>=).
|
||||
static bool xnuBuildAtLeast(const int* threshold, size_t nThreshold, bool fallback = false) {
|
||||
char buf[512];
|
||||
size_t len = sizeof(buf);
|
||||
if (sysctlbyname("kern.version", buf, &len, nullptr, 0) != 0) {
|
||||
return fallback;
|
||||
}
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
const char* p = strstr(buf, "root:xnu-");
|
||||
if (!p) {
|
||||
return fallback;
|
||||
}
|
||||
p += strlen("root:xnu-");
|
||||
|
||||
for (size_t i = 0; i < nThreshold; ++i) {
|
||||
// A component must start with a digit; anything else (end of token,
|
||||
// "~", "/") means the running build has no more components -> treat as 0.
|
||||
int running = 0;
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
char* end = nullptr;
|
||||
long v = strtol(p, &end, 10);
|
||||
running = (int)v;
|
||||
p = end;
|
||||
if (*p == '.') {
|
||||
++p; // step over the separator to the next component
|
||||
}
|
||||
}
|
||||
if (running != threshold[i]) {
|
||||
return running > threshold[i];
|
||||
}
|
||||
}
|
||||
// All compared components equal -> at least the threshold.
|
||||
return true;
|
||||
}
|
||||
|
||||
// XNU build 13432.0.94.501.4~1 — first kernel where the debugserver-style
|
||||
// detach (detach_golden_gate) is correct; older kernels need detach().
|
||||
static const int kGoldenGateXnuBuild[] = {13432, 0, 94, 501, 4};
|
||||
|
||||
typedef const struct dyld_process_info_base* DyldProcessInfo;
|
||||
|
||||
extern "C" DyldProcessInfo _dyld_process_info_create(task_t task, uint64_t timestamp,
|
||||
@@ -211,6 +261,39 @@ public:
|
||||
}
|
||||
|
||||
bool detach() {
|
||||
// PT_DETACH requires the tracee to be in a BSD signal-stop. Under
|
||||
// PT_ATTACHEXC our stops arrive as Mach exceptions (the BRK is a bare
|
||||
// EXC_BREAKPOINT carrying no signal), which PT_DETACH rejects with
|
||||
// EBUSY. So drive the tracee into a SIGSTOP job-control stop and hold
|
||||
// it — the approach debugserver takes. SIGSTOP can't be caught or
|
||||
// ignored, and PT_DETACH only accepts a held signal-stop like this one
|
||||
// (a bare EXC_BREAKPOINT or a running process both give EBUSY).
|
||||
kill(childPid_, SIGSTOP);
|
||||
if (!exc_.reply(0)) { // release the held stop; run into the SIGSTOP
|
||||
return false;
|
||||
}
|
||||
if (!waitForStopped(SIGSTOP)) {
|
||||
fprintf(stderr, "detach: failed to reach SIGSTOP stop\n");
|
||||
return false;
|
||||
}
|
||||
// Restore the task's exception ports, PT_DETACH from the held SIGSTOP
|
||||
// stop, then release the exception (unblocking the thread). Order
|
||||
// matters: PT_DETACH must run while the SIGSTOP exception is still held,
|
||||
// and the release must run after (ptrace is no longer valid post-detach).
|
||||
exc_.restoreAndTearDown();
|
||||
bool ok = true;
|
||||
if (ptrace(PT_DETACH, childPid_, (caddr_t)1, 0) < 0) {
|
||||
perror("ptrace(PT_DETACH)");
|
||||
ok = false;
|
||||
}
|
||||
exc_.release();
|
||||
if (ok) {
|
||||
LOG("Debugger detached.\n");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool detach_golden_gate() {
|
||||
// Exact 1:1 port of lldb debugserver's MachProcess::Detach()
|
||||
// (llvm lldb/tools/debugserver/source/MacOSX/MachProcess.mm). Order:
|
||||
//
|
||||
@@ -1154,7 +1237,21 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// replace the exports in X19 register with the address of the mapped macho
|
||||
dbg.setRegister(MuhDebugger::Register::X19, machoExportsAddress);
|
||||
dbg.detach();
|
||||
|
||||
// Pick the detach path by kernel version. detach_golden_gate() (a 1:1 port
|
||||
// of debugserver's MachProcess::Detach) is correct only on new-enough XNU;
|
||||
// on older kernels it leaves the tracee P_LTRACED (forces SIG_DFL on all
|
||||
// signals, freezing signal-driven GUI apps), so those use detach().
|
||||
bool useGoldenGate = xnuBuildAtLeast(
|
||||
kGoldenGateXnuBuild, sizeof(kGoldenGateXnuBuild) / sizeof(kGoldenGateXnuBuild[0]));
|
||||
|
||||
if (useGoldenGate) {
|
||||
LOG("Using detach_golden_gate (xnu >= 13432.0.94.501.4)\n");
|
||||
dbg.detach_golden_gate();
|
||||
} else {
|
||||
LOG("Using detach (xnu < 13432.0.94.501.4)\n");
|
||||
dbg.detach();
|
||||
}
|
||||
|
||||
// Block until the parent (wine) exits. We can't use waitpid since
|
||||
// the parent is not our child, so use kqueue with EVFILT_PROC.
|
||||
|
||||
Reference in new issue
Block a user