Vinculación con una versión de símbolo anterior en un archivo .so

12 minutos de lectura

avatar de usuario
PlasmaHH

Al usar gcc y ld en x86_64 linux, necesito vincularme con una versión más nueva de una biblioteca (glibc 2.14), pero el ejecutable debe ejecutarse en un sistema con una versión anterior (2.5). Dado que el único símbolo incompatible es memcpy (necesita memcpy@GLIBC_2.2.5 pero la biblioteca proporciona memcpy@GLIBC_2.14), me gustaría decirle al enlazador que en lugar de tomar la versión predeterminada para memcpy, debería tomar una versión anterior que especifique .

Encontré una forma bastante complicada de hacerlo: simplemente especifique una copia del archivo .so antiguo en la línea de comando del enlazador. Esto funciona bien, pero no me gusta la idea de tener varios archivos .so (solo podría hacerlo funcionar especificando todas las bibliotecas antiguas a las que me vinculo que también tienen referencias a memcpy) registradas en el svn y necesarias para mi sistema de compilación .

Así que estoy buscando una manera de decirle al enlazador que tome el símbolo de la versión anterior.

Las alternativas que no funcionan (bien) para mí son:

  • Usando asm .symver (como se ve en Archivo web del blog de Trevor Pounds) ya que esto requeriría que me asegure de que el symver esté antes de todo el código que usa memcpy, lo cual sería muy difícil (base de código compleja con código de terceros)
  • Mantener un entorno de construcción con las bibliotecas antiguas; simplemente porque quiero desarrollar en mi sistema de escritorio y sería imposible sincronizar cosas en nuestra red.

Al pensar en todos los trabajos que hace un enlazador, no parece algo difícil de implementar, después de todo, también tiene un código para descubrir la versión predeterminada de un símbolo.

Cualquier otra idea que tenga el mismo nivel de complejidad que una línea de comando de enlazador simple (como crear un script de enlazador simple, etc.) también es bienvenida, siempre que no sean trucos extraños como editar el binario resultante…

editar:
Para conservar esto para los futuros lectores, además de las siguientes ideas, encontré la opción --wrap al enlazador, que también puede ser útil a veces.

  • Ya sabes, memcpy( ) no ha cambiado en treinta años. Es posible que desee mencionar por qué necesita esto. (Me disculpo por preguntar por qué; odio cuando la gente hace eso. Pero debe tener una razón real e imperativa que podría ser crucial saber, dado que memcpy() ha sido tan estable durante tanto tiempo). ¡Gracias!

    – Pete Wilson

    11 de enero de 2012 a las 17:05

  • @PeteWilson: simplemente porque cuando compilo mi programa con glibc 2.14, no se ejecutará en un sistema con un glibc más antiguo, ya que no proporciona el símbolo versionado memcpy@GLIBC_2.14

    – PlasmaHH

    11 de enero de 2012 a las 17:07

  • @PeteWilson Mi respuesta vincula un informe de error que explica el problema de memcpy, en términos de lo que realmente cambió: glibc realizó un cambio que rompe el código que se basaba en el comportamiento indocumentado (técnicamente indefinido en casos superpuestos) “siempre iterar de izquierda a derecha” de Unix tradicional Implementaciones memcpy. Sin embargo, más relevante para el problema aquí es el hecho de que la versión anterior es obviamente la única proporcionada en versiones anteriores de glibc que tiene que soportar.

    – Aleatorio832

    11 de enero de 2012 a las 17:16


  • Ver también: stackoverflow.com/a/39537664/1546337

    – estofado

    16/09/2016 a las 18:11

  • Esto dice plausible: gcc.gnu.org/ml/gcc-help/2008-11/msg00303.html

    – Johannes Schaub – litb

    14 de enero de 2018 a las 20:07

Encontré la siguiente solución de trabajo. Primero crea el archivo memcpy.c:

#include <string.h>

/* some systems do not have newest memcpy@@GLIBC_2.14 - stay with old good one */
asm (".symver memcpy, memcpy@GLIBC_2.2.5");

void *__wrap_memcpy(void *dest, const void *src, size_t n)
{
    return memcpy(dest, src, n);
}

