UNDER CONSTRUCTION
If a STB_GLOBAL
symbol referenced by a DSO is defined inrelocatable object files but not exported, should the--no-allow-shlib-undefined
feature report an error? You maywant to check out Dependencyrelated linker options for a discussion of this option and the symbolexporting rule.
For quite some time, the --no-allow-shlib-undefined
feature has been implemented in lld/ELF as follows:
1 | for (SharedFile *file : ctx.sharedFiles) { |
Recently I noticed that GNU ld implemented a related error in April2003 (discussion).
1 | echo '.globl _start; _start: call shared' > main.s && clang -c main.s |
1 | % ld.bfd main.o a.so def.o |
A non-local default or protected visibility symbol can satisfy a DSOreference. The linker will export the symbol to the dynamic symboltable. Therefore, ld.bfd main.o a.so def.o
succeeds asintended.
We encounter an error forld.bfd main.o a.so def-hidden.o
because a symbol withhidden visibility cannot be exported, and it's unable to satisfy thereference in a.so
at run-time.
Here is another interesting case: we use a version script to changethe binding of a defined symbol to STB_LOCAL
, causing it tobe unable to satisfy the reference in a.so
at run-time. GNUld also reports an error in this case. 1
2
3% ld.bfd --version-script=local.ver main.o a.so def.o
ld.bfd: a.out: local symbol `foo' in def.o is referenced by DSO
ld.bfd: final link failed: bad value
My recent commit https://github.com/llvm/llvm-project/commit/1981b1b6b92f7579a30c9ed32dbdf3bc749c1b40strengthened LLD's --no-allow-shlib-undefined
to detectcases in which the non-exported definitions are garbage-collected. Ihave also proposed enhancements in https://github.com/llvm/llvm-project/pull/70769 to covernon-garbage-collected cases.
A variation of the scenario mentioned above occurs when a DSOdefinition is also present. Even if the executable does not exportfoo
, another DSO (def.so
) may provide it. GNUld's check allows for this case.
1 | ld.bfd main.o a.so def-hidden.o def.so # succeeded |
It turns out that https://github.com/llvm/llvm-project/commit/1981b1b6b92f7579a30c9ed32dbdf3bc749c1b40unexpectedly strengthened --no-allow-shlib-undefined
toalso catch this ODR violation. More precisely, when all three conditionsare met, the new --no-allow-shlib-undefined
code reports anerror.
SharedSymbol
in lld/ELF).SharedSymbol
is overridden by a non-exported(usually of hidden visibility) definition in a relocatable object file(Defined
).Defined
is garbage-collected(it is not part of .dynsym
and is not marked as live).An exported symbol is a GC root, making its section live. Anon-exported symbol, however, can be discarded when its section isdiscarded.
So, is this error legitimate? At run-time, the undefined symbolfoo
in a.so
will be bound todef.so
, even if the executable does not exportfoo
, so we are fine. This suggests that the--no-allow-shlib-undefined
code probably should not reportan error.
However, both def-hidden.o
and def.so
define foo
, and we know the definitions are different andless likely benign. At the very least, they are not exactly the same dueto different visibilities or one being localized by a versionscript.
A real-world report boils down to 1
2
3
4
5
6
7% ld.lld @response.txt -y _Znam
...
libfdio.so: reference to _Znam
libclang_rt.asan.so: shared definition of _Znam
libc++.a(stdlib_new_delete.cpp.obj): definition of _Znam
ld.lld: error: undefined reference due to --no-allow-shlib-undefined: _Znam
>>> referenced by libfdio.so
How does libfdio.so
obtain a reference to_Znam
? Well, libfdio.so
is linked against bothlibclang_rt.asan.so
and libc++.a
. Due tosymbol processing rules, the definition fromlibclang_rt.asan.so
takes precedence. (See Symbol processing#Sharedobject overriding archive.)
An appropriate solution is to replace libc++a
with anAddressSanitizer-instrumented version that does not define_Znam
.
I have also encountered issues stemming from the combination ofmultiple definitions from libgcc.a
(with hidden visibility)and libclang_rt.builtins.a
(with default visibility),relying on archive member extraction rules. 1
2
3
4
5
6
7
8% ld.lld @response.txt -y __divti3
...
a.so: reference to __divti3
libgcc.a(_divdi3.o): definition of __divti3
libc++.so: shared definition of __divti3
# A lazy symbol in libclang_rt.builtins.a is not reported by -y
ld.lld: error: undefined reference due to --no-allow-shlib-undefined: __divti3
>>> referenced by a.so
a.so
is linked against libc++.so
andlibclang_rt.builtins.a
and obtains a reference to__divti3
due to libc++.so
. For the executablelink, the undesired situation arises as the definition inlibgcc.a
takes precedence. What we actually want is forlibgcc.a
to provide the missing components fromlibclang_rt.builtins.a
.
Some users compile relocatable object files with-fvisibility=hidden
to disallow dynamic linking. However,when their system includes specific shared objects, it increases therisk of conflicting multiple definition symbols.
While this additional check introduced in https://github.com/llvm/llvm-project/commit/1981b1b6b92f7579a30c9ed32dbdf3bc749c1b40may not perfectly fit into --no-allow-shlib-undefined
, Ibelieve it has value. As a result, I have proposed --[no-]allow-non-exported-symbols-shared-with-dso
.
Technically, the check can be extended to default visibility to catchall link-time symbol interposition. However, I suspect that there are alot of benign violations and in the absence of an ignore list mechanism,this extension will not be useful.