LC_DYLD_INFO and LC_DYLD_INFO_ONLY point at tables the dynamic linker uses to load a file: what to rebase, what to bind and what is exported. Files built for recent systems use chained fixups and an LC_DYLD_EXPORTS_TRIE instead.
dyld_info_command
| Offset | Size | Field | Points at |
|---|---|---|---|
| 8 | 4 each | rebase_off, rebase_size | Rebase opcodes. |
| 16 | 4 each | bind_off, bind_size | Bind opcodes. |
| 24 | 4 each | weak_bind_off, weak_bind_size | Weak bind opcodes. |
| 32 | 4 each | lazy_bind_off, lazy_bind_size | Lazy bind opcodes. |
| 40 | 4 each | export_off, export_size | The export trie. |
All the data is byte-sized, so it reads the same in either byte order. Numbers inside it are ULEB128 (or SLEB128 for an addend).
Rebase opcodes
Conceptually the rebase information is a table of segment, offset and type: the places that must be adjusted if the image loads somewhere other than its preferred address. Each opcode byte is a high nibble and an immediate low nibble:
| Opcode | Constant | Effect |
|---|---|---|
0x00 | REBASE_OPCODE_DONE | The table ends. |
0x10 | REBASE_OPCODE_SET_TYPE_IMM | Set the type to the immediate: 1 pointer, 2 32-bit absolute in text, 3 32-bit PC-relative in text. |
0x20 | REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | Set the segment to the immediate and the offset to a ULEB. |
0x30 | REBASE_OPCODE_ADD_ADDR_ULEB | Add a ULEB to the offset. |
0x40 | REBASE_OPCODE_ADD_ADDR_IMM_SCALED | Add the immediate, times the pointer size, to the offset. |
0x50 | REBASE_OPCODE_DO_REBASE_IMM_TIMES | Do a rebase, advancing a pointer each time, as many times as the immediate. |
0x60 | REBASE_OPCODE_DO_REBASE_ULEB_TIMES | The same, for a ULEB count. |
0x70 | REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB | Do one rebase, then advance by a ULEB and one pointer. |
0x80 | REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB | Do a rebase a ULEB number of times, each time skipping another ULEB and a pointer. |
Bind opcodes
The bind information is a table of segment, offset, type, library ordinal, symbol name and addend: pointers to set to imported symbols. The opcodes are:
| Opcode | Constant | Effect |
|---|---|---|
0x00 | BIND_OPCODE_DONE | The table ends. |
0x10 | BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | Set the library ordinal to the immediate. |
0x20 | BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB | Set it to a ULEB. |
0x30 | BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | Set a special ordinal: 0 this image, 0xF main executable (−1), 0xE flat lookup (−2), 0xD weak lookup (−3). |
0x40 | BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | A zero-terminated symbol name follows; the immediate is flags: 1 weak import, 8 non-weak definition. |
0x50 | BIND_OPCODE_SET_TYPE_IMM | Set the type: 1 pointer, 2 32-bit absolute, 3 32-bit PC-relative. |
0x60 | BIND_OPCODE_SET_ADDEND_SLEB | Set the addend to an SLEB. |
0x70 | BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | Set the segment and offset. |
0x80 | BIND_OPCODE_ADD_ADDR_ULEB | Add a ULEB to the offset. |
0x90 | BIND_OPCODE_DO_BIND | Do a bind, then advance a pointer. |
0xA0 | BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB | Do a bind, then advance by a ULEB and a pointer. |
0xB0 | BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED | Do a bind, then advance by a pointer plus the immediate times a pointer. |
0xC0 | BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB | Bind a ULEB number of times, each time skipping another ULEB and a pointer. |
0xD0 | BIND_OPCODE_THREADED | An early arm64e form, with a table of ordinals and pointers linked through the data. |
Weak binding
The same opcodes in a table sorted by symbol name, so that weak definitions can be matched up across every image in a process. There is no library ordinal. A symbol name whose flags include 8 (non-weak definition) binds nothing: it says this image holds a strong definition of that symbol.
Lazy binding
The same opcodes again, with each symbol ending in BIND_OPCODE_DONE, so that the table can be read from any entry. A lazy symbol pointer starts by pointing at the stub helper, which pushes its entry’s offset in this table and calls into dyld.
The export trie
The exported symbols, in a trie that shares common prefixes. Starting at the first byte, each node is:
| Field | Meaning |
|---|---|
| terminal size (ULEB) | The size of the symbol information that follows; 0 means no symbol ends at this node. |
| flags (ULEB) | If the terminal size is not 0: flags, below. |
| address or re-export (ULEB and more) | Normally an offset from the Mach header. With EXPORT_SYMBOL_FLAGS_REEXPORT, a library ordinal then a name. With EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, two ULEB offsets: the stub and the resolver. |
| child count (1 byte) | How many edges leave this node. |
| edges | Each is a zero-terminated string and a ULEB offset of the next node, from the start of the trie. |
A symbol’s name is the strings of the edges from the root to its node, joined. The
flags’ low two bits give its kind: 0 regular, 1 thread-local, 2 absolute. 0x04
is EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION, 0x08 REEXPORT, 0x10
STUB_AND_RESOLVER, and 0x20 STATIC_RESOLVER.