Если objcopy
binutils может изменять динамические символы, а mylib.so
является динамической библиотекой ELF, мы могли бы использовать
mv mylib.so old.mylib.so
objcopy --redefine-sym ioctl=mylib_ioctl old.mylib.so mylib.so
переименовать имя символа в библиотеке с ioctl
на mylib_ioctl
, чтобы мы могли реализовать
int mylib_ioctl(int fd, int request, void *data);
в другой библиотеке или объекте, связанном с окончательными двоичными файлами.
К сожалению, эта функция не реализована (по состоянию на начало 2017 г. по крайней мере ).
Мы можем решить эту проблему с помощью уродливого хака, если имя замещающего символа будет точно такой же длины, как исходное имя. Имя символа представляет собой строку (с предшествующим и последующим нулевым байтом) в файле ELF, поэтому мы можем просто заменить его, используя, например, GNU-сед:
LANG=C LC_ALL=C sed -e 's|\x00ioctl\x00|\x00iqct1\x00|g' old.mylib.so > mylib.so
Это заменяет имя с ioctl()
на iqct1()
. Это явно меньше оптимального, но здесь кажется самым простым вариантом.
Если вы обнаружите, что вам нужно добавить информацию о версии к функции iqct1()
, которую вы реализуете, с помощью GCC вы можете просто добавить строку, аналогичную
__asm__(".symver iqct1,iqct1@GLIBC_2.2.5");
где версия следует за символом @
.
Вот практический пример, показывающий, как я проверял это на практике.
Во-первых, давайте создадим mylib.c, представляющий исходные коды для mylib.c (которых нет в операционной системе, иначе простое изменение исходных кодов и перекомпиляция библиотеки решат проблему):
#include <unistd.h>
#include <errno.h>
int myfunc(const char *message)
{
int retval = 0;
if (message) {
const char *end = message;
int saved_errno;
ssize_t n;
while (*end)
end++;
saved_errno = errno;
while (message < end) {
n = write(STDERR_FILENO, message, (size_t)(end - message));
if (n > 0)
message += n;
else {
if (n == -1)
retval = errno;
else
retval = EIO;
break;
}
}
errno = saved_errno;
}
return retval;
}
Единственная экспортируемая функция — myfunc(message)
, как указано в mylib.h:
#ifndef MYLIB_H
#define MYLIB_H
int myfunc(const char *message);
#endif /* MYLIB_H */
Давайте скомпилируем mylib.c в динамическую разделяемую библиотеку mylib.so
:
gcc -Wall -O2 -fPIC -shared mylib.c -Wl,-soname,libmylib.so -o mylib.so
Вместо write()
из библиотеки C (это функция POSIX, такая же, как ioctl()
, а не стандартная функция C), мы хотим использовать mywrt()
нашей собственной разработки в нашей собственной программе. Приведенная выше команда сохраняет исходную библиотеку как mylib.so
(при внутреннем имени libmylib.so
), поэтому мы можем использовать
sed -e 's|\x00write\x00|\x00mywrt\x00|g' mylib.so > libmylib.so
изменить имя символа, сохранив измененную библиотеку как libmylib.so
.
Далее нам нужен тестовый исполняемый файл, предоставляющий функцию ssize_t mywrt(int fd, const void *buf, size_t count);
(прототип такой же, как у write(2)
, которую он заменяет. test.c:
#include <stdlib.h>
#include <stdio.h>
#include "mylib.h"
ssize_t mywrt(int fd, const void *buffer, size_t bytes)
{
printf("write(%d, %p, %zu);\n", fd, buffer, bytes);
return bytes;
}
__asm__(".symver mywrt,mywrt@GLIBC_2.2.5");
int main(void)
{
myfunc("Hello, world!\n");
return EXIT_SUCCESS;
}
Строка .symver
указывает версию GLIBC_2.2.5
для mywrt
.
Версия зависит от используемой библиотеки C. В данном случае я запустил objdump -T $(locate libc.so) 2>/dev/null | grep -e ' write$'
, что дало мне
00000000000f66d0 w DF .text 000000000000005a GLIBC_2.2.5 write
предпоследнее поле которого является необходимой версией.
Поскольку символ mywrt
необходимо экспортировать для использования в динамической библиотеке, я создал test.syms:
{
mywrt;
};
Чтобы скомпилировать исполняемый файл теста, я использовал
gcc -Wall -O2 test.c -Wl,-dynamic-list,test.syms -L. -lmylib -o test
Поскольку libmylib.so
находится в текущем рабочем каталоге, нам нужно добавить текущий каталог в путь поиска динамической библиотеки:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
Затем мы можем запустить наш тестовый двоичный файл:
./test
Он выведет что-то вроде
write(2, 0xADDRESS, 14);
потому что это то, что делает функция mywrt()
. Если мы хотим проверить немодифицированный вывод, мы можем запустить mv -f mylib.so libmylib.so
и повторно запустить ./test
, который затем выведет только
Hello, world!
Это показывает, что этот подход, хотя и зависит от очень грубой двоичной модификации файла разделяемой библиотеки (с использованием sed
-- но только потому, что objcopy
(пока) не поддерживает --redefine-sym
для динамических символов), на практике должен работать очень хорошо.
Это также прекрасный пример того, как открытый исходный код превосходит проприетарные библиотеки: количество усилий, уже затраченных на попытку исправить эту незначительную проблему, по крайней мере на порядок выше, чем это было бы переименуйте вызов ioctl
в исходниках библиотеки, например, mylib_ioctl()
и перекомпилируйте его.
Вставка dlsym()
(из <dlfcn.h>
, как стандартизовано в POSIX.1-2001) в окончательный двоичный файл кажется необходимым в случае OP.
Предположим, что исходная динамическая библиотека изменена с помощью
sed -e 's|\x00ioctl\x00|\x00iqct1\x00|g;
s|\x00dlsym\x00|\x00d15ym\x00|g;' mylib.so > libmylib.so
и мы реализуем две пользовательские функции как что-то вроде
int iqct1(int fd, unsigned long request, void *data)
{
/* For OP to implement! */
}
__asm__(".symver iqct1,iqct1@GLIBC_2.2.5");
void *d15ym(void *handle, const char *symbol)
{
if (!strcmp(symbol, "ioctl"))
return iqct1;
else
if (!strcmp(symbol, "dlsym"))
return d15ym;
else
return dlsym(handle, symbol);
}
__asm__(".symver d15ym,d15ym@GLIBC_2.2.5");
Убедитесь, что версии соответствуют используемой вами библиотеке C. Соответствующий файл .syms для вышеуказанного будет содержать только
{
i1ct1;
d15ym;
};
в противном случае реализация должна быть такой же, как в практическом примере, показанном ранее в этом ответе.
Поскольку фактическим прототипом ioctl()
является int ioctl(int, unsigned long, ...);
, нет никаких гарантий, что это будет работать для всех общих применений ioctl()
. В Linux второй параметр имеет тип unsigned long
, а третий параметр является либо указателем, либо длинным, либо длинным без знака — во всех архитектурах Linux указатели и длинные/беззнаковые длинные имеют одинаковый размер — поэтому он должен работать, если только драйвер, реализующий ioctl()
, также закрыт, и в этом случае вы просто обливаетесь шлангом и ограничены либо надеждой, что это сработает, либо переключением на другое оборудование с надлежащей поддержкой Linux и драйверами с открытым исходным кодом.
Вышеупомянутые частные случаи являются как исходными символами, так и жестко привязанными к заменяемым функциям. (Я называю эти символы замещенными, а не вставленными, потому что мы действительно заменяем символы, которые вызывает mylib.so
, вместо того, чтобы вставлять вызовы ioctl()
и dlsym()
.)
Это довольно жестокий подход, но помимо использования sed
из-за отсутствия поддержки динамического переопределения символов в objcopy
, он достаточно надежен и понятен в отношении того, что делается и что на самом деле происходит.
person
Nominal Animal
schedule
23.03.2017
mylib.so
? - person Daniel Jour   schedule 24.03.2017