linkerldelfloader

Why is symbol binding maintained in a dynamic symbol table?


My understanding is that -

(1) Symbol binding (global/local/weak) is used by the linker to limit the scope of a symbol to its defining object file or other object files/ libraries linked together, and determine whether it's overridable from them.

(2) Symbol visibility (default/protected/internal/hidden) is used by the loader to control whether a symbol within a linked binary is visible or interposable from different binaries.

Still, symbol tables in executables/shared-libs (i.e. linked binaries) maintain the symbol binding:

$ readelf --all /usr/bin/ls
...
Symbol table '.dynsym' contains 139 entries:
   Num:    Value          Size Type    **Bind**   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_toupper_loc@GLIBC_2.3 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getenv@GLIBC_2.2.5 (3)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sigprocmask@GLIBC_2.2.5 (3)
...

Is my understanding correct? Is symbol binding used in load/run time in some way I'm missing?


Solution

  • Is symbol binding used in load/run time in some way I'm missing?

    It sure is.

    Normally you will only see GLOBAL and WEAK symbols in the dynamic symbol table -- there is no reason to put HIDDEN symbols into it.

    If you have an unresolved WEAK symbol, and no other definition of that symbol, and take an address of it, the loader will resolve such symbol to NULL.

    But if the symbol is unresolved and GLOBAL, you'll get a fatal loader error instead.

    Example:

    // x.c
    #include <stdio.h>
    extern int foo() WEAK;
    void fun() {
      printf("In fun: &foo = %p\n", &foo);
    }
    
    // main.c
    #include <dlfcn.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main() {
      void *h = dlopen("./x.so", RTLD_LAZY);
      if (h == NULL) {
        printf("Failure: %s\n", dlerror());
        return 1;
      }
    
      void (*fun)(void) = dlsym(h, "fun");
      printf("Success, calling fun() @%p\n", fun);
      fun();
      return 0;
    }
    
    gcc main.c -ldl
    
    gcc -DWEAK="" -fPIC -shared -o x.so x.c &&
    ./a.out
    
    Failure: ./x.so: undefined symbol: foo
    

    Now with a weak symbol:

    gcc -DWEAK="__attribute__((weak))" -fPIC -shared -o x.so x.c &&
    ./a.out
    
    Success, calling fun() @0x7f6d37988109
    In fun: &foo = (nil)
    

    P.S. You can observe similar error with direct linking (without use of dlopen): you just need to build x.so with a weak symbol, link a.out against it, then rebuild x.so without the weak symbol.