LINUX.ORG.RU

Как избежать сквозной линковки shared библиотек в cmake?

 , ,


1

3

Доброго времени суток. Подскажите, есть ли возможность сделать цепочную линковку библиотек и исполняемого файла в cmake? На данный момент я пытаюсь написать враппер над библиотекой и уже непосредственно работать с исполняемым файлом через вреппер. В случае если я удалю libmain.so что бы исполняемый файл через ldd указывал что имеет взамисвязь только с libwrapper.so и собственно запускался, но не выполнял никакого полезного действия.


Цепочка выглядит так: output–>libwrapper.so–>libmain.so С данным кодом у меня output смотри напрямую на libmain.so

cmake_minimum_required(VERSION 3.6)
project(example)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(main SHARED main.cpp)
add_library(wrapper SHARED wrapper.cpp)

target_link_libraries(wrapper PUBLIC main)

add_executable(output output.cpp)
target_link_libraries(output PRIVATE wrapper ${CMAKE_THREAD_LIBS_INIT} )

Как выглядит ldd output:

linux-vdso.so.1 (0x00007fff60bd4000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd9fb241000)
	libmain.so => /home/sysos/Desktop/test/example/build/main.so (0x00007fd9fa852000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fd9fa670000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd9fa655000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd9fa463000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fd9fb2a7000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd9fa314000)

Как выглядит ldd libwrapper.so:

linux-vdso.so.1 (0x00007ffef76a5000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f577fa85000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f577fc94000)

Заранее спасибо.

Ответ на: комментарий от bdancer

В таком случае я ловлю:

[100%] Linking CXX executable output
/usr/bin/ld: CMakeFiles/output.dir/output.cpp.o: undefined reference to symbol '_ZN8perfetto8internal18TrackEventInternal21ResetIncrementalStateEPNS_15TraceWriterBaseENS_14TraceTimestampE'
/usr/bin/ld: /home/sysos/Desktop/test/example/build/libmain.so: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

Так как wrapper.h и меет внутри себя #include «main.h»

AndrewNew
() автор топика
Ответ на: комментарий от AndrewNew

Странное желание у тебя. Но ты можешь передать следующие флаги:

g++ ... -Wl,--unresolved-symbols=ignore-all -rdynamic

-rdynamic помещает undefined символы в PLT таблицу (она делает не только это, читать доки в общем).

kvpfs ★★
()
Ответ на: комментарий от AndrewNew

Так как wrapper.h имеет внутри себя #include «main.h»

Так в этом и проблема. У тебя и есть прямая зависимость output от main.

Можно ли включить main.h внутри main.cpp ?

rumgot ★★★★★
()
Ответ на: комментарий от kvpfs

Попробовал, данный флаг увидел следющее. Ни в output ни в wrapper теперь через ldd нету libmain.so.

andrew$ ldd output 
	linux-vdso.so.1 (0x00007ffc1739e000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f965fdd1000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f965fbef000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f965fbd4000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f965f9e2000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f965fe48000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f965f893000)


andrew$ ldd libwrapper.so 
	linux-vdso.so.1 (0x00007ffe5035a000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5195c49000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f5195e58000)

А нужна следующая связка output–>libwrapper.so–>libmain.so


При запуске приложения:

andrew$ ./output 
./output: symbol lookup error: ./output: undefined symbol: _ZN8perfetto14DataSourceBase7OnSetupERKNS0_9SetupArgsE
AndrewNew
() автор топика
Ответ на: комментарий от rumgot

Нельзя. Нужно исключительно через прослойку. Есть ли возможность все депенденси которые хочет output вынести в wrapper?

AndrewNew
() автор топика
Ответ на: комментарий от rumgot

На образе есть 1.5 мегабайта места. А нужно подсововать прослойку и в рантайме подкладывать библиотеку после запуска девайса. сейчас сброшу фул имплементацию

AndrewNew
() автор топика
Ответ на: комментарий от AndrewNew

CMakeLists.txt


cmake_minimum_required(VERSION 3.6)
project(PerfettoExample)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Threads)
include_directories(perfetto/sdk)

set(CMAKE_CXX_FLAGS "-Wl,--unresolved-symbols=ignore-all -rdynamic")

add_library(perfetto SHARED perfetto/sdk/perfetto.cc)
add_library(wrapper SHARED PerfettoWrapper.cpp)

