I'm considering only the ELF format for analysis here. First off, all symbols (static or dynamic) have a symbol-table entry specifying its name ( actually an index into the string table) & an offset (or virtual address). Functions are also symbols, hence they too reside in this symbol table. Furthermore, all programs that are dynamically linked with a shared library also have stub-code within the executable for all functions that are called in the shared-library. This is used at runtime to find its actual address. The stub-code resides in a segment of the memory called PLT ( Procedure Linkage Table ). This is usually ro (read-only) memory. The executable in memory also has a GOT ( Global Offset Table ) that contains the actual address of the symbol at run-time. Whenever a symbol's address has to be resolved at runtime, the dynamic linker queries for the symbol in each of the dependent shared-libraries, finds out the base-address of the library where this symbol is present and populates the symbol's actual run-time address into the GOT. Now, the first instruction of the stub-code in the PLT is always of the form:
Here, got-address specifies the location in the GOT, where the address of the function resides. Initially, however, got-address points to the next instruction in the PLT itself, which pushes certain identifiers for the dynamic linker and jumps to the linker code itself. Now, the dynamic linker locates the load-address of the function and modifies the GOT entry to contain the actual address. So, the next time a function call is made, the overhead of skipping to the dynamic linker is omitted and the function is called directly.
The program that demonstrates all this is shown below (assuming there's a function called SO_FUNC in a shared-library and this file is linked with that library) :
extern void SO_FUNC(void);As we see, the address of SO_FUNC before & after the call are different. To further confirm this, we can look at /proc/
Elf32_Addr *gotAddr, *lazyAddr, *pltAddr;
pltAddr = (Elf32_Addr*)SO_FUNC;
//Skip the opcode and MOD-REG-R/M....
gotAddr = (Elf32_Addr*)( (char*)(pltAddr)+0x2 );
lazyAddr = (Elf32_Addr*)(*gotAddr);
printf("Address of SO_FUNC before the Call: %x\n", *lazyAddr);
printf("Address of SO_FUNC after the Call: %x\n", *lazyAddr);
Address of SO_FUNC before the Call: 804834e
Address of SO_FUNC after the Call: 40024704
Of course, this the lazy way of doing this. The correct method would be to locate the address of
the function from the symbol table, its GOT entry from the relocation section ( the rel & rela sections are somehow not present in memory, however ), and then print its contents. But then, to do that, we need to read the ELF header, the Program Header & the Dynamic Segment or use
hashing. Then again, all programmers are born lazy.