Wie kann ich auf eine bestimmte glibc-Version verlinken?

110

Wenn ich etwas auf meinem Ubuntu Lucid 10.04-PC kompiliere, wird es mit glibc verknüpft. Lucid verwendet 2.11 von glibc. Wenn ich diese Binärdatei auf einem anderen PC mit einem älteren glibc ausführe, schlägt der Befehl fehl und sagt, dass es kein glibc 2.11 gibt ...

Soweit ich weiß, verwendet glibc die Symbolversionierung. Kann ich gcc zwingen, eine Verknüpfung mit einer bestimmten Symbolversion herzustellen?

In meiner konkreten Verwendung versuche ich, eine gcc cross toolchain für ARM zu kompilieren.

Falstaff
quelle
57
Argh, dies ist eines dieser wirklich nervigen Linux-Probleme, bei denen die Lösung immer "Sie sollten das nicht tun" lautet, was natürlich bedeutet "es funktioniert nicht und niemand hat es noch behoben".
Timmmm
2
Die Leute beschwerten sich über die DLL-Hölle unter Windows. Ich erinnere mich , Linux einige Liebhaber zu versuchen , dass bringen aus der Windows - Welt als besonders abschreckendes Beispiel auf. Als ich vor über einem Jahrzehnt zum ersten Mal auf diese Linux-Entwicklung stieß, vergrub ich nur mein Gesicht in meinen Händen.
0xC0000022L

Antworten:

69

Sie haben Recht, dass glibc die Symbolversionierung verwendet. Wenn Sie neugierig sind, wird hier die in glibc 2.1 eingeführte Implementierung der Symbolversionierung beschrieben , die eine Erweiterung des hier beschriebenen Versionsschemas für Symbole von Sun darstellt .

Eine Möglichkeit besteht darin, Ihre Binärdatei statisch zu verknüpfen. Dies ist wahrscheinlich die einfachste Option.

Sie können Ihre Binärdatei auch in einer Chroot-Build-Umgebung oder mit einem glibc- new => glibc- old- Cross-Compiler erstellen .

Laut dem Blog-Beitrag http://www.trevorpounds.com Linking to Older Versioned Symbols (glibc) ist es möglich, die Verknüpfung eines Symbols mit einem älteren zu erzwingen, solange es mit demselben .symverPseudo gültig ist -op, das in erster Linie zum Definieren versionierter Symbole verwendet wird. Das folgende Beispiel ist ein Auszug aus dem Blog-Beitrag .

Im folgenden Beispiel wird der Realpath von glibc verwendet, es wird jedoch sichergestellt, dass er mit einer älteren Version 2.2.5 verknüpft ist.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

__asm__(".symver realpath,realpath@GLIBC_2.2.5");
int main()
{
    const char* unresolved = "/lib64";
    char resolved[PATH_MAX+1];

    if(!realpath(unresolved, resolved))
        { return 1; }

    printf("%s\n", resolved);

    return 0;
}
jschmier
quelle
17
glibc unterstützt keine statische Verknüpfung - statisch verknüpfte glibc-Programme funktionieren auf Systemen mit unterschiedlichen libc-Versionen nicht ordnungsgemäß.
Erinnern Sie sich an Monica
5
glibc's libc.aexistiert weiterhin, glibc unterstützt dies in einigen Fällen, obwohl es nicht empfohlen wird (Drepper) . Sie werden Probleme mit nicht trivialen Programmen haben, insbesondere mit allem, was NSS verwendet (Problemumgehung in den FAQ ).
mr.spuratic
20

Verknüpfung mit -static . Wenn Sie mit -static verknüpfen, bettet der Linker die Bibliothek in die ausführbare Datei ein, sodass die ausführbare Datei größer ist. Sie kann jedoch auf einem System mit einer älteren Version von glibc ausgeführt werden, da das Programm anstelle der des Systems eine eigene Bibliothek verwendet .

Iacchus
quelle
55
Der Grund, warum Sie dies überhaupt tun möchten, ist häufig, dass Sie eine Closed-Source-Anwendung verteilen. In diesem Fall ist es aus Lizenzgründen häufig nicht gestattet, statische Links zu erstellen (dies würde erfordern, dass Sie Ihren gesamten Quellcode freigeben), daher müssen Sie mit -static vorsichtig sein.
Malvineous
3
In der Zwischenzeit kann man oft auf musl-libc zurückgreifen, aber mit C ++ - Programmen kann es komplizierter werden, so dass die Angabe einer Symbolversion möglicherweise noch erforderlich ist.
0xC0000022L
16

Setup 1: Kompilieren Sie Ihren eigenen glibc ohne dedizierten GCC und verwenden Sie ihn

