[Eng]       [Rus]      
Описание Форум Лицензия FAQ Донейшен Главная

Fakelibs или о том как создавать приложения Linux не зависимые от версий библиотек установленных в системе.

Введение.

На сегодняшний день существует достаточно большое количество сборок дистрибутивов Linux и еще большее количество различных версий этих дистрибутивов. Часто бывает, что установить какое-то приложение которое не входит в дистрибутив целое приключение. Откомпилированные бинарные файлы не запускаются т.к. были собраны с другими версиями библиотек и единственный способ установить приложение это пересобрать его из исходников. Часто бывает эти исходники тянут за собой кучу библиотек, иногда случается, что эти библиотеки своими версиями не совпадают с уже установленными, а другие ранее установленные приложения не хотят работать с новыми версиями библиотек... Таким образом простая задача установки требуемых приложений отнимает бездну времени, а зачастую становится непреодолимой преградой для не слишком упорных или не достаточно подготовленных пользователей. Думаю, именно такие сложности являются одним из основных факторов тормозящим массовое распространение великолепной операционной системы, во всем остальном значительно превосходящей аналоги.
Есть несколько способов решения проблемы создания универсальных исполняемых файлов работающих в любом Linux-е. Один из основных способов это статическая линковка всех используемых библиотек. Естественно это приводит к грандиозному увеличению размера исполняемого файла. В случае когда несколько приложений используют одни и те же динамические библиотеки, исполняемый код этих библиотек загружается в оперативную память только один раз. Если представить, что все приложения перешли на статическую линковку, то оперативная память будет расходоваться не эффективно.
Иногда возникают сложности со статической линковкой, для преодоления которых придуманы контейнерные форматы AppImage, Snap, Flatpak, однако они имеют те же недостатки размера и не очень эффективного использования памяти. Именно по этому я хочу предложить вам совершенно другой способ создания универсальных исполняемых файлов использующий именно динамические библиотеки, но в то же время не зависящий от версий установленных библиотек.

Ограничения.

Сразу должен отметить, что этот способ требует от программиста некоторой осторожности в выборе используемых функций и как следствие подходит не всегда и не всем. Однако в большинстве случаев он годится. Годится в случаях, когда программист использует только те функции интерфейс и структуры данных которых не меняются от версии к версии библиотек. Так, например, интерфейс подавляющего большинства функций libc не менялся со времен Кернигана и Ритчи (т.е. с восьмидесятых годов прошлого века). Уже давно существует POSIX стандарт и последние 20 - 25 лет все дистрибутивы Linux стараются его поддерживать. Если ваше приложение не выходит за рамки этого стандарта, этот метод однозначно вам подходит. С другой стороны если приложение использует библиотеки которые встречаются не во всех дистрибутивах Linux или которые существенно меняются от версии к версии то лучше линковать их статически.

Суть метода.

Для того чтобы приложение не зависело от версий динамических библиотек нужно чтобы в линкуемых библиотеках не было информации о версиях. Как это сделать? Конечно можно попытаться пересобрать все библиотеки из исходников, но это долго и нудно... Вместо этого я написал простой скрипт который с помощью стандартных средств генерирует фальшивую библиотеку. В этой фальшивой библиотеке есть только названия функций и экспортируемых переменных. Информации о версии нет. Cкрипт можно скачать отсюда: mkfakelib.pl
mkfakelib64.pl -- для x86-64 платформ...
Он прост, до безобразия. Приведу его полный текст:

