web-dev-qa-db-fra.com

Liaison avec une version de symbole plus ancienne dans un fichier .so

Utilisation de gcc et ld sur x86_64 linux Je dois créer un lien avec une version plus récente d’une bibliothèque (glibc 2.14), mais l’exécutable doit s’exécuter sur un système doté d’une version plus ancienne (2.5). Puisque le seul symbole incompatible est memcpy (nécessitant memcpy@GLIBC_2.2.5 mais la bibliothèque fournissant memcpy@GLIBC_2.14), je voudrais dire à l'éditeur de liens qu'au lieu de prendre la version par défaut pour memcpy, elle devrait prendre une ancienne version que je spécifie. .

J'ai trouvé un moyen assez arkward de le faire: spécifiez simplement une copie de l'ancien fichier .so sur la ligne de commande de l'éditeur de liens. Cela fonctionne bien, mais je n'aime pas l'idée de disposer de plusieurs fichiers .so (je ne pouvais que le faire fonctionner en spécifiant toutes les anciennes bibliothèques vers lesquelles j'ai établi un lien qui ont également des références à memcpy) vérifiées dans le svn et nécessaires à mon système de construction. .

Je cherche donc un moyen de dire à l'éditeur de liens de prendre l'ancien symbole versionné.

Les alternatives qui ne fonctionnent pas (bien) pour moi sont:

  • Utiliser asm .symver (comme indiqué sur Archives Web du blog de Trevor Pounds ), car cela me demanderait de vérifier que le symbole est avant tout le code qui utilise memcpy, ce qui serait très difficile (base complexe avec 3 code du parti)
  • Maintenir un environnement de construction avec les anciennes bibliothèques; tout simplement parce que je souhaite développer sur mon ordinateur de bureau et que ce serait un pita de synchroniser des éléments de notre réseau.

Quand on pense à tous les emplois d'un éditeur de liens, cela ne semble pas être une tâche difficile à améliorer, après tout, il dispose d'un code pour déterminer la version par défaut d'un symbole.

Toutes les autres idées qui ont le même niveau de complexité qu'une simple ligne de commande de l'éditeur de liens (comme créer un simple script de l'éditeur de liens, etc.) sont également les bienvenues, tant qu'elles ne sont pas des hacks bizarres telles que l'édition du fichier binaire résultant ...

edit: Pour conserver cela pour les futurs lecteurs, en plus des idées ci-dessous, j’ai trouvé l’option --wrap dans l’éditeur de liens, ce qui peut parfois être utile.

40
PlasmaHH

Liez simplement memcpy statiquement - extrayez memcpy.o de libc.a ar x /path/to/libc.a memcpy.o (quelle que soit la version - memcpy est quasiment une fonction autonome) et incluez-la dans votre dernier lien. Notez que les liens statiques peuvent compliquer les problèmes de licence si votre projet est distribué au public et non open-source.

Alternativement, vous pouvez simplement implémenter vous-même memcpy, bien que la version Assembly ajustée à la main dans glibc soit probablement plus efficace.

Notez que memcpy@GLIBC_2.2.5 est associé à memmove (les anciennes versions de memcpy étaient systématiquement copiées dans un sens prévisible, ce qui entraînait parfois son utilisation abusive alors que memmove aurait dû être utilisé), ce qui est la seule raison de la version bump - vous pouvez simplement remplacer memcpy par memmove dans votre code pour ce cas spécifique.

Vous pouvez également passer à la liaison statique ou vous assurer que tous les systèmes de votre réseau ont la même version ou une version supérieure à celle de votre machine de génération.

17
Random832

J'ai trouvé la solution de travail suivante. Commencez par créer le fichier 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);
}

Aucun CFLAGS supplémentaire nécessaire pour compiler ce fichier. Ensuite, liez votre programme avec -Wl, - wrap = memcpy .

43
anight

J'ai eu un problème similaire. Une bibliothèque tierce que nous utilisons a besoin de l’ancien memcpy@GLIBC_2.2.5. Ma solution est une approche étendue @anight publiée.

