UNDER CONSTRUCTION
DSO undef and non-exported def
If a STB_GLOBAL symbol referenced by a DSO is defined in
relocatable object files but not exported, should the
--no-allow-shlib-undefined feature report an error? You may
want to check out Dependency
related linker options for a discussion of this option and the symbol
exporting rule.
For quite some time, the --no-allow-shlib-undefined
feature is implemented as follows:
1 | for (SharedFile *file : ctx.sharedFiles) { |
Recently I noticed that GNU ld implemented a related error in April 2003.
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 DSO
reference. The linker will export the symbol to the dynamic symbol
table. Therefore ld.bfd main.o a.so def.o succeeds as
intended.
We get an error for ld.bfd main.o a.so def-hidden.o as a
hidden visibility symbol cannot be exported, unable to satisfy
a.so's reference at run-time.
Here is another interesting case. We use a version script to change
the binding of a defined symbol to STB_LOCAL, causing is
unable to satisfy a.so's reference at run-time. GNU ld
reports an error as well.
1 | % ld.bfd --version-script=local.ver main.o a.so def.o |
My recent https://github.com/llvm/llvm-project/commit/1981b1b6b92f7579a30c9ed32dbdf3bc749c1b40
made LLD's --no-allow-shlib-undefined stronger to catch
cases when the non-exported definition is garbage-collected. I have
proposed https://github.com/llvm/llvm-project/pull/70769 to
enhance the check to cover non-garbage-collected cases.
DSO undef, non-exported def, and DSO def
A variant of the above scenario is when we also have a DSO
definition. Even if the executable does not export foo,
another DSO (def.so) may provide foo. GNU ld's
check allows this case.
1 | ld.bfd main.o a.so def-hidden.o def.so |
It turns out that https://github.com/llvm/llvm-project/commit/1981b1b6b92f7579a30c9ed32dbdf3bc749c1b40
unexpectedly made --no-allow-shlib-undefined stronger to
catch this ODR violation as well. More precisely, when all the three
conditions are satisfied, the new
--no-allow-shlib-undefined code reports an error.
- There is a DSO undef that can be satisfied by a definition from
another DSO (called
SharedSymbolin lld/ELF). - The
SharedSymbolis overridden by a non-exported (usually of hidden visibility) definition in a relocatable object file (Defined). - The section containing the
Definedis garbage-collected (it is not part of.dynsymand is not marked as live).
An exported symbol is a GC root and makes its section live. A non-exported symbol can however be discarded when its section is discarded.
So, is this error legitimate? At run-time, the undefined
foo in a.so will be bound to
def.so, even if the executable does not export
foo, so we are fine. This suggests that the
--no-allow-shlib-undefined code probably should not report
an error.
However, both def-hidden.o and def.so
define foo, and we know the definitions are different and
less likely benign (at least, they are not exactly the same due to
different visibilities or one localized by a version script).
A real-world report boils down to
1 | % ld.lld @response.txt -y _Znam |
How does libfdio.so get a reference to
_Znam? Well, libfdio.so is linked against both
libclang_rt.asan.so and libc++.a. Due to
symbol processing rules, the definition from
libclang_rt.asan.so wins. (See Symbol processing#Shared
object overriding archive.)
An appropriate fix is to switch libc++a to an
asan-instrumented copy that does not define _Znam.
I have also seen problems due to mixing multiple definitions from
libgcc.a (hidden visibility) and
libclang_rt.builtins.a (default visibility) and relying on
archive member extraction rules to work.
Some users compile relocatable object files with
-fvisibility=hidden to allow just static linking.
Nevertheless, their system consists of certain shared objects and there
is a probability of messing up multiple definition symbols.
While this additional check from https://github.com/llvm/llvm-project/commit/1981b1b6b92f7579a30c9ed32dbdf3bc749c1b40
may not fit into --no-allow-shlib-undefined, I feel that
having it is not bad. Therefore, I have proposed --[no-]allow-hidden-symbols-shared-with-dso.