No se necesitan CFLAGS adicionales para compilar este archivo. Luego vincule su programa con -Wl,–wrap=memcpy.

  • Esto no funcionó para mí por alguna razón. Todavía busca el viejo memcpy

    – hookenz

    27 de enero de 2014 a las 1:45

  • La respuesta de Ortwin Angermeier no funcionó para mí, pero para mi sorpresa, funcionó perfectamente y la dependencia de glibc 2.14 se eliminó como un mal hábito. ¡Gracias!

    – Kaa

    4 de marzo de 2017 a las 2:37

  • Esto funciona porque la interfaz para memcpy no cambia. En el caso general, no solo necesitamos los símbolos antiguos, sino también los archivos de encabezado antiguos. Al igual que los casos en los que los miembros de la estructura cambian, la versión anterior de la función espera una estructura anterior.

    – Kaz

    2 de diciembre de 2018 a las 20:16

  • Esto no funciona si está compilando en una arquitectura que no se construyó en 2002 cuando se agregó x86-64 por primera vez; obtendrá un error que indica que los símbolos con versión solicitados no están disponibles. necesitas algo como #if defined(__x86_64__) && !defined(__ILP32__) && defined(__GNU_LIBRARY__) para protegerse contra esto (__ILP32__ es para ITB x32 que se introdujo en 2012).

    – Cosechador verde

    6 de julio de 2019 a las 21:49


avatar de usuario
Aleatorio832

Simplemente vincule memcpy estáticamente: extraiga memcpy.o de libc.a ar x /path/to/libc.a memcpy.o (cualquiera que sea la versión, memcpy es prácticamente una función independiente) e inclúyalo en su enlace final. Tenga en cuenta que la vinculación estática puede complicar los problemas de licencia si su proyecto se distribuye al público y no es de código abierto.

Alternativamente, podría simplemente implementar memcpy usted mismo, aunque es probable que la versión ensamblada ajustada a mano en glibc sea más eficiente

Tenga en cuenta que memcpy@GLIBC_2.2.5 está asignado a memmove (versiones antiguas de memmove se copiaban consistentemente en una dirección predecible, lo que conducía a veces a que se usara incorrectamente cuando se debería haber usado memmove), y esta es la única razón para el cambio de versión: simplemente podría reemplazar memcpy con memmove en su código para este caso específico.

O podría ir a la vinculación estática, o podría asegurarse de que todos los sistemas en su red tengan la misma o mejor versión que su máquina de compilación.

  • Discutiré la idea de proporcionar un memcpy.o con mis compañeros de trabajo mañana, pero la ventaja de poder decirle al enlazador la versión probablemente sería no tener que hacer lo mismo para cuando en versiones posteriores cambien otras cosas. En cuanto a la segunda parte, como mencioné, el código base es complejo y contiene código de terceros, por lo que, lamentablemente, reemplazar memmove por memcpy no es viable (de hecho, alrededor del 95 % de las llamadas de memcpy provienen de código de terceros)

    – PlasmaHH

    11 de enero de 2012 a las 17:10

  • Bueno, la mejor solución a largo plazo es asegurarse de que la máquina de construcción (es decir, su máquina de escritorio) tenga el mínimo común denominador de la versión de la biblioteca, ya sea manteniendo un entorno de construcción separado o actualizando a todos los demás.

    – Aleatorio832

    11 de enero de 2012 a las 17:14

  • +1 por mencionar memmove(); En mi opinión, no hay una buena razón para usar memcpy() con su estúpida restricción sobre cadenas superpuestas.

    – Pete Wilson

    11 de enero de 2012 a las 17:14

  • @Random832: Esta es la lucha común entre administración y desarrollo. Quiero estar en versiones recientes y no puedo trabajar con las cosas antiguas que proporciona el sistema en el servidor, y quieren mantenerse estables durante una década:/

    – PlasmaHH

    11 de enero de 2012 a las 17:25

  • Solo debe reemplazar memcpy() con memmove() en x86_64, no en otras arquitecturas que no hayan versionado memcpy() y, por lo tanto, no superen la dependencia. No sacrifique el rendimiento innecesariamente. (y asegúrese de probar con memcpy())

    – usuario72421

    23 de enero de 2013 a las 6:28

