[Eng]       [Rus]      
Description Forum License FAQ Donation Home

Fakelibs or about how to create Linux applications that are not dependent on the version of libraries installed in the system.

Introduction.

To date, there are a fairly large number of builds of Linux distributions and an even greater number of different versions of these distributions. It often happens that installing an application that is not included in the distribution is a whole adventure. Compiled binaries do not start because were compiled with other versions libraries and the only way to install the application is to rebuild it from the source. It often happens that these sources drag a bunch of libraries along with them, sometimes it happens that these libraries do not match with their versions already installed, and other previously installed applications do not want to work with new versions of libraries ... Thus, the simple task of installing the required applications takes an abyss of time, and often it becomes an insurmountable barrier for not too stubborn or not enough trained users. I think it is precisely such difficulties that are one of the main factors hindering the mass distribution of a magnificent operating system, which in all other respects is significantly superior to its analogues.
There are several ways to solve the problem of creating universal executable files working in any Linux. One of the main methods is the static linking of all used libraries. Naturally, this leads to a tremendous increase in the size of the executable file. In the case when several applications use the same dynamic libraries, the executable code of these libraries is loaded into RAM only once. If you imagine that all applications switched to static linking, then RAM will be consumed extremely inefficiently.
Sometimes there are difficulties with static linking, to overcome which the container formats AppImage, Snap, Flatpak were invented, however, they have the same disadvantages of size and not very efficient use of memory. That’s why I want to offer you a completely different way of creating universal executable files using dynamic libraries, but at the same time independent of the versions of installed libraries.

Limitations.

I must immediately note that this method requires some caution from the programmer in choosing the functions used and, as a result, is not always suitable for everyone. However, in most cases, it is suitable. It is suitable in cases when the programmer uses only those functions whose interface and data structures do not change from version to version of libraries. For example, the interface of the vast majority of functions libc has not changed since the time of Kernighan and Ritchie (i.e., from the eighties of the last century). There has long been a POSIX standard, and over the past 20 to 25 years, all Linux distributions have tried to support it. If your application does not go beyond the scope of this standard, this method is definitely suitable for you. On the other hand, if the application uses libraries that are not found on all Linux distributions or that change significantly from version to version, it is better to link them statically.

The essence of the method.

In order for the application not to depend on the versions of dynamic libraries, it is necessary that the linked libraries do not have version information. How to do it? Of course, you can try to rebuild all the libraries from the source, but this is long and tedious ... Instead, I wrote a simple script that generates a fake library using standard tools. This fake library has only function names and exported variables. No version information. The script can be downloaded from here:
mkfakelib.pl
mkfakelib64.pl -- for x86-64 system...

He is simple to disgrace. I will give its full text:

#!/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);

  


The libraries thus obtained are used only for building. When building in makefile, you must add the -L/path/to/fakelibs switch before the -l switch for each library used.
Usage example:

 
 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
 

 
Applications collected in this way are easily transferred from one system to another, as a rule, they start and work stably. However, I ran into a small problem occurring in 64-bit Ubuntu 16.04 On this system, the compiled applications also worked completely correctly, except that the "dlopen" function returned error 2 "invalid caller" for any arguments. At the same time, the 32-bit version of the same application on the same system worked fine. So far, I have not found a way to defeat this problem. If you know this method, please write to me or leave a comment on the forum .

M. Feoktistov