Je modifie également la commande memcpy, mais je devais utiliser une approche légèrement différente, car la solution publiée par @anight ne fonctionnait pas.

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.map:

GLIBC_2.2.5 {
   memcpy;
};

Construisez le wrapper:

gcc -c memcpy_wrap.c -o memcpy_wrap.o

Maintenant, enfin, lorsque vous liez le programme, ajoutez 

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

de sorte que vous allez vous retrouver avec quelque chose comme:

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

J'avais un problème similaire. En essayant d'installer des composants Oracle sur RHEL 7.1, j'ai eu ceci:

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

Il semble que la glibc de (ma) RHEL ne définisse que 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

J'ai donc réussi à contourner ce problème en créant d'abord un fichier memcpy.c sans le wrapper, comme suit:

#include <string.h>
asm (".symver old_memcpy, memcpy@GLIBC_2.2.5");       // hook old_memcpy as [email protected]
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);
}

et un fichier memcpy.map qui exporte notre memcpy en tant que memcpy@GLIBC_2.14:

GLIBC_2.14 {
   memcpy;
};

J'ai ensuite compilé mon propre memcpy.c dans une bibliothèque partagée comme celle-ci:

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

, déplacé libmemcpy-2.14.so dans/some/Oracle/lib (pointé par -L arguments dans ma liaison), et lié à nouveau par

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

(compilé sans erreur) et vérifié par:

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

Cela a fonctionné pour moi. J'espère que ça le fait pour vous aussi.

5
avarvit

Je suis clairement en train de réagir tard mais j’ai récemment mis à niveau (plus de raisons de ne jamais effectuer de mise à niveau) mon système d’exploitation Linux vers XUbuntu 14.04, fourni avec la nouvelle libc. Je compile sur ma machine une bibliothèque partagée utilisée par les clients qui, pour des raisons légitimes, n'ont pas mis à niveau leur environnement depuis la version 10.04. La bibliothèque partagée que j'ai compilée n'est plus chargée dans leur environnement car gcc met une dépendance sur memcpy glibc v. 2.14 (ou supérieure). Laissons de côté la folie de cela. La solution de contournement dans tout mon projet était triple:

  1. ajouté à mes cflags gcc: -include glibc_version_nightmare.h
  2. créé le glibc_version_nightmare.h
  3. créé un script Perl pour vérifier les symboles dans le .so

glibc_version_nightmare.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

Fragment de script 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;
4
Oliver K.

Cette solution de contournement ne semble pas compatible avec l'option de compilation -flto.

Ma solution appelle memmove. memove effectue exactement les mêmes tâches que memcpy . La seule différence réside dans le fait que les zones src et dest se chevauchent, que memmove est sécurisé et que memcpy est imprévisible. Nous pouvons donc toujours appeler memmove en toute sécurité à la place de memcpy

#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
1

Je pense que vous pouvez vous en sortir en créant un simple fichier C contenant l’instruction symver et peut-être une fonction factice appelant memcpy. Ensuite, vous devez simplement vous assurer que le fichier objet résultant est le premier fichier donné à l'éditeur de liens. 

1
zvrba

Je vous suggère de lier statcpy () statiquement; ou trouvez la source de memcpy () et compilez-la en tant que votre propre bibliothèque.

1
Pete Wilson

Cela peut être causé par l'ancienne version de ld (gnu link) . Pour résoudre le problème simple suivant:

#include <string.h>
#include <stdio.h>
int main(int argc,char **argv)
{
    char buf[5];
    memset(buf,0,sizeof(buf));
    printf("ok\n");
    return 0;
}

Lorsque j'utilise ld 2.19.1, memset est déplacé vers: memset @@ GLIBC_2.0 et provoque un crash . Après la mise à niveau vers la version 2.25, il s'agit de: memset @ plt, et le problème est résolu.

0
jeanerpp

Exemple minimal exécutable autonome

GitHub en amont .

principal c

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

a.c

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

a.h

#ifndef A_H
#define A_H

int a(void);

#endif

une carte

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

Testé sur Ubuntu 16.04.