wake-up-neo.com

Verknüpfung mit älteren Symbolversionen in einer .so-Datei

Bei Verwendung von gcc und ld unter x86_64 linux muss eine Verknüpfung mit einer neueren Version einer Bibliothek (glibc 2.14) hergestellt werden, die ausführbare Datei muss jedoch auf einem System mit einer älteren Version (2.5) ausgeführt werden. Da das einzige inkompatible Symbol memcpy ist (es ist [email protected]_2.2.5 erforderlich, aber die Bibliothek [email protected]_2.14), möchte ich dem Linker mitteilen, dass anstelle der Standardversion für memcpy eine alte Version verwendet werden sollte, die ich angegeben habe .

Ich habe einen ziemlich schwierigen Weg gefunden, es zu tun: Geben Sie einfach eine Kopie der alten .so-Datei in der Linker-Befehlszeile an. Das funktioniert gut, aber ich mag nicht die Idee, mehrere .so-Dateien haben zu lassen (ich könnte es nur schaffen, indem ich alle alten Bibliotheken angibt, auf die ich verweise, die auch Verweise auf Memcpy enthalten), die in das SVN eingecheckt werden und von meinem Build-System benötigt werden .

Ich suche also nach einer Möglichkeit, dem Linker das alte versionierte Symbol zu geben.

Alternativen, die (gut) für mich nicht funktionieren, sind:

  • Verwenden Sie asm .symver (wie im Webarchiv des Blogs von Trevor Pounds zu sehen ist), da ich vor dem gesamten Code, der memcpy verwendet, sicher ist, dass der Symver vorkommt, was sehr schwer ist (komplexe Codebase mit 3rd.) Party-Code)
  • Pflege einer Build-Umgebung mit den alten Bibliotheken; Einfach, weil ich auf meinem Desktopsystem entwickeln möchte, und es wäre eine Pita, Dinge in unserem Netzwerk zu synchronisieren.

Wenn Sie über alle Jobs nachdenken, die ein Linker ausführt, scheint es nicht schwer zu implementieren, schließlich hat er etwas Code, um auch die Standardversion eines Symbols herauszufinden.

Alle anderen Ideen, die sich auf derselben Komplexität wie eine einfache Linker-Befehlszeile befinden (z. B. das Erstellen eines einfachen Linker-Skripts usw.), sind ebenfalls willkommen, sofern es sich nicht um seltsame Hacks wie das Bearbeiten der resultierenden Binärdatei handelt.

edit: Um dies für die zukünftigen Leser zu erhalten, habe ich zusätzlich zu den folgenden Ideen die Option --wrap für den Linker gefunden, die manchmal auch nützlich sein kann.

40
PlasmaHH

Verknüpfen Sie einfach memcpy statisch - ziehen Sie memcpy.o aus libc.a ar x /path/to/libc.a memcpy.o (egal welche Version - memcpy ist eigentlich eine eigenständige Funktion) und fügen Sie es in Ihren endgültigen Link ein. Beachten Sie, dass die statische Verknüpfung Lizenzprobleme verursachen kann, wenn Ihr Projekt an die Öffentlichkeit und nicht an Open Source verteilt wird.

Alternativ können Sie memcpy einfach selbst implementieren, obwohl die von Hand abgestimmte Assembly-Version in glibc wahrscheinlich effizienter ist

Beachten Sie, dass [email protected]_2.2.5 auf memmove abgebildet ist (alte Versionen von memcpy wurden regelmäßig in eine vorhersagbare Richtung kopiert, was dazu führte, dass sie manchmal missbraucht wurde, wenn memmove verwendet werden sollte) version bump - Sie können memcpy für diesen speziellen Fall einfach durch memmove im Code ersetzen.

Oder Sie gehen zu einer statischen Verknüpfung, oder Sie können sicherstellen, dass alle Systeme in Ihrem Netzwerk dieselbe oder eine bessere Version haben als Ihr Build-Computer.

17
Random832

Ich habe die folgende funktionierende Lösung gefunden. Erstellen Sie zuerst die Datei memcpy.c:

#include <string.h>

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

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

Zum Kompilieren dieser Datei waren keine zusätzlichen CFLAGS erforderlich. Dann verknüpfen Sie Ihr Programm mit -Wl, - wrap = memcpy .

43
anight

Ich hatte ein ähnliches Problem. Eine von uns verwendete Drittanbieter-Bibliothek benötigt den alten [email protected]_2.2.5. Meine Lösung ist ein erweiterter Ansatz von @anight posted.

Ich warp auch den memcpy Befehl, aber ich musste einen etwas anderen Ansatz verwenden, da die @anight-Lösung für mich nicht funktionierte.

memcpy_wrap.c:

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

asm (".symver wrap_memcpy, [email protected]_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;
};

Bauen Sie den Wrapper auf:

gcc -c memcpy_wrap.c -o memcpy_wrap.o

Nun endlich beim Verlinken das Programm hinzufügen 

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

so dass Sie mit etwas wie enden:

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

