Jak mogę utworzyć link do określonej wersji biblioteki glibc?

111

Kiedy kompiluję coś na moim komputerze z Ubuntu Lucid 10.04, zostaje to połączone z glibc. Lucid używa 2.11 glibc. Kiedy uruchamiam ten plik binarny na innym komputerze ze starszym glibc, polecenie nie powiedzie się, mówiąc, że nie ma glibc 2.11 ...

O ile wiem, glibc używa wersji symboli. Czy mogę zmusić gcc do łączenia się z określoną wersją symbolu?

W moim konkretnym zastosowaniu staram się skompilować cross toolchain gcc dla ARM.

falstaff
źródło
58
To jeden z tych naprawdę irytujących problemów z linuxem, na przykład rozwiązanie zawsze brzmi „nie powinieneś tego robić”, co oczywiście oznacza „to nie działa i nikt go jeszcze nie naprawił”.
Timmmm
4
Ludzie narzekali na piekło DLL w systemie Windows. Pamiętam, że niektórzy miłośnicy Linuksa próbowali przywołać to jako szczególnie okropny przykład ze świata Windows. Kiedy po raz pierwszy wpadł na ten robi rozwoju Linux ponad dekadę temu wszystko zrobiłem było pochować moją twarz w dłoniach.
0xC0000022L

Odpowiedzi:

70

Masz rację, ponieważ glibc używa wersji symboli. Jeśli jesteś ciekaw, symbol wersjonowania realizację wprowadzony w glibc 2.1 jest opisana tutaj i jest przedłużeniem symbolem Słońca schemat wersjonowania opisaną tutaj .

Jedną z opcji jest statyczne połączenie pliku binarnego. To prawdopodobnie najłatwiejsza opcja.

Możesz także zbudować swój plik binarny w środowisku kompilacji chroot lub używając starego cross-kompilatora glibc- new => glibc- old .

Zgodnie z wpisem na blogu http://www.trevorpounds.com Linking to Older Versioned Symbols (glibc) , możliwe jest wymuszenie powiązania dowolnego symbolu ze starszym, o ile jest ważny przy użyciu tego samego .symverpseudo -op, który służy do definiowania symboli wersjonowanych w pierwszej kolejności. Poniższy przykład pochodzi z wpisu na blogu .

Poniższy przykład wykorzystuje realpath glibc, ale zapewnia, że ​​jest on połączony ze starszą wersją 2.2.5.

#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
źródło
18
glibc nie obsługuje linkowania statycznego - statycznie łączone programy glibc nie działają zwykle na systemach z różnymi wersjami libc.
Pamiętaj o Monice
5
glibc libc.anadal istnieje, glibc obsługuje to w niektórych przypadkach, chociaż nie jest to zalecane (Drepper) . Będziesz miał problemy z nietrywialnymi programami, szczególnie ze wszystkim, co używa NSS (obejście w FAQ ).
pan spuratic
20

Połącz z -static . Kiedy łączysz się z -static, linker osadza bibliotekę wewnątrz pliku wykonywalnego, więc plik wykonywalny będzie większy, ale może być wykonany na systemie ze starszą wersją glibc, ponieważ program użyje własnej biblioteki zamiast biblioteki systemowej .

Iacchus
źródło
55
Często powodem, dla którego w ogóle chcesz to zrobić, jest dystrybucja aplikacji o zamkniętym źródle. W takim przypadku ze względów licencyjnych często nie można łączyć statycznie (wymagałoby to wydania całego kodu źródłowego), więc z -static trzeba uważać.
Malvineous
3
W międzyczasie przynajmniej można często uciec się do musl-libc, ale z programami C ++ sprawy mogą się komplikować, więc określenie wersji symbolu może być nadal konieczne.
0xC0000022L
16

Konfiguracja 1: skompiluj własną bibliotekę glibc bez dedykowanego GCC i używaj jej

Ponieważ wydaje się niemożliwe, aby zrobić to samo z hackami do wersjonowania symboli, przejdźmy o krok dalej i skompilujmy glibc samodzielnie.

Ta konfiguracja może działać i jest szybka, ponieważ nie rekompiluje ponownie całego zestawu narzędzi GCC, tylko glibc.

Ale to nie jest wiarygodne, ponieważ korzysta gospodarz C Runtime obiektów takich jak crt1.o, crti.oi crtn.odostarczane przez glibc. Wspomina się o tym na: https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Te obiekty wykonują wczesną konfigurację, na której opiera się glibc, więc nie zdziwiłbym się, gdyby coś się zawiesiło i niesamowicie subtelne sposoby.

