Files built for recent systems replace the rebase and bind opcodes with chained
fixups. Each pointer in the data, in spare bits of its 8 bytes, holds the distance to
the next pointer on its page, and either a target to rebase to or an index into a
table of imports. LC_DYLD_CHAINED_FIXUPS points at a block in
__LINKEDIT with that table and with where each chain starts.
dyld_chained_fixups_header
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 4 | fixups_version | 0. |
| 4 | 4 | starts_offset | Offset of the chain starts from the start of this header. |
| 8 | 4 | imports_offset | Offset of the imports table. |
| 12 | 4 | symbols_offset | Offset of the symbol names. |
| 16 | 4 | imports_count | Number of imports. |
| 20 | 4 | imports_format | 1 DYLD_CHAINED_IMPORT, 2 with a 32-bit addend, 3 with a 64-bit addend. |
| 24 | 4 | symbols_format | 0 plain, 1 zlib compressed. |
Chain starts
dyld_chained_starts_in_image is a seg_count and
seg_info_offset[seg_count], one for each segment in order; 0 means the
segment has no fixups. Each offset, from the start of this structure, leads to:
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 4 | size | The size of this structure. |
| 4 | 2 | page_size | 0x1000 or 0x4000. |
| 6 | 2 | pointer_format | The format of the pointers. |
| 8 | 8 | segment_offset | Offset of the segment in memory. |
| 16 | 4 | max_valid_pointer | For 32-bit formats: any value beyond this is not a pointer. |
| 20 | 2 | page_count | Pages in the array. |
| 22 | 2 each | page_start | The offset in each page of its first fixup; 0xFFFF if there is none. |
To read one, start at page_start in each page and follow the pointers.
In each pointer the field next gives the distance to the next one in
units of the format’s stride (4 or 8 bytes); 0 ends the chain. A segment in a
format with several starts per page sets the top bit of
page_start instead, and lists the starts after page_start.
Pointer formats
| Value | Name | Stride | Target |
|---|---|---|---|
| 1 | DYLD_CHAINED_PTR_ARM64E | 8 | A rebase target that is an address. |
| 2 | DYLD_CHAINED_PTR_64 | 4 | A rebase target that is an address. |
| 3 | DYLD_CHAINED_PTR_32 | 4 | A rebase target that is an address. |
| 4 | DYLD_CHAINED_PTR_32_CACHE | 4 | |
| 5 | DYLD_CHAINED_PTR_32_FIRMWARE | 4 | |
| 6 | DYLD_CHAINED_PTR_64_OFFSET | 4 | An offset from the image. |
| 7 | DYLD_CHAINED_PTR_ARM64E_KERNEL | 4 | An offset. |
| 8 | DYLD_CHAINED_PTR_64_KERNEL_CACHE | 4 | Offset from a cache base. |
| 9 | DYLD_CHAINED_PTR_ARM64E_USERLAND | 8 | An offset. |
| 10 | DYLD_CHAINED_PTR_ARM64E_FIRMWARE | 4 | An address. |
| 11 | DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE | 1 | |
| 12 | DYLD_CHAINED_PTR_ARM64E_USERLAND24 | 8 | An offset, with a 24-bit bind ordinal. |
| 13 | DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE | 8 | Offsets from the shared cache. |
| 14 | DYLD_CHAINED_PTR_ARM64E_SEGMENTED | 4 | A segment index and offset. |
| 15 | DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE_V3 | 8 | Offsets from the shared cache. |
| 16 | DYLD_CHAINED_PTR_SHARED_CACHE_V2 | An offset, on x86-64. |
The common 64-bit form (2 and 6), low bit first: a rebase has target
36 bits, high8 8, reserved 7, next 12,
bind 1 (0). A bind has ordinal 24 bits, addend
8, reserved 19, next 12, bind 1 (1).
The arm64e forms (1, 9 and 12) have an auth bit at the top and a
bind bit below it. A plain rebase has target 43 bits,
high8 8, next 11. A bind has ordinal 16 bits (24
in format 12) and a 19-bit addend. An authenticated pointer holds
target or ordinal, then diversity 16 bits,
addrDiv 1 and key 2.
Imports
| Format | Size | Layout |
|---|---|---|
| DYLD_CHAINED_IMPORT | 4 | lib_ordinal 8 bits, weak_import 1, name_offset 23. |
| DYLD_CHAINED_IMPORT_ADDEND | 8 | The same, then a signed 32-bit addend. |
| DYLD_CHAINED_IMPORT_ADDEND64 | 16 | lib_ordinal 16 bits, weak_import 1, reserved 15, name_offset 32, then a 64-bit addend. |
name_offset is measured from the start of the symbol names. A
lib_ordinal of 0 means this image, -1 the main executable
(stored as 0xFF, or 0xFFFF in the 64-bit form), −2 flat lookup and −3 weak lookup.
In the explorer
The explorer lists the header, the chain starts and the imports, and walks each chain to list every fixup. The pointer formats it decodes are 1, 2, 3, 6, 7, 8, 9, 11 and 12: the ones used in Mach-O files outside the shared cache and firmware.