559 lines
24 KiB
Markdown
559 lines
24 KiB
Markdown
# All about symbol versioning
|
|
|
|
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.
|
|
|
|
When a shared object is updated, the behavior of a symbol changes (ABI changes
|
|
(such as changing the type of parameters or return values) or behavior
|
|
changes), traditionally a `DT_SONAME` bump is required. Otherwise a dependent
|
|
application/shared object built with the old version may run abnormally. This
|
|
can be inconvenient if the number of dependent applications is large.
|
|
|
|
Symbol versioning provides 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 related to symbol versioning. `.gnu.version_r` and
|
|
`.gnu.version_d` among them are optional:
|
|
|
|
* `.gnu.version` (version symbol section). The `DT_VERSYM` tag in the dynamic
|
|
table points to the section. Assuming there are N entries in `.dynsym`,
|
|
`.gnu.version` contains N `uint16_t` values, with the i-th entry indicating
|
|
the version ID of the i-th symbol. Put it another way, `.gnu.version` is a
|
|
parallel table to `.dynsym`.
|
|
* `.gnu.version_r` (version requirement section). The `DT_VERNEED`/
|
|
`DT_VERNEEDNUM` tags in the dynamic table delimiter this section. This
|
|
section describes the version information used by the undefined versioned
|
|
symbol in the module.
|
|
* `.gnu.version_d` (version definition section). The `DT_VERDEF`/`DT_VERDEFNUM`
|
|
tags in the dynamic table delimiter this section. This section describes the
|
|
version information used by the defined versioned symbols in the module.
|
|
|
|
```c
|
|
// Version definitions
|
|
typedef struct {
|
|
Elf64_Half vd_version; // version: 1
|
|
Elf64_Half vd_flags; // VER_FLG_BASE (index 1) or 0 (index != 1)
|
|
Elf64_Half vd_ndx; // version index
|
|
Elf64_Half vd_cnt; // number of associated aux entries, always 1 in practice
|
|
Elf64_Word vd_hash; // SysV hash of the version name
|
|
Elf64_Word vd_aux; // offset in bytes to the verdaux array
|
|
Elf64_Word vd_next; // offset in bytes to the next verdef entry
|
|
} Elf64_Verdef;
|
|
|
|
typedef struct {
|
|
Elf64_Word vda_name; // version name
|
|
Elf64_Word vda_next; // offset in bytes to the next verdaux entry
|
|
} Elf64_Verdaux;
|
|
|
|
// Version needs
|
|
typedef struct {
|
|
Elf64_Half vn_version; // version: 1
|
|
Elf64_Half vn_cnt; // number of associated aux entries
|
|
Elf64_Word vn_file; // .dynstr offset of the depended filename
|
|
Elf64_Word vn_aux; // offset in bytes to vernaux array
|
|
Elf64_Word vn_next; // offset in bytes to next verneed entry
|
|
} Elf64_Verneed;
|
|
|
|
typedef struct {
|
|
Elf64_Word vna_hash; // SysV hash of vna_name
|
|
Elf64_Half vna_flags; // usually 0; copied from vd_flags of the depended so
|
|
Elf64_Half vna_other; // unused
|
|
Elf64_Word vna_name; // .dynstr offset of the version name
|
|
Elf64_Word vna_next; // offset in bytes to next vernaux entry
|
|
} Elf64_Vernaux;
|
|
```
|
|
|
|
Currently GNU ld does not set the `VER_FLG_WEAK` flag. [BZ24718#c15](https://sourceware.org/bugzilla/show_bug.cgi?id=24718#c15) proposed "set
|
|
`VER_FLG_WEAK` on version reference if all symbols are weak".
|
|
|
|
The advantage of using a parallel table for `.gnu.version` is that symbol
|
|
versioning is optional. ld.so implementations which do not support symbol
|
|
versioning can freely assume no symbol has a version. The behavior is that all
|
|
references as if bind to the default version definitions. musl ld.so falls into
|
|
this category.
|
|
|
|
### Version index values
|
|
|
|
Index 0 is called `VER_NDX_LOCAL`. The binding of the symbol will be changed to
|
|
`STB_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). The `VERSYM_HIDDEN` bit of the
|
|
version ID is set.
|
|
|
|
Undefined versioned symbols have only the `foo@v2` 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.)
|
|
|
|
### 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).
|
|
The `VER_FLG_BASE` flag 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 depended 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.
|
|
|
|
```
|
|
% readelf -V /usr/bin/lua5.3
|
|
|
|
Version symbols section '.gnu.version' contains 248 entries:
|
|
Addr: 0x0000000000002af4 Offset: 0x002af4 Link: 5 (.dynsym)
|
|
000: 0 (*local*) 3 (GLIBC_2.3) 4 (GLIBC_2.2.5) 4 (GLIBC_2.2.5)
|
|
004: 5 (GLIBC_2.3.4) 4 (GLIBC_2.2.5) 4 (GLIBC_2.2.5) 4 (GLIBC_2.2.5)
|
|
...
|
|
|
|
Version definition section '.gnu.version_d' contains 2 entries:
|
|
Addr: 0x0000000000002ce8 Offset: 0x002ce8 Link: 6 (.dynstr)
|
|
000000: Rev: 1 Flags: BASE Index: 1 Cnt: 1 Name: lua5.3
|
|
0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: LUA_5.3
|
|
|
|
Version needs section '.gnu.version_r' contains 3 entries:
|
|
Addr: 0x0000000000002d20 Offset: 0x002d20 Link: 6 (.dynstr)
|
|
000000: Version: 1 File: libdl.so.2 Cnt: 1
|
|
0x0010: Name: GLIBC_2.2.5 Flags: none Version: 9
|
|
0x0020: Version: 1 File: libm.so.6 Cnt: 1
|
|
0x0030: Name: GLIBC_2.2.5 Flags: none Version: 6
|
|
0x0040: Version: 1 File: libc.so.6 Cnt: 6
|
|
0x0050: Name: GLIBC_2.11 Flags: none Version: 10
|
|
0x0060: Name: GLIBC_2.14 Flags: none Version: 8
|
|
0x0070: Name: GLIBC_2.4 Flags: none Version: 7
|
|
0x0080: Name: GLIBC_2.3.4 Flags: none Version: 5
|
|
0x0090: Name: GLIBC_2.2.5 Flags: none Version: 4
|
|
0x00a0: Name: GLIBC_2.3 Flags: none Version: 3
|
|
```
|
|
|
|
### Symbol versioning in object files
|
|
|
|
The GNU scheme allows `.symver` directives to label the versions of the symbols
|
|
in objec 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 `foo` and `foo@v1` with the same binding
|
|
(`STB_LOCAL`, `STB_WEAK`, or `STB_GLOBAL`) and `st_other` value (i.e. the
|
|
same visibility). Personally I think this behavior is a design flaw
|
|
[{gas-copy}](). The proposed [V4 PATCH gas: Extend .symver directive](https://sourceware.org/pipermail/binutils/2020-April/110622.html)
|
|
can address this problem.
|
|
* `.symver foo, foo@@v1`
|
|
* If foo is undefined, error
|
|
* If foo is defined, produce `foo` and `foo@v1` with the same binding and `st_other` value.
|
|
* `.symver foo, foo@@@v1`
|
|
* If foo is undefined, produce `foo@v1`
|
|
* If foo is defined, produce `foo@@v1`
|
|
|
|
Personal recommendation:
|
|
|
|
* To define a default version symbol: use `.symver foo, foo@@@v2` so that foo
|
|
is not present.
|
|
* 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 GNU as 2.35 ([PR25295](https://sourceware.org/bugzilla/show_bug.cgi?id=25295)),
|
|
you can use `.symver foo_v1, foo@v1, remove`
|
|
* The version of an undefined symbol is usually bound at link time. It is
|
|
usually unnecessary to set the version with `.symver`. If required, prefer
|
|
`.symver foo, foo@@@v1` to `.symver foo, foo@v1`.
|
|
|
|
## 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 symbol to represent versioned symbols. There are
|
|
complicated rules, and these rules are not documented. The symbol resolution
|
|
rules that I personally derived:
|
|
|
|
* Defined `foo` resolves undefined `foo` (traditional unversioned rule)
|
|
* Defined `foo@v1` resolves undefined `foo@v1` (a non-default version symbol is
|
|
like a separate symbol)
|
|
* Defined `foo@@v1` (default version) resolves both undefined `foo` and `foo@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 see 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](combining-versions.md) describes the problem.
|
|
* `gold/symtab.cc Symbol_table::define_default_version` uses a heuristic rule
|
|
to solve this problem. It special cases on visibility, but I feel that this
|
|
rule is unneeded.
|
|
* Before 2.26, GNU ld reported a bogus multiple definition error for defined
|
|
weak `foo@@v1` and defined global `foo@v1` [PR ld/26978](https://sourceware.org/bugzilla/show_bug.cgi?id=26978)
|
|
* Before 2.26, GNU ld had a bug that the visibility of undefined `foo@v1` does
|
|
not affect the output visibility of `foo@@v1`: [PR ld/26979](https://sourceware.org/bugzilla/show_bug.cgi?id=26979)
|
|
* I fixed the object file side problem of LLD 12.0 in https://reviews.llvm.org/D92259
|
|
`foo` Archive files and lazy object files may still have incompatibility issues.
|
|
|
|
When 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`. Becase 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.
|
|
|
|
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](https://sourceware.org/bugzilla/show_bug.cgi?id=27210).
|
|
|
|
## Version script
|
|
|
|
To define a versioned symbol in a shared object or an executable, a version
|
|
script must be specified. If all versioned symbols are undefined, then the
|
|
version script can be omitted.
|
|
|
|
```
|
|
# Make all symbols other than foo and bar local.
|
|
{ global: foo; bar; local: *; };
|
|
|
|
# Assign version FBSD_1.0 to malloc and version FBSD_1.3 to mallocx,
|
|
# and make internal local.
|
|
FBSD_1.0 { malloc; local: internal; };
|
|
FBSD_1.3 { mallocx; };
|
|
```
|
|
|
|
A version script has three purposes:
|
|
|
|
* Define versions.
|
|
* Specify some patterns so that matched defined symbols (which do not have `@`
|
|
in the name) are tied to the specified version.
|
|
* Scope reduction: for a defined unversioned symbol matched by a `local:`
|
|
pattern, its binding will be changed to `STB_LOCAL` and will not be exported
|
|
to the dynamic symbol table.
|
|
|
|
A version script can consist 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 wins.
|
|
* Otherwise, the first version tag with a `*` pattern wins.
|
|
|
|
The gotcha is that `**` is a wildcard pattern which matches any symbol but its
|
|
precedence is higher than `*`.
|
|
|
|
Most patterns are exact so gold and LLD iterate patterns instead of symbols to
|
|
improve performance.
|
|
|
|
## How a versioned symbol is produced
|
|
|
|
An undefined symbol can be assigned a version if:
|
|
|
|
* its name does not contain `@` (`.symver` is 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,
|
|
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 errors version node not found for symbol. This exception looks
|
|
strange to me so I have filed [PR ld/26980](https://sourceware.org/bugzilla/show_bug.cgi?id=26980).
|
|
* If `-no-pie` or `-pie`, a version definition is unneeded in GNU ld. This
|
|
behavior is strange.
|
|
|
|
## ld.so behavior
|
|
|
|
/Linux Standard Base Core Specification, Generic Part/ describes the behavior
|
|
of 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 requires versions
|
|
and required shared object names (`Vernaux::vna_name`).
|
|
|
|
For each Vernaux entry (a Verneed's auxilliary entry) without the
|
|
`VER_FLG_WEAK` bit, ld.so checks whether the referenced shared object has the
|
|
`DT_VERDEF` table. If no, ld.so handles the case as a graceful degradation; if
|
|
yes and the table does not define the version, ld.so reports an error.
|
|
[verneed-check]
|
|
|
|
Usually a minor release does not bump soname. Suppose that libB.so depends on
|
|
the libA 1.3 (soname is libA.so.1) and calls an 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.
|
|
|
|
In the symbol resolution stage:
|
|
|
|
* An undefined foo can be resolved to a definition of `foo` or `foo@@v2` (only
|
|
the definitions with index number 1 (`VER_NDX_GLOBAL`) and 2 are used in the
|
|
reference match).
|
|
* An undefined `foo@v1` can be resolved to a definition of `foo`, `foo@v1`, or
|
|
`foo@@v1`.
|
|
|
|
Note (undefined `foo` resolving to `foo@v1`) is allowed by ld.so but not
|
|
allowed by the linker [{reject-non-default}](). This difference provides a
|
|
mechanism to refuse linking against old symbols while keeping compatibility
|
|
with unversioned old libraries. 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 links against `bar` are
|
|
disallowed.
|
|
|
|
## Upgraded symbols in glibc
|
|
|
|
Note that GNU nm before binutils 2.35 does not display `@` or `@@`.
|
|
|
|
```
|
|
nm -D /lib/x86_64-linux-gnu/libc.so.6 | \
|
|
awk '$2!="U" {i=index($3,"@"); if(i){v=substr($3,i); $3=substr($3,1,i-1); m[$3]=m[$3]" "v}} \
|
|
END {for(f in m)if(m[f]~/@.+@/)print f, m[f]}'
|
|
```
|
|
|
|
The output on my x86-64 system:
|
|
|
|
```
|
|
pthread_cond_broadcast @GLIBC_2.2.5 @@GLIBC_2.3.2
|
|
clock_nanosleep @@GLIBC_2.17 @GLIBC_2.2.5
|
|
_sys_siglist @@GLIBC_2.3.3 @GLIBC_2.2.5
|
|
sys_errlist @@GLIBC_2.12 @GLIBC_2.2.5 @GLIBC_2.3 @GLIBC_2.4
|
|
quick_exit @GLIBC_2.10 @@GLIBC_2.24
|
|
memcpy @@GLIBC_2.14 @GLIBC_2.2.5
|
|
regexec @GLIBC_2.2.5 @@GLIBC_2.3.4
|
|
pthread_cond_destroy @GLIBC_2.2.5 @@GLIBC_2.3.2
|
|
nftw @GLIBC_2.2.5 @@GLIBC_2.3.3
|
|
pthread_cond_timedwait @@GLIBC_2.3.2 @GLIBC_2.2.5
|
|
clock_getres @GLIBC_2.2.5 @@GLIBC_2.17
|
|
pthread_cond_signal @@GLIBC_2.3.2 @GLIBC_2.2.5
|
|
fmemopen @GLIBC_2.2.5 @@GLIBC_2.22
|
|
pthread_cond_init @GLIBC_2.2.5 @@GLIBC_2.3.2
|
|
clock_gettime @GLIBC_2.2.5 @@GLIBC_2.17
|
|
sched_setaffinity @GLIBC_2.3.3 @@GLIBC_2.3.4
|
|
glob @@GLIBC_2.27 @GLIBC_2.2.5
|
|
sys_nerr @GLIBC_2.2.5 @GLIBC_2.4 @@GLIBC_2.12 @GLIBC_2.3
|
|
_sys_errlist @GLIBC_2.3 @GLIBC_2.4 @@GLIBC_2.12 @GLIBC_2.2.5
|
|
sys_siglist @GLIBC_2.2.5 @@GLIBC_2.3.3
|
|
clock_getcpuclockid @GLIBC_2.2.5 @@GLIBC_2.17
|
|
realpath @GLIBC_2.2.5 @@GLIBC_2.3
|
|
sys_sigabbrev @GLIBC_2.2.5 @@GLIBC_2.3.3
|
|
posix_spawnp @@GLIBC_2.15 @GLIBC_2.2.5
|
|
posix_spawn @@GLIBC_2.15 @GLIBC_2.2.5
|
|
_sys_nerr @@GLIBC_2.12 @GLIBC_2.4 @GLIBC_2.3 @GLIBC_2.2.5
|
|
nftw64 @GLIBC_2.2.5 @@GLIBC_2.3.3
|
|
pthread_cond_wait @GLIBC_2.2.5 @@GLIBC_2.3.2
|
|
sched_getaffinity @GLIBC_2.3.3 @@GLIBC_2.3.4
|
|
clock_settime @GLIBC_2.2.5 @@GLIBC_2.17
|
|
glob64 @@GLIBC_2.27 @GLIBC_2.2.5
|
|
```
|
|
|
|
* `realpath@@GLIBC_2.3`: the previous version returns `EINVAL` when the second
|
|
parameter is NULL
|
|
* `memcpy@@GLIBC_2.14` [BZ12518](https://sourceware.org/bugzilla/show_bug.cgi?id=12518):
|
|
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.24` [BZ20198](https://sourceware.org/bugzilla/show_bug.cgi?id=20198):
|
|
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:
|
|
|
|
```
|
|
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:
|
|
|
|
```
|
|
# 32-bit
|
|
cp in.so out.so
|
|
r2 -wqc '/x feffff6f00000000 @ section..dynamic; w0 16 @ hit0_0' out.so
|
|
llvm-objcopy -R .gnu.version out.so
|
|
|
|
# 64-bit
|
|
cp in.so out.so
|
|
r2 -wqc '/x feffff6f @ section..dynamic; w0 8 @ hit0_0' out.so
|
|
llvm-objcopy -R .gnu.version out.so
|
|
```
|
|
|
|
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:
|
|
|
|
```
|
|
0x000000006ffffffb (FLAGS_1) Flags: NOW
|
|
- 0x000000006ffffffe (VERNEED) 0x8ef0
|
|
- 0x000000006fffffff (VERNEEDNUM) 5
|
|
- 0x000000006ffffff0 (VERSYM) 0x89c0
|
|
- 0x000000006ffffff9 (RELACOUNT) 1536
|
|
0x0000000000000000 (NULL) 0x0
|
|
```
|
|
|
|
## LLD
|
|
|
|
* If an undefined symbol is not defined by a shared object, GNU ld will report
|
|
an error. LLD before 12.0 did not error (I fixed it in
|
|
https://reviews.llvm.org/D92260).
|
|
|
|
## 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:
|
|
|
|
```c
|
|
// include/features.h
|
|
#define __REDIR(x,y) __typeof__(x) x __asm__(#y)
|
|
|
|
// API header include/sys/time.h
|
|
int utimes(cosnt char *, const struct timeval [2]);
|
|
__REDIR(utimes, __utimes_time64);
|
|
|
|
// Implementation src/linux/utimes.c
|
|
int utimes(const char *path, const struct timeval times[2]) { ... }
|
|
|
|
// Internal header compat/time32/time32.h
|
|
int __utimes_time32() __asm__("utimes");
|
|
|
|
// Compat implementation compat/time32/utimes_time32.c
|
|
int __utimes_time32(const char *path, const struct timeval32 times32[2]) { ... }
|
|
```
|
|
|
|
* In .o, the time32 symbol remains `utimes` and is compatible with the ABI
|
|
required by programs linked against old musl versions; the time64 symbol is
|
|
`__utimes_time64`.
|
|
* The public header redirects utimes to `__utimes_time64`.
|
|
* cons: if the user declares utimes by themself, they will not link against
|
|
the correct `__utimes_time64`.
|
|
* The "good-looking" name `utimes` is used for the preferred time64
|
|
implementation internally and the "ugly" name `__utimes_time32` is 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:
|
|
|
|
```c
|
|
// API header include/sys/time.h
|
|
int utimes(cosnt char *, const struct timeval [2]);
|
|
|
|
// Implementation src/linux/utimes.c
|
|
int utimes(const char *path, const struct timeval times[2]) { ... }
|
|
|
|
// Internal header compat/time32/time32.h
|
|
// Probably __asm__(".symver __utimes_time32, utimes@time32, rename"); if supported
|
|
__asm__(".symver __utimes_time32, utimes@time32");
|
|
|
|
// Implementation compat/time32/utimes_time32.c
|
|
int __utimes_time32(const char *path, const struct timeval32 times32[2])
|
|
{
|
|
...
|
|
}
|
|
```
|
|
|
|
Note that it is `@@@` 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-list` might have
|
|
been developed if version scripts did not provide `local:`.
|
|
* 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](https://maskray.me/blog/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` In foobehavior defined [{gas-copy}](): reserved symbol
|
|
`foo`(redundant symbol has a link), binding / `st_other`sync (not convenient
|
|
to set different binding / visibility)
|
|
* Verdaux is a bit redundant. In practice, one Verdef has only one auxilliary
|
|
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](http://sourceware.org/PR24741)
|
|
relaxes this requirement, essentially ignoring `Vernaux::vna_name`.
|
|
|
|
Before that, glibc used a forwarder to move `clock_*` functions from librt.so
|
|
to libc.so:
|
|
|
|
```c
|
|
// rt/clock-compat.c
|
|
__typeof(clock_getres) *clock_getres_ifunc(void) asm("clock_getres");
|
|
__typeof(clock_getres) *clock_getres_ifunc(void) { return &__clock_getres; }
|
|
```
|
|
|
|
libc.so defines `__clock_getres` and `clock_getres`. librt.so defines an ifunc
|
|
called `clock_getres` which forwards to libc.so `__clock_getres`.
|
|
|
|
## Related links
|
|
|
|
* [Combining Versions](combining-versions.md)
|
|
* [Version Scripts](version-scripts.md)
|
|
* https://invisible-island.net/ncurses/ncurses-mapsyms.html
|
|
|