Aby uzyskać bardziej niezawodną konfigurację, zobacz Konfiguracja 2 poniżej.

Skompiluj glibc i zainstaluj lokalnie:

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`

Konfiguracja 1: sprawdź kompilację

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

Skompiluj i uruchom z 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

Program daje oczekiwane:

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

Polecenie zaadaptowano z https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location, ale --sysrootzawiodło:

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

więc go usunąłem.

ldddane wyjściowe potwierdzają, że lddbiblioteki i, które właśnie zbudowaliśmy, są używane zgodnie z oczekiwaniami:

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

Wynik gccdebugowania kompilacji pokazuje, że były używane obiekty wykonawcze mojego hosta, co jest złe, jak wspomniano wcześniej, ale nie wiem, jak to obejść, np. Zawiera:

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

Konfiguracja 1: zmodyfikuj glibc

Teraz zmodyfikujmy glibc za pomocą:

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

Następnie ponownie skompiluj i ponownie zainstaluj glibc oraz ponownie skompiluj i ponownie uruchom nasz program:

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

i widzimy hackedwydrukowane kilka razy zgodnie z oczekiwaniami.

To dodatkowo potwierdza, że ​​faktycznie używaliśmy skompilowanego glibc, a nie hosta.

Testowane na Ubuntu 18.04.

Konfiguracja 2: nieskazitelna konfiguracja crosstool-NG

Jest to alternatywa dla konfiguracji 1, i to jest najbardziej poprawna konfiguracja Mam osiągnąć daleko: wszystko jest w porządku o ile mogę obserwować, w tym C Runtime obiektów takich jak crt1.o, crti.oi crtn.o.

W tej konfiguracji skompilujemy w pełni dedykowany łańcuch narzędzi GCC, który używa potrzebnego glibc.

Jedyną wadą tej metody jest to, że kompilacja potrwa dłużej. Ale nie ryzykowałbym konfiguracji produkcyjnej z czymś mniejszym.

crosstool-NG to zestaw skryptów, które pobierają i kompilują wszystko ze źródła dla nas, w tym GCC, glibc i binutils.

Tak, system kompilacji GCC jest tak zły, że potrzebujemy do tego osobnego projektu.

Ta konfiguracja nie jest idealna tylko dlatego, że crosstool-NG nie obsługuje budowania plików wykonywalnych bez dodatkowych -Wlflag , co wydaje się dziwne, odkąd zbudowaliśmy samo GCC. Ale wszystko wydaje się działać, więc jest to tylko niedogodność.

Zdobądź crosstool-NG i skonfiguruj go:

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

Jedyną obowiązkową opcją, jaką widzę, jest dopasowanie do wersji jądra hosta, aby używał poprawnych nagłówków jądra. Znajdź wersję jądra hosta za pomocą:

uname -a

co pokazuje mi:

4.15.0-34-generic

więc menuconfigrobię:

  • Operating System
    • Version of linux

więc wybieram:

4.14.71

która jest pierwszą równą lub starszą wersją. Musi być starszy, ponieważ jądro jest kompatybilne wstecz.

Teraz możesz budować za pomocą:

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

a teraz poczekaj około trzydziestu minut do dwóch godzin na kompilację.

Konfiguracja 2: opcjonalne konfiguracje

Ten .config, który wygenerowaliśmy, ./ct-ng x86_64-unknown-linux-gnuma:

CT_GLIBC_V_2_27=y

Aby to zmienić, menuconfigwykonaj:

  • C-library
  • Version of glibc

zapisz plik .configi kontynuuj tworzenie.

Lub, jeśli chcesz użyć własnego źródła glibc, np. Użyć glibc z najnowszego gita, postępuj w ten sposób :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: ustawione na true
  • C-library
    • Source of glibc
      • Custom location: Powiedz tak
      • Custom location
        • Custom source location: wskazuje na katalog zawierający twoje źródło glibc

gdzie glibc został sklonowany jako:

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

Konfiguracja 2: przetestuj

Po zbudowaniu odpowiedniego łańcucha narzędzi przetestuj go za pomocą:

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

Wygląda na to, że wszystko działa tak, jak w Instalatorze 1, z wyjątkiem tego, że teraz zostały użyte poprawne obiekty wykonawcze:

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

Instalacja 2: nieudana próba wydajnej ponownej kompilacji glibc

Nie wydaje się to możliwe w przypadku crosstool-NG, jak wyjaśniono poniżej.

Jeśli po prostu przebudujesz;

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

wtedy twoje zmiany w niestandardowej lokalizacji źródłowej glibc są brane pod uwagę, ale buduje wszystko od zera, przez co nie nadaje się do iteracyjnego rozwoju.

Jeśli zrobimy:

./ct-ng list-steps

daje ładny przegląd kroków kompilacji:

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.

dlatego widzimy, że istnieją kroki glibc przeplatane z kilkoma krokami GCC, w szczególności libc_start_fileswystępuje wcześniej cc_core_pass_2, co jest prawdopodobnie najdroższym krokiem razem z cc_core_pass_1.

Aby zbudować tylko jeden krok, musisz najpierw ustawić .configopcję „Zapisz kroki pośrednie” dla początkowej kompilacji:

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

a potem możesz spróbować:

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

ale niestety +wymagane, jak wspomniano na: https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

Należy jednak pamiętać, że ponowne uruchomienie w kroku pośrednim powoduje zresetowanie katalogu instalacyjnego do stanu, jaki miał podczas tego kroku. Oznacza to, że będziesz mieć przebudowaną bibliotekę libc - ale nie będzie ostatecznego kompilatora zbudowanego z tą biblioteką (i stąd też nie będzie żadnych bibliotek kompilatorów, takich jak libstdc ++).

i zasadniczo nadal sprawia, że ​​przebudowa jest zbyt wolna, aby była możliwa do opracowania, i nie widzę, jak to przezwyciężyć bez łatania crosstool-NG.

Co więcej, rozpoczęcie od libckroku nie Custom source locationpowodowało ponownego skopiowania źródła z , co dodatkowo czyniło tę metodę bezużyteczną.

Bonus: stdlibc ++

Bonus, jeśli interesuje Cię również standardowa biblioteka C ++: Jak edytować i przebudowywać standardowe źródło biblioteki GCC libstdc ++ C ++?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
musl-libcto kolejna opcja, jeśli chodzi o środowisko wykonawcze C.
0xC0000022L
0

Moim zdaniem najbardziej leniwe rozwiązanie (zwłaszcza jeśli nie polegasz na najnowszych funkcjach C / C ++ lub najnowszych funkcjach kompilatora) nie zostało jeszcze wspomniane, więc oto jest:

Po prostu zbuduj system z najstarszym GLIBC, który nadal chcesz obsługiwać.

W dzisiejszych czasach jest to całkiem proste dzięki technologiom takim jak chroot, KVM / Virtualbox lub docker, nawet jeśli tak naprawdę nie chcesz używać takiej starej dystrybucji bezpośrednio na dowolnym komputerze. W szczegółach, aby utworzyć maksymalnie przenośny plik binarny swojego oprogramowania, zalecam wykonanie następujących kroków:

  1. Po prostu wybierz swoją truciznę piaskownicy / wirtualizacji / ... cokolwiek i użyj jej, aby zdobyć wirtualną starszą wersję Ubuntu LTS i skompiluj z gcc / g ++, które ma tam domyślnie. To automatycznie ogranicza GLIBC do tego dostępnego w tym środowisku.

  2. Unikaj polegania na zewnętrznych bibliotekach poza podstawowymi: na przykład, powinieneś dynamicznie łączyć rzeczy systemowe z poziomu podstawowego, takie jak glibc, libGL, libxcb / X11 / wayland things, libasound / libpulseaudio, prawdopodobnie GTK +, jeśli tego używasz, ale poza tym najlepiej statycznie łączyć zewnętrzne libs / wyślij je razem, jeśli możesz. Szczególnie samodzielne biblioteki, takie jak programy ładujące obrazy, dekodery multimediów itp., Mogą powodować mniej awarii w innych dystrybucjach (awaria może być spowodowana np. Jeśli występuje tylko w innej głównej wersji), jeśli je wysyłasz statycznie.

Dzięki takiemu podejściu otrzymujesz plik binarny zgodny ze starym GLIBC bez żadnych ręcznych poprawek symboli, bez wykonywania w pełni statycznego pliku binarnego (co może się zepsuć w przypadku bardziej złożonych programów, ponieważ glibc tego nienawidzi i może powodować problemy z licencjonowaniem) i bez ustawiania w górę dowolnego niestandardowego łańcucha narzędzi, dowolnej niestandardowej kopii glibc lub cokolwiek innego.

ET
źródło