A call to a function in another library cannot reach it directly, because the library’s address is not known when the program is linked. The program calls a small stub instead, or loads an address from a table the loader fills in.
Symbol stubs
A section of type S_SYMBOL_STUBS holds one fixed-size stub for each
imported function. reserved2 gives the size of a stub and
reserved1 the index of the first in the
indirect symbol table, so the
n-th stub belongs to entry reserved1 + n. A stub loads an address from a
pointer and jumps to it.
Common names are __TEXT,__stubs and, on arm64e,
__TEXT,__auth_stubs, whose stubs authenticate the pointer before jumping
through it.
Symbol pointers
Non-lazy symbol pointers (S_NON_LAZY_SYMBOL_POINTERS) are filled in
before the code runs. __DATA_CONST,__got and __auth_got are
of this kind. Code also loads addresses from the GOT directly.
Lazy symbol pointers (S_LAZY_SYMBOL_POINTERS, usually
__DATA,__la_symbol_ptr) start out pointing into
__TEXT,__stub_helper. The first call goes through the helper into dyld,
which uses the lazy binding table to
find the real function and overwrites the pointer. Files with
chained fixups do not use lazy
pointers.
Each pointer section, like a stub section, has an index in
reserved1 into the indirect symbol table.
Resolving names
The explorer names each stub and each pointer from the indirect symbol table. When
that entry was stripped to INDIRECT_SYMBOL_LOCAL, a pointer is named from
the symbol at its target, and a pointer that chained fixups bind to an import is
named from the imports table.
In the disassembly, a call to a stub or a load from a pointer shows that name.