7. GNU ELF Symbol Versioning
Note
Support for symbol versioning is planned for a future release. Stay tuned for updates
This document continues to show a minimal end-to-end demonstration of GNU ELF symbol versioning, then documents:
What the
.symver/__attribute__((symver))mechanisms do.What a GNU ld version script does.
Which ELF sections the linker/loader create/use for symbol versioning.
The example exports two versions of the same symbol and preserves backward compatibility across library releases.
7.1. Goal
Export two versions of an API symbol (foo), keeping ABI compatibility
for old applications while making a new implementation the default for
new applications.
7.2. Files
// v1 of the library: exports foo()
#include <stdio.h>
void foo(void) { puts("foo v1"); }
DEMO_1 {
global: foo;
local: *;
};
This assigns foo to version node DEMO_1 and hides everything else.
#include <stdio.h>
/* Approach A: classic assembler directive (works with GCC & Clang) */
__asm__(".symver foo_v1,foo@DEMO_1"); // non-default (old) foo
__asm__(".symver foo_v2,foo@@DEMO_2"); // default (new) foo
void foo_v1(void) { puts("foo v1"); }
void foo_v2(void) { puts("foo v2 (default)"); }
/* New API symbol in v2 */
void bar(void) { puts("bar v2"); }
DEMO_1 {
global: foo;
local: *;
};
DEMO_2 {
global: foo; bar;
} DEMO_1; /* DEMO_2 inherits from DEMO_1 */
void foo(void);
int main(void) { foo(); }
void foo(void);
void bar(void);
int main(void) { foo(); bar(); }
CC?=clang
CFLAGS=-fPIC -O2 -Wall -Wextra
all: stage1 stage2 inspect
stage1: libdemo.so.1.0 p_old
libdemo.so.1.0: demo_v1.c demo_v1.map
$(CC) $(CFLAGS) -shared -Wl,-soname,libdemo.so.1 \
-Wl,--version-script=demo_v1.map -o $@ demo_v1.c
ln -sf $@ libdemo.so
p_old: main_old.c libdemo.so
$(CC) -Wl,-rpath,'$$ORIGIN' -L. -o $@ main_old.c -ldemo
stage2: libdemo.so.2.0 p_new
libdemo.so.2.0: demo_v2.c demo_v2.map
$(CC) $(CFLAGS) -shared -Wl,-soname,libdemo.so.1 \
-Wl,--version-script=demo_v2.map -o $@ demo_v2.c
ln -sf $@ libdemo.so
p_new: main_new.c libdemo.so
$(CC) -Wl,-rpath,'$$ORIGIN' -L. -o $@ main_new.c -ldemo
inspect:
@echo "==> Exported (with versions) in libdemo.so"
@nm -D --with-symbol-versions libdemo.so | egrep ' foo| bar' || true
@echo; echo "==> Version sections in libdemo.so"
@readelf -W --version-info libdemo.so | sed -n '1,120p'
clean:
rm -f p_old p_new libdemo.so libdemo.so.* *.o
7.3. Build & Run
# Stage 1: build first library + "old" app
make stage1
LD_LIBRARY_PATH=. ./p_old
# -> prints: foo v1
# Stage 2: replace lib with v2 (keeps SONAME), rebuild "new" app
make stage2
LD_LIBRARY_PATH=. ./p_old
# -> still prints: foo v1 (old app bound to DEMO_1)
LD_LIBRARY_PATH=. ./p_new
# -> prints: "foo v2 (default)" and "bar v2"
7.4. Inspecting the Result
See exported symbols and their versions:
nm -D --with-symbol-versions libdemo.so | egrep ' foo| bar'
# ... foo@DEMO_1
# ... foo@@DEMO_2
# ... bar@@DEMO_2
@@ marks the default version; @ marks a non-default (older) version.
See version metadata sections:
readelf -W --version-info libdemo.so
You should see:
Version definitions (from
.gnu.version_d).Per-symbol version indices in
.gnu.version.(In executables) Version needs in
.gnu.version_r.
Optional runtime trace from the dynamic linker:
LD_DEBUG=versions LD_LIBRARY_PATH=. ./p_new 2>&1 | grep -E 'checking for version|needed'
7.4.1. B. What .symver / __attribute__((symver)) Do
.symver(assembler directive) - Binds a specific implementation symbol to a public name and a version node. - Syntax:.symver <impl>, <public>@<NODE>or.symver <impl>, <public>@@<NODE>. -@@marks the default implementation selected by new linkers. - The version node must exist in the version script when you build the DSO.__attribute__((symver("...")))- Front-end attribute that makes compiler emit the corresponding.symver. - Handy with LTO because it attaches at the language level.
Exactly one default definition should exist for a symbol (the one with @@).
7.4.2. C. What a Version Script Does
A GNU ld version script (use with -Wl,--version-script=...):
Declares version nodes and binds symbols to those nodes.
Controls visibility (
global:exported;local:hidden).Supports inheritance between nodes to evolve the ABI without bumping the SONAME.
For C++, you can match demangled names with an
extern "C++" { ... }block.
If you want to tag all currently exported symbols with one version without changing visibility, this works:
V1 { *; };
This does not force hidden symbols to become exported; it only tags whatever is already exported.
7.4.3. D. ELF Sections Used by Symbol Versioning
When you link with versioned symbols, the following GNU extension sections (referenced by dynamic tags) are produced/used:
.gnu.version(SHT_GNU_versym, referenced byDT_VERSYM) - A table parallel to.dynsymthat stores a 16-bit version index per symbol..gnu.version_d(SHT_GNU_verdef, referenced byDT_VERDEF/DT_VERDEFNUM) - Version definitions provided by this DSO (node names, indices, hashes)..gnu.version_r(SHT_GNU_verneed, referenced byDT_VERNEED/DT_VERNEEDNUM) - Version requirements (what this module needs from its dependencies).
readelf -W --version-info summarizes these sections so you can verify bindings and defaults.
7.4.4. E. Practical Notes & Common Pitfalls
C++ name mangling: - If you version C++ functions with
.symver, use the mangled name. - In version scripts you can use demangled names insideextern "C++" { ... }.Default vs. non-default: - Default shows as
@@; older variants show as@. - Newly linked apps bind to the default; previously linked apps keep using their recorded version.Diagnostics: -
nm -D --with-symbol-versions: quick view offoo@@V2/foo@V1. -readelf -W --version-info: detailed view of.gnu.version*sections. -LD_DEBUG=versions: watch the dynamic linker’s version checks at runtime.
7.4.5. F. One-Page Cheat Sheet
.symver/__attribute__((symver)): - Attach a version to a symbol definition; exactly one default (@@) per symbol. - Ensure the version node exists in the link-time version script.Version script: - Define version nodes, bind symbols, control visibility, and express inheritance. - Can version all currently exported symbols via
V1 { *; };.ELF sections: -
.gnu.version— per-dynamic-symbol version indices (DT_VERSYM). -.gnu.version_d— version definitions (DT_VERDEF*). -.gnu.version_r— version requirements (DT_VERNEED*).
7.4.6. Appendix: Quick Commands
# Build first version and old app
make stage1
LD_LIBRARY_PATH=. ./p_old
# Upgrade library, build new app
make stage2
LD_LIBRARY_PATH=. ./p_old
LD_LIBRARY_PATH=. ./p_new
# Inspect exports and versions
nm -D --with-symbol-versions libdemo.so | egrep ' foo| bar'
readelf -W --version-info libdemo.so
# Trace loader checks