Weak symbol
2021-04-25 16:00:00 Author: maskray.me(查看原文) 阅读量:153 收藏

UNDER CONSTRUCTION

In ELF, there are three main symbol bindings. The ELF specification says:

  • STB_LOCAL: Local symbols are not visible outside the object file containing their definition. Local symbols of the same name may exist in multiple files without interfering with each other.
  • STB_GLOBAL: Global symbols are visible to all object files being combined. One file's definition of a global symbol will satisfy another file's undefined reference to the same global symbol.
  • STB_WEAK: Weak symbols resemble global symbols, but their definitions have lower precedence.

In the GNU ABI, there is another binding STB_GNU_UNIQUE, which is like STB_GLOBAL with extra semantics (unique even with RTLD_LOCAL, nodelete).

In GNU as flavored assembly, you can set the binding of a symbol via .weak sym.

Since a symbol has only one binding, a symbol cannot be global and weak at the same time. However, in GNU as, .weak overrides .globl since 1990+. In the LLVM integrated assembler, the last directive wins. Since LLVM 12 (https://reviews.llvm.org/D90108), the integrated assembler errors/warns for changed binding. For .globl sym; .weak sym, it reports a warning instead of an error because the behavior actually matches GNU as, but relying on directive overridding is error-prone.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# error: local changed binding to STB_GLOBAL
local:
.local local
.globl local

## `.globl x; .weak x` matches the GNU as behavior. llvm-mc issues a warning.
# warning: global changed binding to STB_WEAK
global:
.global global
.weak global

# error: weak changed binding to STB_LOCAL
weak:
.weak weak
.local weak

A weak symbol can be either a definition or a reference (i.e. undefined). This is distinguished by the section index:

  • st_shndx==SHN_UNDEF: weak reference
  • st_shndx!=SHN_UNDEF: weak definition

Semantics required by the ELF specification

The specification says very little about a weak symbol.

When the link editor combines several relocatable object files, it does not allow multiple definitions of STB_GLOBAL symbols with the same name. On the other hand, if a defined global symbol exists, the appearance of a weak symbol with the same name will not cause an error. The link editor honors the global definition and ignores the weak ones. Similarly, if a common symbol exists (that is, a symbol whose st_shndx field holds SHN_COMMON), the appearance of a weak symbol with the same name will not cause an error. The link editor honors the common definition and ignores the weak ones.

When the link editor searches archive libraries [see ``Archive File'' in Chapter 7], it extracts archive members that contain definitions of undefined global symbols. The member's definition may be either a global or a weak symbol. The link editor does not extract archive members to resolve undefined weak symbols. Unresolved weak symbols have a zero value.

Weak definitions allow multiple definitions. While a global definition can override a weak definition, it is unspecified what the linker should do when there are two weak definitions of the same name but no global definition. In GNU ld/gold/ld.lld, the linker selects the first weak definition and resolves all references to it.

An undefined weak symbol does not extract archive members. (LLD uses a weak LazyArchive/LazyObject to represent such a symbol.)

There is a remark

The behavior of weak symbols in areas not specified by this document is implementation defined. Weak symbols are intended primarily for use in system software. Applications using weak symbols are unreliable since changes in the runtime environment might cause the execution to fail.

Weak definitions

In C++, inline functions, template instantiations and a few other things can be defined in multiple object files but need deduplication at link time.

Before .gnu.linkonce.*/GRP_COMDAT were invented, the implementations used weak definitions to avoid linker multiple definition errors. The weak definition convention remains post GRP_COMDAT. The weak definition can be used for compatibility for linkers which do not understand pre-COMDAT .gnu.linkonce.* or COMDAT. Using STB_GLOBAL for COMDAT definitions can detect ODR violations caused by a COMDAT definition and a non-COMDAT definition. This will however be a significant behavior change. gold has an option --detect-odr-violations: the option checks whether there are two weak definitions with different file:line debugging information.

A replaceable definition can be declared weak. A STB_GLOBAL definition from another translation unit can override it. This is a great way providing a default/fallback definition in a library which can be redefined by applications.

1
2
3
4
5
6
7
8
9
10
11
12
13

__attribute__((weak)) void fun() {
...
}

void feature() {
fun();
}


void fun() {
...
}

A weak alias is a special form of weak definitions. It defines a weak symbol by reusing an existing definition.

1
2
static void impl() {}
__attribute__((weak,alias("impl"))) void fun();

PE-COFF does not have a direct counterpart but can emulate a weak definition with an IMAGE_SYM_CLASS_WEAK_EXTERNAL IMAGE_SYM_UNDEFINED symbol with a defined IMAGE_SYM_CLASS_EXTERNAL auxiliary symbol (named .weak.<weaksymbol>.<relatedstrongsymbol> in GNU). Is the symbol does not have a regular definition in another object file, the linker will select the auxiliary definition. However, if you are concerned about MSVC compatibility, there is a not-so-greak linker solution. MSVC link.exe supports /alternatename: which can achieve similar effects. The option specifies a fallback definition for a symbol. If the symbol is otherwise undefined, the linker will pick up the fallback definition.

Weak references

The ELF specification says "Unresolved weak symbols have a zero value." This property can be used to check whether a definition is provided. A common pattern is to implement an optional hook.

1
2
3
4
__attribute__((weak)) void undef_weak_fun();

if (&undef_weak_fun)
undef_weak_fun();

Historically, toolchains, especially for the lesser-used architectures, tend to have more bugs with weak references, so musl refrains from using weak references. The above pattern can be replaced with a weak alias.

1
2
3
4
static void noop() {}
__attribute__((weak,alias("noop"))) void undef_weak_fun();

undef_weak_fun();

In most cases weak references resolve to a GOT entry for the symbol.

For ELF -fno-pic, there is an optimization: the emitted code may use an absolute relocation to check whether the address is zero. However, if the symbol turns out to be defined in a shared object and the linked unit needs a dynamic section, there will be a canonical PLT entry.

PE-COFF can emulate this feature with an IMAGE_SYM_CLASS_WEAK_EXTERNAL IMAGE_SYM_UNDEFINED symbol with an IMAGE_SYM_CLASS_EXTERNAL IMAGE_SYM_ABSOLUTE auxiliary symbol (named .weak.<weaksymbol>.<relatedstrongsymbol> in GNU).

Weak references and shared objects

A weak reference can be satisfied by a shared object definition. The weak reference is no different from a regular reference.

Weak references and archives

This is a lesser-known rule. When an ELF linker sees a weak reference, it does not extract an archive member to satisfy the weak reference. Please make sure the archive member is extracted due to other symbols.

There is a related longstanding problem in libstdc++ (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58909): because references to pthread_* are weak, the relevant members in -lpthread may not be extracted in a static link:

1
2
3
4
5
6
% cat a.cc
#include <condition_variable>
int main() { std::condition_variable a; }
% g++ -static -pthread a.cc
% ./a.out
Segmentation fault

You can use -Wl,-y to understand why this happens. GNU ld discards an archive if it does not need to be extracted, so I have to use LLD to show lazy definition.

1
2
3
% g++ -fuse-ld=lld -static a.cc -lpthread -Wl,-y,pthread_cond_destroy
/usr/lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/libpthread.a: lazy definition of pthread_cond_destroy
/usr/lib/gcc/x86_64-linux-gnu/10/libstdc++.a(condition_variable.o): reference to pthread_cond_destroy

Binding of an undefined symbol

If an object file has an undefined symbol not defined by another object file (for archives, we consider extracted archive members the same as object files), the symbol is undefined in the linked image. The symbol may be defined by a shared object, but the linked image still has an undefined symbol.

A STB_LOCAL undefined symbol is not allowed, so the binding can be either STB_GLOBAL or STB_WEAK. The binding is STB_WEAK if all undefined symbols in object files are STB_WEAK, otherwise the binding is STB_GLOBAL. Note: symbols in shared objects do not affect the binding.

Unresolved references

If an undefined symbol in the linked image does not resolve to a link-time shared object, and there is a relocation from a live section (not discarded by --gc-sections), the linker will usually report an undefined symbol error if the symbol is STB_GLOBAL. (-z undefs and --no-allow-shlib-undefined are the default when linking an executable; see Explain GNU style linker options for details).

The symbol is unsatisfied. If it is weak, the linker will suppress the diagnostic.

Unresolved weak references and R_*_GLOB_DAT

The linker may or may not emit a dynamic relocation R_*_GLOB_DAT for the GOT entry. If there is no R_*_GLOB_DAT, the GOT entry is always zero at runtime. If there is an R_*_GLOB_DAT, the GOT entry may be non-zero if an immediately loaded shared object defines the symbol at runtime.

It seems that by default, GNU ld emits an R_*_GLOB_DAT for -pie and -shared links and suppress the relocation for default no-pie links. GNU ld x86 can disable the relocation with -z dynamic-undefined-weak (https://sourceware.org/bugzilla/show_bug.cgi?id=19636).

LLD emits an R_*_GLOB_DAT only for -shared links. For -no-pie links, the dynamic relocation R_*_GLOB_DAT must be suppressed (the absolute relocation R_X86_64_32 should resolve to zero) because the undefined weak symbols in GCC crtbegin.o have st_type==STT_NOTYPE: no canonical PLT entry/copy relocation can be created. The -pie behavior is different from GNU ld. Users should not depend on either behavior.

ld.so

A STB_GLOBAL definition and STB_WEAK definition in the dynamic symbol table is equivalent in glibc ld.so and musl ld.so. glibc before 2.2 provided a different behavior: a STB_WEAK definition could be overridden by a subsequent STB_GLOBAL definition. FreeBSD ld.so still uses the legacy glibc behavior.

A weak reference in the dynamic symbol table does not cause a symbol lookup error if no binding is found.


文章来源: http://maskray.me/blog/2021-04-25-weak-symbol
如有侵权请联系:admin#unsafe.sh