target_compile_definitions(wrapper PUBLIC ORIGINAL_PERFETTO)
target_link_libraries(wrapper PRIVATE perfetto)

add_executable(output SystemBackendExample.cpp)
target_link_libraries(output PRIVATE wrapper ${CMAKE_THREAD_LIBS_INIT} )


PerfettoWrapper.h

#ifndef PERFETTOWRAPPER_H
#define PERFETTOWRAPPER_H

#ifdef ORIGINAL_PERFETTO

#include <perfetto.h>
#include <memory>

std::unique_ptr<perfetto::TracingSession> tracing_session{nullptr};

#define CATEGORIES_LIST(...) \
  PERFETTO_DEFINE_CATEGORIES(__VA_ARGS__); \
  PERFETTO_TRACK_EVENT_STATIC_STORAGE();

#define INITIALIZE_PERFETTO() \
  perfetto::TracingInitArgs args; \
  args.backends = perfetto::kInProcessBackend; \
  perfetto::Tracing::Initialize(args); \
  perfetto::TrackEvent::Register(); \
  perfetto::TraceConfig traceConfig; \
  auto* destination_cfg = traceConfig.add_data_sources()->mutable_config(); \
  destination_cfg->set_name("track_event"); 

#define SETUP_EVENT() \
  perfetto::TraceConfig cfg; \
  cfg.add_buffers()->set_size_kb(1024); \
  auto* ds_cfg = cfg.add_data_sources()->mutable_config(); \
  ds_cfg->set_name("track_event"); \
  tracing_session = perfetto::Tracing::NewTrace(); \
  tracing_session->Setup(cfg); \
  tracing_session->StartBlocking();

#define START_TRACING(category, eventName) \
  TRACE_EVENT_BEGIN(category, eventName);

