web-dev-qa-db-ja.com

.soファイル内の古いシンボルバージョンに対するリンク

X86_64 Linuxでgccとldを使用すると、ライブラリの新しいバージョン(glibc 2.14)に対してリンクする必要がありますが、実行可能ファイルは古いバージョン(2.5)のシステムで実行する必要があります。互換性のないシンボルはmemcpy(memcpy@GLIBC_2.2.5が必要ですが、memcpy @ GLIBC_2.14を提供するライブラリが必要)だけなので、memcpyのデフォルトバージョンを使用する代わりに、指定した古いバージョンを使用する必要があることをリンカーに伝えたい。

リンカのコマンドラインで古い.soファイルのコピーを指定するだけです。これは正常に機能しますが、複数の.soファイル(リンクするすべての古いライブラリを指定し、memcpyへの参照も指定することによってのみ機能する)がsvnにチェックインされ、ビルドシステムで必要になるという考えが好きではありません。

そこで、リンカに古いバージョンのシンボルを取得するように指示する方法を探しています。

私にとってうまくいかない代替案は次のとおりです:

  • Asm .symverを使用する( Trevor PoundsのブログのWebアーカイブ を参照)。これにより、memcpyを使用しているすべてのコードの前にsymverが存在することを確認する必要があります。サードパーティコードを含むコードベース)
  • 古いライブラリを使用してビルド環境を維持します。単純にデスクトップシステムで開発したいので、ネットワーク内でデータを同期するのはピタです。

リンカが行うすべてのジョブについて考えると、シンボルのデフォルトバージョンを把握するためのコードもあるため、それを実装するのは難しいことではありません。

結果のバイナリを編集するような奇妙なハックでない限り、単純なリンカーコマンドラインと同じ複雑さのレベルにある他のアイデア(単純なリンカースクリプトの作成など)も歓迎します...

edit:これを将来の読者のために保存するために、以下のアイデアに加えて、オプション--wrapリンカへ。これも時々役立つかもしれません。

41
PlasmaHH

Memcpyを静的にリンクするだけで、memcpy.oをlibc.aから引き出しますar x /path/to/libc.a memcpy.o(どのバージョンでも-memcpyはほとんどスタンドアロンの機能です)、最終リンクに含めます。プロジェクトがオープンソースではなく一般に配布されている場合、静的リンクはライセンスの問題を複雑にする可能性があることに注意してください。

または、membcyを自分で簡単に実装することもできますが、glibcの手動で調整されたアセンブリバージョンの方が効率的です

memcpy@GLIBC_2.2.5 はmemmoveにマップされることに注意してください(memcpyの古いバージョンは一貫して予測可能な方向にコピーされ、memmoveを使用する必要があるときに誤用されることがありました)。バージョンバンプの理由-この特定のケースでは、コード内のmemcpyをmemmoveに置き換えるだけです。

または、静的リンクに移動するか、ネットワーク上のすべてのシステムがビルドマシンと同じか、より良いバージョンであることを確認できます。

18
Random832

次の実用的なソリューションを見つけました。最初にファイル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);
}

このファイルをコンパイルするために追加のCFLAGSは必要ありません。次に、プログラムを-Wl、-wrap = memcpyにリンクします。

45
anight

同様の問題がありました。使用するサードパーティライブラリには、古いmemcpy@GLIBC_2.2.5。私の解決策は、@ anightが投稿した拡張アプローチです。

memcpyコマンドもワープしますが、@ anightが投稿した解決策が役に立たなかったため、少し異なるアプローチを使用する必要がありました。

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

ラッパーのビルド:

gcc -c memcpy_wrap.c -o memcpy_wrap.o

最後に、プログラムをリンクするときに追加します

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

次のような結果になります。

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

同様の問題がありました。 RHEL 7.1にいくつかのOracleコンポーネントをインストールしようとすると、次のようになりました。

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

(私の)RHELのglibcは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

そこで、最初に次のようにラップせずにmemcpy.cファイルを作成することで、この問題を回避できました。

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

memcpyをmemcpy@GLIBC_2.14としてエクスポートするmemcpy.mapファイル:

GLIBC_2.14 {
   memcpy;
};

次に、自分のmemcpy.cを次のような共有ライブラリにコンパイルしました。

$ 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を/ some/Oracle/libに移動し(リンクの-L引数で示されます)、再度リンクされます

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

(エラーなしでコンパイルされた)、次の方法で検証しました。

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

これは私のために働いた。私もそれがあなたのためになることを願っています。

5
avarvit

私は明らかにこれに対応するのが少し遅れていますが、最近、Linux OSを新しいlibcに付属するXUbuntu 14.04にアップグレードしました(アップグレードしない理由は他にもあります)。正当な理由が何であれ、環境を10.04からアップグレードしていないクライアントが使用する共有ライブラリをマシン上でコンパイルします。 gccはmemcpy glibc v。2.14(以降)に依存するため、コンパイルした共有ライブラリは環境にロードされなくなりました。これの狂気を無視しましょう。プロジェクト全体の回避策は3つありました。

  1. 私のgcc cflagsに追加:-include glibc_version_nightmare.h
  2. glibc_version_nightmare.hを作成しました
  3. .soのシンボルを検証するPerlスクリプトを作成しました

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

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.

この回避策は、-fltoコンパイルオプションと互換性がないようです。

私の解決策はmemmoveを呼び出すことです。 memoveはmemcpyとまったく同じ仕事をします。唯一の違いは、srcゾーンとdestゾーンが重複している場合、memmoveは安全で、memcpyは予測不能です。そのため、memcpyの代わりにいつでもmemmoveを安全に呼び出すことができます

#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

Symverステートメントと、おそらくmemcpyを呼び出すダミー関数を含む単純なCファイルを作成しても問題ないと思います。次に、結果のオブジェクトファイルがリンカーに渡される最初のファイルであることを確認する必要があります。

1
zvrba

Memcpy()を静的にリンクすることをお勧めします。または、memcpy()のソースを見つけて、独自のライブラリとしてコンパイルします。

1
Pete Wilson

古いld(gnuリンク)バージョンが原因の可能性があります。次の簡単な問題の場合:

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

Ld 2.19.1を使用すると、memsetがmemset @@ GLIBC_2.0に再配置され、クラッシュが発生します。 2.25にアップグレードすると、memset @ pltになり、クラッシュが解決しました。

0
jeanerpp

最小限の実行可能な自己完結型の例

GitHubアップストリーム

main.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;
}

交流

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

ああ

#ifndef A_H
#define A_H

int a(void);

#endif

地図

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

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

メイクファイル

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

Ubuntu 16.04でテスト済み。