Search

Strong Symbol

8 min read 0 views
Strong Symbol

Introduction

The term strong symbol refers to a class of symbols in object files and libraries that participate in the symbol resolution process during linking. A strong symbol is defined by a definite address in a program or shared object, and it takes precedence over any weak or undefined references that refer to the same name. Strong symbols are the default form of symbol in most object file formats and are integral to the behavior of both static and dynamic linkers. The concept is essential for understanding how executable binaries are constructed, how libraries expose functionality, and how symbol conflicts are resolved across translation units.

Background

Object File Formats

Object files, which are produced by compilers and assemblers, contain several sections that store code, data, and metadata. Among the metadata is a symbol table, which maps symbolic names to addresses or other information needed by the linker. Three primary object file formats used in modern operating systems are the Executable and Linkable Format (ELF) on Unix-like systems, the Mach-O format on macOS, and the Portable Executable (PE/COFF) format on Windows. Each format defines its own representation of symbol strength, but the semantics are largely equivalent across them: a strong symbol is an explicitly defined entity with an address, while weak symbols are optional or can be overridden.

Symbol Strength Classification

Symbols in object files are classified into three categories:

  • Strong symbols – defined with an explicit address; they must not be overridden during linking.
  • Weak symbols – may be defined multiple times; the linker selects the strongest definition or treats them as zero if none exist.
  • Undefined symbols – referenced but not defined in the current object file; they must be resolved by the linker from other objects or libraries.

This classification enables the linker to resolve references deterministically, even when multiple definitions are present.

Definition and Behavior

Strong vs Weak Symbols

A strong symbol is identified in an object file by setting its binding to STB_GLOBAL (in ELF) or by not marking it as weak. Weak symbols are marked with STB_WEAK in ELF or with specific attributes in other formats. When the linker processes a relocation that refers to a symbol, it searches the symbol table in a defined order. Strong definitions are preferred over weak ones. If two strong definitions with the same name exist, the link fails with a duplicate symbol error, unless the linker is explicitly instructed to allow such overrides (for example, via linker scripts or the -Bsymbolic flag).

Strong Symbol Resolution Rules

During linking, the following resolution rules apply to strong symbols:

  1. The linker first scans the object files for definitions of the referenced symbol.
  2. If a single strong definition is found, it is selected.
  3. If multiple strong definitions exist, the linker reports a conflict unless overridden.
  4. If no strong definition is found but a weak definition exists, the weak definition is chosen.
  5. If no definition is found at all, the linker reports an undefined symbol error.

These rules ensure that the final executable contains a unique address for each symbol, preserving the integrity of program behavior.

Multiple Definitions

In practice, multiple strong definitions can arise when the same source file is compiled into several object files or when libraries provide duplicate symbols. Linkers typically support options to suppress duplicate symbol errors. For example, the GNU linker ld offers the -Bsymbolic and -Bsymbolic-allow-multiple-definition flags to handle specific use cases, such as implementing shared libraries with internal references that should not be resolved externally.

Implementation in Operating Systems and Linkers

GNU Toolchain (ld, gold, lld)

The GNU Binutils project implements the ELF format and provides several linkers. ld is the traditional linker, while gold and lld are newer alternatives focusing on performance. All three handle strong symbols according to the rules described above. The documentation for strong symbols can be found in the Binutils manual: https://sourceware.org/binutils/docs/ld/Strong-Symbols.html.

Mach-O on macOS

Apple’s Mach-O format also distinguishes between strong and weak symbols. The ld64 linker interprets attribute((weak_import)) and attribute((weak)) annotations to mark weak symbols. Strong symbols are treated as default and cannot be overridden unless the Mach-O file specifies a WEAK symbol section. Documentation is available at https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/Introduction/Introduction.html.

PE/COFF on Windows

Windows uses the PE/COFF format, where symbol definitions are encoded in the COFF symbol table. The linker distinguishes between external and local symbols, but does not have a built-in weak symbol concept. Weak behavior is simulated through the use of the __declspec(selectany) attribute in C++ or by providing alternate definitions in import libraries. The PE/COFF documentation can be found at https://docs.microsoft.com/en-us/windows/win32/debug/pe-format.

Common Use Cases

Providing Default Implementations

Libraries often provide default implementations for functions that can be overridden by application developers. For example, the C library glibc uses strong symbols for standard functions, but developers can supply their own definitions in a separate object file. Because the application's definition is also a strong symbol, the linker will detect a duplicate and the build will fail unless the library is built with weak symbols or the linker is instructed to allow overrides.

Overriding in Subsystems

Operating systems may supply a core library but allow individual services to provide specialized implementations. For instance, the Linux kernel uses strong symbols for core functions while providing weak aliases for device-specific hooks that can be overridden by loadable modules. This mechanism enables modularity without compromising the core functionality.

Weak Symbols in Linux