#!/usr/bin/perl
#
#  Copyright (C) Maksim Feoktistov
# 
#  This script is free for commercial and non-commercial use.
#


 $cflag=0;
 $cmd = 'Command line:
 mkfakelib.pl  libxx.so  [TARGET_DIR]  [-c]
 ';

  die($cmd)   if($#ARGV < 0 || $ARGV[0] !~ /(.*)\.so/ );

  $bname = $1 ;
  $bname =~ s/.*\///g;
  $soname = $ARGV[0] ;
  $soname =~ s/.*\///g;

  $data= "\n\n.data\n\n";
  
  $pref='';
  
  if($#ARGV > 0)
  {
   $pref=$ARGV[1]  ; 
   $pref .= "./"  if($pref !~ /\/$/);
   if($#ARGV > 1 && $ARGV[2] eq '-c' )
   {
     $cflag=1 ;
     $data= "\n";
   };
  }
 
  
  print "src: $ARGV[0] base:$bname target: $pref$bname.c\n" ;
  
  $tsrcname=($cflag) ? "$pref$bname.c" : "$pref$bname.s" ;
  open FILEC,">$tsrcname"  || die("Can't open $pref$bname.s");
  print FILEC "\n\n";
  print FILEC "\n\n.text\n\n";
  
  @str = `readelf  --wide --dyn-syms $ARGV[0] `;  
  foreach $_  (@str)
  {
  
    if(/FUNC[ \t]+.*[ \t]+DEFAULT[ \t]+[0-9]+[ \t]+(.+)\@\@/)
    {
      $n=$1;
       print FILEC (cflag)? "void $n(){}; \n" : 
                            " .globl $n\n .type $n, \@function\n$n:\n .cfi_startproc\n ret\n .cfi_endproc\n .size   $n, .-$n\n"; 
 
    }
    elsif(/[0-9]+:[ \t]+([0-9a-f]+)[ \t]+[0-9]+[ \t]+OBJECT[ \t]+(GLOBAL|WEAK)[ \t]+DEFAULT[ \t]+[0-9]+[ \t]+(.+)\@\@/)
    {
      ($offs,$n)=($1,$3);
      if($offs =~ /[1-9a-f]/ )
      {
         $data .= (cflag) ? "void * $n;\n" : 
                            "\n  .align 4\n .globl $n\n .type  $n, \@object\n .size   $n, 4\n$n: \n  .long  0\n" ;
      }
      
    }
  
  }
  
  print FILEC  $data ;

  print FILEC "\n\n\n";
  close FILEC ;
  $r = (cflag)?
     `gcc -m32 -o $pref$bname.so  -fPIC -fno-builtin -fno-rtti -fno-threadsafe-statics -fno-access-control -fno-nonansi-builtins -fno-elide-constructors -shared -O2 $tsrcname  -Wl,-soname=$soname`
     : `gcc -m32 -o $pref$bname.so   -fPIC -fno-builtin  -shared -O2 $tsrcname -Wl,-soname=$soname` ;
  
  print $r ;
  
  exit(0);

  


Полученные таким образом библиотеки используются только для сборки. При сборке в строку компиляции надо добавить ключ -L/path/to/fakelibs перед ключем -l для каждой используемой библиотеки.
Пример использования:

 
 mkdir /usr/local/fakelibs
 ./mkfakelib.pl /usr/lib/i386-linux-gnu/libc.so  /usr/local/fakelibs/
 ./mkfakelib.pl /usr/lib/i386-linux-gnu/libpthread.so  /usr/local/fakelibs/

 mkdir /usr/local/fakelibs64
 ./mkfakelib64.pl /lib/x86_64-linux-gnu/libc.so.6  /usr/local/fakelibs64/ -c
 
 gcc -m32 app.o -o app  -dynamic-linker -L/usr/local/fakelibs/ -lpthread -L/usr/local/fakelibs -lc -lc_nonshared
 gcc -m64 app64.o -o app64  -dynamic-linker -L/usr/local/fakelibs64 -lc -lc_nonshared
 
Приложения собранные таким образом легко переносятся с одной системы на другую, как правило запускаются и стабильно работают. Однако я столкнулся с небольшой проблемой, возникающей в 64-битной версией Ubuntu 16.04 В этой системе собранные приложения так же полностью корректно работали, за тем исключением, что функция dlopen для любых аргументов возвращала ошибку 2 "invalid caller". При этом 32-ух битная версия того же приложения в этой же системе, работала нормально. Пока я не нашел способа победить эту проблему. Если такой способ известен Вам, пожалуйста, напишите мне или оставьте комментарий на форуме

Максим Феоктистов