Why and how are some shared libraries runnable, as though they are executables?

29,050

Solution 1

That library has a main() function or equivalent entry point, and was compiled in such a way that it is useful both as an executable and as a shared object.

Here's one suggestion about how to do this, although it does not work for me.

Here's another in an answer to a similar question on S.O, which I'll shamelessly plagiarize, tweak, and add a bit of explanation.

First, source for our example library, test.c:

#include <stdio.h>                  

void sayHello (char *tag) {         
    printf("%s: Hello!\n", tag);    
}                                   

int main (int argc, char *argv[]) { 
    sayHello(argv[0]);              
    return 0;                       
}                   

Compile that:

gcc -fPIC -pie -o libtest.so test.c -Wl,-E

Here, we are compiling a shared library (-fPIC), but telling the linker that it's a regular executable (-pie), and to make its symbol table exportable (-Wl,-E), such that it can be usefully linked against.

And, although file will say it's a shared object, it does work as an executable:

> ./libtest.so 
./libtest.so: Hello!

Now we need to see if it can really be dynamically linked. An example program, program.c:

#include <stdio.h>

extern void sayHello (char*);

int main (int argc, char *argv[]) {
    puts("Test program.");
    sayHello(argv[0]);
    return 0;
}

Using extern saves us having to create a header. Now compile that:

gcc program.c -L. -ltest

Before we can execute it, we need to add the path of libtest.so for the dynamic loader:

export LD_LIBRARY_PATH=./

Now:

> ./a.out
Test program.
./a.out: Hello!

And ldd a.out will show the linkage to libtest.so.

Note that I doubt this is how glibc is actually compiled, since it is probably not as portable as glibc itself (see man gcc with regard to the -fPIC and -pie switches), but it demonstrates the basic mechanism. For the real details you'd have to look at the source makefile.

Solution 2

Let's dive for an answer in a random glibc repo on GitHub. This version provides a „banner“ in the file version.c.

In the same file there are some interesting points: the __libc_print_version function that prints the text to stdout and the __libc_main (void) symbol which is documented as the entry point. So this symbol is called when running the library.

So how does the linker or compiler knows exactly that this is the entry point function?

Let's dive into the makefile. In the linker flags there is an interesting one:

# Give libc.so an entry point and make it directly runnable itself.
LDFLAGS-c.so += -e __libc_main

So this is the linker flag for setting the entry point for the library. When building a library you can provide the -e function_name flag for the linker to enable an executable behavior. What does it really do? Let's look into the manual (somewhat outdated but still valid):

The linker command language includes a command specifically for defining the first executable instruction in an output file (its entry point). Its argument is a symbol name:

ENTRY(symbol)

Like symbol assignments, the ENTRY command may be placed either as an independent command in the command file, or among the section definitions within the SECTIONS command--whatever makes the most sense for your layout.

ENTRY is only one of several ways of choosing the entry point. You may indicate it in any of the following ways (shown in descending order of priority: methods higher in the list override methods lower down).

the `-e' entry command-line option;
the ENTRY(symbol) command in a linker control script;
the value of the symbol start, if present;
the address of the first byte of the .text section, if present;
The address 0. 

For example, you can use these rules to generate an entry point with an assignment statement: if no symbol start is defined within your input files, you can simply define it, assigning it an appropriate value---

start = 0x2020;

The example shows an absolute address, but you can use any expression. For example, if your input object files use some other symbol-name convention for the entry point, you can just assign the value of whatever symbol contains the start address to start:

start = other_symbol ;

(the current documentation can be found here)

The ld linker actually does create an executable with an entry point function if you provide the command line option -e (which is the most popular solution), provide a function symbol start, or spcify a symbol address for the assembler.

However please note that it is not guaranteed to work with other linkers (I do not know if llvm's lld has the same flag). I am not aware why this should be useful for the purposes other than providing the information about the SO file.

Share:
29,050

Related videos on Youtube

Ho1
Author by

Ho1

Updated on September 18, 2022

Comments

  • Ho1
    Ho1 over 1 year

    On 32-bit Linux systems, invoking this

    $ /lib/libc.so.6
    

    and on 64-bit systems this

    $ /lib/x86_64-linux-gnu/libc.so.6
    

    in a shell, provides an output like this:

    GNU C Library stable release version 2.10.1, by Roland McGrath et al.
    Copyright (C) 2009 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.
    There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
    PARTICULAR PURPOSE.
    Compiled by GNU CC version 4.4.0 20090506 (Red Hat 4.4.0-4).
    Compiled on a Linux >>2.6.18-128.4.1.el5<< system on 2009-08-19.
    Available extensions:
        The C stubs add-on version 2.1.2.
        crypt add-on version 2.1 by Michael Glad and others
        GNU Libidn by Simon Josefsson
        Native POSIX Threads Library by Ulrich Drepper et al
        BIND-8.2.3-T5B
        RT using linux kernel aio
    For bug reporting instructions, please see:
    <http://www.gnu.org/software/libc/bugs.html>.
    

    Why and how does this happen, and how is it possible to do the same in other shared libraries?

    I looked at /usr/lib to find executables, and I found /usr/lib/libvlc.so.5.5.0. Running it led to a segmentation fault. :-/

    • Joshua
      Joshua almost 9 years
      It addition to all the following answers, my recall is if you set the x bit on a shared library, it was (maybe still is) possible to load it from an executable even with the r bit clear. It was once considered a good security practice to banish r bit from world on system executables and libraries. Due to widespread open source, this only really applies to the anonymous ftp directory's /bin/ls anymore. To me, leaving the x bit set looks like a visage of that old practice.
  • Ho1
    Ho1 almost 9 years
    I found two others: ld and libpthread.
  • Random832
    Random832 almost 9 years
    @Ho1 ld.so is special in other ways. To some extent, it's more of a real executable than a normal dynamically-linked executable is.
  • Erik Aronesty
    Erik Aronesty about 7 years
    If it was python it would provide unit tests.
  • parasrish
    parasrish over 5 years
    @goldilocks any suggestions for passing the value through cmake? (-pie) (-Wl,-E) Something like -fPIC= POSITION_INDEPENDENT_CODE variable in cmake to be set (or similar).
  • goldilocks
    goldilocks over 5 years
    @parasrish You can pass compiler flags by setting CFLAGS or CMAKE_C_FLAGS: file:///doc/HTML/cmake-v3.11/envvar/CFLAGS.html, although you might try and more properly set the linker flag -Wl,-E via a different variable.
  • parasrish
    parasrish over 5 years
    For now with cmake, it can resolved as: add_compile_options("-pie") link_libraries("-pie") in the CMakeLists.txt. Seemingly, there is an unresolved issue for the cmake in this context : gitlab.kitware.com/cmake/cmake/issues/14983
  • parasrish
    parasrish over 5 years
    Above options even though create the executable-shared-library, but is incomplete in the sense, that it flags error, when some executable tries to link to this. Detailed sample reference added here : unix.stackexchange.com/a/479334/152034
  • goldilocks
    goldilocks about 4 years
    The header would then be a normal library header and would have to be #included in program.c.
  • Demi-Lune
    Demi-Lune over 3 years
    See also the compile/link options of this stackoverflow.com/a/7494273/7212665 answer, including the -nostartfiles in its comments.
  • Irfan Latif
    Irfan Latif about 3 years
    Built with gcc 9.3.0 on Ubuntu, throws cannot dynamically load position-independent executable. May be because of this: patchwork.ozlabs.org/project/glibc/patch/…
  • x4444
    x4444 over 2 years
    -fpic is position-independent code (PIC). Shared gcc flag is -shared