Como posso vincular a uma versão glibc específica?

110

Quando eu compilo algo no meu Ubuntu Lucid 10.04 PC, ele é vinculado ao glibc. Lucid usa 2,11 de glibc. Quando executo este binário em outro PC com uma glibc mais antiga, o comando falha dizendo que não há glibc 2.11 ...

Pelo que eu sei, a glibc usa versão de símbolo. Posso forçar o gcc a vincular a uma versão de símbolo específica?

Em meu uso concreto, tento compilar um conjunto de ferramentas cruzadas gcc para ARM.

falstaff
fonte
58
Argh, este é um daqueles problemas de linux realmente irritantes, como onde a solução é sempre "você não deveria fazer isso", o que obviamente significa "não funciona e ninguém o corrigiu ainda".
Timmmm
3
As pessoas reclamaram do inferno das DLLs no Windows. Lembro-me de alguns aficionados do Linux tentando trazer isso à tona como um exemplo particularmente horrível do mundo do Windows. Quando me deparei com isso fazendo desenvolvimento para Linux há mais de uma década, tudo que fiz foi enterrar meu rosto em minhas mãos.
0xC0000022L

Respostas:

69

Você está correto ao dizer que a glibc usa versão de símbolo. Se você estiver curioso, a implementação de versão de símbolo introduzida na glibc 2.1 é descrita aqui e é uma extensão do esquema de versão de símbolo da Sun descrito aqui .

Uma opção é vincular estaticamente seu binário. Esta é provavelmente a opção mais fácil.

Você também pode construir seu binário em um ambiente de construção chroot, ou usando um cross-compiler glibc- new => glibc- old .

De acordo com a postagem do blog http://www.trevorpounds.com Linking to Older Versioned Symbols (glibc) , é possível forçar qualquer símbolo a ser vinculado a um mais antigo, desde que seja válido usando o mesmo .symverpseudo -op que é usado para definir símbolos com versão em primeiro lugar. O exemplo a seguir foi extraído da postagem do blog .

O exemplo a seguir usa o realpath do glibc, mas garante que ele esteja vinculado a uma versão 2.2.5 mais antiga.

#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
fonte
18
A glibc não suporta vinculação estática - programas glibc vinculados estaticamente não funcionam suualmente em sistemas com diferentes versões libc.
Lembre
5
glibc libc.acontinua a existir, glibc suporta isso em alguns casos, embora não seja recomendado (Drepper) . Você terá problemas com programas não triviais, especialmente qualquer coisa que use NSS (solução alternativa no FAQ ).
mr.spuratic
20

Faça a ligação com -static . Quando você vincula com -static, o vinculador incorpora a biblioteca dentro do executável, então o executável será maior, mas pode ser executado em um sistema com uma versão mais antiga da glibc porque o programa usará sua própria biblioteca em vez da do sistema .

Iacchus
fonte
55
Freqüentemente, o motivo pelo qual você deseja fazer isso é porque você está distribuindo um aplicativo de código-fonte fechado. Nesse caso, geralmente não é permitido vincular estaticamente por motivos de licenciamento (isso exigiria que você liberasse todo o seu código-fonte), portanto, você precisa ter cuidado com -static.
Malvineous
3
Enquanto isso, pelo menos alguém pode frequentemente recorrer ao musl-libc, mas com programas C ++ as coisas podem ficar mais complicadas, portanto, especificar uma versão de símbolo ainda pode ser necessário.
0xC0000022L
16

Configuração 1: compilar seu próprio glibc sem GCC dedicado e usá-lo

Visto que parece impossível fazer apenas com os hacks de versão de símbolo, vamos dar um passo adiante e compilar o glibc nós mesmos.

Esta configuração pode funcionar e é rápida, pois não recompila todo o conjunto de ferramentas do GCC, apenas glibc.

Mas não é confiável, pois usa anfitrião C runtime objetos como crt1.o, crti.oe crtn.ofornecido pela glibc. Isso é mencionado em: https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Esses objetos fazem a configuração inicial em que a glibc depende, então eu não ficaria surpreso se as coisas travassem de forma maravilhosa e maneiras incrivelmente sutis.

Para uma configuração mais confiável, consulte a Configuração 2 abaixo.