Da es unmöglich erscheint, nur mit Hacks zur Symbolversionierung zu arbeiten, gehen wir noch einen Schritt weiter und kompilieren glibc selbst.

Dieses Setup funktioniert möglicherweise und ist schnell, da nicht die gesamte GCC-Toolchain neu kompiliert wird, sondern nur glibc.

Aber es ist nicht zuverlässig , wie es Host - C - Laufzeit - Objekte verwendet wie crt1.o, crti.ound crtn.ovon glibc zur Verfügung gestellt. Dies wird erwähnt unter: https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Diese Objekte führen eine frühe Einrichtung durch, auf die sich glibc stützt. Ich wäre also nicht überrascht, wenn die Dinge wunderbar abstürzen würden und unglaublich subtile Wege.

Eine zuverlässigere Einrichtung finden Sie unter Setup 2 unten.

Erstellen Sie glibc und installieren Sie es lokal:

export glibc_install="$(pwd)/glibc/build/install"

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28
mkdir build
cd build
../configure --prefix "$glibc_install"
make -j `nproc`
make install -j `nproc`

Setup 1: Überprüfen Sie den Build

test_glibc.c

#define _GNU_SOURCE
#include <assert.h>
#include <gnu/libc-version.h>
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int acnt;
int cnt;

int f(void* thr_data) {
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
    }
    return 0;
}

int main(int argc, char **argv) {
    /* Basic library version check. */
    printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version());

    /* Exercise thrd_create from -pthread,
     * which is not present in glibc 2.27 in Ubuntu 18.04.
     * /programming/56810/how-do-i-start-threads-in-plain-c/52453291#52453291 */
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);
    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

Kompilieren und ausführen mit test_glibc.sh:

#!/usr/bin/env bash
set -eux
gcc \
  -L "${glibc_install}/lib" \
  -I "${glibc_install}/include" \
  -Wl,--rpath="${glibc_install}/lib" \
  -Wl,--dynamic-linker="${glibc_install}/lib/ld-linux-x86-64.so.2" \
  -std=c11 \
  -o test_glibc.out \
  -v \
  test_glibc.c \
  -pthread \
;
ldd ./test_glibc.out
./test_glibc.out

Das Programm gibt Folgendes aus:

gnu_get_libc_version() = 2.28
The atomic counter is 10000
The non-atomic counter is 8674

Befehl angepasst von https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location, aber --sysrootfehlgeschlagen mit:

cannot find /home/ciro/glibc/build/install/lib/libc.so.6 inside /home/ciro/glibc/build/install

also habe ich es entfernt.

lddDie Ausgabe bestätigt, dass die lddund Bibliotheken, die wir gerade erstellt haben, tatsächlich wie erwartet verwendet werden:

+ ldd test_glibc.out
        linux-vdso.so.1 (0x00007ffe4bfd3000)
        libpthread.so.0 => /home/ciro/glibc/build/install/lib/libpthread.so.0 (0x00007fc12ed92000)
        libc.so.6 => /home/ciro/glibc/build/install/lib/libc.so.6 (0x00007fc12e9dc000)
        /home/ciro/glibc/build/install/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc12f1b3000)

Die gccDebug-Ausgabe der Kompilierung zeigt, dass meine Host-Laufzeitobjekte verwendet wurden, was, wie bereits erwähnt, schlecht ist, aber ich weiß nicht, wie ich es umgehen soll, z. B. enthält es:

COLLECT_GCC_OPTIONS=/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o

Setup 1: Glibc ändern

Jetzt ändern wir glibc mit:

diff --git a/nptl/thrd_create.c b/nptl/thrd_create.c
index 113ba0d93e..b00f088abb 100644
--- a/nptl/thrd_create.c
+++ b/nptl/thrd_create.c
@@ -16,11 +16,14 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */

+#include <stdio.h>
+
 #include "thrd_priv.h"

 int
 thrd_create (thrd_t *thr, thrd_start_t func, void *arg)
 {
+  puts("hacked");
   _Static_assert (sizeof (thr) == sizeof (pthread_t),
                   "sizeof (thr) != sizeof (pthread_t)");

Dann kompilieren Sie glibc neu und installieren Sie es erneut. Kompilieren Sie unser Programm neu und führen Sie es erneut aus:

cd glibc/build
make -j `nproc`
make -j `nproc` install
./test_glibc.sh

und wir sehen hackedeinige Male wie erwartet gedruckt.

Dies bestätigt weiter, dass wir tatsächlich den von uns kompilierten Glibc verwendet haben und nicht den Host.

Getestet unter Ubuntu 18.04.

Setup 2: Crosstool-NG makelloses Setup

Dies ist eine Alternative zur Einrichtung 1, und es ist die richtige Setup ich bisher erreicht habe: alles korrekt ist, soweit ich beobachten kann, einschließlich der C - Laufzeit - Objekte wie crt1.o, crti.o, und crtn.o.

In diesem Setup kompilieren wir eine vollständige dedizierte GCC-Toolchain, die den gewünschten glibc verwendet.

Der einzige Nachteil dieser Methode ist, dass der Build länger dauert. Aber ich würde kein Produktionssetup mit weniger riskieren.

crosstool-NG ist eine Reihe von Skripten, die alles von der Quelle für uns herunterladen und kompilieren, einschließlich GCC, glibc und binutils.

Ja, das GCC-Build-System ist so schlecht, dass wir dafür ein separates Projekt benötigen.

Dieses Setup ist nur nicht perfekt, da Crosstool-NG das Erstellen der ausführbaren Dateien ohne zusätzliche -WlFlags nicht unterstützt , was sich seltsam anfühlt, da wir GCC selbst erstellt haben. Aber alles scheint zu funktionieren, daher ist dies nur eine Unannehmlichkeit.

Holen Sie sich Crosstool-NG und konfigurieren Sie es:

git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng
git checkout a6580b8e8b55345a5a342b5bd96e42c83e640ac5
export CT_PREFIX="$(pwd)/.build/install"
export PATH="/usr/lib/ccache:${PATH}"
./bootstrap
./configure --enable-local
make -j `nproc`
./ct-ng x86_64-unknown-linux-gnu
./ct-ng menuconfig

Die einzige obligatorische Option, die ich sehen kann, besteht darin, sie an Ihre Host-Kernel-Version anzupassen, um die richtigen Kernel-Header zu verwenden. Finden Sie Ihre Host-Kernel-Version mit:

uname -a

was mir zeigt:

4.15.0-34-generic

also menuconfigmache ich:

  • Operating System
    • Version of linux

also wähle ich:

4.14.71

Dies ist die erste gleiche oder ältere Version. Es muss älter sein, da der Kernel abwärtskompatibel ist.

Jetzt können Sie bauen mit:

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

und warten Sie nun etwa dreißig Minuten bis zwei Stunden für die Zusammenstellung.

Setup 2: optionale Konfigurationen

Das .config, mit dem wir generiert haben, ./ct-ng x86_64-unknown-linux-gnuhat:

CT_GLIBC_V_2_27=y

Um dies zu ändern, menuconfigtun Sie Folgendes:

  • C-library
  • Version of glibc

Speichern Sie die .configund fahren Sie mit dem Build fort.

Oder, wenn Sie Ihre eigene glibc Quelle verwenden möchten, zB glibc aus dem aktuellen git zu verwenden, gehen Sie wie folgt aus :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: auf true setzen
  • C-library
    • Source of glibc
      • Custom location: Sag ja
      • Custom location
        • Custom source location: Zeigen Sie auf ein Verzeichnis, das Ihre Glibc-Quelle enthält

wo glibc geklont wurde als:

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28

Setup 2: Testen Sie es

Sobald Sie die gewünschte Toolchain erstellt haben, testen Sie sie mit:

#!/usr/bin/env bash
set -eux
install_dir="${CT_PREFIX}/x86_64-unknown-linux-gnu"
PATH="${PATH}:${install_dir}/bin" \
  x86_64-unknown-linux-gnu-gcc \
  -Wl,--dynamic-linker="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib/ld-linux-x86-64.so.2" \
  -Wl,--rpath="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib" \
  -v \
  -o test_glibc.out \
  test_glibc.c \
  -pthread \
;
ldd test_glibc.out
./test_glibc.out

Alles scheint wie in Setup 1 zu funktionieren, außer dass jetzt die richtigen Laufzeitobjekte verwendet wurden:

COLLECT_GCC_OPTIONS=/home/ciro/crosstool-ng/.build/install/x86_64-unknown-linux-gnu/bin/../x86_64-unknown-linux-gnu/sysroot/usr/lib/../lib64/crt1.o

Setup 2: Fehlgeschlagener effizienter Glibc-Neukompilierungsversuch

Mit Crosstool-NG scheint dies nicht möglich zu sein, wie unten erläutert.

Wenn Sie nur neu erstellen;

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

Dann werden Ihre Änderungen am benutzerdefinierten Glibc-Quellspeicherort berücksichtigt, aber alles wird von Grund auf neu erstellt, sodass es für die iterative Entwicklung unbrauchbar wird.

Wenn wir es tun:

./ct-ng list-steps

Es gibt einen schönen Überblick über die Build-Schritte:

Available build steps, in order:
  - companion_tools_for_build
  - companion_libs_for_build
  - binutils_for_build
  - companion_tools_for_host
  - companion_libs_for_host
  - binutils_for_host
  - cc_core_pass_1
  - kernel_headers
  - libc_start_files
  - cc_core_pass_2
  - libc
  - cc_for_build
  - cc_for_host
  - libc_post_cc
  - companion_libs_for_target
  - binutils_for_target
  - debug
  - test_suite
  - finish
Use "<step>" as action to execute only that step.
Use "+<step>" as action to execute up to that step.
Use "<step>+" as action to execute from that step onward.

Daher sehen wir, dass es glibc-Schritte gibt, die mit mehreren GCC-Schritten verflochten sind, insbesondere libc_start_filesvorher cc_core_pass_2, was wahrscheinlich der teuerste Schritt zusammen mit ist cc_core_pass_1.

Um nur einen Schritt zu erstellen, müssen Sie zuerst die .configOption "Zwischenschritte speichern" in der Option für den ersten Build festlegen:

  • Paths and misc options
    • Debug crosstool-NG
      • Save intermediate steps

und dann können Sie versuchen:

env -u LD_LIBRARY_PATH time ./ct-ng libc+ -j`nproc`

aber leider +erforderlich wie unter https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536 angegeben

Beachten Sie jedoch, dass durch einen Neustart in einem Zwischenschritt das Installationsverzeichnis auf den Status zurückgesetzt wird, den es während dieses Schritts hatte. Das heißt, Sie werden eine neu erstellte libc haben - aber keinen endgültigen Compiler, der mit dieser libc erstellt wurde (und daher auch keine Compiler-Bibliotheken wie libstdc ++).

und im Grunde macht der Wiederaufbau immer noch zu langsam, um für die Entwicklung machbar zu sein, und ich sehe nicht, wie ich dies überwinden kann, ohne Crosstool-NG zu patchen.

Darüber hinaus libcschien das Starten ab dem Schritt nicht erneut über die Quelle zu kopieren Custom source location, was diese Methode weiter unbrauchbar machte.

Bonus: stdlibc ++

Ein Bonus, wenn Sie auch an der C ++ - Standardbibliothek interessiert sind: Wie kann ich die GCC libstdc ++ C ++ - Standardbibliotheksquelle bearbeiten und neu erstellen?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
musl-libcist eine weitere Option für die C-Laufzeit.
0xC0000022L
0

Meiner Meinung nach wurde die faulste Lösung (insbesondere wenn Sie sich nicht auf die neuesten C / C ++ - Funktionen oder die neuesten Compilerfunktionen verlassen) noch nicht erwähnt.

Bauen Sie einfach auf dem System mit dem ältesten GLIBC auf, den Sie noch unterstützen möchten.

Dies ist heutzutage mit Technologien wie chroot, KVM / Virtualbox oder Docker ziemlich einfach, selbst wenn Sie eine so alte Distribution nicht direkt auf einem PC verwenden möchten. Im Detail empfehle ich die folgenden Schritte, um eine maximale tragbare Binärdatei Ihrer Software zu erstellen:

  1. Wählen Sie einfach Ihr Gift aus Sandbox / Virtualisierung / ... was auch immer und verwenden Sie es, um sich ein virtuelles älteres Ubuntu LTS zu besorgen und mit dem darin enthaltenen gcc / g ++ zu kompilieren. Dadurch wird Ihr GLIBC automatisch auf den in dieser Umgebung verfügbaren GLIBC beschränkt.

  2. Vermeiden Sie es, von externen Bibliotheken außerhalb der grundlegenden zu abhängig zu sein: Sie sollten beispielsweise bodennahe Systemdaten wie glibc, libGL, libxcb / X11 / wayland-Dinge, libasound / libpulseaudio und möglicherweise GTK + dynamisch verknüpfen, wenn Sie diese verwenden, ansonsten aber vorzugsweise statisch externe verknüpfen libs / versende sie mit, wenn du kannst. Insbesondere meistens in sich geschlossene Bibliotheken wie Bildlader, Multimedia-Decoder usw. können bei anderen Distributionen weniger Bruch verursachen (Bruch kann beispielsweise verursacht werden, wenn sie nur irgendwo in einer anderen Hauptversion vorhanden sind), wenn Sie sie statisch versenden.

Mit diesem Ansatz erhalten Sie eine alte GLIBC-kompatible Binärdatei ohne manuelle Symboländerungen, ohne eine vollständig statische Binärdatei (die bei komplexeren Programmen möglicherweise nicht funktioniert, weil glibc dies hasst und Lizenzprobleme für Sie verursachen kann) und ohne Einstellung Erstellen Sie eine benutzerdefinierte Toolchain, eine benutzerdefinierte Glibc-Kopie oder was auch immer.

ET
quelle