Updated in 2026-01.
Many people just want to know how to define or reference versioned symbols properly. You may jump to Recommended usage below.
In 1995, Solaris' link editor and ld.so introduced the symbol versioning mechanism. Ulrich Drepper and Eric Youngdale borrowed Solaris' symbol versioning in 1997 and designed the GNU style symbol versioning for glibc.
gnu-gabi specification: https://sourceware.org/gnu-gabi/program-loading-and-dynamic-linking.txt#:~:text=versioning
When a shared object is updated and the behavior of a symbol changes,
a DT_SONAME version bump is traditionally required to
indicate ABI incompatibility (such as changing the type of parameters or
return values). The DT_SONAME version bump can be
inconvenient when there are many dependent applications. If we don't
bump DT_SONAME, a dependent application/shared object built
with the old version may run abnormally at run-time.
Symbol versioning provides a way to maintain backward compatibility
without changing DT_SONAME.
The following part describes the representation, and then describes the behaviors from the perspectives of assembler, linker, and ld.so. One may wish to skip the representation part when reading for the first time.
Representation
In a shared object or executable file that uses symbol versioning,
there are up to three sections. .gnu.version_r and
.gnu.version_d are optional:
.gnu.version(typeSHT_GNU_versym, pointed to byDT_VERSYM): A parallel table to.dynsymcontaining Nuint16_tversion IDs, one per dynamic symbol..gnu.version_r(typeSHT_GNU_verneed, delimited byDT_VERNEED/DT_VERNEEDNUM): Describes versions required by undefined symbols..gnu.version_d(typeSHT_GNU_verdef, delimited byDT_VERDEF/DT_VERDEFNUM): Describes versions of defined symbols.
The parallel table design makes symbol versioning optional - ld.so implementations that ignore it (like musl) treat all references as binding to default versions.
1 | // Version definitions |
Verdef entries describe version definitions in the
module. Each Verdef has one or more Verdaux
entries (vd_aux): the first gives the version name, and
subsequent entries (if any, vda_next) name parent versions
for version inheritance (e.g., v2 {} v1;).
vd_cnt equals one plus the number of parent version
definitions. However, it is not useful in practice—glibc and FreeBSD
rtld ignore it, and ld.lld simply hard-codes it to 1.
Verneed entries describe version requirements from other
shared objects (vn_file). Each Verneed has one
or more Vernaux entries specifying the required version
names (vna_name).
Version names are not globally unique. The following cases are valid:
- A
Verdefentry defines versionv1while aVerneedentry requires versionv1froma.so. - A
Verneedentry requires versionv1froma.sowhile anotherVerneedentry requires versionv1fromb.so.
VER_FLG_WEAK
GNU ld sets VER_FLG_WEAK in
Verdef::vd_flags when a version node has no associated
symbol, matching Solaris behavior. BZ24718#c15
proposed "set VER_FLG_WEAK on version reference if all symbols are weak"
but was rejected. A 2026
comment requires to revisit whether VER_FLG_WEAK
version references should support optional dependencies—e.g., an
executable with a weak reference to foo links against
libA.so.1 (which provides foo@@v1), but at
runtime runs with a libA.so.1 that lacks the version
definition for v1. Currently this causes an rtld error.
Version index values
- Index 0 is called
VER_NDX_LOCAL(misleading name, should have beenVER_NDX_NONE). For defined symbols, the binding of the symbol will be changed toSTB_LOCAL. - Index 1 is called
VER_NDX_GLOBAL. It has no special effect and is used for unversioned symbols. - Index 2 to 0xffef are used for user defined versions.
Defined versioned symbols have two forms:
foo@@v2, the default version.foo@v2, a non-default version (hidden version). TheVERSYM_HIDDENbit of the version ID is set.
Undefined versioned symbols have only the foo@v2
form.
There is a special case: a version symbol referenced by a copy
relocation in an executable. The symbol acts as a definition in runtime
relocation processing but its version ID references
.gnu.version_r instead of .gnu.version_d. The
resolution of PR28158
picks the form @.
Usually versioned symbols are only defined in shared objects, but executables can have defined versioned symbols as well. (When a shared object is updated, the old symbols are retained so that other shared objects do not need to be relinked, and executable files usually do not provide versioned symbols for other shared objects to reference.)
In the .gnu.version section (described by the
DT_VERSYM tag), each symbol is assigned a version
index:
- Unversioned undefined symbols use index 0. However, LLD before 22 and GNU ld versions between 2.35 and 2.45 use index 1. See https://sourceware.org/bugzilla/show_bug.cgi?id=33577#c34
- Versioned undefined symbols use index >= 2
- Unversioned defined symbols use index 1
- Versioned defined symbols use index >= 2
Defined symbols of index 0 are local and should not have an entry in
.dynsym or .gnu.version.
Example
readelf -V can dump the symbol versioning tables.
In the .gnu.version_d output below:
- Version index 1 (
VER_NDX_GLOBAL) is the filename (soname if shared object). TheVER_FLG_BASEflag is set. - Version index 2 is a user defined version. Its name is
LUA_5.3.
In the .gnu.version_r output below, each of version
indexes 3~10 represents a version in a needed shared object. The name
GLIBC_2.2.5 appears thrice, each for a different shared
object.
The .gnu.version table assigns a version index to each
.dynsym entry. An entry (version ID) corresponds to a
Index: entry in .gnu.version_d or a
Version: entry in .gnu.version_r.
1 | % readelf -V /usr/bin/lua5.3 |
Symbol versioning in object files
The GNU scheme allows .symver directives to label the
versions of the symbols in relocatable object files. The symbol names
residing in .o contain @ or @@.
Assembler behavior
GNU as and LLVM integrated assembler provide implementation.
.symver foo, foo@v1- If foo is undefined, produce
foo@v1 - If foo is defined, produce
fooandfoo@v1with the same binding (STB_LOCAL,STB_WEAK, orSTB_GLOBAL) andst_othervalue (i.e. the same visibility). Personally I think this behavior is a design flaw {gas-copy}. The proposed V4 PATCH gas: Extend .symver directive can address this problem.
- If foo is undefined, produce
.symver foo, foo@@v1- If foo is undefined, error
- If foo is defined, produce
fooandfoo@v1with the same binding andst_othervalue.
.symver foo, foo@@@v1- If foo is undefined, produce
foo@v1 - If foo is defined, produce
foo@@v1
- If foo is undefined, produce
With GNU as 2.35 (PR25295) or Clang 13:
.symver foo, foo@v1, remove- If foo is undefined, produce
foo@v1 - If foo is defined, produce
foo@v1 - This is a recommended way to define a non-default version symbol.
- Unfortunately, in GNU as,
foocannot be used in a relocation (PR28157).
- If foo is undefined, produce
Linker behavior
The linker enters the symbol resolution stage after reading in object files, archive files, shared objects, LTO files, linker scripts, etc.
GNU ld uses indirect symbols to represent versioned symbols. There are complicated rules, and these rules are not documented. The symbol resolution rules that I personally derived:
- Defined
fooresolves undefinedfoo(traditional unversioned rule) - Defined
foo@v1resolves undefinedfoo@v1(a non-default version symbol is like a separate symbol) - Defined
foo@@v1(default version) resolves both undefinedfooandfoo@v1
If there are multiple default version definitions (such as
foo@@v1 foo@@v2), a duplicate definition error should be
issued even if one is weak. Usually a symbol has zero or one default
version (@@) definition, and an arbitrary number of
non-default version (@) definitions.
If the linker sees undefined foo and foo@v1
first, it will treat them as two symbols. When the linker sees the
definition foo@@v1, conceptually foo and
foo@@v1 should be combined. If the linker sees
foo@@v2 instead, foo@@v2 should resolve
foo and foo@v1 should be a separate
symbol.
- Combining Versions describes the problem.
gold/symtab.cc Symbol_table::define_default_versionuses a heuristic rule to solve this problem. It special cases on visibility, but I feel that this rule is unneeded.- Before 2.36, GNU ld reported a bogus multiple definition error for
defined weak
foo@@v1and defined globalfoo@v1PR ld/26978 - Before 2.36, GNU ld had a bug that the visibility of undefined
foo@v1does not affect the output visibility offoo@@v1: PR ld/26979 - I fixed the object file side problem of ld.lld 12.0 in https://reviews.llvm.org/D92259
fooArchive files and lazy object files may still have incompatibility issues.
When ld.lld sees a defined foo@@v, it adds both
foo and foo@v1 into the symbol table, thus
foo@@v1 can resolve both undefined foo and
foo@v1. After processing all input files, a pass iterates
symbols and redirects foo@v1 to foo@@v1.
Because ld.lld treats them as separate symbols during input processing,
a defined foo@v cannot suppress the extraction of an
archive member defining foo@@v1, leading to a behavior
incompatible with GNU ld. This probably does not matter, though.
If both foo and foo@v1 are defined (at the
same position), foo will be removed. GNU ld has another
strange behavior: if both foo and foo@v1 are
defined, foo will be removed. I strongly believe it is an
issue in GNU ld but the maintainer rejected PR
ld/27210. I implemented a similar hack in ld.lld 13.0.0 (https://reviews.llvm.org/D107235) but hoped binutils can
fix the assembler issue (https://sourceware.org/pipermail/binutils/2021-August/117677.html).
ld.lld assigns version indexes as follows: first, each
Verdef entry gets an index starting from 2; then, for each
dynamic symbol resolving to a shared object definition, a new
Verneed/Vernaux index is assigned if the
(file, version) pair hasn't been seen.
Version script
To define a versioned symbol in a shared object or an executable, a version script must be specified. If there is no defined versioned symbol, the version script can be omitted.
1 | # Make all symbols other than foo and bar local. |
A version script has three purposes:
- Define versions.
- Specify some patterns so that matched defined non-local symbols
(which do not have
@in the name) are tied to the specified version. - Scope reduction
- for a matched symbol, its binding will be changed to
STB_LOCALand will not be exported to the dynamic symbol table. - for a defined unversioned symbol, it can be matched by a
local:pattern in any version node. E.g.foocan be matched byv1 { local: foo; }; - for a defined versioned symbol, it can be matched by a
local:pattern in the associated version node. E.g. bothfoo@@v1andfoo@v1can be matched byv1 { local: foo; };.
- for a matched symbol, its binding will be changed to
A version script consists of one anonymous version tag
({...};) or a list of named version tags
(v1 {...};). If you use an anonymous version tag with other
version tags, GNU ld will error:
anonymous version tag cannot be combined with other version tags.
A local: part can be placed in any version tag. Which
version tag is used does not matter.
If a defined symbol is matched by multiple version tags, the
following precedence rules apply
(binutils-gdb/bfd/linker.c:find_version_for_sym):
- The first version tag with an exact pattern (i.e. there is no wildcard) wins.
- Otherwise, the last version tag with a non-
*wildcard pattern inglobal:wins. - Otherwise, the last version tag with a non-
*wildcard pattern inlocal:wins. - Otherwise, the last version tag with a
*pattern wins.
In gold and ld.lld, the rules are like:
- The first version tag with an exact pattern (i.e. there is no wildcard) wins.
- Otherwise, the last version tag with a non-
*wildcard pattern wins. If the version tag has non-*wildcard patterns in bothglobal:andlocal:, theglobal:one wins. - Otherwise, the last version tag with a
*pattern wins. (Prior to LLD 18, the first instead of the last)
For example, given
v1 { local: p*;}; v2 { global: pq*;}; v3 { local: pqr*;};,
local: pqr* is selected for a defined non-local symbol
pqrs in gold and ld.lld while global: pq* is
slected in GNU ld.
** is also a catch-all pattern, but its precedence is
higher than *.
GNU ld reports an error when a pattern appears in both
global: and local:.
Most patterns are exact so gold and ld.lld iterate patterns instead of symbols to improve performance.
GNU ld and gold add an absolute symbol
(st_shndx=SHN_ABS) for each defined version to
.symtab and .dynsym. ld.so does not need the
symbol, so this behavior looks strange to me.
In a -r link, --version-script is ignored.
Technically local: version nodes may be useful together
with -r, but GNU ld and ld.lld just ignore
--version-script.
How a versioned symbol is produced
An undefined symbol can be assigned a version if:
- its name does not contain
@(.symveris unused) and a shared object provides a default version definition. - its name contains
@and a shared object defines the symbol. GNU ld errors if there is no such a shared object. After https://reviews.llvm.org/D92260, ld.lld will report an error as well.
A defined symbol can be assigned a version if:
- its name does not contain
@and it is matched by a pattern in a named version tag in a version script. - its name contains
@- If
-shared, the version should be defined by a version script, otherwise GNU ld errorsversion node not found for symbol. This exception looks strange to me so I have filed PR ld/26980. - If
-no-pieor-pie, a version definition is unneeded in GNU ld. This behavior is strange.
- If
Recommended usage
Personal recommendation:
To define a default version symbol, don't use .symver.
Just list the symbol name in a version node in the version script. If
you really want to use .symver, use
.symver foo, foo@@@v2 so that foo is not
present. If you require binutils>=2.35 or Clang>=13,
.symver foo, foo@@v2, remove works as well.
To define a non-default version symbol, add a suffix to the original
symbol name (.symver foo_v1, foo@v1) to prevent conflicts
with foo. This will however leave (usually undesirable)
foo_v1. If you don't strip foo_v1 from the
object file, you may localize it with a local: pattern in
the version script. With the newer toolchain, you can use
.symver foo_v1, foo@v1, remove.
1 | cat > a.c <<e |
1 | % readelf -W --dyn-syms a.so | grep @ |
Most of the time, you want an undefined symbol to be bound to the
default version symbol at link time. It is usually unnecessary to set
the version with .symver.
If you really need to set a version, either
.symver foo_v1, foo@@@v1 or
.symver foo_v1, foo@v1 is fine.
1 | cat > b.c <<e |
The reference is bound to the non-default version
foo@v1: 1
2% readelf -W --dyn-syms b.so | grep foo
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foo@v1 (2)
If you omit .symver, the reference will be bound to the
default version foo@@v2.
Why is
.symver xxx, foo@v1 bad with a defined symbol?
There are two cases.
First, xxx is not foo (the unadorned name of the
versioned symbol). This is the most common usage. Without loss of
generality, we use .symver foo_v1, foo@v1 as the
example.
If the version script does not localize foo_v1, we will
get foo_v1 in .dynsym. The extra symbol is
almost always undesired.
Second, xxx is foo. The foo definition can
satisfy unversioned references from other TUs. If you think about it, it
is very rare for a non-default version definition to be used outside the
TU.
1 | # a.s |
If the version script contains v1 {};, the output will
have just foo@v with GNU ld and ld.lld>=13.0.0. The
output will have both foo and foo@v1 with gold
and older ld.lld.
If the version script contains v1 { foo; };, the output
will have just foo@v1 with GNU ld, gold, and
ld.lld>=13.0.0. The output will have both foo and
`foo@v1 with older ld.lld.
If the version script contains v2 { foo; };, the patterm
will be ignored. Unfortunately no linker reports a warning for this
error-prone case.
Having distinct behaviors is unfortunate. And the second case requires complexity in the linker internals.
rtld behavior
Linux Standard Base Core Specification, Generic Part describes the behavior of rtld (ld.so). Kan added symbol versioning support to FreeBSD rtld in 2005.
The DT_VERNEED and DT_VERNEEDNUM tags in
the dynamic table delimiter the version requirement by a shared
object/executable file: the required (needed) versions and required
(needed) shared object names (Vernaux::vna_name).
When an object with DT_VERNEEDED is loaded, glibc rtld
performs some checks (_dl_check_all_versions). For each
Vernaux entry (a Verneed's auxiliary entry), glibc rtld checks whether
the referenced shared object has a DT_VERDEF table. If no,
ld.so handles the case as a graceful degradation and prints
no version information available (required by %s); if yes
and the table does not define the version, ld.so reports (if the Vernaux
entry has the VER_FLG_WEAK bit) a warning or (otherwise) an
error. [verneed-check]
Usually a minor release does not bump soname. Suppose that libB.so
depends on libA 1.3 (soname is libA.so.1) and calls a function which
does not exist in libA 1.2. If PLT lazy binding is used, libB.so may
seem to work on a system with libA 1.2, until the PLT of the 1.3 symbol
is called. If symbol versioning is not used and you want to solve this
problem, you have to record the minor version number
(libA.so.1.3) in the soname. However, bumping soname is
all-or-nothing: all the dependent shared objects need to be relinked. If
symbol versioning is used, you can continue to use the soname
libA.so.1. ld.so will report an error if libA 1.2 is used,
because the 1.3 version required by libB.so does not exist.
When searching a definition for foo,
- for an object without
DT_VERSYM- it can be bound to
foo
- it can be bound to
- for an object with
DT_VERSYM- it can be bound to
fooof versionVER_NDX_GLOBAL. This takes precendence over the next two rules - it can be bound to
fooof any default version - it can be bound to
fooof non-default version index 2 in relocation resolving phase (not dlsym/dlvsym). The rule retains compatibility when a shared object becomes versioned.
- it can be bound to
Note (undefined foo binding to foo@v1 with
version index 2) is allowed by ld.so but not allowed by the linker
{reject-non-default}. The rtld behavior
is to retains compatibility when a shared object becomes versioned: the
symbols with the smallest version (index 2) indicate the previously
unversioned symbols. If a new version of a shared object needs to
deprecate an unversioned bar, you can remove
bar and define bar@compat instead. Libraries
using bar are unaffected but new linking against
bar is disallowed.
When there are multiple versions of foo,
dlsym(RTLD_DEFAULT, ...) returns the default version. On
glibc before 2.36, dlsym(RTLD_NEXT, ...) returned
the first version (BZ14932). This was because in
elf/dl-sym.c:do_sym, the RTLD_NEXT branch did
not pass the flags DL_LOOKUP_RETURN_NEWEST to
dl_lookup_symbol_x. FreeBSD does not have the issue.
When searching a definition for foo@v1,
- for an object without
DT_VERSYM- it can be bound to
foo. In glibc,elf/dl-lookup.c:check_matchasserts that the filename does not match thevn_filefilename
- it can be bound to
- for an object with
DT_VERSYM- it can be bound to
foo@v1orfoo@@v1 - it can be bound to
fooof versionVER_NDX_GLOBALin relocation resolving phase (not dlsym/dlvsym)
- it can be bound to
Say b.so references malloc@GLIBC_2.2.5. The
executable defines an unversioned malloc due to linking in
a malloc implementation. At run-time, malloc@GLIBC_2.2.5 in
b.so will bind to the executable. For example,
address/memory/thread sanitizers leverage this behavior: shared objects
do not need to link in interceptors; having the interceptor in the
executable is sufficient. libxml2 relied on the behavior to drop
versioning on symbols while retaining compatibility for objects
linking against older versions of libxml2.
When a versioned referenced is bound to a shared object without
symbol versioning, in glibc versions before 2.41,
elf/dl-lookup.c:check_match used
to assert that the filename did not match the vn_file
filename 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17echo 'void foo(); int main() { foo(); }' > a.c
echo 'v1 { foo; };' > c0.ver
echo 'void foo() {}' > c.c
sed 's/^ /\t/' > Makefile <<'eof'
.MAKE.MODE := meta curdirOk=1
CFLAGS := -fpic
LDFLAGS := -Wl,--no-as-needed
a: a.c c.so c0.so
$(LINK.c) a.c c0.so -Wl,-rpath=$$PWD -o $@
c0.so: c.c c0.ver
$(LINK.c) -shared -Wl,-soname=c.so,--version-script=c0.ver c.c -o $@
c.so: c.c
$(LINK.c) -shared -Wl,-soname=c.so -nostdlib c.c -o $@
clean:
rm -f a *.so *.o *.meta
eof1
2
3
4
5
6% bmake && ./a # glibc<2.41
./a: /tmp/d/c.so: no version information available (required by ./a)
Inconsistency detected by ld.so: dl-lookup.c: 107: check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!
% bmake && ./a # glibc>=2.41
./a: /tmp/d/c.so: no version information available (required by ./a)
This glibc<2.41 check is pretty dumb, as most shared objects have
DT_VERSYM due to versioned references to libc like
__cxa_finalize@GLIBC_2.2.5 (from GCC
crtbeginS.o).
Aside from this assertion, vn_file is essentially
ignored for symbol search since glibc 2.30 BZ24741. Previously during
relocation resolving, after an object failed to provide a match, if it
matched vn_file, rtld would report an error
symbol %s version %s not defined in file %s with link time reference.
glibc
2.30 ld.so: Support moving versioned symbols between sonames [BZ
#24741] has a side benefit for weak references. Previously, if
b.so had a versioned weak reference foo@v1
(where v1 referenced c.so), rtld would error
with
symbol %s version %s not defined in file %s with link time reference
when c.so lacked foo@@v1 or
foo@v1—contrary to weak reference semantics. Newer rtld
tolerates this as long as the runtime c.so defines version
v1:
1 | echo '#include <stdio.h>\nvoid fb(); int main() { fb(); puts("a"); }' > a.c |
If c.so lacks the required version entirely, rtld still
reports a fatal error: 1
2
3
4
5
6% bmake
...
% ./a
a
% LD_PRELOAD=c2.so ./a
./a: /tmp/t/v2/c2.so: version `v1' not found (required by /tmp/t/v2/b.so)
The BZ24718#319
comment in 2026 requested a feature: When all undefined references
to a version are weak, set vna_flags to
VER_FLG_WEAK in the .gnu.version_r section.
While this remains unimplemented, I have implemented this idea for lld
in January 2026.
If the v1 Verneed in a has the
VER_FLG_WEAK flag, we shall see the following instead:
1 | % LD_PRELOAD=c2.so ./a |
Example
Run the following code to create a.c b.c b0.c Makefile,
then run bmake.
1 | cat > ./a.c <<'eof' |
Before glibc 2.36, the output is: 1
2
3
4% ./a
foo(0) = 0, ok
foo(0) = 3, ok
foo(0) = 0, wrong
Upgraded symbols in glibc
When to prevent execution of new binaries with old glibc has a summary about when a new symbol version is introduced.
Note that GNU nm before binutils 2.35 does not display @
or @@.
1 | nm -D /lib/x86_64-linux-gnu/libc.so.6 | \ |
The output on my x86-64 system:
1 | pthread_cond_broadcast @GLIBC_2.2.5 @@GLIBC_2.3.2 |
realpath@@GLIBC_2.3: the previous version returnsEINVALwhen the second parameter isNULLmemcpy@@GLIBC_2.14BZ12518: the previous version guarantees a forward copying behavior. Shockwave Flash at that time had a "memcpy downward" bug which required the workaround.quick_exit@@GLIBC_2.24BZ20198: the previous version copies the destructors of thread_local objects.glob64@@GLIBC_2.27: the previous version does not follow dangling symlinks.
How to remove symbol versioning
Imagine that you want to build an application with a prebuilt shared
object which has versioned references, but you can only find shared
objects providing the unversioned definitions. The linker will helpfully
error: 1
ld.lld: error: undefined reference to foo@v1 [--no-allow-shlib-undefined]
As the diagnostic suggests, you can add
--allow-shlib-undefined to get rid of the error. It is not
recommended but the built application may happen to work.
For this case, an alternative hacky solution is:
1 | # 64-bit |
With the removal of .gnu.version, the linker will think
that out.so references foo instead of
foo@v1. However, llvm-objcopy will zero out the section
contents. At runtime, glibc ld.so will complain
unsupported version 0 of Verneed record. To make glibc
happy, you can delete DT_VER* tags from the dynamic table.
The above code snippet uses an r2 command to locate
DT_VERNEED(0x6ffffffe) and rewrite it to
DT_NULL(a DT_NULL entry stops the parsing of
the dynamic table). The difference of the readelf -d output
is roughly:
1 | 0x000000006ffffffb (FLAGS_1) Flags: NOW |
ld.lld
- If an undefined symbol is not defined by a shared object, GNU ld will report an error. ld.lld before 12.0 did not error (I fixed it in https://reviews.llvm.org/D92260).
GNU function attribute
There is a GNU function attribute which is lowers to a
.symver assembly directive. The attribute is implemented by
GCC but not by Clang.
1 | extern "C" __attribute__((symver("foo@@v2"))) void foo() {} |
Unfortunately, @@@ and ,remove are not
supported. Along with the reason that Clang does not implement the
function attribute, I discourage using this feature.
Remarks
GCC/Clang supports asm specifier and
#pragma redefine_extname renaming a symbol. For example, if
you declare int foo() asm("foo_v1"); and then reference
foo, the symbol in .o will be foo_v1.
For example, the biggest change in musl v1.2.0 is the time64 support for its supported 32-bit architectures. musl adopted a scheme based on asm specifiers:
1 | // include/features.h |
- In .o, the time32 symbol remains
utimesand is compatible with the ABI required by programs linked against old musl versions; the time64 symbol is__utimes_time64. - The public header redirects
utimesto__utimes_time64.- cons: if the user declares
utimesby themself, they will not link against the correct__utimes_time64.
- cons: if the user declares
- The "good-looking" name
utimesis used for the preferred time64 implementation internally and the "ugly" name__utimes_time32is used for the legacy time32 implementation.- If the time32 implementation is called elsewhere, the "ugly" name can make it stand out.
For the above example, here is an implementation with symbol versioning:
1 | // API header include/sys/time.h |
Note that @@@ cannot be used. The header is included in
a defining translation unit and @@@ will lead to a default
version definition while we want a non-default version definition.
According to Assembler behavior,
the undesirable __utimes_time32 is present. Be careful to
use a version script to localize it.
So what is the significance of symbol versioning? I think carefully:
- Refuse linking against old symbols while keeping compatibility with unversioned old libraries. {reject-non-default}
- No need to label declarations.
- The version definition can be delayed until link time. The version script provides a flexible pattern matching mechanism to assign versions.
- Scope reduction. Arguably another mechanism like
--dynamic-listmight have been developed if version scripts did not providelocal:. - There are some semantic issues in renaming builtin functions with asm specifiers in GCC and Clang (they do not know that the renamed symbol has built-in semantic). See 2020-10-15-intra-call-and-libc-symbol-renaming
- [verneed-check]
For the first item, the asm specifier scheme uses conventions to prevent problems (users should include the header); and symbol versioning can be forced by ld.
Design flaws:
.symver foo, foo@v1{gas-copy}- Verdaux is a bit redundant. In practice, one Verdef has only one auxiliary Verdaux entry.
- This is arguably a minor problem but annoying for a framework
providing multiple shared objects. ld.so requires "a versioned symbol is
implemented in the same shared object in which it was found at link
time", which disallows moving definitions between shared objects.
Fortunately, glibc 2.30 BZ24741 relaxes this
requirement, essentially ignoring
Vernaux::vna_name.
Before that, glibc used a forwarder to move clock_*
functions from librt.so to libc.so:
1 | // rt/clock-compat.c |
libc.so defines __clock_getres and
clock_getres. librt.so defines an ifunc called
clock_getres which forwards to libc.so
__clock_getres.