Come posso collegarmi a una specifica versione di glibc?


110

Quando compilo qualcosa sul mio PC Ubuntu Lucid 10.04, viene collegato a glibc. Lucid usa 2.11 di glibc. Quando eseguo questo binario su un altro PC con un vecchio glibc, il comando non riesce dicendo che non c'è glibc 2.11 ...

Per quanto ne so glibc usa il controllo delle versioni dei simboli. Posso forzare il collegamento a gcc con una versione di simboli specifica?

Nel mio utilizzo concreto cerco di compilare una toolchain incrociata gcc per ARM.


58
Argh questo è uno di quei problemi Linux davvero fastidiosi come dove la soluzione è sempre "non dovresti farlo", che ovviamente significa "non funziona e nessuno l'ha ancora risolto".
Timmmm

3
La gente si è lamentata dell'inferno DLL su Windows. Ricordo che alcuni appassionati di Linux cercavano di tirarlo fuori come un esempio particolarmente orribile dal mondo Windows. Quando mi sono imbattuto per la prima volta in questo sviluppo di Linux più di dieci anni fa, tutto quello che ho fatto è stato seppellire la faccia tra le mani.
0xC0000022L

Risposte:


69

Hai ragione in quanto glibc usa il controllo delle versioni dei simboli. Se sei curioso, l'implementazione del controllo delle versioni dei simboli introdotta in glibc 2.1 è descritta qui ed è un'estensione dello schema di controllo delle versioni dei simboli di Sun descritto qui .

Un'opzione è collegare staticamente il tuo file binario. Questa è probabilmente l'opzione più semplice.

Potresti anche costruire il tuo binario in un ambiente di compilazione chroot, o usando un glibc- new => glibc- old cross-compilatore.

Secondo il post del blog http://www.trevorpounds.com Collegamento a simboli con versioni precedenti (glibc) , è possibile forzare il collegamento di qualsiasi simbolo a uno più vecchio fintanto che è valido utilizzando lo stesso .symverpseudo -op che viene utilizzato per definire i simboli con versione in primo luogo. Il seguente esempio è tratto dal post del blog .

L'esempio seguente utilizza il realpath di glibc, ma si assicura che sia collegato a una versione 2.2.5 precedente.

#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;
}

18
glibc non supporta il collegamento statico - i programmi glibc collegati staticamente non funzionano normalmente su sistemi con differenti versioni di libc.
Ricorda Monica

5
Glibc libc.acontinuano ad esistere, glibc lo supporta in alcuni casi, sebbene non sia raccomandato (Drepper) . Avrai problemi con programmi non banali, specialmente con qualsiasi cosa che utilizzi NSS (soluzione alternativa nelle FAQ ).
mr.spuratic

20

Collegamento con -static . Quando ci si collega con -static il linker incorpora la libreria all'interno dell'eseguibile, quindi l'eseguibile sarà più grande, ma può essere eseguito su un sistema con una versione precedente di glibc perché il programma utilizzerà la propria libreria invece di quella del sistema .


55
Spesso il motivo per cui vuoi farlo è perché stai distribuendo un'applicazione closed-source. In questo caso spesso non è consentito il collegamento statico per motivi di licenza (così facendo sarebbe necessario rilasciare tutto il codice sorgente) quindi è necessario fare attenzione con -static.
Malvineous

3
Nel frattempo almeno si può spesso ricorrere a musl-libc, ma con i programmi C ++ le cose possono diventare più complicate, quindi potrebbe essere necessario specificare una versione di simboli.
0xC0000022L

16

Setup 1: compila la tua glibc senza GCC dedicato e usala

Poiché sembra impossibile fare solo con gli hack di versioning dei simboli, facciamo un ulteriore passo avanti e compiliamo glibc da soli.

Questa configurazione potrebbe funzionare ed è veloce in quanto non ricompila l'intera toolchain di GCC, ma solo glibc.

