Reputation: 2355
Given the following simple code snippet:
int main (void) {
void* foo = scanf;
void* bar = sscanf;
}
Here's the disassembly (Taken from the mach-o executable):
Small part of the non-lazy symbol pointers:
Small part of the symbol table:
I don't seem to understand the 5th and the 6th lines in the executable file (The movq of scanf & sscanf into rax/rcx). How do foo and bar (eventually) have the addresses of scanf & sscanf respectively. I think that it has something to do with the dynamic libraries which are mapped to the process (And the non-lazy symbol pointers most likely point there or something) but I can't understand how.
thanks
Upvotes: 1
Views: 764
Reputation: 8012
The generated code loads the address of scanf
from the non-lazy symbol pointer. There's nothing special there. As dyld loads an executable it "binds" each non-lazy symbol pointer, setting its value to the symbol's correct address.
Non-lazy symbol pointers are used for references that (1) point to something in a different library, and (2) are data references, not calls to a function. In your example, you're not calling scanf
directly so it's a data reference, and scanf
is not in your executable so it's a reference to a different library.
References within the same executable used a fixed PC-relative offset: the compiler and linker know that the code and the data will be loaded adjacent to each other so they can choose the offset at build time. References for function calls are lazy: at runtime the first call to the function is routed through dyld first, which looks up the symbol and binds the lazy symbol pointer for future calls.
As 0xced pointed out, you can set environment variable DYLD_PRINT_BINDINGS
to watch dyld work. The dyld man page describes other variables you can set.
Upvotes: 1
Reputation: 26558
dyld, the dynamic linker, is responsible for binding symbols. To see what happens, first compile your test project with the -fno-pie
option in order to disable Position Independent Executable. This way the offset you see in MachOView will be the same at runtime. Then launch the executable with the DYLD_PRINT_BINDINGS
environment variable set to YES
. Here is the result:
$ DYLD_PRINT_BINDINGS=YES ./a.out
dyld: bind: a.out:0x100001010 = libsystem_c.dylib:_scanf, *0x100001010 = 0x7FFF94578017
dyld: bind: a.out:0x100001018 = libsystem_c.dylib:_sscanf, *0x100001018 = 0x7FFF94578707
...
Upvotes: 1