Skip to content

Plan: Full Xcode Integration

Date: 2026-04-13 Status: CDS inject WORKING. Device visible in Xcode as state=connected. Pair click currently fails with "error communicating with remote process" because the CDS+0xB896 hook short-circuits Swift async continuation. Action interception strategy needs redesign — see action-interception-full-picture.md.

Goal

iPhone connected to Linux → appears as native device in Xcode on macOS VM.

CURRENT STATUS

What works (session 4-5, 2026-04-11/12)

  • Device visible in Xcode Devices & Simulators as "iPhone (iosmux)"
  • devicectl: state=connected, model=iPhone SE (3rd generation) (iPhone14,6)
  • Real RSD transport: xpc_remote_connection via pymobiledevice3 tunnel
  • RSD Handshake: 46 device properties + 74 services with ports
  • RSDDeviceWrapper: created successfully
  • DYLD_INTERPOSE: all hooks via clean C functions, no shared cache __TEXT patches
  • DDI: mounted on iPhone, DeviceInfo reflects this correctly

Current blocker

Clicking "Pair" in Xcode → "error communicating with remote process".

Two related causes (one fixed in source, untested as of writing):

  1. Wrapper read overflow (fixed in commit 59ca5cd, untested) g_hook_wrapper = *(void **)((uint8_t *)sdr + 104) was reading 16 bytes past the end of the 88-byte SDR object. CDS+0xCCB0 / CDS+0x5E2D0 hooks then returned that garbage pointer to CDS, which dereferenced it and crashed. Fixed by storing wrapper in g_hook_wrapper directly in Step 9b.

  2. CDS+0xB896 broken hook (still active) mov al, 1; ret short-circuits invoke(anyOf:usingContentsOf:). Mercury never sends an XPC reply → Xcode timeout → "error communicating". Correct disposition: convert to passthrough trampoline (movabs r10, <orig>; jmp r10) and intercept actions at a higher level (XPC dispatch or Swift invoke).

Next steps

  1. Test wrapper-fix in isolation. May reveal that the "Pair crash" was wrapper-related, not action-related.
  2. Convert CDS+0xB896 hook to passthrough trampoline.
  3. Capture real Mercury XPC messages with a logging interpose to learn the wire format.
  4. Design proper action interceptor based on captured data.
  5. Set missing DeviceInfo fields (unverified hypothesis — may not be needed).
  6. Test app deployment through tunnel.

DEAD PATHS — DO NOT RETRY

1. remotepairingd patching — DEAD

Why: DataVaultHelper checks remotepairingd's entitlements via Mercury XPC. Ad-hoc codesign = empty entitlements in Mercury view. Independent of SIP and AMFI. Cannot produce DER provisioned entitlements without Apple signing cert.

Gates bypassed via lldb (all 3) but objects die after detach.

2. CoreDevice Plugin (.coredeviceplugin) — DEAD

Why: CoreDevice.framework has NO public .swiftmodule or .swiftinterface files. Synthetic .swiftinterface compiled but CRASHED at runtime: protocol witness tables from fake interfaces don't match real CoreDevice witness tables. No CoreDevice plugins exist in the wild — this API is not viable.

3. usbmuxd / usbfluxd — DEAD for iOS 17+

Why: iOS 17 moved developer services from lockdown to RemoteXPC protocol. usbfluxd can forward lockdown port 62078 but developer services aren't there.

4. lockdown pair via TCP — DEAD

Why: lockdownd rejects pairing via network: GetProhibitedError.

5. pymobiledevice3 remote pair — DEAD (no manual-pairing service)

Why: _remotepairing-manual-pairing._tcp not advertised on bridge interface.

LIVE PATH: CDS Inject + Real RSD + DYLD_INTERPOSE

Architecture

iPhone ←USB→ Linux (iosmux bridge) ←virbr0→ macOS VM
                                              ├── pymobiledevice3 tunneld (TUN tunnel, IPv6)
                                              └── CoreDeviceService + iosmux_inject.dylib
                                                   ├── DYLD_INTERPOSE (7 function hooks)
                                                   ├── CDS binary patches (2 safe hooks)
                                                   ├── xpc_remote_connection (RSD transport)
                                                   └── Direct TCP to tunnel services (74 ports)

Hook inventory

DYLD_INTERPOSE (safe, GOT rewrite, no __TEXT modification)

Function Replacement Purpose
remote_device_copy_service_names static array Prevent blocking XPC query
remote_device_copy_service return NULL Prevent blocking XPC query
remote_device_copy_property return NULL Prevent blocking XPC query
remote_device_heartbeat callback(true) Prevent blocking XPC heartbeat
remote_service_create_connected_socket TCP to tunnel Direct service connection
remote_service_connect_socket TCP to tunnel Direct service connection
xpc_remote_connection_create_with_remote_service create from fd RemoteXPC service connection

CDS binary inline patches (safe, not shared cache)

Location Purpose
CDS+0xCCB0 Helper hook — returns wrapper for our UUID
CDS+0x5E2D0 callq patch — intercepts withRSDDeviceWrapper chain

Key technical facts

  • xpc_remote_connection = HTTP/2 server mode (receives Handshake, can't send request/reply)
  • Tunnel port (dynamic) = direct TUN route to iPhone remoted (not TCP proxy)
  • 74 services available via Handshake (ports + properties + UsesRemoteXPC flags)
  • Go relay NOT needed — all data from Handshake + direct TCP
  • DDI mounted on iPhone (IsMounted=true, MountPath=/System/Developer)
  • DevicePreparedness: 1=devMode, 2=ddi, 4=extendedInfo, 8=powerAssertion, 0xF=all
  • DeviceState: 0=unavailable, 1=disconnected, 2=connecting, 3=connected
  • PairingState: 0=unpaired, 1=pairingInProgress, 2=paired, 3=unsupported
  • NEVER use launchctl bootout/bootstrap on system XPC services

Bugs fixed (sessions 4-6)

# Bug Root cause Fix
1 Enum tags swapped state=2 is connecting not connected state=3, pairingState=2
2 copy_service hook crash fprintf with invalid ptr (wrong ABI) Plain xor eax; ret
3 Heartbeat blocks init XPC via server-mode connection Interpose callback(true)
4 Shared cache SIGSEGV COW pages from vm_protect DYLD_INTERPOSE migration
5 sdr_lookup crash strrchr NULL+1 in fprintf Removed verbose logging
6 xpc_remote_connection hook Was breaking own RSD connection Removed from hook list
7 Wrapper read overflow at sdr+104 SDR is 88 bytes, read 16 bytes past g_hook_wrapper = wrapper directly in Step 9b

VM Configuration

SIP: disabled
Boot args: keepsyms=1 amfi_get_out_of_my_way=1
Xcode: 26.4
pymobiledevice3: tunneld on /Users/nullweft/pymobiledevice3-venv/ (root)
CDS inject: /Library/Developer/CoreDevice/iosmux_inject.dylib (via insert_dylib)