PVOID - О переносимых исполняемых файлах в Linux

О переносимых исполняемых файлах в Linux

 Речь пойдет о переносимости бинарных файлов между дистрибутивами Linux на одной аппаратной платформе.

Если нужно собрать бинарный deb или rpm пакет или просто собрать переносимый бинарник, но нет времени / возможности / желания собирать дистрибутив под каждую версию каждого дистрибутива, можно скомпоновать в него все библиотеки статически, оставив только зависимость от стандартной библиотеки си. Не рассматриваем вариант статической компоновки библиотеки си. Пишем на C++ и хотим стандарт C++ не ниже 11, а значит и gcc 4.8 как минимум. Очень удобным вариантом для сборки является виртуальная машина с CentOS 7. Получаем gcc 4.8.5 с glibc 2.17, которая в итоге и будет единственной зависимостью бинарника. Все, что нам нужно, можно установить из пакетов дистрибутива.

Статическая компоновка стандартных библиотек


Пишем программу main.cpp :

#include <iostream>
 
 int main(){
   std::cout << "Hello, world!" << std::endl;
   return 0;
 }

Собираем:

c++ -o app main.cpp

Смотрим зависимости:

ldd ./app
linux-vdso.so.1 =>  (0x00007ffd76b10000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fdc18ec2000)
libm.so.6 => /lib64/libm.so.6 (0x00007fdc18bc0000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fdc189aa000)
libc.so.6 => /lib64/libc.so.6 (0x00007fdc185dc000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdc191c9000)

Из этого списка нас беспокоят:

  • libstdc++.so.6
  • libgcc_s.so.1

Чтобы скомпоновать статически эти библиотеки нужно передать флаги компоновщику:

c++ -o app -static-libstdc++ -static-libgcc main.cpp

Смотрим зависимости:

linux-vdso.so.1 =>  (0x00007ffdc87ce000)
libm.so.6 => /lib64/libm.so.6 (0x00007f13e3030000)
libc.so.6 => /lib64/libc.so.6 (0x00007f13e2c62000)
/lib64/ld-linux-x86-64.so.2 (0x00007f13e3332000)

Вероятность, что на целевой машине не будет этих библиотек... не встречал такого )))

Для статической компоновки упомянутых библиотек, необходимо сначала установить их статические версии, например:

yum search libstdc++

Среди прочего получим:

libstdc++-static.x86_64 : Static libraries for the GNU standard C++ library
libstdc++-static.i686 : Static libraries for the GNU standard C++ library

И устанавливаем нужный нам пакет.

 

Статическая компоновка других библиотек


 К примеру, мы используем библиотеку libmodbus. Чтобы скомпоновать ее статически необходимо указать компоновщику полный путь до библиотеки, но... как бы ни хотелось установить статическую версию библиотеки из пакета, такого пакета нет и придется собирать вручную. Вообще при статической компоновке стоит ожидать, что все библиотеки придется собирать из исходников, библиотеки все разные и собираются по-разному, поэтому просто предположим, что мы уже собрали библиотеку libmodbus.a :

c++ -o app -static-libstdc++ -static-libgcc main.cpp /usr/lib/libmodbus.a

 

Возможные проблемы


 При статической компоновке общей библиотеки (.so) необходимо, чтобы статическая библиотека была собрана с флагом -fPIC. На патформе x86_64 статические библиотеки, устанавливаемые из пакетов, например, Boost, собраны без этого флага, поэтому на 64-разрядной системе точно придется пересобирать все библиотеки, которые будут компоноваться в so, с флагом -fPIC.

Есть высокая вероятность того, что полученные бинарные файлы будут запускаться и на платформе с glibc младших версий, если не будут использованы символы, определенные в glibc старшей версии, чем на целевой машине. Например, описанный выше исходный код будет запускаться на Ubuntu 8, хотя на ней glibc 2.15. Однако, если в программу добавить невинный на первый взгляд вызов std::chrono::system_clock::now(), можем получить зависимость от символа clock_gettime@GLIBC_2.17, о чем мы непременно узнаем, пытаясь запустить бинарник на целевой платформе. В таких случаях:

  •  На нашей виртуалке запускаем gdb ./app
  •  break clock_gettime
  •  run
  •  where
  •  по стеку смотрим какая функция делает этот вызов и по возможности переписываем его другим способом.