Есть ли способ "статически" вставить общую библиотеку .so(.o) в исполняемый файл?

Прежде всего рассмотрим следующий случай.

Ниже приведена программа:

// test.cpp
extern "C" void printf(const char*, ...);

int main() {
        printf("Hello");
}

Ниже представлена ​​библиотека:

// ext.cpp (the external library)
#include <iostream>

extern "C" void printf(const char* p, ...);

void printf(const char* p, ...) {
        std::cout << p << " World!\n";
}

Теперь я могу скомпилировать вышеуказанную программу и библиотеку двумя способами.

Первый способ заключается в компиляции программы без ссылки на внешнюю библиотеку:

$ g++ test.cpp -o test
$ ldd test
        linux-gate.so.1 =>  (0xb76e8000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7518000)
        /lib/ld-linux.so.2 (0xb76e9000)

Если я запустил указанную выше программу, она будет печатать:

$ ./test 
Hello

Второй способ заключается в компиляции программы со ссылкой на внешнюю библиотеку:

$ g++ -shared -fPIC ext.cpp -o libext.so
$ g++ test.cpp -L./ -lext  -o test
$ export LD_LIBRARY_PATH=./
$ ldd test
        linux-gate.so.1 =>  (0xb773e000)
        libext.so => ./libext.so (0xb7738000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb756b000)
        libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb7481000)
        /lib/ld-linux.so.2 (0xb773f000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb743e000)
        libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb7421000)
$ ./test
Hello World!

Как вы можете видеть, в первом случае программа использует printf из libc.so, а во втором случае она использует printf из libext.so.

Мой вопрос: из исполняемого, полученного как в первом случае, и объектного кода libext (либо как .so, либо .o), можно ли получить исполняемый файл, как во втором дело? Другими словами, можно ли заменить ссылку на libc.so ссылкой на libext.so для всех символов, определенных в последнем?

** Обратите внимание, что interposition через LD_PRELOAD не то, что я хочу. Я хочу получить exectuable, который напрямую связан с библиотеками, в которых я нуждаюсь. Я снова подчеркиваю тот факт, что у меня есть только доступ к первому двоичному файлу и к внешнему объекту, который я хочу "статически" вставить **

Ответы

Ответ 1

Это возможно. Подробнее о вкладке разделяемой библиотеки:

Когда скомпилирована программа, использующая динамические библиотеки, в двоичный файл входит список символов undefined, а также список библиотек, с которыми связана программа. Между символами и библиотеками нет соответствия; в двух списках просто скажите загрузчику, какие библиотеки загружаются и какие символы нужно разрешить. Во время выполнения каждый символ разрешается с использованием первой библиотеки, которая его предоставляет. Это означает, что если мы можем получить библиотеку, содержащую наши функции-обертки для загрузки перед другими библиотеками, символы undefined в программе будут разрешены для наших оболочек вместо реальных функций.

Ответ 2

Вам придется изменить двоичный файл. Взгляните на patchelf http://nixos.org/patchelf.html

Он позволит вам установить или изменить либо RPATH, либо даже "интерпретатор", то есть ld-linux-x86-64.so, чтобы что-то еще.

Из описания утилиты:

Динамически связанные исполняемые файлы ELF всегда указывают динамический компоновщик или интерпретатор, который представляет собой программу, которая фактически загружает исполняемый файл наряду со всеми его динамически связанными библиотеками. (Ядро просто загружает интерпретатор, а не исполняемый файл.) Например, на Система Linux/x86. Интерпретатором ELF обычно является файл /lib/ld -linux.so.2.

Итак, что вы можете сделать, это запустить patchelf в рассматриваемом двоичном файле (т.е. test) с вашим собственным интерпретатором, который затем загружает вашу библиотеку... Это может быть сложно, но исходный код для ld-linux-so доступен...

Вариант 2 - это изменить список библиотек самостоятельно. По крайней мере patchelf дает вам отправную точку в том, что код перебирается по списку библиотек (см. DT_NEEDED в коде).

Документация эльфа указывает, что порядок действительно важен:

DT_NEEDED: этот элемент содержит смещение таблицы строк с нулевым завершением string, указав имя нужной библиотеки. Смещение является индексом в таблицу, записанную в записи DT_STRTAB. См. "Общий объект" Зависимости для получения дополнительной информации об этих именах. Динамический массив может содержать несколько записей с этим типом. Эти записи относительный порядок значителен, хотя их отношение к записям других типов нет.

Характер вашего вопроса указывает на то, что вы знакомы с программированием:-) Может быть, самое подходящее время для добавления дополнения к patchelf... Изменение зависимостей библиотек в двоичном формате.

Или, может быть, вы намерены сделать именно то, что было создано patchelf... Во всяком случае, надеюсь, что это поможет!

Ответ 3

Statifier, вероятно, делает то, что вы хотите. Он выполняет исполняемый файл и все разделяемые библиотеки и выводит статический исполняемый файл.

Ответ 4

То, о чем вы просите, традиционно НЕ возможно. Об этом уже говорилось здесь и здесь.

Суть вашего вопроса -

Как статически связать динамический общий объект?

Это невозможно. Причина в том, что статическая привязка библиотеки фактически совпадает с результатами компиляции этой библиотеки, распаковкой их в вашем текущем проекте и использованием их, как если бы они были вашими собственными объектами. *.a - это всего лишь архивы из нескольких файлов *.o со всей информацией, интактной внутри них. С другой стороны, динамические библиотеки уже связаны; информация о повторном местоположении символа уже отбрасывается и, следовательно, не может быть статически связана с исполняемым файлом.

Однако у вас есть другие альтернативы, чтобы обойти это техническое ограничение.


Итак, каковы ваши варианты?

