Linkers, Loaders & Object Formats

symbol versioning

A shared library evolves over years, and sometimes a function's behavior or its argument types must change in a way that would break old programs. But those old programs, already compiled and shipped, still call the old function by name. How can a single libc.so satisfy a 2005 program that wants the old behavior and a 2025 program that wants the new one — both calling a function with the same name? Symbol versioning is the answer: it lets one library export multiple versions of the same symbol name, and binds each caller to the version it was built against.

Concretely, each exported symbol can carry a version tag, such as foo@GLIBC_2.2.5 versus foo@@GLIBC_2.34, where the double-at marks the current default. When you compile a program that calls foo, the linker records not just the name foo but the version it found at build time. At load time, the dynamic linker matches your recorded version against the versions the library actually provides, so an old binary keeps getting the old foo and a new one gets the new foo, from the very same .so file. Library authors control all this with a version script: a small text file passed to the linker (via --version-script) that lists which symbols are global (exported) under which version node and which are local (hidden), letting them both define versions and prune the exported surface in one place.

It matters because it is how glibc maintains its remarkable backward compatibility: a binary built decades ago still runs against today's libc, because the old symbol versions are kept around. It is also how libraries practice clean encapsulation — a version script can declare exactly which symbols are public and hide everything else, which is better than relying on per-symbol visibility attributes scattered through the code. The common gotcha you will actually meet is the error 'version GLIBC_2.34 not found': it means a binary was built against a newer libc than the one present at runtime, and it needs a symbol version the older library cannot provide.

/* version script: ver.map */ LIBFOO_1.0 { global: foo; bar; local: *; }; /* export foo,bar; hide rest */ LIBFOO_2.0 { global: baz; } LIBFOO_1.0; /* 2.0 adds baz, depends on 1.0 */ $ gcc -shared -Wl,--version-script,ver.map -o libfoo.so ...

A version script names exported symbols under version nodes and hides the rest with 'local: *', so one .so can serve old and new callers.

'version GLIBC_2.xx not found' means the binary was built against a newer library than the one present at runtime — it is a forward-compatibility failure, not a missing file. Versioning provides backward compatibility (old binaries on new libs), not forward (new binaries on old libs).

Also called
versioned symbolsversion scripts符號版本版本腳本