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?
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.