DSO undef and non-exported definition
2023-10-31 15:0:0 Author: maskray.me(查看原文) 阅读量:24 收藏

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
2
3
4
5
6
7
8
9
10
11
12
for (SharedFile *file : ctx.sharedFiles) {
bool allNeededIsKnown =
llvm::all_of(file->dtNeeded, [&](StringRef needed) {
return symtab.soNames.count(CachedHashStringRef(needed));
});
if (!allNeededIsKnown)
continue;
for (Symbol *sym : file->requiredSymbols)
if (sym->isUndefined() && !sym->isWeak())
diagnose("undefined reference due to --no-allow-shlib-undefined: " +
toString(*sym) + "\n>>> referenced by " + toString(file));
}

Recently I noticed that GNU ld implemented a related error in April 2003.

1
2
3
4
echo '.globl _start; _start: call shared' > main.s && clang -c main.s
echo '.globl shared; shared: call foo' > a.s && clang -shared -fpic a.s -o a.so
echo '.globl foo; foo:' > def.s && clang -c def.s && clang -shared def.o -o def.so
echo '.globl foo; .hidden foo; foo:' > def-hidden.s && clang -c def-hidden.s
1
2
3
4
% ld.bfd main.o a.so def.o
% ld.bfd main.o a.so def-hidden.o
ld.bfd: a.out: hidden symbol `foo' in def-hidden.o is referenced by DSO
ld.bfd: final link failed: bad value

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
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 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
2
ld.bfd main.o a.so def-hidden.o def.so  
ld.lld 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
2
3
4
5
% 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

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.


文章来源: https://maskray.me/blog/2023-10-31-dso-undef-and-non-exported-definition
如有侵权请联系:admin#unsafe.sh