[llvm-dev] Weak undefined symbols and dynamic libraries

Rui Ueyama via llvm-dev
2017-10-14T01:10:23Z

lld as well as other linkers work hard to make weak undefined symbols work
beyond ELF binaries when dynamic linking involved, but unless everything is
compiled with -fPIC, they don't actually work as people would expect.

I think most people do not know that fact, and even for those who have
knowledge on ELF, the current half-broken behavior is confusing and not
useful. So I'd like to propose we simplify it.

Let me explain why it is half-broken. Assume that we have foo.c with the
following contents:

  __attribute__((weak)) void weakfn(void) {}
  int main() { if (weakfn) weakfn(); }

What it's intended to do is to call weakfn only when the function is
defined. If you link foo.o against a shared library providing a definition
of weakfn, the symbol is added to the executable's dynamic symbol table as
a weak undefined symbol.

  # Create a shared library
  $ echo 'void weakfn() { puts("hello"); }' | clang -xc -o
bar.so -shared
-fPIC -

  # Link foo.o and bar.so to create an executable
  $ clang -c foo.c
  $ clang foo.o bar.so
  $ LD_LIBRARY_PATH=. ./a.out
  hello

Looks good so far. weakfn is in the dynamic symbol table as a weak
undefined symbol.

  $ objdump --dynamic-syms a.out |grep weak
  0000000000400500  w   DF *UND*  0000000000000000              weakfn

But, is it really weak? Not really. If we remove the symbol from bar.so,
the main executable starts to crash.

  $ clang -xc -o bar.so -shared -fPIC /dev/null
  $ LD_LIBRARY_PATH=. ./a.out
  Segmentation fault (core dumped)

This is because weakfn is always resolved to its PLT entry's address in the
main executable. Since the PLT slot address is not zero, weakfn in `if
(weakfn) weakfn()` is always called even if real weakfn is missing. If
weakfn is missing, it's PLT entry jumps to address zero, so calling the
function caused a crash.

We cannot avoid it if we are creating a non-PIC binary, because for non-PIC
code, function addresses need to be known at link-time. For imported
functions, we use their PLT addresses as their symbol values. Dynamic weak
undefined symbol is not representable in non-PIC.

If we are linking a position-independent code, weak undefined symbols work
fine. In this case, function addresses are read from GOT slots, and their
values can be zero or non-zero depending on their load-time symbol
resolution results.


I think the current behavior is bad. I'd like to propose the following
changes:

1. If a linker is creating a non-PIC ELF binary, and if it finds a DSO
symbol foo for an undefined weak symbol foo, then it adds foo as a *strong*
undefined symbol to the dynamic symbol table. This prevents the above crash
because the program fails to start if foo is not found at load-time,
instead of crashing at run-time.

2. If a linker is creating a non-PIC ELF binary, and if it *cannot* find a
DSO symbol foo for an undefined weak symbol foo, then it *does not* add foo to
the dynamic symbol table, and it sets foo's value to zero.

In other words, my suggestion is to make the linker to not try too hard for
weak undefined symbols in non-PIC. In non-PIC, if weak undefined symbols
cannot be resolved at link-time, their values should be set to zero.
Otherwise, they should be handled as if they were regular undefined symbol.

I believe it simplifies the semantics and also simplifies the
implementation of the linker. What do you think?
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://lists.llvm.org/pipermail/llvm-dev/attachments/20171013/47ad77ae/attachment-0001.html>