¿Cómo puedo vincularme a una versión específica de glibc?

110

Cuando compilo algo en mi PC Ubuntu Lucid 10.04, se vincula con glibc. Lucid usa 2.11 de glibc. Cuando ejecuto este binario en otra PC con un glibc anterior, el comando falla diciendo que no hay glibc 2.11 ...

Hasta donde yo sé, glibc usa versiones de símbolos. ¿Puedo obligar a gcc a que se vincule con una versión de símbolo específica?

En mi uso concreto, trato de compilar una cadena de herramientas cruzada gcc para ARM.

falstaff
fuente
58
Argh, este es uno de esos problemas de Linux realmente molestos, como donde la solución siempre es "no deberías hacer eso", lo que por supuesto significa "no funciona y nadie lo ha solucionado todavía".
Timmmm
3
La gente se quejaba del infierno de DLL en Windows. Recuerdo que algunos aficionados de Linux trataron de mencionarlo como un ejemplo particularmente horrible del mundo de Windows. Cuando me encontré por primera vez con esto haciendo desarrollo de Linux hace más de una década, todo lo que hice fue enterrar mi cara en mis manos.
0xC0000022L

Respuestas:

69

Tiene razón en que glibc usa versiones de símbolos. Si tiene curiosidad, la implementación de control de versiones de símbolos introducida en glibc 2.1 se describe aquí y es una extensión del esquema de control de versiones de símbolos de Sun descrito aquí .

Una opción es vincular estáticamente su binario. Esta es probablemente la opción más sencilla.

También puede construir su binario en un entorno de construcción chroot, o usando un compilador cruzado glibc- new => glibc- old .

Según la publicación del blog http://www.trevorpounds.com Vinculación a símbolos con versiones más antiguas (glibc) , es posible forzar que cualquier símbolo se vincule con uno más antiguo siempre que sea válido utilizando el mismo .symverpseudo -op que se utiliza para definir símbolos versionados en primer lugar. El siguiente ejemplo está extraído de la publicación del blog .

El siguiente ejemplo hace uso de la ruta real de glibc, pero se asegura de que esté vinculado a una versión 2.2.5 anterior.

#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
fuente
18
glibc no admite enlaces estáticos: los programas glibc enlazados estáticamente no funcionan normalmente en sistemas con diferentes versiones de libc.
Recuerda a Monica el
5
glibc libc.asigue existiendo, glibc admite esto en algunos casos, aunque no se recomienda (Drepper) . Tendrá problemas con programas no triviales, especialmente cualquier cosa que use NSS (solución alternativa en las Preguntas frecuentes ).
mr.spuratic
20

Enlace con -static . Cuando se vincula con -static, el vinculador incrusta la biblioteca dentro del ejecutable, por lo que el ejecutable será más grande, pero se puede ejecutar en un sistema con una versión anterior de glibc porque el programa usará su propia biblioteca en lugar de la del sistema. .

Iacchus
fuente
55
A menudo, la razón por la que desea hacer esto es porque está distribuyendo una aplicación de código cerrado. En este caso, a menudo no se permite vincular estáticamente por razones de licencia (hacerlo requeriría que libere todo su código fuente) por lo que debe tener cuidado con -static.
Malvineous
3
Mientras tanto, al menos uno puede recurrir a musl-libc, pero con los programas C ++ las cosas pueden complicarse más, por lo que puede ser necesario especificar una versión de símbolo.
0xC0000022L
16

Configuración 1: compile su propio glibc sin GCC dedicado y úselo

Dado que parece imposible hacerlo solo con trucos de control de versiones de símbolos, vayamos un paso más allá y compilemos glibc nosotros mismos.

Esta configuración podría funcionar y es rápida, ya que no recompila toda la cadena de herramientas de GCC, solo glibc.

Pero no es fiable, ya que utiliza anfitrión tiempo de ejecución C objetos tales como crt1.o, crti.oy crtn.oproporcionado por glibc. Esto se menciona en: https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Esos objetos realizan una configuración temprana en la que se basa glibc, por lo que no me sorprendería si las cosas fallaran de manera maravillosa y formas asombrosamente sutiles.

Para una configuración más confiable, consulte Configuración 2 a continuación.