Ich hatte ein ähnliches Problem. Beim Versuch, einige Oracle-Komponenten auf RHEL 7.1 zu installieren, habe ich Folgendes erhalten:

$ gcc -o /some/Oracle/bin/foo .... -L/some/Oracle/lib ... 
/some/Oracle/lib/libfoo.so: undefined reference to `[email protected]_2.14'

Es scheint, dass (mein) RHELs glibc nur [email protected]_2.2.5 definiert:

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

So gelang es mir, dies zu umgehen, indem ich zunächst eine memcpy.c-Datei ohne Umbruch wie folgt erstellte:

#include <string.h>
asm (".symver old_memcpy, [email protected]_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);
}

und eine memcpy.map-Datei, die unser memcpy als [email protected]_2.14 exportiert:

GLIBC_2.14 {
   memcpy;
};

Ich kompilierte dann mein eigenes memcpy.c in eine freigegebene Bibliothek wie folgt:

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

, libmemcpy-2.14.so in/some/Oracle/lib verschoben (von -L-Argumenten in meiner Verknüpfung gezeigt) und erneut durch verknüpft

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

(welche ohne Fehler kompiliert wurde) und verifiziert durch:

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

Das hat bei mir funktioniert. Ich hoffe es tut es auch für dich.

5
avarvit

Ich bin offensichtlich etwas spät dran, aber ich habe kürzlich mein Linux-Betriebssystem auf XUbuntu 14.04 aufgerüstet (mehr Gründe, um nie ein Upgrade durchzuführen), das mit der neuen libc geliefert wurde. Ich kompiliere auf meinem Computer eine gemeinsam genutzte Bibliothek, die von Clients verwendet wird, die aus irgendeinem legitimen Grund keine Aktualisierung ihrer Umgebung von 10.04 vorgenommen haben. Die gemeinsam genutzte Bibliothek, die ich kompiliert habe, wurde nicht mehr in ihre Umgebung geladen, da gcc eine Abhängigkeit von memcpy glibc v. 2.14 (oder höher) eingab. Lassen wir den Wahnsinn davon beiseite. Der Workaround für mein gesamtes Projekt war dreifach:

  1. zu meinen gcc cflags hinzugefügt: -include glibc_version_nightmare.h
  2. hat die glibc_version_nightmare.h erstellt
  3. ein Perl-Skript erstellt, um die Symbole in der .so zu überprüfen

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,[email protected]_2.2.5");
#endif
#undef _FEATURES_H      /* so gets reloaded if necessary */
#endif

Perl-Skriptfragment:

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

Diese Problemumgehung scheint mit der Kompilierungsoption -flto nicht kompatibel zu sein.

Meine Lösung ruft memmove auf. memove führt genau die gleichen Jobs aus wie memcpy ..__ Der einzige Unterschied besteht darin, dass sich src und dest-Zone überschneiden, memmove sicher ist und memcpy unvorhersehbar ist. So können wir memmove sicher immer statt memcpy aufrufen

#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

Ich denke, Sie können eine einfache C-Datei erstellen, die die symver-Anweisung und möglicherweise eine Dummy-Funktion enthält, die memcpy aufruft. Dann müssen Sie nur sicherstellen, dass die resultierende Objektdatei die erste Datei ist, die der Linker erhält. 

1
zvrba

Ich schlage vor, dass Sie memcpy () statisch verlinken. oder finden Sie die Quelle von memcpy () und kompilieren Sie es als Ihre eigene Bibliothek.

1
Pete Wilson

Es kann durch die alte ld (gnu link) Version ..__ verursacht werden. Für folgendes einfaches Problem:

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

Wenn ich ld 2.19.1 verwende, wird memset nach: memset @@ GLIBC_2.0 verschoben und zum Absturz führen ..__ Nach dem Upgrade auf 2.25 ist es: memset @ plt und der Absturz wurde behoben.

0
jeanerpp

Minimales lauffähiges eigenständiges Beispiel

GitHub Upstream .

haupt c

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

#include "a.h"

#if defined(V1)
__asm__(".symver a,[email protected]_1");
#Elif defined(V2)
__asm__(".symver a,[email protected]_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,[email protected]_1");
int a1(void) {
    return 1;
}

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

ah

#ifndef A_H
#define A_H

int a(void);

#endif

eine Landkarte

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 '[email protected]' -lcirosantilli_a

main1.out: main.c libcirosantilli_a.so
    $(CC) -DV1 -L'.' main.c -o '[email protected]' -lcirosantilli_a

main2.out: main.c libcirosantilli_a.so
    $(CC) -DV2 -L'.' main.c -o '[email protected]' -lcirosantilli_a

a.o: a.c
    $(CC) -fPIC -c '$<' -o '[email protected]'

libcirosantilli_a.so: a.o
    $(CC) -Wl,--version-script,a.map -L'.' -shared a.o -o '[email protected]'

libcirosantilli_a.o: a.c
    $(CC) -fPIC -c '$<' -o '[email protected]'

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

Getestet auf Ubuntu 16.04.