Tuve un problema similar. Una biblioteca de terceros que usamos necesita la antigua memcpy@GLIBC_2.2.5. Mi solución es un enfoque extendido publicado por @anight.

También deformo el memcpy comando, pero tuve que usar un enfoque ligeramente diferente, ya que la solución publicada por @anight no funcionó para mí.

memcpy_wrap.c:

#include <stddef.h>
#include <string.h>

asm (".symver wrap_memcpy, memcpy@GLIBC_2.2.5");
void *wrap_memcpy(void *dest, const void *src, size_t n) {
  return memcpy(dest, src, n);
}

memcpy_wrap.mapa:

GLIBC_2.2.5 {
   memcpy;
};

Construye el envoltorio:

gcc -c memcpy_wrap.c -o memcpy_wrap.o

Ahora, finalmente, al vincular el programa, agregue

  • -Wl,--version-script memcpy_wrap.map
  • memcpy_wrap.o

para que termines con algo como:

g++ <some flags> -Wl,--version-script memcpy_wrap.map <some .o files> memcpy_wrap.o <some libs>

avatar de usuario
avarvit

Tuve un problema similar. Al intentar instalar algunos componentes de Oracle en RHEL 7.1, obtuve esto:

$ gcc -o /some/oracle/bin/foo .... -L/some/oracle/lib ... 
/some/oracle/lib/libfoo.so: undefined reference to `memcpy@GLIBC_2.14'

Parece que (mi) glibc de RHEL solo define memcpy@GLIBC_2.2.5:

$ readelf -Ws /usr/lib/x86_64-redhat-linux6E/lib64/libc_real.so | fgrep memcpy@
   367: 000000000001bfe0    16 FUNC    GLOBAL DEFAULT    8 memcpy@@GLIBC_2.2.5
  1166: 0000000000019250    16 FUNC    WEAK   DEFAULT    8 wmemcpy@@GLIBC_2.2.5

Entonces, logré solucionar esto creando primero un archivo memcpy.c sin envolver, de la siguiente manera:

#include <string.h>
asm (".symver old_memcpy, memcpy@GLIBC_2.2.5");       // hook old_memcpy as memcpy@2.2.5
void *old_memcpy(void *, const void *, size_t );
void *memcpy(void *dest, const void *src, size_t n)   // then export memcpy
{
    return old_memcpy(dest, src, n);
}

y un archivo memcpy.map que exporta nuestro memcpy como memcpy@GLIBC_2.14:

GLIBC_2.14 {
   memcpy;
};

Luego compilé mi propio memcpy.c en una biblioteca compartida como esta:

$ gcc -shared -fPIC -c memcpy.c
$ gcc -shared -fPIC -Wl,--version-script memcpy.map -o libmemcpy-2.14.so memcpy.o -lc

moví libmemcpy-2.14.so a /some/oracle/lib (señalado por argumentos -L en mi vinculación), y lo vinculé nuevamente mediante

$ gcc -o /some/oracle/bin/foo .... -L/some/oracle/lib ... /some/oracle/lib/libmemcpy-2.14.so -lfoo ...

(que compiló sin errores) y lo verificó por:

$ ldd /some/oracle/bin/foo
    linux-vdso.so.1 =>  (0x00007fff9f3fe000)
    /some/oracle/lib/libmemcpy-2.14.so (0x00007f963a63e000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007f963a428000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f963a20c000)
    librt.so.1 => /lib64/librt.so.1 (0x00007f963a003000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f9639c42000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f963aa5b000)

Esto funcionó para mí. Espero que lo haga por ti también.

Claramente llegué un poco tarde a responder a esto, pero recientemente actualicé (más razones para nunca actualizar) mi sistema operativo Linux a XUbuntu 14.04 que vino con la nueva libc. Compilo una biblioteca compartida en mi máquina que utilizan los clientes que, por razones legítimas, no han actualizado su entorno desde 10.04. La biblioteca compartida que compilé ya no se cargaba en su entorno porque gcc dependía de memcpy glibc v. 2.14 (o superior). Dejemos de lado la locura de esto. La solución en todo mi proyecto fue triple:

  1. agregado a mis cflags de gcc: -include glibc_version_nightmare.h
  2. creó el glibc_version_nightmare.h
  3. creó un script perl para verificar los símbolos en el .so

glibc_version_pesadilla.h:

#if defined(__GNUC__) && defined(__LP64__)  /* only under 64 bit gcc */
#include <features.h>       /* for glibc version */
#if defined(__GLIBC__) && (__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 14)
/* force mempcy to be from earlier compatible system */
__asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
#endif
#undef _FEATURES_H      /* so gets reloaded if necessary */
#endif

fragmento de secuencia de comandos perl:

...
open SYMS, "nm $flags $libname |";

my $status = 0;

sub complain {
my ($symbol, $verstr) = @_;
print STDERR "ERROR: $libname $symbol requires $verstr\n";
$status = 1;
}

while (<SYMS>) {
next unless /\@\@GLIBC/;
chomp;
my ($symbol, $verstr) = (m/^\s+.\s(.*)\@\@GLIBC_(.*)/);
die "unable to parse version from $libname in $_\n"
    unless $verstr;
my @ver = split(/\./, $verstr);
complain $symbol, $verstr
    if ($ver[0] > 2 || $ver[1] > 10);
}
close SYMS;

exit $status;

avatar de usuario
Gilles Olivier Vollant

Esta solución parece no ser compatible con la opción de compilación -flto.

Mi solución es llamar a memmove. memove hace exactamente el mismo trabajo que memcpy. La única diferencia es cuando src y dest zone se superponen, memmove es seguro y memcpy es impredecible. Así que siempre podemos llamar a memmove en lugar de memcpy de forma segura.

#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif

    void *__wrap_memcpy(void *dest, const void *src, size_t n)
    {
        return memmove(dest, src, n);
    }

#ifdef __cplusplus
}
#endif

avatar de usuario
Ciro Santilli Путлер Капут 六四事

Ejemplo autocontenido ejecutable mínimo

GitHub ascendente.

C Principal

#include <assert.h>
#include <stdlib.h>

#include "a.h"

#if defined(V1)
__asm__(".symver a,a@LIBA_1");
#elif defined(V2)
__asm__(".symver a,a@LIBA_2");
#endif

int main(void) {
#if defined(V1)
    assert(a() == 1);
#else
    assert(a() == 2);
#endif
    return EXIT_SUCCESS;
}

C.A

#include "a.h"

__asm__(".symver a1,a@LIBA_1");
int a1(void) {
    return 1;
}

/* @@ means "default version". */
__asm__(".symver a2,a@@LIBA_2");
int a2(void) {
    return 2;
}

ah

#ifndef A_H
#define A_H

int a(void);

#endif

un mapa

LIBA_1{
    global:
    a;
    local:
    *;
};

LIBA_2{
    global:
    a;
    local:
    *;
};

Makefile

CC := gcc -pedantic-errors -std=c89 -Wall -Wextra

.PHONY: all clean run

all: main.out main1.out main2.out

run: all
    LD_LIBRARY_PATH=. ./main.out
    LD_LIBRARY_PATH=. ./main1.out
    LD_LIBRARY_PATH=. ./main2.out

main.out: main.c libcirosantilli_a.so
    $(CC) -L'.' main.c -o '$@' -lcirosantilli_a

main1.out: main.c libcirosantilli_a.so
    $(CC) -DV1 -L'.' main.c -o '$@' -lcirosantilli_a

main2.out: main.c libcirosantilli_a.so
    $(CC) -DV2 -L'.' main.c -o '$@' -lcirosantilli_a

a.o: a.c
    $(CC) -fPIC -c '$<' -o '$@'

libcirosantilli_a.so: a.o
    $(CC) -Wl,--version-script,a.map -L'.' -shared a.o -o '$@'

libcirosantilli_a.o: a.c
    $(CC) -fPIC -c '$<' -o '$@'

clean:
    rm -rf *.o *.a *.so *.out

Probado en Ubuntu 16.04.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad