Versionierte Symbole in gemeinsam genutzten Bibliotheken

FAQ

Wie helfen versionierte Symbole Benutzern?

Wenn Software zu einer dynamisch gelinkten ausführbaren Datei kompiliert wird, enthält diese Verweise auf gemeinsam genutzte Bibliotheken (Shared Libraries) sowie auf die darin enthaltenen Symbole. Wird diese Datei auf einem System ausgeführt, auf dem Symbole in einer solchen Bibliothek fehlen – etwa weil die Anwendung installiert wurde, ohne die vorhandenen Bibliotheken zu aktualisieren –, schlägt die Ausführung fehl.

[root v1.1]# Bar 5 10 area
Bar: symbol lookup error: Bar: undefined symbol: rectangle_area

Die Fehlermeldung teilt dem Benutzer zwar mit, worin das Problem besteht, verrät ihm jedoch nicht, wie es sich beheben lässt. Sie nennt weder die veraltete Bibliothek noch die erforderliche Mindestversion.

Bei versionierten Symbolen ist die Fehlermeldung wesentlich hilfreicher.

[root v1.1]# Bar 5 10 area
Bar: lib-versioned/libFoo.so.1: version `+LIBFOO_1.1+' not found (required by Bar)

Diese Fehlermeldung gibt an, welche gemeinsam genutzte Bibliothek das Problem verursacht, und liefert einen recht deutlichen Hinweis darauf, wie das Problem zu beheben ist (nämlich durch die Installation von libFoo.so.1 in Version 1.1 oder neuer).

RPM kann diese Informationen auch nutzen, um automatisch bessere Abhängigkeitsinformationen zu generieren und zu verhindern, dass diese Probleme überhaupt auftreten.

Was sind versionierte Symbole?

Symbol-„Versionen“ sind eine Klartextbezeichnung, die sowohl auf ein dynamisches gemeinsam genutztes Objekt als auch auf Symbole innerhalb dynamischer gemeinsam genutzter Objekte angewendet wird.

Wo werden versionierte Symbole verwendet?

Versionierte Symbole sind bei den Kernbibliotheken von GNU/Linux-Systemen weit verbreitet, insbesondere bei Entwicklern, die großen Wert auf die Wahrung stabiler ABIs legen. Sie bieten einen Mechanismus, um Änderungen einzuführen, die ohne Symbolversionierung einen Bruch der ABI sowie eine Änderung des Sonames (soname bump) erfordern würden.

Beeinträchtigen versionierte Symbole die Abwärtskompatibilität?

Das Hinzufügen von Symbolversionen ist eine abwärtskompatible Änderung.

Was passiert, wenn ich eine Binärdatei, die derzeit gegen meine Bibliothek (ohne Versionierung) gelinkt ist, auf einem System mit der versionierten Bibliothek ausführe?

Es funktioniert einfach!

Demo

libFoo bietet eine einfache Demonstration versionierter Bibliotheken in C und C++ sowie Beispiele für die Einrichtung der Bausysteme Automake, CMake und Meson.

In diesem Projekt finden Sie foo.c – eine Datei, die zwei Funktionen bereitstellt – sowie libFoo.map; diese Datei beschreibt, in welcher Version der Bibliothek das jeweilige Symbol erstmals auftrat:

LIBFOO_1.0 {
    global:
        rectangle_perimeter;
    local:
        *;
};
LIBFOO_1.1 {
    global:
        rectangle_area;
};

Diese Zuordnungsdatei und ein einziges zusätzliches Argument, -Wl,--version-script=libFoo.map, sind alles, was benötigt wird, um einer Bibliothek Symbolversionen hinzuzufügen.

Auch die Wartung ist unkompliziert. Wann immer eine neue Version neue Symbole enthält, die Sie exportieren möchten, fügen Sie der Zuordnung einen neuen Satz von Symbolen hinzu. Sätze innerhalb einer Symbolversionszuordnung sollten nach einer Veröffentlichung niemals mehr geändert werden.

Wenn Sie dieses Projekt mit make erstellen, erhalten Sie eine Anwendung namens Bar, die mit der nicht versionierten Variante der gemeinsam genutzten Bibliothek verknüpft ist, sowie eine Anwendung namens Bar-versioned, die mit der versionierten Variante der gemeinsam genutzten Bibliothek verknüpft ist.

ldd ./Bar zeigt, dass Bar standardmäßig die gemeinsam genutzte Bibliothek aus dem Verzeichnis lib-unversioned verwendet (der Pfad ist mittels rpath eingebettet). Durch Ausführen von env LD_LIBRARY_PATH=lib-versioned ./Bar lässt sich die Bibliotheksvariante mit versionierten Symbolen nutzen; dies demonstriert, dass die Bibliothek auch bei Hinzufügung versionierter Symbole abwärtskompatibel bleibt.

Wenn Sie eine Bibliothek entwickeln, die derzeit noch keine versionierten Symbole enthält, lassen Sie sich bitte nicht von der Notwendigkeit, die Versionshistorie zu erstellen, von deren Einführung abhalten. Der einfachste Weg besteht darin, alle Ihre Symbole der Version zuzuordnen, die Sie als aktuell betrachten, und für alle Symbole, die Sie künftig einführen, neue Versionen hinzuzufügen.

How-To

Untersuchen Sie die vom binären RPM bereitgestellten Funktionen mittels rpm -qp --provides <package>. Ein Paket mit gemeinsam genutzten Bibliotheken (Shared Libraries) führt die Bibliothek als libc.so.6()(64bit) auf; stellt die Bibliothek zudem versionierte Symbole bereit, wird sie auch mit Versionsangabe aufgeführt, etwa als libm.so.6(GLIBC_2.41)(64bit).

Das Hinzufügen von Symbolversionen ist für die meisten Bibliotheken einfach und erfordert zunächst lediglich eine Symbolzuordnung sowie ein zusätzliches Argument für den Linker während des Build-Vorgangs.

generate_initial_map.sh
#!/bin/sh
echo "/* Avoid modifying a symbol set after it has been released"
echo "   When adding features in a new release, add a new set"
echo "   Removing features is a breaking change */"
echo "$2 {"
echo "  global:"
objdump -T $1 | \
  grep -F .text | \
  awk '{print $7;}' | \
  c++filt | \
  awk '/[() ]/ {print "    \"" $0 "\";";} \
      !/[() ]/ {print "    " $0 ";";}' | \
  sort
echo "};"

Führen Sie generate_initial_map.sh /Pfad/zur/Bibliothek.so.1 <BIBLIOTHKEK>_<VERSION> aus, um eine Zuordnungsdatei zu generieren.

Versionsskript zu Automake hinzufügen

Das Handbuch der GNU Portability Library (gnulib) enthält Beispiele für die Verwendung von Versionsskripten in Automake. In der Datei Makefile.am:

if HAVE_LD_VERSION_SCRIPT
libfoo_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libfoo.map
endif

Versionsskript zu CMake hinzufügen

Das unter der BSD-3-Clause-Lizenz stehende Projekt protobuf enthält Beispiele für die Verwendung eines Versionsskripts in CMake.

Prüfen Sie in der CMakeLists.txt, ob der Linker die Unterstützung bietet:

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/cmaketest.map
"{
  global:
    main;
  local:
    *;
};")
# CheckLinkerFlag module available in CMake >=3.18.
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.18)
  include(CheckLinkerFlag)
  check_linker_flag(CXX -Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/cmaketest.map project_HAVE_LD_VERSION_SCRIPT)
endif()
file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/cmaketest.map)

Und außerdem, wo die Bibliothek definiert ist:

if(project_HAVE_LD_VERSION_SCRIPT)
  target_link_options(libfoo PRIVATE -Wl,--version-script=${protobuf_source_dir}/src/libfoo.map)
  set_target_properties(libfoo PROPERTIES
    LINK_DEPENDS ${project_source_dir}/src/libfoo.map)
endif()

Versionsskript zu Meson hinzufügen

Die Testfälle von Meson enthalten Beispiele für die Verwendung eines Versionsskripts.

# Solaris 11.4 ld supports --version-script only when you also specify
# -z gnu-version-script-compat
if meson.get_compiler('c').get_linker_id() == 'ld.solaris'
  add_project_link_arguments('-Wl,-z,gnu-version-script-compat', language: 'C')
endif

# Static map file
mapfile = 'bob.map'
vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile)

l = shared_library('bob', 'bob.c', link_args : vflag, link_depends : mapfile)