Weak symbols are widely used in Linux for optional features. A typical pattern is to declare a function as weak and then provide a real implementation only when the feature is compiled in. If the weak symbol is missing, the linker will substitute a zero address, allowing the program to check for the function's existence at runtime. This approach is common in dynamic loading of drivers and plug‑in systems.

Examples

Example 1: Strong Symbol Definition

c // strong.c int strong_func(void) { return 42; }

When compiled, strong_func becomes a strong symbol. If another translation unit defines strong_func with the same name, the linker will raise an error unless overridden.

Example 2: Weak Symbol Overridden

c // weak.h extern int attribute((weak)) weakfunc(void); // weak.c int weakfunc(void) { return 0; // weak definition } // override.c int weak_func(void) { return 1; // strong override }

Linking weak.c with override.c results in the strong definition from override.c being selected. If override.c is omitted, the weak definition is used. This pattern is useful for optional features.

Example 3: Linker Script

ld /* script.ld */ SECTIONS { . = 0x1000; .text : { *(.text) } .data : { *(.data) } /* Resolve symbol conflicts explicitly */ PROVIDE (common_symbol = 0x2000); }

The linker script can define a strong symbol common_symbol that overrides any weak or undefined references to the same name in the object files.

Interaction with Garbage Collection and Reference Counting

Strong References in Objective‑C and Swift

While the term “strong symbol” primarily refers to object file symbols, the concept of a strong reference appears in memory management systems. In Objective‑C, strong references are default pointer attributes that keep an object alive; weak references do not affect the reference count. Swift adopts a similar model with the strong and weak qualifiers. Though unrelated to symbol resolution, these notions share terminology that may cause confusion. The strong references in memory management are governed by the Automatic Reference Counting (ARC) system and have no direct impact on the linker’s symbol resolution.

Security Implications

Symbol Hijacking

Attacks that manipulate strong symbols can redirect program flow. If a program dynamically links against a shared library that exports a strong symbol, an attacker may replace the library with a malicious version that contains a strong symbol of the same name but a different implementation. The operating system’s dynamic loader will bind the program to the attacker’s symbol, potentially compromising security. Modern systems mitigate this by requiring signatures or enforcing strong verification of shared objects.

Symbol Stripping and Relocation

Symbol stripping removes symbol table entries to reduce binary size or hide implementation details. When stripping a binary, all strong symbols are typically removed from the symbol table, but relocations remain. Attackers can exploit remaining relocations to modify the program’s behavior. Careful management of symbol visibility (using attribute((visibility("hidden"))) in GCC) and thorough stripping policies help reduce these risks.

Symbol Versioning in glibc

glibc implements symbol versioning to allow multiple versions of the same symbol to coexist. Each symbol is associated with a version tag; strong symbols in different versions are treated as distinct by the linker. This mechanism is documented in the GNU C Library Manual at https://www.gnu.org/software/libc/manual/html_node/Symbol-Versioning.html.

Strong vs Weak in C++ Vtables

In C++, virtual tables (vtables) are generated as strong symbols. When multiple inheritance or template instantiation occurs, duplicate vtable definitions may arise. The compiler typically resolves them by emitting a single strong definition and marking others as weak to avoid duplicate symbol errors. This process is part of the C++ ABI and is covered in the Itanium C++ ABI specifications.

Best Practices

Defining Symbols in Libraries

Library authors should expose only the necessary symbols as strong. Functions that are intended to be overridden should be declared weak or placed in separate files that can be compiled with the application. This practice reduces symbol collisions and simplifies maintenance.

Using Linker Options Appropriately

When building complex applications that intentionally override library symbols, developers must carefully choose linker flags. The -Bsymbolic option can prevent unintended external resolution of internal references. However, using this option indiscriminately may mask genuine duplicate symbol errors. Developers should also use linker scripts to resolve conflicts explicitly if required.

Testing for Weak Symbol Presence

To safely test for the existence of optional features, applications can declare weak symbols and then check at runtime whether the symbol resolves to a non‑zero address. This pattern is effective in plug‑in architectures and reduces the need for extensive configuration code.

References

References & Further Reading

Sources

The following sources were referenced in the creation of this article. Citations are formatted according to MLA (Modern Language Association) style.

  1. 1.
    "https://docs.microsoft.com/en-us/windows/win32/debug/pe-format." docs.microsoft.com, https://docs.microsoft.com/en-us/windows/win32/debug/pe-format. Accessed 16 Apr. 2026.
  2. 2.
    "https://www.gnu.org/software/libc/manual/html_node/Symbol-Versioning.html." gnu.org, https://www.gnu.org/software/libc/manual/html_node/Symbol-Versioning.html. Accessed 16 Apr. 2026.
Was this helpful?

Share this article

See Also

Suggest a Correction

Found an error or have a suggestion? Let us know and we'll review it.

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!