Shared Libraries

Created

Statically Linked

Loading statically-linked executable object files:

  • If two programs are compiled with --static, they have a global variable, and executed at the same time, that global variable will have the same address

Dynamically Linked

Dynamically linked shared libraries

  • Once the program is executed, the loader calls dynamic linker
  • Dynamic linker "loads" shared libary
  • Nothing is actually loaded
  • Memory mapping: pretend it’s in memory (operating system deals with mapping of RAM address). Refer to vitual memory.

Shared libraries is the idea that multiple processes use the same shared library. We do this by putting the shared library into a dedicated place in memory. However, some questions stand:

  • There may be many libraries, so we may run out of address space (or at least waste it).

So instead, we compile into position-independent code

Global Offset Table

Position-Independent Code No matter where the library is loaded into memory, the address between addresses are the same. We use a global offset table (GOT) where

  • table in data segment (relative position known)
  • contains absolute addresses of variables and functions
  • get filled with correct values by the dynamic linker

It uses the instruction point register (%rip) The linker knows the offset between %rip and the GOT.

For example, Assume b is a global variable defined in a different exectuable object (like a shared library), and our source code performs b++.

The global offset table (in data segment) will look something like this:

| | | |--|--|--| |0|address of symbol a| |1|address of symbol b| |2|...|

The generate assembly code looks like

mov 0x2008b9(%rip), %rax 
addl $1, (%rax)
  • The distance between code line and GOT entry 1 is 0x2008b9 bytes
  • The first line of code loads actual address of variable
  • The second line increases b by 1.

Filling GOT entries

References to data (to global variable)

  • When an executable object is loaded, the dynamic linker eagerly determines run-time addresses for all referenced global variables, and stores them in the appropriate GOT entries
  • Each loaded executable object (including shared libraries) has a symbol table indicating relative offsets of code and data definitions
  • The dynamic linker uses the symbol table information to determine the run-time address of any code or data definition
    • It knows the base address of each executable object, and the names and offsets of every symbol definition within it
    • So it’s capable of producing an address for any defined symbol

References to code (functions)

  • Finding the address of called functions (like calling printf, which is in the libc shared library) is much more complicated.

  • Function addresses are resolved lazily

    • Each (externally-defined) function has an entry in the GOT
    • Initial address in the GOT calls into the PLT (procedure linkage table), which invokes the dynamic loader to resolve the address of the called function and store it in the GOT
    • Subsequent calls load the function's address from the GOT.

This way, it avoids start up overhead that, since many programs won't use all the functions in one library.

Tools for Manipulating Object Files

ToolDescription
ARCreates static libraries, and inserts, deletes, and extracts members STRINGS Lists all printable strings
STRIPDeletes symbol table information
NMLists symbols defined in symbol table
READELFDisplays complete structure
OBJDUMPDisplays all information, useful to disassemble code
  • These are useful to figure out where something will be loaded.

Dynamic Loading

(of shared libraries)

Shared Libraries are loaded into memory at runtime.

  • Loading and symbol resolution happens automatically when an executable is linked against shared libraries.
    • E.g., every C program is linked against the C library (libc), so the libc shared library is loaded automatically when the C program runs
    • We don't have to do anything explicit.
  • However, programs can also load shared libraries (and resolve symbols in them) dynamically. You can do this explicitly in your program.

The mechanism on Linux to explicitly dynamically load shared library is with dlfcn.h. Use it by #include <dlfcn.h>.

  • Call dlopen to dynamically load a shared library
  • Once a shared library has been loaded, call dlsym to get the addresses of data and functions within it
  • Call dlclose to unload the shared library
  • Link the executable or shared library with -ldl

Function pointers is a pointer to a function. Use them exactly as though they were functions.

Example:

// Declare a pointer to a function returning int and taking
// a single const char * parameter.
int (*ptr)(const char *);

// Make the function pointer point to a compatible function.
ptr = puts;

// Use the pointer to call the function it points to.
ptr("Hello world");

Dynamic loading is useful when

  • Interpositioning to “redefine” functions
  • Extending program capabilities using “plugins”

Interpositioning

Interpositioning is a technique useful for "redefining" library functions. It means linking (statically or dynamically) functions with the same name into the executable

  • This is done to debug or trace program behavior, or to change and extend the behavior of the function.

This is also called "instrumenting" the executable in which functions are redefined by interpositioning.

  • Dynamic loading allows the interposed function(s) to call the “real” function(s)
  • The LD_PRELOAD environment variable can be used to “inject” interposed definitions into an arbitrary program