Target: implement the Darwin ARM64 support to complete the port#207
Conversation
118cf12 to
72df59a
Compare
andrurogerz
left a comment
There was a problem hiding this comment.
looks good
This gives us a complete implementation for the Darwin ARM64 target
Do Thread::step and Thread::afterResume also need implementing for full functionality?
Ah, crud, forgot about those! Thanks |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9d6e4bc84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fb500e946
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b9b2a08b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e04688194d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9148f7caeb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b51975c9d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Process::allocateMemory/deallocateMemory inject a small syscall stub and run it via PTrace::execute, which reads/writes CPU state and memory through ptrace directly. Darwin's ptrace can't do either -- Host::Darwin::PTrace::readCPUState/writeCPUState/readMemory/writeMemory are all DS2BUG stubs, since Darwin uses Mach for register/memory access instead -- so execute() aborts the server the first time anything allocates memory in the inferior (e.g. an LLDB _M packet on x86_64, where this path already existed). Add Process::executeCode(), running the same save-state/inject/ resume/restore sequence through Mach for state/memory and the existing PTrace resume/wait to actually run the code. It takes the code as a ByteVector const&, matching Linux::Process::executeCode's signature, rather than a raw pointer/length pair. Point x86_64's allocate/ deallocateMemory at it instead of the ptrace path it was on before. resume/wait/kill need a process-only ProcessThreadId, not the thread-specific one Mach's calls use. POSIX::PTrace::wait rejects anything else outright (tid must be <= kAnyThreadId) before calling waitpid, so it always returned kErrorInvalidArgument without waiting -- an error the unconditional restore then silently overwrote, letting executeCode report success without the injected code ever running. Darwin::PTrace::kill is worse: it hits a fatal DS2BUG for anything but a process-only ptid, aborting the whole server on any injection failure instead of killing the inferior. Both now use a separate process-only ptid, and the restore no longer clobbers an earlier failure's error/result. executeCode() also translates Darwin's syscall ABI for its callers. Darwin signals failure via the carry flag with the return register holding a positive errno (XNU's arm_prepare_u64_syscall_return); Linux encodes failure as a negative return value directly instead (Linux::Process::checkMemoryErrorCode: "the raw kernel return value ... or -errno if the call fails"), which is the convention the MAP_FAILED/`< 0` checks here were actually written for. executeCode now checks the carry flag (CPSR bit 29 on ARM64, EFLAGS bit 0 on X86_64) and synthesizes -1 to match. On AArch64, writing the injected/restored code also needs a cache flush before the thread resumes: unlike x86, AArch64 doesn't keep instruction fetches coherent with data writes, so the thread could fetch a stale cached instruction after either write instead of what was just written. Add Mach::flushInstructionCache() (via mach_vm_machine_attribute/MATTR_CACHE) and call it after both the injection and restore writes, ARM64-only.
Implements ARM64 mmap/munmap syscall generation for Process::allocateMemory/deallocateMemory (through the new Process::executeCode(), not the ptrace path that doesn't work on Darwin), and hardware single-step for Thread::step/afterResume, giving Darwin ARM64 a complete target implementation. CI moves the macOS build onto a real arm64/x86_64 matrix now that both are supported, setting CMAKE_OSX_ARCHITECTURES so Clang actually targets the arch CMAKE_SYSTEM_PROCESSOR tells ds2's own source selection to build for (the latter alone only picks source files, it doesn't make the compiler emit that architecture's code). CMAKE_SYSTEM_PROCESSOR itself doesn't track CMAKE_OSX_ARCHITECTURES on Apple platforms -- it reflects the host running the configure, not the target -- so DS2_ARCHITECTURE resolved to the host's arch (ARM64) even on the x86_64 leg, leaving X86/RegistersDescriptors.h ungenerated. CMakeLists.txt now overrides CMAKE_SYSTEM_PROCESSOR from CMAKE_OSX_ARCHITECTURES itself (when it names exactly one architecture) before DS2_ARCHITECTURE is computed, so this holds regardless of what the caller passes; CI's now-redundant -D CMAKE_SYSTEM_PROCESSOR is dropped. The mmap/munmap syscall stubs encode a small AArch64 instruction sequence and execute it in the inferior. They use x16 for the syscall number, Darwin's calling convention (Linux AArch64 uses x8 instead, a plain argument register here). mmap's size operand loads from a literal pool entry placed after the 9-instruction stub; the ldr's offset now correctly points at that literal instead of one instruction short of it. x86_64 single-steps by toggling EFLAGS.TF in the GP register file this target already reads/writes; AArch64 has no equivalent GP bit. Its hardware single-step needs two pieces of state armed together: PSTATE.SS (CPSR bit 21, reachable via the existing GP CPUState) and MDSCR_EL1.SS (bit 0 of a separate debug register set Mach never exposed), and the CPU clears PSTATE.SS itself once the step traps. Mach::setSingleStep() (ARM64-only) gets/sets the ARM_DEBUG_STATE64 thread state to flip the MDSCR_EL1 bit; step() arms both bits before resuming, afterResume() clears them, mirroring ThreadX86_64.cpp. step() also has to set _state to kStepped after resume() returns: resume() itself sets kRunning, but Process::wait()'s ignored-signal path (SIGSTOP/SIGCHLD) checks kStepped to decide whether an interrupt mid-step should be continued with another step or a plain resume. Without it, an interrupt during a requested step lets the inferior run freely instead of completing exactly one instruction.
Thread::step() sets EFLAGS.TF and resumes, but never records that a step is in progress: resume() sets _state to kRunning and step() returns whatever it returns, without setting kStepped afterward. Process::wait()'s ignored-signal path (SIGSTOP/SIGCHLD) checks kStepped to decide whether an interrupt mid-step should be continued with another step or a plain resume; without it, an interrupt during a requested step lets the inferior run freely instead of completing exactly one instruction.
This implements the necessary ARM64 specific logic for mmap and munmap syscalls to be able to allocate and deallocate memory in the inferior. This gives us a complete implementation for the Darwin ARM64 target.