<div dir="ltr">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.<div><br></div><div>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.</div><div><br></div><div>Let me explain why it is half-broken. Assume that we have <font face="monospace, monospace">foo.c</font> with the following contents:</div><div><br></div><div><font face="monospace, monospace">  __attribute__((weak)) void weakfn(void) {}</font></div><div><font face="monospace, monospace">  int main() { if (weakfn) weakfn(); }</font></div><div><br></div><div>What it's intended to do is to call <font face="monospace, monospace">weakfn</font> only when the function is defined. If you link foo.o against a shared library providing a definition of <span style="font-family:monospace,monospace">weakfn</span>, the symbol is added to the executable's dynamic symbol table as a weak undefined symbol.</div><div><br></div><div><font face="monospace, monospace">  # Create a shared library</font></div><div><font face="monospace, monospace">  $ echo 'void weakfn() { puts("hello"); }' | clang -xc -o bar.so -shared -fPIC -</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">  # Link foo.o and bar.so to create an executable</font></div><div><font face="monospace, monospace">  $ clang -c foo.c</font></div><div><font face="monospace, monospace">  $ clang foo.o bar.so</font></div><div><font face="monospace, monospace">  $ LD_LIBRARY_PATH=. ./a.out</font></div><div><font face="monospace, monospace">  hello</font></div><div><br></div><div>Looks good so far. <font face="monospace, monospace">weakfn</font> is in the dynamic symbol table as a weak undefined symbol.</div><div><br></div><div><div>  $ objdump --dynamic-syms a.out |grep weak</div><div>  0000000000400500  w   DF *UND*  0000000000000000              weakfn</div></div><div><br></div><div>But, is it really weak? Not really. If we remove the symbol from bar.so, the main executable starts to crash.</div><div><br></div><div><font face="monospace, monospace">  $ clang -xc -o bar.so -shared -fPIC /dev/null</font></div><div><div><font face="monospace, monospace">  $ LD_LIBRARY_PATH=. ./a.out </font></div><div><font face="monospace, monospace">  Segmentation fault (core dumped)</font></div></div><div><br></div><div>This is because <span style="font-family:monospace,monospace">weakfn</span> is always resolved to its PLT entry's address in the main executable. Since the PLT slot address is not zero, <span style="font-family:monospace,monospace">weakfn</span> in `<font face="monospace, monospace">if (weakfn) weakfn()</font>` is always called even if real <span style="font-family:monospace,monospace">weakfn</span><font face="arial, helvetica, sans-serif"> is missing.</font> If <span style="font-family:monospace,monospace">weakfn</span> is missing, it's PLT entry jumps to address zero, so calling the function caused a crash.</div><div><br></div><div>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.</div><div><br></div><div>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.</div><div><br></div><div><br></div><div>I think the current behavior is bad. I'd like to propose the following changes:</div><div><br></div><div>1. If a linker is creating a non-PIC ELF binary, and if it finds a DSO symbol <font face="monospace, monospace">foo</font> for an undefined weak symbol <font face="monospace, monospace">foo</font>, then it adds <span style="font-family:monospace,monospace">foo</span> as a <i>strong</i> undefined symbol to the dynamic symbol table. This prevents the above crash because the program fails to start if <font face="monospace, monospace">foo</font> is not found at load-time, instead of crashing at run-time.</div><div><br></div><div>2. If a linker is creating a non-PIC ELF binary, and if it <i>cannot</i> find a DSO symbol <font face="monospace, monospace">foo</font> for an undefined weak symbol <font face="monospace, monospace">foo</font>, then it <i>does not</i> add <font face="monospace, monospace">foo</font> to the dynamic symbol table, and it sets <font face="monospace, monospace">foo</font><font face="arial, helvetica, sans-serif">'s value to zero.</font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><font face="arial, helvetica, sans-serif">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.</font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><font face="arial, helvetica, sans-serif">I believe it simplifies the semantics and also simplifies the implementation of the linker. </font><span style="font-family:arial,helvetica,sans-serif">What do you think?</span></div></div>