Ma non è affidabile in quanto utilizza oggetti host di runtime C come crt1.o, crti.oe crtn.ofornito da glibc. Questo è menzionato su: https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Quegli oggetti eseguono una configurazione iniziale su cui si basa glibc, quindi non sarei sorpreso se le cose andassero in crash in modo meraviglioso e modi incredibilmente sottili.

Per una configurazione più affidabile, vedere Configurazione 2 di seguito.

Crea glibc e installa localmente:

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`

Configurazione 1: verifica la 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);
}

Compila ed esegui con 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

Il programma produce quanto previsto:

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

Comando adattato da https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location ma --sysrootnon riuscito con:

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

quindi l'ho rimosso.

lddl'output conferma che le lddlibrerie e che abbiamo appena creato vengono effettivamente utilizzate come previsto:

+ 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)

L' gccoutput di debug della compilazione mostra che sono stati utilizzati i miei oggetti runtime host, il che è negativo come accennato in precedenza, ma non so come aggirarlo, ad esempio contiene:

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

Configurazione 1: modifica glibc

Ora modifichiamo glibc con:

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)");

Quindi ricompilare e reinstallare glibc, quindi ricompilare e rieseguire il nostro programma:

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

e vediamo hackedstampato un paio di volte come previsto.

Ciò conferma ulteriormente che abbiamo effettivamente utilizzato la glibc che abbiamo compilato e non quella host.

Testato su Ubuntu 18.04.

Configurazione 2: configurazione incontaminata di crosstool-NG

Questa è un'alternativa alla messa a punto 1, ed è la configurazione più corretta che ho raggiunto fino ad ora: tutto è corretto, per quanto posso osservare, compresi gli oggetti il runtime C, come crt1.o, crti.oe crtn.o.

In questa configurazione, compileremo una toolchain GCC completamente dedicata che utilizza la glibc che vogliamo.

L'unico svantaggio di questo metodo è che la compilazione richiederà più tempo. Ma non rischierei una configurazione di produzione con qualcosa di meno.

crosstool-NG è un insieme di script che scarica e compila tutto dal sorgente per noi, inclusi GCC, glibc e binutils.

Sì, il sistema di compilazione di GCC è così pessimo che abbiamo bisogno di un progetto separato per questo.

Questa configurazione non è perfetta perché crosstool-NG non supporta la creazione di eseguibili senza -Wlflag aggiuntivi , il che sembra strano dato che abbiamo creato GCC stesso. Ma tutto sembra funzionare, quindi questo è solo un inconveniente.

Ottieni crosstool-NG e configuralo:

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

L'unica opzione obbligatoria che posso vedere è far corrispondere la versione del kernel host per utilizzare le intestazioni del kernel corrette. Trova la tua versione del kernel host con:

uname -a

che mi mostra:

4.15.0-34-generic

così in menuconfigio:

  • Operating System
    • Version of linux

quindi seleziono:

4.14.71

che è la prima versione uguale o precedente. Deve essere più vecchio poiché il kernel è retrocompatibile.

Ora puoi costruire con:

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

e ora attendi da trenta minuti a due ore per la compilazione.

Setup 2: configurazioni opzionali

Quello .configche abbiamo generato con ./ct-ng x86_64-unknown-linux-gnuha:

CT_GLIBC_V_2_27=y

Per cambiarlo, in menuconfigdo:

  • C-library
  • Version of glibc

salva il .confige continua con la compilazione.

Oppure, se vuoi usare il tuo sorgente glibc, ad esempio per usare glibc dall'ultimo git, procedi in questo modo :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: impostato su true
  • C-library
    • Source of glibc
      • Custom location: dì di si
      • Custom location
        • Custom source location: punta a una directory contenente il tuo sorgente glibc

dove glibc è stato clonato come:

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

Configurazione 2: provalo

Dopo aver costruito la toolchain che desideri, provala con:

#!/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

Tutto sembra funzionare come in Setup 1, tranne che ora sono stati utilizzati gli oggetti runtime corretti:

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

Configurazione 2: tentativo di ricompilazione efficiente di glibc fallito

Non sembra possibile con crosstool-NG, come spiegato di seguito.

Se ricostruisci solo;

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

quindi vengono prese in considerazione le modifiche alla posizione dei sorgenti di glibc personalizzata, ma costruisce tutto da zero, rendendolo inutilizzabile per lo sviluppo iterativo.

Se lo facciamo:

./ct-ng list-steps

offre una bella panoramica dei passaggi di costruzione:

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.

quindi, vediamo che ci sono passaggi glibc intrecciati con diversi passaggi GCC, in particolare libc_start_filesviene prima cc_core_pass_2, che è probabilmente il passaggio più costoso insieme a cc_core_pass_1.

Per creare un solo passaggio, devi prima impostare l' .configopzione "Salva passaggi intermedi" per la build iniziale:

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

e poi puoi provare:

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

ma sfortunatamente, il +richiesto come menzionato su: https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

Si noti tuttavia che il riavvio in una fase intermedia reimposta la directory di installazione allo stato che aveva durante quella fase. Cioè, avrai una libc ricostruita, ma nessun compilatore finale compilato con questa libc (e quindi, nemmeno le librerie del compilatore come libstdc ++).

e fondamentalmente rende ancora la ricostruzione troppo lenta per essere fattibile per lo sviluppo, e non vedo come superare questo problema senza applicare patch a crosstool-NG.

Inoltre, a partire dal libcpassaggio non sembra che si copi nuovamente la sorgente da Custom source location, rendendo ulteriormente inutilizzabile questo metodo.

Bonus: stdlibc ++

Un bonus se sei interessato anche alla libreria standard C ++: come modificare e ricostruire il sorgente della libreria standard GCC libstdc ++ C ++?


musl-libcè un'altra opzione per quanto riguarda il runtime C.
0xC0000022L

0

A mio parere, la soluzione più pigra (soprattutto se non ti affidi alle ultime funzionalità C / C ++ all'avanguardia o alle ultime funzionalità del compilatore) non è stata ancora menzionata, quindi eccola qui:

Costruisci semplicemente sul sistema con il GLIBC più vecchio che desideri ancora supportare.

Questo è in realtà abbastanza facile da fare al giorno d'oggi con tecnologie come chroot, o KVM / Virtualbox, o docker, anche se non vuoi davvero usare una distribuzione così vecchia direttamente su qualsiasi PC. In dettaglio, per realizzare un binario portatile al massimo del tuo software ti consiglio di seguire questi passaggi:

  1. Scegli il tuo veleno di sandbox / virtualizzazione / ... qualunque cosa, e usalo per procurarti un vecchio Ubuntu LTS virtuale e compilare con gcc / g ++ che ha lì per impostazione predefinita. Ciò limita automaticamente il tuo GLIBC a quello disponibile in quell'ambiente.

  2. Evita di dipendere da librerie esterne al di fuori di quelle fondamentali: come, dovresti collegare dinamicamente cose di sistema a livello di terra come glibc, libGL, libxcb / X11 / wayland things, libasound / libpulseaudio, possibilmente GTK + se lo usi, ma altrimenti preferibilmente link staticamente esterno libs / spediscili se puoi. Soprattutto le librerie per lo più autonome come caricatori di immagini, decoder multimediali, ecc. Possono causare meno rotture su altre distribuzioni (la rottura può essere causata, ad esempio, se presenti solo da qualche parte in una versione principale diversa) se le spedisci staticamente.

Con questo approccio si ottiene un binario compatibile con GLIBC vecchio senza alcuna modifica manuale dei simboli, senza eseguire un binario completamente statico (che potrebbe non funzionare per programmi più complessi perché glibc lo odia e che potrebbe causare problemi di licenza per te) e senza impostare su qualsiasi toolchain personalizzata, qualsiasi copia glibc personalizzata o qualsiasi altra cosa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.