AWS-LC uses ELF symbol versioning for its shared libraries on UNIX systems (Linux and BSDs). Symbol versioning provides:
- ABI Stability Tracking: Each exported symbol is assigned to a specific version namespace
- Backward Compatibility: Applications link to specific symbol versions, preventing breakage
- Concurrent Installation: Multiple AWS-LC versions can coexist on the same system
- Distribution Packaging: Standard practice for system libraries in Linux distributions
Symbol versioning is automatically enabled when building with distribution packaging mode (-DENABLE_DIST_PKG=1).
AWS-LC assigns every exported symbol to a version node. Both libcrypto and libssl share the same symbol version namespace:
- AWS_LC_1.0 (current baseline - ~3,000 libcrypto symbols, ~640 libssl symbols)
When you link an application against AWS-LC, the linker records which symbol versions your application uses. At runtime, the dynamic linker ensures your application gets the correct symbol versions.
// Your application code
#include <openssl/evp.h>
int main() {
EVP_MD_CTX *ctx = EVP_MD_CTX_new(); // Uses EVP_MD_CTX_new@@AWS_LC_1.0
// ...
}When compiled and linked:
$ gcc myapp.c -o myapp -lcrypto-awslc
$ nm -D myapp | grep EVP_MD_CTX_new
U EVP_MD_CTX_new@@AWS_LC_1.0The @@AWS_LC_1.0 suffix indicates your application requires the AWS_LC_1.0 version of EVP_MD_CTX_new.
- UNIX system (Linux or BSD)
- GNU ld or compatible linker
- CMake 3.0+
- Ninja or Make
Symbol versioning is enabled automatically with distribution packaging:
cmake -GNinja -B build \
-DBUILD_SHARED_LIBS=ON \
-DENABLE_DIST_PKG=ON \
-DCMAKE_BUILD_TYPE=Release
ninja -C buildThis produces versioned shared libraries whose file name carries the full
library version (SOFTWARE_VERSION) and whose SONAME carries the ABI version
(ABI_VERSION). For example, on AWS-LC 5.1.0 with ABI_VERSION=1:
build/crypto/libcrypto-awslc.so.5.1.0— real file, withAWS_LC_1.0versioned symbolsbuild/ssl/libssl-awslc.so.5.1.0— real file, withAWS_LC_1.0versioned symbols
along with the usual symlinks: libcrypto-awslc.so.1 (SONAME) → libcrypto-awslc.so.5.1.0, and the linker/dev symlink libcrypto-awslc.so.
Note: the symbol version node (
AWS_LC_1.0) is independent of the file version. The node only changes when new API is added or an ABI break starts a new series; the file version tracks each release.
Verify symbol versioning is applied (use the libcrypto-awslc.so dev symlink so
the commands do not depend on a specific version string):
# Check version definitions
readelf --version-info build/crypto/libcrypto-awslc.so
# List versioned symbols
nm -D build/crypto/libcrypto-awslc.so | grep @AWS_LC_1.0 | head -10
# Verify all symbols are versioned
nm -D build/crypto/libcrypto-awslc.so | grep ' T ' | grep -v '@'
# (should be empty - all symbols should have a version suffix)Symbol versioning is driven by two per-library files that are checked into the tree:
- Registry (source of truth):
crypto/libcrypto.txt,ssl/libssl.txt - Version script (generated):
crypto/libcrypto.map,ssl/libssl.map
Each registry line records a symbol, its version node, and its visibility:
AES_encrypt AWS_LC_1.0 PUBLIC
CRYPTO_once AWS_LC_1.0 PRIVATE
ssl_cert_check_key_usage AWS_LC_1.0 PRIVATE_CXX
Visibility values:
PUBLIC— public API frominclude/openssl/*.h; can never be removedPRIVATE— internal API with C linkage fromcrypto/**/*.h; may be removedPRIVATE_CXX— internal API with C++ linkage fromssl/**/*.h; may be removed
The .map files are auto-generated from the registry and must not be edited by
hand. CMake attaches the .map to each library via apply_version_script()
(see cmake/GenerateVersionScript.cmake).
The registry and version scripts are managed by Go tools and shell wrappers in util/:
| Tool | Purpose |
|---|---|
util/read_public_symbols |
Extracts exported symbols from headers and classifies visibility (PUBLIC / PRIVATE / PRIVATE_CXX). |
util/generate_version_script |
Generates a .map version script from a registry .txt. Deterministic. |
util/generate_initial_version_scripts.sh |
Bootstraps both registries and .map files from scratch (used once to establish the baseline). |
util/update_symbol_version.sh |
Adds newly introduced API to a new version node and regenerates the .map files. |
AWS_LC_1.0 {
global:
AES_encrypt;
AES_decrypt;
EVP_MD_CTX_new;
/* ... all symbols in this node ... */
local:
*; /* Hide all other symbols */
};
This defines:
- global: Symbols exported with version
AWS_LC_1.0 - local: *: Hide all other symbols (internal implementation)
When more than one version node exists, each node inherits from its predecessor
(e.g. AWS_LC_1.1 { global: ...; } AWS_LC_1.0;). generate_version_script
emits this inheritance automatically; only the oldest (base) node carries the
local: *; catch-all.
When new public APIs are added, the new symbols must be assigned to a new version
node. Use update_symbol_version.sh rather than editing the registry or .map
files by hand:
# Build so the new OPENSSL_EXPORT symbols exist in the headers, then:
./util/update_symbol_version.sh AWS_LC_1.1This extracts the current symbol set from the headers, identifies symbols not yet
in the registry, appends them to the registry under the given node with their
visibility, re-sorts the registry, and regenerates crypto/libcrypto.map and
ssl/libssl.map. Commit the updated .txt and .map files together.
While a version series is unreleased, newly added symbols may simply be folded into the existing baseline node (
AWS_LC_1.0) by regenerating the registry; once a version has shipped, its node is frozen and new API goes into a new node.
- Format:
AWS_LC_<MAJOR>.<MINOR> - Increment: Bump minor version for each API addition
- Examples:
AWS_LC_1.0- Initial releaseAWS_LC_1.1- First update with new symbolsAWS_LC_1.2- Second update with new symbolsAWS_LC_2.0- After ABI break (new SONAME)
Removing a PUBLIC symbol breaks ABI compatibility and should be avoided. If absolutely necessary:
-
Increment
ABI_VERSIONinCMakeLists.txt(this drives the SONAME):set(ABI_VERSION 2) # Was 1
-
Start a new version series by assigning symbols to a new major node:
./util/update_symbol_version.sh AWS_LC_2.0
-
SONAME changes accordingly:
- Old:
libcrypto-awslc.so.1 - New:
libcrypto-awslc.so.2
- Old:
-
Document the breaking change: release notes must prominently document this.
AWS-LC CI monitors the symbol registry and version scripts on every PR via
.github/workflows/abidiff.yml.
Six jobs run:
- libcrypto symbol check (incremental) — compares the registry between the PR base and head; flags additions (warning) and PUBLIC removals (error).
- libssl symbol check (incremental) — same for libssl.
- libcrypto symbol check (baseline) — extracts symbols from the headers and verifies every one is present in the registry (catches unregistered new API).
- libssl symbol check (baseline) — same for libssl.
- libcrypto version script drift check — regenerates
crypto/libcrypto.mapfrom the registry and verifies it matches the committed.mapbyte-for-byte. - libssl version script drift check — same for libssl.
These are implemented by
.github/docker_images/symbol_check/check_symbols.sh
in incremental, baseline, and mapcheck modes.
- Symbol additions:
⚠️ Warning (allowed, but verify intentional) - PUBLIC symbol removals: ❌ Error (blocks the build — ABI break)
- PRIVATE / PRIVATE_CXX removals:
⚠️ Warning (allowed) - Unregistered new API (baseline): ❌ Error (run
update_symbol_version.sh) .mapout of sync with registry (drift): ❌ Error (regenerate the.map)
❌ UNREGISTERED SYMBOLS (1):
CRYPTO_tls13_hkdf_expand_label
🛑 New symbols are not in the registry.
Run: util/update_symbol_version.sh <version>
Action: run ./util/update_symbol_version.sh <version> and commit the updated .txt/.map.
❌ PUBLIC SYMBOLS REMOVED FROM REGISTRY (2):
OldFunction1
OldFunction2
🛑 ABI BREAK: removing public symbols from the registry breaks compatibility.
Action: restore the symbols if possible; otherwise follow the ABI break procedure above.
❌ crypto/libcrypto.map is out of sync with crypto/libcrypto.txt (diff above).
🛑 The version script is auto-generated and must not drift.
Action: regenerate the .map with go run ./util/generate_version_script -in crypto/libcrypto.txt -out crypto/libcrypto.map (or util/generate_initial_version_scripts.sh) and commit it.
No action needed. Symbol versioning is transparent.
- Add new
OPENSSL_EXPORTfunctions to headers. - Register the new symbols and regenerate the version scripts:
./util/update_symbol_version.sh AWS_LC_1.1
- Optionally verify against a build:
cmake -GNinja -B build -DBUILD_SHARED_LIBS=ON -DENABLE_DIST_PKG=ON ninja -C build nm -D build/crypto/libcrypto-awslc.so | grep @AWS_LC_1.1 | head
- Commit the updated registry (
crypto/libcrypto.txt,ssl/libssl.txt) and version scripts (crypto/libcrypto.map,ssl/libssl.map) together.
To rebuild the registries and version scripts from the current headers (rarely needed):
./util/generate_initial_version_scripts.sh
git add crypto/libcrypto.txt crypto/libcrypto.map ssl/libssl.txt ssl/libssl.map
git commit -m "Regenerate symbol registry and version scripts"Applications using AWS_LC_1.0 symbols work with AWS_LC_1.1 libraries because version inheritance ensures all AWS_LC_1.0 symbols remain available.
Applications using AWS_LC_1.1 symbols require AWS_LC_1.1 or later. They won't work with AWS_LC_1.0-only libraries.
Package managers can enforce version requirements:
# Application package metadata
Requires: libcrypto-awslc.so.1(AWS_LC_1.1)
This ensures users have a compatible AWS-LC version installed.
- Linux: All distributions (Amazon Linux, Ubuntu, Fedora, Debian, RHEL, etc.)
- BSD: FreeBSD, OpenBSD, NetBSD (requires GNU ld or compatible)
- macOS: Uses different versioning mechanism (compatibility_version/current_version)
- Windows: PE format uses DEF files for exports
- Static libraries: Symbol versioning only applies to shared libraries
On unsupported platforms, ENABLE_DIST_PKG builds libraries without symbol versioning.
Cause: The .map version script file is missing.
Solution:
# Regenerate registries and version scripts
./util/generate_initial_version_scripts.shCause: Application was built against a newer library version than is installed.
Solution: Install AWS-LC 1.1 or later, or rebuild the application against the installed version.
See When CI Fails above for the specific remediation per check.
- GNU ld version scripts: https://sourceware.org/binutils/docs/ld/VERSION.html
- Symbol versioning in glibc: https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility
- Symbol registry:
crypto/libcrypto.txt,ssl/libssl.txt - Build documentation:
BUILDING.md
Potential improvements to symbol versioning:
- Automatic registry updates in CI
- Symbol visibility analysis tool
- Historical symbol database across all versions
- Deprecation warnings for old symbol versions
- Symbol aliasing for renamed functions