Compile glibc e instálelo 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`

Configuración 1: verificar la compilación

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

Compilar y ejecutar 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

El programa produce lo 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 pero --sysroothizo que fallara con:

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

así que lo quité.

lddLa salida confirma que las lddbibliotecas y que acabamos de crear se están utilizando como se esperaba:

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

La gccsalida de depuración de la compilación muestra que se usaron los objetos de tiempo de ejecución de mi host, lo cual es malo como se mencionó anteriormente, pero no sé cómo solucionarlo, por ejemplo, contiene:

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

Configuración 1: modificar glibc

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

Luego recompile y reinstale glibc, y recompile y vuelva a ejecutar nuestro programa:

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

y lo vemos hackedimpreso unas cuantas veces como se esperaba.

Esto confirma además que en realidad usamos el glibc que compilamos y no el host.

Probado en Ubuntu 18.04.

Configuración 2: configuración prístina de crosstool-NG

Esta es una alternativa a la configuración 1, y es la configuración más correcta que he logrado mucho: todo es correcto por lo que yo puedo observar, incluyendo el tiempo de ejecución C objetos tales como crt1.o, crti.o, y crtn.o.

En esta configuración, compilaremos una cadena de herramientas GCC dedicada que usa el glibc que queremos.

El único inconveniente de este método es que la construcción llevará más tiempo. Pero no arriesgaría una configuración de producción con nada menos.

crosstool-NG es un conjunto de scripts que descarga y compila todo desde la fuente para nosotros, incluidos GCC, glibc y binutils.

Sí, el sistema de compilación de GCC es tan malo que necesitamos un proyecto separado para eso.

Esta configuración solo no es perfecta porque crosstool-NG no admite la construcción de ejecutables sin -Wlindicadores adicionales , lo que se siente extraño ya que hemos construido GCC. Pero todo parece funcionar, así que esto es solo un inconveniente.

Obtenga crosstool-NG y configúrelo:

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

La única opción obligatoria que puedo ver es hacer que coincida con la versión del kernel de su host para usar los encabezados correctos del kernel. Encuentre la versión del kernel de su host con:

uname -a

que me muestra:

4.15.0-34-generic

así que menuconfigyo hago:

  • Operating System
    • Version of linux

entonces selecciono:

4.14.71

que es la primera versión igual o anterior. Tiene que ser más antiguo ya que el kernel es compatible con versiones anteriores.

Ahora puedes construir con:

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

y ahora espere entre treinta minutos y dos horas para la compilación.

Configuración 2: configuraciones opcionales

El .configque generamos con ./ct-ng x86_64-unknown-linux-gnutiene:

CT_GLIBC_V_2_27=y

Para cambiar eso, menuconfighaz lo siguiente:

  • C-library
  • Version of glibc

guarde el .configy continúe con la compilación.

O, si desea usar su propia fuente glibc, por ejemplo, para usar glibc desde el último git, proceda así :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: establecido en verdadero
  • C-library
    • Source of glibc
      • Custom location: decir que sí
      • Custom location
        • Custom source location: apunte a un directorio que contenga su fuente glibc

donde glibc se clonó como:

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

Configuración 2: pruébelo

Una vez que haya construido la cadena de herramientas que desea, pruébela 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

Todo parece funcionar como en la Configuración 1, excepto que ahora se usaron los objetos de tiempo de ejecución correctos:

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

Configuración 2: intento fallido de recompilación de glibc eficiente

No parece posible con crosstool-NG, como se explica a continuación.

Si solo reconstruye;

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

luego, se tienen en cuenta los cambios realizados en la ubicación de la fuente glibc personalizada, pero se construye todo desde cero, lo que lo hace inutilizable para el desarrollo iterativo.

Si lo hacemos:

./ct-ng list-steps

ofrece una buena descripción general de los pasos de compilación:

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.

por lo tanto, vemos que hay pasos de glibc entrelazados con varios pasos de GCC, el más notable libc_start_filesviene antes cc_core_pass_2, que es probablemente el paso más caro junto con cc_core_pass_1.

Para construir un solo paso, primero debe configurar la .configopción "Guardar pasos intermedios" en la compilación inicial:

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

y luego puedes probar:

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

pero desafortunadamente, el +requerido como se menciona en: https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

Sin embargo, tenga en cuenta que reiniciar en un paso intermedio restablece el directorio de instalación al estado que tenía durante ese paso. Es decir, tendrá una libc reconstruida, pero ningún compilador final construido con esta libc (y, por lo tanto, tampoco bibliotecas de compilación como libstdc ++).

y básicamente todavía hace que la reconstrucción sea demasiado lenta para que sea factible para el desarrollo, y no veo cómo superar esto sin parchear crosstool-NG.

Además, a partir del libcpaso no parecía volver a copiar la fuente Custom source location, lo que hacía que este método fuera inutilizable.

Bono: stdlibc ++

Una ventaja si también está interesado en la biblioteca estándar de C ++: ¿Cómo editar y reconstruir la fuente de la biblioteca estándar de GCC libstdc ++ C ++?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
musl-libces otra opción en lo que respecta al tiempo de ejecución de C.
0xC0000022L
0

En mi opinión, la solución más perezosa (especialmente si no confía en las últimas funciones de C / C ++ de vanguardia o las últimas funciones del compilador) aún no se mencionó, así que aquí está:

Simplemente desarrolle el sistema con el GLIBC más antiguo que aún desea admitir.

En realidad, esto es bastante fácil de hacer hoy en día con tecnologías como chroot, KVM / Virtualbox o Docker, incluso si realmente no desea usar una distribución tan antigua directamente en cualquier PC. En detalle, para hacer un binario portátil máximo de su software, le recomiendo seguir estos pasos:

  1. Simplemente elija su veneno de sandbox / virtualización / ... lo que sea, y úselo para obtener un Ubuntu LTS virtual más antiguo y compile con el gcc / g ++ que tiene allí de forma predeterminada. Eso limita automáticamente su GLIBC al disponible en ese entorno.

  2. Evite depender de bibliotecas externas fuera de las fundamentales: por ejemplo, debe vincular dinámicamente elementos del sistema a nivel del suelo como glibc, libGL, libxcb / X11 / wayland things, libasound / libpulseaudio, posiblemente GTK + si lo usa, pero de lo contrario, preferiblemente vincular estáticamente externo libs / envíelos si puede. Especialmente en su mayoría, las bibliotecas autónomas como cargadores de imágenes, decodificadores multimedia, etc. pueden causar menos roturas en otras distribuciones (la rotura puede ser causada, por ejemplo, si solo está presente en alguna parte en una versión principal diferente) si las envía estáticamente.

Con ese enfoque, obtiene un binario compatible con GLIBC antiguo sin ningún ajuste manual de símbolos, sin hacer un binario completamente estático (que puede romperse para programas más complejos porque glibc lo odia y puede causarle problemas de licencia), y sin configurar crear cualquier cadena de herramientas personalizada, cualquier copia personalizada de glibc o lo que sea.

ET
fuente