Hai ragione in quanto glibc utilizza 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 binario. Questa è probabilmente l'opzione più semplice.
Puoi anche creare il tuo binario in un ambiente di compilazione chroot o usando un glibc-new => glibc-vecchio 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 precedente purché sia valido utilizzando lo stesso .symver
pseudo-op che viene utilizzato per definire i simboli con versione in primo luogo. L'esempio seguente è tratto dal post del blog.
L'esempio seguente utilizza il percorso reale di glibc, ma assicura che sia collegato a una versione 2.2.5 precedente.
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
__asm__(".symver realpath,[email protected]_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;
}
Setup 1:compila la tua glibc senza GCC dedicato e usala
Dal momento che sembra impossibile farlo solo con gli hack di versionamento dei simboli, facciamo un ulteriore passo avanti e compiliamo noi stessi glibc.
Questa configurazione potrebbe funzionare ed è veloce in quanto non ricompila l'intera toolchain GCC, solo glibc.
Ma non è affidabile in quanto utilizza oggetti di runtime host C come crt1.o
, crti.o
e crtn.o
fornito da glibc. Questo è menzionato in:https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Questi oggetti eseguono la 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.
Compila 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`
Setup 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.
* https://stackoverflow.com/questions/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 l'atteso:
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 --sysroot
l'ha fatto fallire con:
cannot find /home/ciro/glibc/build/install/lib/libc.so.6 inside /home/ciro/glibc/build/install
quindi l'ho rimosso.
ldd
l'output conferma che ldd
e le librerie 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'gcc
l'output di debug della compilazione mostra che sono stati utilizzati i miei oggetti di runtime host, il che è negativo come accennato in precedenza, ma non so come aggirarlo, ad es. contiene:
COLLECT_GCC_OPTIONS=/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o
Setup 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)");
Poi ricompila e reinstalla glibc, e ricompila e riesegui il nostro programma:
cd glibc/build
make -j `nproc`
make -j `nproc` install
./test_glibc.sh
e vediamo hacked
stampato alcune volte come previsto.
Ciò conferma ulteriormente che abbiamo effettivamente utilizzato la glibc che abbiamo compilato e non quella dell'host.
Testato su Ubuntu 18.04.
Configurazione 2:configurazione originaria di crosstool-NG
Questa è un'alternativa alla configurazione 1, ed è la configurazione più corretta che ho raggiunto finora:tutto è corretto per quanto posso osservare, inclusi gli oggetti di runtime C come crt1.o
, crti.o
e crtn.o
.
In questa configurazione, compileremo una toolchain GCC completamente dedicata che utilizza la glibc che vogliamo.
L'unico aspetto negativo di questo metodo è che la compilazione richiederà più tempo. Ma non rischierei una configurazione di produzione con qualcosa di meno.
crosstool-NG è un set di script che scarica e compila per noi tutto dal sorgente, 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 solo perché crosstool-NG non supporta la compilazione degli eseguibili senza -Wl
extra flags, 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 è farla corrispondere alla 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
quindi in menuconfig
Sì:
Operating System
Version of linux
quindi seleziono:
4.14.71
che è la prima versione uguale o precedente. Deve essere più vecchio poiché il kernel è compatibile con le versioni precedenti.
Ora puoi costruire con:
env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`
e ora attendi da circa trenta minuti a due ore per la compilazione.
Setup 2:configurazioni opzionali
Il .config
che abbiamo generato con ./ct-ng x86_64-unknown-linux-gnu
ha:
CT_GLIBC_V_2_27=y
Per cambiarlo, in menuconfig
fare:
C-library
Version of glibc
salva il .config
e continua con la compilazione.
Oppure, se vuoi usare la tua fonte glibc, ad es. per usare glibc dall'ultimo git, procedi così:
Paths and misc options
Try features marked as EXPERIMENTAL
:impostato su true
C-library
Source of glibc
Custom location
:dire di sìCustom location
Custom source location
:punta a una directory contenente il tuo sorgente di 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 creato 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 per il fatto che ora sono stati utilizzati gli oggetti di 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
Setup 2:tentativo di ricompilazione efficiente di glibc fallito
Non sembra possibile con crosstool-NG, come spiegato di seguito.
Se ricostruisci semplicemente;
env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`
quindi vengono prese in considerazione le tue modifiche alla posizione di origine della glibc personalizzata, ma crea tutto da zero, rendendolo inutilizzabile per lo sviluppo iterativo.
Se lo facciamo:
./ct-ng list-steps
fornisce una bella panoramica dei passaggi della creazione:
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.
pertanto, vediamo che ci sono passaggi di glibc intrecciati con diversi passaggi GCC, in particolare libc_start_files
viene prima di cc_core_pass_2
, che è probabilmente il passaggio più costoso insieme a cc_core_pass_1
.
Per costruire un solo passaggio, devi prima impostare il "Salva passaggi intermedi" in .config
opzione 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 indicato su:https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536
Si noti tuttavia che il riavvio in un passaggio intermedio reimposta la directory di installazione allo stato che aveva durante tale passaggio. Cioè, avrai una libc ricostruita, ma nessun compilatore finale compilato con questa libc (e quindi, nemmeno librerie di compilatori come libstdc++).
e fondamentalmente rende ancora la ricostruzione troppo lenta per essere fattibile per lo sviluppo, e non vedo come superare questo problema senza patchare crosstool-NG.
Inoltre, partendo dal libc
step non sembrava copiare di nuovo la fonte da Custom source location
, rendendo ulteriormente questo metodo inutilizzabile.
Bonus:stdlibc++
Un bonus se sei interessato anche alla libreria standard C++:come modificare e ricostruire il sorgente della libreria standard C++ GCC libstdc++?