Construa o glibc e instale 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`

Configuração 1: verifique a construção

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

Compile e execute com 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

O programa produz o esperado:

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

Comando adaptado de https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location, mas --sysrootfalhou com:

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

então eu o removi.

lddA saída confirma que as lddbibliotecas e que acabamos de construir estão realmente sendo usadas conforme o esperado:

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

A gccsaída de depuração da compilação mostra que meus objetos de tempo de execução do host foram usados, o que é ruim, conforme mencionado anteriormente, mas não sei como contornar isso, por exemplo, contém:

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

Configuração 1: modificar glibc

Agora vamos modificar o glibc com:

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

Em seguida, recompile e reinstale o glibc, e recompile e execute novamente o nosso programa:

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

e vemos hackedimpresso algumas vezes conforme o esperado.

Isso confirma ainda mais que realmente usamos a glibc que compilamos e não a do host.

Testado no Ubuntu 18.04.

Configuração 2: configuração do crosstool-NG pristine

Esta é uma alternativa para a configuração 1, e é a configuração mais correta que consegui agora: tudo está correto, tanto quanto eu posso observar, incluindo o tempo de execução C objetos como crt1.o, crti.o, e crtn.o.

Nesta configuração, compilaremos um conjunto de ferramentas GCC dedicado e completo que usa a glibc que desejamos.

A única desvantagem desse método é que a construção levará mais tempo. Mas eu não arriscaria uma configuração de produção com nada menos.

crosstool-NG é um conjunto de scripts que baixa e compila tudo do código-fonte para nós, incluindo GCC, glibc e binutils.

Sim, o sistema de compilação do GCC é tão ruim que precisamos de um projeto separado para isso.

Esta configuração só não é perfeita porque crosstool-NG não suporta a construção de executáveis ​​sem -Wlsinalizadores extras , o que parece estranho já que construímos o próprio GCC. Mas tudo parece funcionar, então isso é apenas um inconveniente.

Obtenha o crosstool-NG e configure-o:

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

A única opção obrigatória que posso ver é fazer com que corresponda à versão do kernel do host para usar os cabeçalhos de kernel corretos. Encontre a versão do kernel do host com:

uname -a

que me mostra:

4.15.0-34-generic

então menuconfigeu faço:

  • Operating System
    • Version of linux

então eu seleciono:

4.14.71

qual é a primeira versão igual ou mais antiga. Tem que ser mais antigo, pois o kernel é compatível com versões anteriores.

Agora você pode construir com:

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

e agora aguarde cerca de trinta minutos a duas horas para a compilação.

Configuração 2: configurações opcionais

O .configque geramos com ./ct-ng x86_64-unknown-linux-gnutem:

CT_GLIBC_V_2_27=y

Para mudar isso, menuconfigfaça:

  • C-library
  • Version of glibc

salve o .confige continue com a construção.

Ou, se você quiser usar seu próprio código-fonte glibc, por exemplo, para usar glibc do git mais recente, proceda assim :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: definido como verdadeiro
  • C-library
    • Source of glibc
      • Custom location: diga sim
      • Custom location
        • Custom source location: aponta para um diretório contendo seu código-fonte glibc

onde glibc foi clonado como:

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

Configuração 2: teste

Depois de criar o conjunto de ferramentas que deseja, teste-o com:

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

Tudo parece funcionar como na Configuração 1, exceto que agora os objetos de tempo de execução corretos foram usados:

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

Configuração 2: falha na tentativa de recompilação eficiente de glibc

Não parece possível com o crosstool-NG, conforme explicado abaixo.

Se você apenas reconstruir;

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

então, suas alterações no local de origem glibc customizado são levadas em consideração, mas ele constrói tudo do zero, tornando-o inutilizável para desenvolvimento iterativo.

Se nós fizermos:

./ct-ng list-steps

dá uma boa visão geral das etapas de construção:

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.

portanto, vemos que há etapas glibc entrelaçadas com várias etapas GCC, mais notavelmente libc_start_filesvem antes cc_core_pass_2, que é provavelmente a etapa mais cara junto com cc_core_pass_1.

Para construir apenas uma etapa, você deve primeiro definir a opção "Salvar etapas intermediárias" .configpara a construção inicial:

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

e então você pode tentar:

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

mas, infelizmente, o +exigido conforme mencionado em: https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

Observe, no entanto, que reiniciar em uma etapa intermediária redefine o diretório de instalação para o estado que tinha durante essa etapa. Ou seja, você terá uma libc reconstruída - mas nenhum compilador final construído com esta libc (e, portanto, nenhuma biblioteca de compilador como libstdc ++ também).

e basicamente ainda torna a reconstrução muito lenta para ser viável para o desenvolvimento, e não vejo como superar isso sem corrigir o crosstool-NG.

Além disso, começar a partir da libcetapa não parece copiar a fonte novamente Custom source location, tornando esse método inutilizável.

Bônus: stdlibc ++

Um bônus se você também estiver interessado na biblioteca padrão C ++: Como editar e reconstruir a fonte da biblioteca padrão GCC libstdc ++ C ++?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
musl-libcé outra opção no que diz respeito ao tempo de execução C.
0xC0000022L
0

Na minha opinião, a solução mais preguiçosa (especialmente se você não depende dos recursos C / C ++ mais recentes ou dos recursos mais recentes do compilador) ainda não foi mencionada, então aqui está:

Basta construir no sistema com o GLIBC mais antigo que você ainda deseja oferecer suporte.

Na verdade, isso é muito fácil de fazer hoje em dia com tecnologias como chroot, ou KVM / Virtualbox ou docker, mesmo se você realmente não quiser usar uma distro tão antiga diretamente em qualquer PC. Em detalhes, para criar um binário portátil máximo de seu software, recomendo seguir estas etapas:

  1. Basta escolher o seu veneno de sandbox / virtualização / ... seja o que for, e usá-lo para obter um Ubuntu LTS virtual mais antigo e compilar com o gcc / g ++ que ele tem por padrão. Isso limita automaticamente o seu GLIBC ao disponível naquele ambiente.

  2. Evite depender de libs externas fora das básicas: como, você deve vincular dinamicamente coisas do sistema no nível do solo como glibc, libGL, libxcb / X11 / wayland things, libasound / libpulseaudio, possivelmente GTK + se você usar isso, mas de outra forma, preferencialmente link externo estaticamente libs / envie-os junto, se puder. Principalmente bibliotecas independentes, como carregadores de imagens, decodificadores de multimídia, etc, podem causar menos falhas em outras distros (falhas podem ser causadas, por exemplo, se estiverem presentes em algum lugar em uma versão principal diferente) se você os enviar estaticamente.

Com essa abordagem, você obtém um binário compatível com GLIBC antigo sem nenhum ajuste manual de símbolo, sem fazer um binário totalmente estático (que pode falhar para programas mais complexos porque a glibc odeia isso e pode causar problemas de licenciamento para você) e sem configuração qualquer conjunto de ferramentas personalizado, qualquer cópia glibc personalizada ou qualquer outra coisa.

ET
fonte