Maintain a Debian DEBIAN/symbols File Without Losing ABI History
You will finish with a small, reviewable DEBIAN/symbols file for a shared library, including its SONAME, dependency template, exported symbols and minimum package versions. The examples follow the installed deb-symbols(5) manual from the Ubuntu build of dpkg 1.22.6, package version 1.22.6ubuntu6.6. Allow about twenty minutes, plus time to obtain the real symbol list from the library you are packaging.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is packaging work, normally done in a source tree or a temporary package staging directory. The examples do not install a package, alter the system database or require elevated privileges. You need dpkg-dev, a shared library to inspect, and enough knowledge of the package that provides its runtime and development interfaces.
1. Confirm the format on this machine
Check the package version and read the installed manual before editing a file. This keeps the syntax tied to the toolchain you will actually use:
$ dpkg-query -W -f='${Package} ${Version}\n' dpkg-dev dpkg
dpkg-dev 1.22.6ubuntu6.6
dpkg 1.22.6ubuntu6.6
$ man 5 deb-symbols
The file is installed inside a binary package as DEBIAN/symbols. Its entries are consumed when Debian tooling calculates shared-library dependencies. It is not a general linker script, and it does not tell the dynamic loader where to find a library.
Checkpoint: identify the package being built and the library's exact SONAME. Do not copy the filename from a development symlink and assume it is the SONAME.
2. Record the library SONAME and dependency
Use objdump to inspect the library that will be shipped. This is read-only:
$ objdump -p /path/to/libexample.so.1 | grep SONAME
SONAME libexample.so.1
The first line of a symbols entry starts with that exact SONAME, followed by the main dependency template. The #MINVER# marker is replaced when a dependency is generated. If the symbol requires version 1.4-2, the generated dependency can become libexample1 (>= 1.4-2); if an unversioned dependency is enough, the marker contributes no version constraint.
libexample.so.1 libexample1 #MINVER#
Use the package name that actually contains the runtime library. A -dev package normally supplies headers and the unversioned linker name, but it is not automatically the runtime dependency.
3. Add one line for each public symbol
Each symbol line has three columns: the exported symbol and ABI version, the minimum package version where it is available, and an optional alternative-template identifier. Columns are separated by one whitespace character:
libexample.so.1 libexample1 #MINVER#
public_api@Base 1.0-1
public_api_v2@Base 1.4-2
Base is the version label used when the library does not use symbol versioning. If the ELF symbol has another version label, retain the name reported by your symbol inspection tooling. Do not put every private implementation detail into the file by habit: the useful set is the ABI that consumers may link against.
The version is a package version, not necessarily the upstream library version. It must identify the first Debian package version that shipped that symbol. An incorrect older value can let an installation resolve successfully and fail later when a program looks up the missing symbol.
Checkpoint: compare the proposed list with the library's exported dynamic symbols and with the previous package revision. Removing a symbol from the file is an ABI statement, so review that change as carefully as a source-code interface change.
4. Add an alternative dependency when packages can provide the ABI
A library can have a main dependency and one or more alternatives. Add an alternative line beginning with |, then give it a numeric identifier based on its order:
libexample.so.1 libexample1 #MINVER#
| libexample-compat #MINVER#
* Build-Depends-Package: libexample-dev
public_api@Base 1.0-1
compatibility_api@Base 1.2-1 1
The first alternative template is 1, the second is 2, and so on. The main template is always part of the dependency; the identifier on a symbol selects the additional template that is combined with it. In the example, compatibility_api requires both the main dependency and the first alternative.
Only use this form when the alternative package genuinely provides the required ABI. It is easy to create a dependency that is satisfiable on paper but cannot run the consumer. Test the resulting package dependency with the real packages in the distributions you support.
5. Add metadata that affects dependency generation
Metadata lines begin with an asterisk and belong to the library entry. The common field is the development package that must be reflected in generated dependencies:
* Build-Depends-Package: libexample-dev
Build-Depends-Packages accepts a comma-separated list and overrides Build-Depends-Package if both are present:
* Build-Depends-Packages: libexample-dev, libexample-compat-dev
Use the plural field for a deliberate transition or metapackage arrangement, not as a way to silence a dependency mismatch. On dpkg 1.22.6, the supported internal-symbol field is Allow-Internal-Symbol-Groups; its value is a whitespace-separated list such as aeabi or gomp on ELF and GNU systems. The older Ignore-Blacklist-Groups name is deprecated.
6. Review the file before packaging
Read the finished file as a dependency map, not just as text. A complete small example looks like this:
libexample.so.1 libexample1 #MINVER#
| libexample-compat #MINVER#
* Build-Depends-Package: libexample-dev
public_api@Base 1.0-1
compatibility_api@Base 1.2-1 1
Check the SONAME, runtime package, first-seen package versions, alternative numbering and metadata spelling. Keep the leading spaces shown by the format: the manual specifies the entry columns and metadata layout, and a malformed line can make the package build fail or produce the wrong dependency.
Then build the package in your normal unprivileged packaging workflow and inspect the generated control metadata. For a staged package directory, a safe first check is:
$ dpkg-deb --build /path/to/package-tree /tmp/example.deb
$ dpkg-deb -I /tmp/example.deb | sed -n '/^ Depends:/p'
Do not use sudo for this build. If you test-install the resulting package, treat that as a separate state-changing operation, take a backup or use a disposable environment, and follow your normal package rollback procedure. Removing a package to undo a test can also remove dependants, so do not make that the first validation step.
Common traps
- A SONAME such as
libexample.so.1is not interchangeable with the development linker namelibexample.so. - An upstream release number and a Debian package version can differ. Record the first package version that shipped each symbol.
- An alternative identifier is positional. Inserting a new alternative above an existing one changes the meaning of every later identifier.
Build-Depends-Packagesoverrides the singular field. Do not leave both with conflicting intent.- A successful package build does not prove that a consumer can run. Test representative consumers against the generated dependency.
Done means
- The file uses the library's exact SONAME and the correct runtime package.
- Every listed public symbol has the first Debian package version that supplied it.
- Alternative templates are numbered in order and used only for symbols they can provide.
- Metadata names match the installed
deb-symbols(5)format, including plural-field precedence. - The built package's generated dependency was inspected before any test installation.