Связывание со старой версией символа в файле.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 (или выше). Давайте оставим в стороне безумие этого. Обходной путь по всему моему проекту был трехкратным:
- добавлено в мои gcc cflags: -include glibc_version_nightmare.h
- создал glibc_version_nightmare.h
- создал скрипт на 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;
Минимальный управляемый автономный пример
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, библиотеки должны работать. Вот что мы сделали:
Включите файлы glibc 2.35 libmvec.so.1 и libm.so.6 в отдельную подпапку. Они содержат необходимые векторизованные математические функции. В примере приложения «hello codec» мы ссылаемся на них в цели ссылки в зависимости от того, какие версии дистрибутива, gcc и glibc найдены Makefile. Более или менее, для чего-либо с glibc v2.35 или выше ссылаются на высокопроизводительные библиотеки, в противном случае ссылаются на более медленные библиотеки.
Чтобы справиться с отсутствующими символами — темой этой ветки — мы использовали модификацию решения 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 */
- Затем -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, и сбой решен.