Связывание со старой версией символа в файле.so

Используя gcc и ld в x86_64 linux, мне нужно создать ссылку на более новую версию библиотеки (glibc 2.14), но исполняемый файл должен работать в системе с более старой версией (2.5). Так как единственный несовместимый символ - это memcpy (требуется memcpy@GLIBC_2.2.5, но есть библиотека, предоставляющая memcpy@GLIBC_2.14), я бы хотел сказать компоновщику, что вместо использования версии по умолчанию для memcpy, он должен использовать старую версию, указанную мной,

Я нашел довольно неуклюжий способ сделать это: просто указать копию старого.so файла в командной строке компоновщика. Это прекрасно работает, но мне не нравится идея иметь несколько файлов.so (я мог только заставить это работать, указав все старые библиотеки, на которые я ссылаюсь, также есть ссылки на memcpy), проверенные в svn и необходимые моей системе сборки,

Поэтому я ищу способ сказать компоновщику взять старый версионный символ.

Альтернативы, которые не работают (хорошо) для меня:

  • Использование asm.symver (как видно из веб-архива блога Trevor Pounds), так как это потребовало бы от меня убедиться, что символ находится перед всем кодом, использующим memcpy, что будет очень сложно (сложная кодовая база с кодом сторонних разработчиков)
  • Поддержание среды сборки со старыми библиотеками; просто потому, что я хочу развиваться в своей настольной системе, и было бы неплохо синхронизировать вещи в нашей сети.

Размышляя обо всех работах, которые выполняет компоновщик, это не кажется трудным для выполнения, ведь у него есть некоторый код, чтобы выяснить версию символа по умолчанию.

Любые другие идеи, которые находятся на том же уровне сложности, что и простая командная строка компоновщика (например, создание простого сценария компоновщика и т. Д.), Также приветствуются, если они не являются странными взломами, как редактирование полученного двоичного файла...

редактировать: чтобы сохранить это для будущих читателей, в дополнение к нижеприведенным идеям я нашел вариант --wrap компоновщику, что иногда может быть полезно.

12 ответов

Решение

Просто статически свяжите memcpy - вытащите memcpy.o из libc.a ar x /path/to/libc.a memcpy.o (независимо от версии - memcpy является в значительной степени автономной функцией) и включите ее в свою последнюю ссылку. Обратите внимание, что статическое связывание может усложнить вопросы лицензирования, если ваш проект распространяется для общественности, а не с открытым исходным кодом.

В качестве альтернативы, вы можете просто реализовать memcpy самостоятельно, хотя версия сборки с ручной настройкой в ​​glibc, вероятно, будет более эффективной

Обратите внимание, что memcpy@GLIBC_2.2.5 сопоставлен с memmove (старые версии memcpy последовательно копируются в предсказуемом направлении, что иногда приводило к неправильному использованию при использовании memmove), и это единственная причина для повышения версии - вы может просто заменить memcpy на memmove в вашем коде для этого конкретного случая.

Или вы можете перейти к статическому линкованию, или вы можете убедиться, что все системы в вашей сети имеют ту же или лучшую версию, чем ваша сборочная машина.

Я нашел следующее рабочее решение. Сначала создайте файл 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.

У меня была похожая проблема. Сторонней библиотеке, которую мы используем, нужна старая 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>

У меня была похожая проблема. Пытаясь установить некоторые компоненты Oracle на RHEL 7.1, я получил это:

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

Кажется, что (мой) glibc RHEL определяет только 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 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);
}

и файл memcpy.map, который экспортирует наш memcpy как memcpy@GLIBC_2.14:

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)

Это сработало для меня. Я надеюсь, что это делает это и для вас.

Я явно немного опаздываю, отвечая на это, но недавно я обновил (больше причин никогда не обновлять) свою ОС Linux до XUbuntu 14.04, которая поставлялась с новым libc. Я собираю на своем компьютере разделяемую библиотеку, которая используется клиентами, которые по каким-либо законным причинам не обновили свою среду с 10.04. Общая библиотека, которую я скомпилировал, больше не загружается в их среде, потому что gcc устанавливает зависимость от memcpy glibc v. 2.14 (или выше). Давайте оставим в стороне безумие этого. Обходной путь по всему моему проекту был трехкратным:

  1. добавлено в мои gcc cflags: -include glibc_version_nightmare.h
  2. создал glibc_version_nightmare.h
  3. создал скрипт на Perl для проверки символов в.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

