File:  [NetBSD Developer Wiki] / wikisrc / symbol_versions.mdwn
Revision 1.5: download - view: text, annotated - select for diffs
Tue Jan 5 02:23:32 2021 UTC (10 months, 3 weeks ago) by riastradh
Branches: MAIN
CVS tags: HEAD
Minor tweaks for clarity.  Cite rumphijack as LD_PRELOAD example.

[[!meta title="Symbol Versions in NetBSD Libraries"]]

NetBSD implements various standard C language interfaces such as the
[[!template id=man name="time" section="3"]] function in POSIX in
`libc`, which has a prototype like this:

    time_t time(time_t *);

However, between NetBSD 5 and NetBSD 6, the definition of the type
`time_t` in NetBSD changed on many architectures from 32-bit to 64-bit
to avoid the
[year 2038 problem](
So programs compiled in NetBSD<=5 saw a declaration like

    int time(int *);

which on most architectures is 32-bit, while programs compiled in
NetBSD>=6 see a declaration like

    int64_t time(int64_t *);

These declarations are not compatible -- consider a program with a
fragment like:

    int before, after;


This would work in NetBSD<=5, but in NetBSD>=6, the calls to
[[!template id=man name="time" section="3"]] might overwrite adjacent
positions on the stack, or crash altogether because the argument is

Programs written and compiled on older versions of NetBSD are supposed
to continue to work -- with suitable emulators/compatNN packages and
compatNN.kmod modules or COMPAT_NN kernel options -- on newer versions
of NetBSD.

To make this work, NetBSD's `libc` provides _two_ symbols:

- `time`, which still implements the legacy prototype as before; and
- `__time50` (yes, this is not a typo for `__time60`), which implements
  the new 64-bit prototype.

The declaration in newer NetBSD
[time.h]( is actually:

    time_t time(time_t *) __RENAME(__time50);

where `__RENAME(__time50)` is a
expanding to `__asm("__time50")`, which has the effect that the
compiler will use the symbol `__time50` for calls to the C function
this declares.
Thus, old programs with calls to the symbol `time` using the 32-bit
prototype will continue to work, and new programs will be compiled to
call the symbol `__time50` using the 64-bit prototype.
([Details on how the symbols are implemented in `libc`.](

# [[!template id=man name="dlsym" section="3"]] and symbol interposition

**Programs that use
[[!template id=man name="dlsym" section="3"]],
such as C foreign function interfaces in dynamic languages like Python,
need to know that if they want the legacy 32-bit time() function, they
must use the symbol `time`, and if they want the modern 64-bit time()
function, they must use the symbol `__time50`.**

**Similarly, programs that use `LD_PRELOAD` (see
[[!template id=man name="ld.elf_so" section="3"]])
to interpose their own definitions of symbols, such as
[[!template id=man name="rumphijack" section="3"]]
must know to define `__time50` if they want to replace the new
semantics in new programs, or `time` if they want to replace the old
semantics in old programs.**

The same applies to many other standard C functions, such as
[[!template id=man name="clock_gettime" section="3"]]
(`__clock_gettime50`) and
[[!template id=man name="socket" section="3"]]
(`__socket30`), which have all had their prototypes or semantics
revised at some point.

Symbol interposition is very difficult to get right, and it is hard to
make programs that do it reliably.
On NetBSD, it should be reserved for certain standard library functions
like `malloc` and `free` (and `calloc` and everything else in that
family), and some system call stubs; except for the `__...50`
pseudo-versioned renames of public functions, you should not try to
interpose your own definition of any symbol beginning with ‘`_`’ (a
single underscore), which is reserved to the implementation in C.

# Appendix: ELF symbol versions

The renaming scheme of `__time50` is informal -- any symbol can be
renamed the same way, and NetBSD uses it for some other purposes too,
such as exposing a slightly different
[[!template id=man name="rename" section="2"]]
function via the symbol `__posix_rename` in programs that define

The GNU ELF toolchain (gcc, ld, &c.) supports a formal concept of
‘symbol versions’ with sections called `.gnu.version` (associating
versions with symbols), `.gnu.version_d` (versions defined in an
object), and `.gnu.version_n` (versions needed in an object).
As of 2020, NetBSD does not use ELF symbol versions, although the
linker and loader support them for libraries developed outside NetBSD.

The semantics is:

- When creating a library, a version map may be specified like so:

      NetBSD_BASE {

      NetBSD_6 {

  The library can specify what versioned symbol each definition in the
  library is exposed with:

      __asm(".symver time_legacy,time@NetBSD_BASE");
      int time_legacy(int *t) { ... }

      __asm(".symver time64,time@@NetBSD_6");         /* default version */
      int64_t time64(int64_t *t) { ... }

      __asm(".symver __time50,__time50@NetBSD_BASE");
      __typeof(time) __time50 __attribute__((__alias__("time64")));

  Versions marked with `@@` are _default_ versions; versions marked
  with `@` are non-default.

- When running a program that was linked _without_ ELF symbol versions,
  from before the library had ELF symbol versions (like `libc` today),
  the first version in the map is used to resolve symbols:

  - Old programs calling the legacy `time` symbol will get
    `time@NetBSD_BASE`, which is defined via `time_legacy` above.

  - Programs calling `__time50` will get `__time50@NetBSD_BASE`, which
    is defined via `time64` above.

- When linking a program against a library with symbol versions, the
  linker will record what the default version was; when later running
  the program, the stored symbol version will be used.
  If there is no default version, and the program did not request a
  specific version with `.symver`, then the linker refuses to link, so
  obsolete symbols can be ‘removed’ by giving them only non-default
  versions -- thus old programs continue to work but new programs can't
  be made that use the obsolete symbols.

  For example, if [[!template id=man name="time" section="3"]] is
  declared in a header file as simply

      typedef int64_t time_t;
      time_t time(time_t *);

  then new programs will be linked against `time@NetBSD_6`, which is
  the default version for the symbol name `time`.
  If NetBSD ever changed the prototype of
  [[!template id=man name="time" section="3"]]
  again, and defined a `time@NetBSD_11` as the new default version,
  existing programs compiled with `time@NetBSD_6` would continue to get
  the semantics they were built against.

- When a program uses
  [[!template id=man name="dlsym" section="3"]],
  it always gets the default version, if any.
  Programs can request specific versions with
  [[!template id=man name="dlvsym" section="3"]].

## ELF symbol versions versus `__...50` pseudo-versions

ELF symbol versions and NetBSD's `__time50` pseudo-version renaming
scheme both try to address the same problem: making sure old programs
that were built under the assumption of the old semantics continue to
run unmodified with new libraries.

Both of them run into problems with
[[!template id=man name="dlsym" section="3"]]
and symbol interposition:

- A program written _today_ that expects to find the function time() in
  `libc`, such as a C foreign function interface for a dynamic language
  like Python, needs to know to call `dlsym("__time50")`; otherwise it
  will get an obsolete definition that does not match the semantics of
  the current definition of `time_t`, possibly leading to data
  corruption, crashes, or worse.

- If `libc` used used ELF symbol versions, then `dlsym("time")` would
  return the modern symbol.

  But any _old_ programs that used `dlsym("time")` assuming it
  returned the legacy definition (which was the ‘modern’ definition at
  the time the programs were written and built) will break if it
  instead returns the 64-bit definition.

  And if we ever modified
  [[!template id=man name="time" section="3"]]
  again (hypothetically, to extend it to 128-bit galactic-scale times),
  programs written assuming that `dlsym("time")` returns the 64-bit
  definition will break if it begins to return the 128-bit definition.
  Programs could future-proof themselves by using `dlsym("time",
  "NetBSD_6")` explicitly, but this is no better than writing
  `dlsym("__time50")` explicitly.

**Thus, switching from the pseudo-versions we use to ELF symbol
versions doesn't improve the
[[!template id=man name="dlsym" section="3"]]
situation -- in fact, it makes the situation _worse_, by breaking old
programs and providing no way for new programs to bind to the name of
the current version.**

Perhaps we could create a compiler builtin `__builtin_asm_name` which
would expand to the `__asm("...")` name by which a C identifier has
been declared -- then programs could instead do:

    __typeof(time) *timep = dlsym(dso, __builtin_asm_name(time));

This way the text of the program is the same no matter how
[[!template id=man name="time" section="3"]]
is declared in the header file, but it will continue to work across
changes to the signature of the
[[!template id=man name="time" section="3"]]
function in newer releases of NetBSD.

# References

- Jörg Sonnenberger,
  [How to break long-term compatibility in NetBSD](,
  AsiaBSDcon 2016.

- Ulrich Drepper,
  [How To Write Shared Libraries](,

- Ulrich Drepper,
  [ELF Symbol Versioning](

CVSweb for NetBSD wikisrc <> software: FreeBSD-CVSweb