◒ daybreak

Inside the Backstep loader

Follow a small Mach-O fixture from bytes to mapped memory. The interesting work happens between finding an entry point and supplying the environment that makes entering it meaningful.

Read the image before executing it

Mach-O describes segment placement, file-backed bytes, entry metadata, and dynamic linking information. Apple’s loader definitions distinguish file offsets from virtual addresses and segment file size from virtual size. A zero-filled tail occupies memory without requiring equivalent bytes in the file. [loader]

Backstep first derives a load plan rather than blindly copying an executable to a guessed address. The mapping and bind stages are separate. The educational diagram below uses a tiny invented image so an address change has an obvious consequence. [map]

Figure 1. Synthetic Mach-O image → host memory. Addresses are invented; relocation arithmetic is live.
Rebasing relocates an internal reference. Binding resolves an external function. Selecting a different arena changes the former; the illustrative host symbol stays fixed. The rejected-input states are shown explicitly. Segment sizes and addresses are teaching values, not a captured binary.

A relocatable arena, not guest-address MAP_FIXED

The inspected mapper asks the host for an arena and translates guest addresses into mapped-region pointers. It does not demand that the host overwrite mappings at the guest’s preferred address. Within a covered region, the offset from that region’s guest base identifies the corresponding host byte. [map]

illustrative guest base = 0x100000000
illustrative host base  = 0x080000000
guest reference        = 0x100000120
host reference         = host base + 0x120 = 0x080000120

This arithmetic is a teaching simplification of the region lookup. A real lookup must first establish that an address belongs to a mapped region. Gaps are not valid objects just because subtraction produces a plausible pointer.

Copying and fixups happen while the arena is writable. Final permissions are built from the planned initial protections at the host’s page granularity. The mapper flushes executable ranges, protects gaps, and rejects a host page that would become writable and executable. Host page size matters because distinct segments may share a page. [map]

Rebase and bind solve different problems

A rebase repairs a reference whose target belongs to the mapped image. A bind supplies the address of a dependency outside that image. In the inspected execution path, link-edit application requests host pointer space and uses the libSystem resolver. A symbol being present is only a first condition: the call must match the target function’s ABI and semantics. [map], [bind]

The resolver’s explicit handling of printf and snprintf is a useful example. Non-Darwin builds avoid simply selecting host printf through dlsym for these entries; the bridge provides special handling for the Apple variadic calling convention. A name match would not, by itself, make the parameter layout correct. [bind]

A missing import should terminate the attempt before entry rather than become a plausible-looking null or guessed target. The missing-import control shows that boundary. It does not emulate an Objective-C framework or build an entire dependency graph.

LC_MAIN is called on the host C stack

loadExecutable lays out argc, argv, envp, and apple-vector data in a separate mapping. However, callEntry calls a C function pointer directly. Its comment explicitly says that the guest uses the host C stack; the prepared stack pointer is not switched into service here. [map]

Building something shaped like a Darwin stack is not the same operation as starting a Darwin process on that stack.

The distinction affects what can reasonably be tested. A small function can return through its host caller. An application expecting process-wide signal ownership, runtime initialization, thread-local lifetime rules, or a particular unwinder requires more than that entry call. The execute path also rejects arm64e with an authenticated-fixup explanation; parsing that architecture is not the same as executing it. [map]

Why an iOS app needs more than a loader

Loading code establishes addresses, not behavior. The next questions are concrete: which imported functions are invoked before main, what object metadata is registered, which services are contacted, how does a scene become active, and what happens when the process returns from the background? Treating these as separate contracts makes failures investigable.

A share sheet is a good example of the distinction. Calling an Android chooser from a Swift-facing shim would demonstrate a host share bridge. Presenting and completing a guest iOS sharing flow would exercise a different chain of framework, lifecycle, data-transfer, and service behavior. Neither follows from a successful canary return.

For a useful experiment, choose one self-authored fixture and one observable outcome. Preserve its build recipe, import inventory, runtime mode, expected result, and failure output. The downloadable loader snapshot is intended for that scale of work; it is not a universal app installer.

Repeat a bounded experiment

Start with parse-only inventory. Then execute only the included self-authored canary in a separate process. The download page records the isolated build and test result, checksum, platform, and remaining distribution questions. Android cross-build and on-device behavior are separate checks.

swift build
.build/debug/machparse Tests/MachParseTests/Fixtures/canary_ios
.build/debug/machparse exec Tests/MachParseTests/Fixtures/canary_ios

The execution example is for a compatible arm64 host and the included fixture. A nonzero canary result can be the intended test outcome, so read the fixture source and captured result rather than interpreting every nonzero status as a loader failure.

References and evidence

Implementation references describe a working-tree snapshot. They are not all present in a public repository. The source inventory records file hashes; historical notes are not new device tests.

  1. [loader] Apple, Mach-O loader structures. Primary format definitions; rolling source, accessed 2026-09-10.
  2. [map] Daybreak, SegmentMapper.swift / ApplyLinkEdit.swift / Execute.swift. Inspected working tree, 2026-09-10; machparse/Sources/MachParse/. Mapping, relocation, protection and host-stack entry. Private workspace source; collaborator access required.
  3. [bind] Daybreak, LibSystemStubs.swift. Inspected working tree, 2026-09-10; libSystem-only resolver and explicit variadic ABI handling. Private workspace source; collaborator access required.

Full source inventory and provenance →

← Research index · Daybreak Terminal proposal →