#define START_WRITE_EVENT(category, callPlace, ...) \
  TRACE_EVENT(category, callPlace, ##__VA_ARGS__);

#define FINISH_WRITE_EVENT(category) \
  TRACE_EVENT_END(category);

#define CLOSE_TRACING_SESSION() \
  perfetto::TrackEvent::Flush(); \
  tracing_session->StopBlocking(); \
  std::vector<char> trace_data(tracing_session->ReadTraceBlocking()); \
  std::ofstream output; \
  output.open("example.pftrace", std::ios::out | std::ios::binary); \
  output.write(&trace_data[0], trace_data.size()); \
  output.close(); 
#endif // ORIGINAL_PERFETTO

#ifdef DUMMY_PERFETTO
// DUMMY IMPLEMENTATION
#define CATEGORIES_LIST(...)
#define INITIALIZE_PERFETTO()
#define SETUP_EVENT()
#define START_TRACING(category, eventName) 
#define START_WRITE_EVENT(category, callPlace, ...)
#define FINISH_WRITE_EVENT(category)
#define CLOSE_TRACING_SESSION()

#endif // DUMMY_PERFETTO
#endif  // PERFETTOWRAPPER_H

PerfettoWrapper.cpp

#include "PerfettoWrapper.h"

SystemBackendExample.cpp

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <fstream>
#include "PerfettoWrapper.h"

CATEGORIES_LIST(perfetto::Category("FirstEvent").SetDescription("FirstEventDescription"),
                perfetto::Category("SecondEvent").SetDescription("SecondEventDescription"));

void firstFunc(int data) {
  START_WRITE_EVENT("FirstEvent", "firstFunc", "EQUEAL", data)
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

void secondFunc(int data) {
  START_WRITE_EVENT("SecondEvent", "secondFunc", "EQUEAL", data)
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

void callFirstAndSecond() {
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  START_TRACING("FirstEvent", "one");
  firstFunc(100);
  FINISH_WRITE_EVENT("FirstEvent");
  
  START_TRACING("SecondEvent", "two");
  secondFunc(200);
  FINISH_WRITE_EVENT("SecondEvent");
}

int main()
{
  std::cout << "START" << std::endl;
  INITIALIZE_PERFETTO();
  SETUP_EVENT();
  callFirstAndSecond();
  CLOSE_TRACING_SESSION();
  std::cout << "FINISH" << std::endl;
  
  return 0;
}

AndrewNew
() автор топика
Ответ на: комментарий от AndrewNew

Короче так. Если ты хочешь убрать зависимость output от perfetto, тебе нужно все типы из PerfettoWrapper.h переносить в PerfettoWrapper.cpp. Исключение указатели и ссылки. Иначе у тебя прямая зависимость output от perfetto.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Мне кажется ты путаешь линковку и инклюды. От того какие ты .h куда включишь с библиотеками ничего не поменяется.

Впрочем чего пытается добиться автор я не понимаю. При запуске проги в любом случае нужны будут все .so по цепочке.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от AndrewNew

Инклюды тут ни при чём, вообще.

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

firkax ★★★★★
()
Ответ на: комментарий от AndrewNew

Не используй ldd, он сканит зависимости рекурсивно. Те флаги что я тебе сказал - делают ровно то, что просил:

cmake_minimum_required(VERSION 3.6)
project(example)

set(CMAKE_CXX_FLAGS "-Wl,--unresolved-symbols=ignore-all -rdynamic")

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(main SHARED main.cpp)
add_library(wrapper SHARED wrapper.cpp)

target_link_libraries(wrapper PRIVATE main)

add_executable(output output.cpp)
target_link_libraries(output PRIVATE wrapper)

Вот ссылки на либы, которые сохраняются в elf’ах, без всякого рекурсивного прохода (как это делает ldd):

$ readelf -d output 
Dynamic section at offset 0x2da8 contains 31 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libwrapper.so]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

$ readelf -d libwrapper.so 
Dynamic section at offset 0x2e00 contains 26 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libmain.so]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

Не используй ldd, он сканит зависимости рекурсивно.

Ему и нужно рекурсивно. Он хочет чтобы прога могла запуститься без .so.

firkax ★★★★★
()
Ответ на: комментарий от rumgot

Повторюсь. Мне необходимо что бы output работал без libperfetto.so посмотрите внизу PerfettoWrapper.h есть пустая имплементация. Зависимости на perfetto.h в таком случае не должно остаться

AndrewNew
() автор топика
Ответ на: комментарий от firkax

Ну она будет запускаться, у бинаря зависимость лишь от wrapper.so, линкует к врапперу что хочет после этого, функции-заглушки и тп.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от AndrewNew

define это не имплементация, это команды препроцессора. Из враппера надо экспортировать функции либо методы, препроцессор для имплементации засунь в wrapper.cpp, в wrapper.h напиши прототипы экспортиремых функций.

firkax ★★★★★
()

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

Нечто подобное делал в своем говнокоде, возможно вдохновит:

https://github.com/xDShot/Quakespasm/blob/dd34e8722d0d389d20e7342dda4dbd966dd...

xDShot ★★★★★
()
Ответ на: комментарий от firkax

А перекомпилить враппер проблема что ли? Ну или заглушку либу подсунуть? Тут другое проблема:

после удаления запускался, но не выполнял никакого полезного действия

Тут немного нужно заморочиться и не тупо удалить libmain.so, а сделать заглушкой.

kvpfs ★★
()
Ответ на: комментарий от firkax

Есть какая-то утилита для создания заглушки либы. Тут как бы 320 упомнианий через

nm output | c++filt | grep "W" | grep "perfetto::" | wc -l``` 
AndrewNew
() автор топика
Ответ на: комментарий от firkax

Не знаю как там ему надо, он просил не зависеть бинарю от libmain.so - я показал. Что он там в своём враппере подсунет и какие заглушки - дело его. Главное - перекомпилять исполняемый бинарь он не будет, линк только к libwrapper.so.

Да и задача странная какая-то поставлена и так делать не надо, надо испоьзовать плагины как отметили выше через dlopen.

kvpfs ★★
()
Ответ на: комментарий от AndrewNew

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

firkax ★★★★★
()
Ответ на: комментарий от firkax

Ты не фига не понял, что я имел ввиду. Основная мысль пошла от поста про:

target_link_libraries(wrapper PRIVATE main)

после которой ТС ответил, что так получает ошибку:

[100%] Linking CXX executable output
/usr/bin/ld: CMakeFiles/output.dir/output.cpp.o: undefined reference to symbol '_ZN8perfetto8internal18TrackEventInternal21ResetIncrementalStateEPNS_15TraceWriterBaseENS_14TraceTimestampE'
/usr/bin/ld: /home/sysos/Desktop/test/example/build/libmain.so: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

На что я написал ему, что нужно все типы от Perfetto прятать в PerfettoWrapper.cpp и чтобы в PerfettoWrapper.h не было вообще никаких типов из Perfetto. Я это к тому, чтобы можно было делать

target_link_libraries(wrapper PRIVATE main)

без ошибок.

Собственно указанная ошибка и возникает, потому что в output есть попытка прямого использования сущности из Perfetto.

Кроме того, в зависимости от расположения заголовочников Perfetto, вообще не факт, что они будут доступны при сборке output. А они ему потребуются, т.к. в нем подключается заголовочник PerfettoWrapper.h, который в свою очередь подключает Perfetto.h.

Если ты не делаешь target_link_libraries() с некоторой библиотекой, но как минимум используешь ее заголовочники - это говнокод в мире сборки. Потому что указанная команда в числе прочего обеспечивает доступность путей поиска заголовочников указанной цели.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 7)
Ответ на: комментарий от firkax

ты пишешь так что тебя очень легко неправильно понять

Ну конкретно в этой теме согласен, как-то смазано получилось.

rumgot ★★★★★
()
Ответ на: комментарий от AndrewNew

На образе есть 1.5 мегабайта места. А нужно подсововать прослойку и в рантайме подкладывать библиотеку после запуска девайса. сейчас сброшу фул имплементацию

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

neon1ks ★★
()
Ответ на: комментарий от neon1ks

Мы смогли зашифровать библиотеку, вложить её в зашифрованном виде в код в виде ресурса. И во время выполнения программы — расшифровать в оперативную память. Затем оттуда запустить на выполнение и вызывать из нее функции.

neon1ks ★★
()
Последнее исправление: neon1ks (всего исправлений: 1)
Ответ на: комментарий от AndrewNew

Нужно исключительно через прослойку. Есть ли возможность все депенденси которые хочет output вынести в wrapper?

А нужно подсововать прослойку и в рантайме подкладывать библиотеку после запуска девайса. сейчас сброшу фул имплементацию

Это немного не так работает. Сразу уточняю, что подразумеваю, что взятие адреса через dlsym() ты отверг. Обращение к внешним функциям из других модулей происходит примерно так:

     1. ссылка на exfn() в коде (в .text секции)
     2. переход на "трамплин" в .plt секции - plt@exfn()
     3. переход на разыменованный указатель из .got.plt, если переразмещение
        уже было произведено, то попадаем на exfn(), иначе:
        3.1. возврат в plt@exfn(), в стек кладётся смещение в .rel.plt
             секции Elf32_Rel структуры и указатель на link_map список
        3.2. вызов ld.so, правится указатель в .got.plt
        3.3. переход на exfn().

Уточнение - без разницы как ты подгружаешь либу к бинарю: через -l… флаг или через dlopen(), обе либы попадут в linkmap и если есть какие-то ленивые ссылки, то ld.so будет использовать обе одинаково. Т.е. например ты открыл какую-то либу через dlopen(, RTLD_GLOBAL), в коде у тебя есть ленивые ссылки на внешние символы, после первого вызова секция .got.plt исполняемого бинаря будет отредактирована и дальнейшее dlclose() + загрузка нового модуля дадут обращение к невалидному мусору в образе процесса, заново загрузчик не будет делать релокации. Но ты можешь попробовать делать следующее (сам не пробовал):

  1. После запуска бинаря сохранять начальный образ его .got.plt секции
  2. Делать dlopen(, RTLD_GLOBAL) нужной либы (как хочешь - из враппера или бинаря)
  3. Вдруг захотелось сменить в рантайме либу, для этого:
    3.1 Делаешь dlclose()
    3.2 Записываешь начальный образ .got.plt из сделанной копии
    3.3 Открываешь новую либу через dlopen(, RTLD_GLOBAL)

Естественно, что все подгружаемые либы должны экспортировать одинаковые функции, иначе софтина упадёт при обращении к функции после ошибки ld.so.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

Единственная разница между либами (может и нет, но я другой не припомню), которые ты линкуешь через -l флаг и подгружаешь через dlopen() - -l либы на старте используются загрузчиком для релокаций ссылок в исполняемом бинаре. Как оставить ленивые ссылки с возможностью ленивой релокации - писал выше.

kvpfs ★★
()
Ответ на: комментарий от Harald

Отличный совет. Ну вот ни добавить, ни убавить.

rumgot ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.