фрагмент 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;

Минимальный управляемый автономный пример

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

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

Проверено на Ubuntu 16.04.

Для nim-lang я разработал решение, которое нашел с помощью компилятора C. --include= отметьте следующим образом:

Создайте файл symver.h с:

__asm__(".symver fcntl,fcntl@GLIBC_2.4");

Создайте свою программу с nim c ---passC:--include=symver.h

Что касается меня, я тоже занимаюсь кросс-компиляцией. Я компилирую сnim c --cpu:arm --os:linux --passC:--include=symver.h ... и я могу получить версии символов, используя arm-linux-gnueabihf-objdump -T ../arm-libc.so.6 | grep fcntl

Мне пришлось удалить ~/.cache/nimв какой-то момент. И вроде работает.

Этот обходной путь кажется несовместимым с опцией -flto compile.

Мое решение - вызов memmove. memove выполняет ту же работу, что и memcpy. Разница лишь в том, что когда зоны src и dest перекрываются, memmove безопасен, а memcpy непредсказуем. Таким образом, мы можем всегда безопасно вызывать memmove вместо 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

Я думаю, что вы можете сделать простой C-файл, содержащий оператор symver и, возможно, фиктивную функцию, вызывающую memcpy. Тогда вам просто нужно убедиться, что результирующий объектный файл является первым файлом, переданным компоновщику.

У нас была аналогичная проблема, но вместо одного старого символа GLIBC мы должны предоставить в наших библиотеках .so сочетание более новых с необходимой функциональностью и более старых, на которые наши библиотеки могут ссылаться, но недоступны. Эта ситуация возникает из-за того, что мы поставляем клиентам высокопроизводительные библиотеки кодеков с векторизованными математическими функциями и не можем предъявлять требования к используемой версии дистрибутива ОС, gcc или glibc. Пока их машина имеет соответствующие расширения SSE и AVX, библиотеки должны работать. Вот что мы сделали:

  1. Включите файлы glibc 2.35 libmvec.so.1 и libm.so.6 в отдельную подпапку. Они содержат необходимые векторизованные математические функции. В примере приложения «hello codec» мы ссылаемся на них в цели ссылки в зависимости от того, какие версии дистрибутива, gcc и glibc найдены Makefile. Более или менее, для чего-либо с glibc v2.35 или выше ссылаются на высокопроизводительные библиотеки, в противном случае ссылаются на более медленные библиотеки.

  2. Чтобы справиться с отсутствующими символами — темой этой ветки — мы использовали модификацию решения user540286, основанную, в свою очередь, на решении user1149316, но без использования параметра -Wl,--wrap=xxx.

Файл .map выглядит так:

      GLIBC_2.35 {
   hypot;
   :
   : (more function symbols as needed)
};

GLIBC_2.32 {
   exp10;
   :
   :  (more function symbols as needed)
};

 :
 : (more version nodes as needed)

и в "stublib".so у нас есть:

      #define _GNU_SOURCE
#include <math.h>

asm(".symver hypot_235, hypot@GLIBC_2.35");
asm(".symver exp10_232, exp10f@GLIBC_2.32");
/* ... more as needed */

double hypot_235(double x, double y) { return hypot(x, y); }
double exp10_232(double x) { return exp10(x); }
/* ... more as needed */
  1. Затем -lstublib.so включается в сборку приложения как последний элемент ссылки , даже после -lm.

Этот ответ и этот также предлагают подсказки, но они не обрабатывают общий случай .so, достаточно гибкого для использования в самых разных системах.

Я предлагаю вам либо статически связать memcpy(); или найдите источник memcpy() и скомпилируйте его как свою собственную библиотеку.

Это может быть вызвано старой версией ld (gnu link). Для следующей простой задачи:

#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, и сбой решен.

Другие вопросы по тегам