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
SharedSymbol
in lld/ELF). - The
SharedSymbol
is overridden by a non-exported (usually of hidden visibility) definition in a relocatable object file (Defined
). - The section containing the
Defined
is garbage-collected (it is not part of.dynsym
and 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
.