1. Используйте LD_PRELOAD в целевой системе

Интерполяция общих библиотек хорошо описана в Ответ Максима.

2. Подготовьте предварительно подключенный автономный исполняемый файл

elf-statifier - это инструмент для создания переносимых автономных исполняемых файлов Linux.

Он пытается объединить динамически связанный исполняемый файл и все динамически связанные библиотеки в один отдельный исполняемый файл. Этот файл можно скопировать и запустить на другом компьютере независимо.

Итак, теперь на вашей машине разработки вы можете установить LD_PRELOAD и запустить исходный исполняемый файл и убедиться, что он работает правильно. На этом этапе elf-statifier создается моментальный снимок образа памяти процесса. Этот снимок сохраняется как исполняемый файл ELF со всеми необходимыми разделяемыми библиотеками (включая пользовательский libext.so) внутри. Следовательно, нет необходимости вносить какие-либо изменения (например, LD_PRELOAD) в целевую систему, в которой запускается только что созданный автономный исполняемый файл.

Однако этот подход не гарантирует работу во всех сценариях. Это связано с тем, что последние ядра Linux ввели VDSO и ASLR.

Коммерческая альтернатива этому ermine. Он может работать с ограничениями VDSO и ASLR.

Ответ 5

Возможно, вы сможете выполнить то, что вы просите, динамически загружая библиотеку, используя dlopen(), обращаясь к символу для функции как указатель функции, используя dlsym(), а затем вызывая его с помощью указателя функции. Вот хороший пример того, что делать на этом сайте.

Я применил этот пример к вашему примеру выше:

// test.cpp
#include <stdio.h>
typedef void (*printf_t)(const char *p, ...);

int main() {

  // Call the standard library printf
  printf_t my_printf = &printf;
  my_printf("Hello"); // should print "Hello"

  // Now dynamically load the "overloaded" printf and call it instead
  void* handle = dlopen("./libext.so", RTLD_LAZY);
  if (!handle) {
    std::cerr << "Cannot open library: " << dlerror() << std::endl;
    return 1;
  }

  // reset errors
  dlerror();

  my_printf = (printf_t) dlsym(handle, "printf");
  const char *dlsym_error = dlerror();
  if (dlsym_error) {
    std::cerr << "Cannot load symbol 'printf': " << dlsym_error << std::endl;
    dlclose(handle);
    return 1;
  }

  my_printf("Hello"); // should print "Hello, world"

  // close the library
  dlclose(handle);

}

Страница руководства для dlopen и dlsym должна обеспечить более глубокое понимание. Вам нужно попробовать это, так как неясно, как dlsym будет обрабатывать конфликтующий символ (в вашем примере, printf) - если он заменяет существующий символ, вам может понадобиться "отменить" ваше действие позже. Это действительно зависит от контекста вашей программы и того, что вы пытаетесь сделать в целом.

Ответ 6

Не статически, но вы можете перенаправить динамически загруженные символы в общей библиотеке на свои собственные функции с помощью утилиты elf-hook, созданной Энтони Шомихиным.

Типичное использование - перенаправление определенных вызовов функций из сторонней разделяемой библиотеки, которую вы не можете редактировать.

Скажем, ваша сторонняя библиотека находится в /tmp/libtest.so, и вы хотите перенаправить вызовы printf, сделанные из библиотеки, но оставляйте вызовы на printf из других мест без изменений.

Пример Exemplar:

lib.h

#pragma once

void test();

lib.cpp

#include "lib.h"
#include <cstdio>

void test()
{
    printf("hello from libtest");
}

В этом примере указанные выше 2 файла скомпилированы в общую библиотеку libtest.so и сохранены в /tmp

main.cpp

#include <iostream>
#include <dlfcn.h>
#include <elf_hook.h>
#include "lib.h"

int hooked_printf(const char* p, ...)
{
    std::cout << p << " [[ captured! ]]\n";
    return 0;
}

int main()
{
    // load the 3rd party shared library
    const char* fn = "/tmp/libtest.so";
    void* h = dlopen(fn, RTLD_LAZY);

    // redirect printf calls made from within libtest.so
    elf_hook(fn, LIBRARY_ADDRESS_BY_HANDLE(h), "printf", (void*)hooked_printf);

    printf("hello from my app\n"); // printf in my app is unaffected

    test(); // test is the entry point to the 3rd party library

    dlclose(h);
    return 0;
}

Выход

hello from my app
hello from libtest [[ captured! ]]

Таким образом, вы можете увидеть, что вы можете вставлять свои собственные функции без LB_PRELOAD, с дополнительным преимуществом, что вы имеете более тонкое управление функциями, которые перехватываются.

Однако функции не статически помещены, а скорее динамически перенаправлены

Источник GitHub для библиотеки эльфа-крючка здесь, а полная статья кода, написанная Энтони Шомихиным, здесь

Ответ 7

Это возможно. Вам просто нужно отредактировать заголовок ELF и добавить свою библиотеку в раздел Dynamic. Вы можете проверить содержимое "Динамического раздела" с помощью readelf -d <executable>. Также readelf -S <executable> скажет вам смещение .dynsym и .dynstr. В .dynsym вы можете найти массив структур Elf32_Dyn или Elf64_Dyn, где ваш d_tag ​​должен быть DT_NEEDED и d_un.d_ptr должен указывать на строку "libext.so", расположенную в разделе .dynstr.

Заголовки ELF описаны в /usr/include/elf.h.

Ответ 8

Можно изменить двоичный файл.

Например, с помощью такого инструмента, как ghex, вы можете изменить шестнадцатеричный код двоичного кода, который вы просматриваете в коде для каждого экземпляра libc.so, и вы заменяете его libext.so