How to make a linux shared object (library) runnable on its own?

I wrote a blog post on this subject where I go more in depth because I found it intriguing. You can find my original answer below.


You can specify a custom entry point to the linker with the -Wl,-e,entry_point option to gcc, where entry_point is the name of the library’s “main” function.

void entry_point()
{
    printf("Hello, world!\n");
}

The linker doesn’t expect something linked with -shared to be run as an executable, and must be given some more information for the program to be runnable. If you try to run the library now, you will encounter a segmentation fault.

The .interp section is a part of the resulting binary that is needed by the OS to run the application. It’s set automatically by the linker if -shared is not used. You must set this section manually in the C code if building a shared library that you want to execute by itself. See this question.

The interpreter’s job is to find and load the shared libraries needed by a program, prepare the program to run, and then run it. For the ELF format (ubiquitous for modern *nix) on Linux, the ld-linux.so program is used. See it’s man page for more info.

The line below puts a string in the .interp section using GCC attributes. Put this in the global scope of your library to explicitly tell the linker that you want to include a dynamic linker path in your binary.

const char interp_section[] __attribute__((section(".interp"))) = "/path/to/ld-linux";

The easiest way to find the path to ld-linux.so is to run ldd on any normal application. Sample output from my system:

jacwah@jacob-mint17 ~ $ ldd $(which gcc)
    linux-vdso.so.1 =>  (0x00007fff259fe000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faec5939000)
    /lib64/ld-linux-x86-64.so.2 (0x00007faec5d23000)

Once you’ve specified the interpreter your library should be executable! There’s just one slight flaw: it will segfault when entry_point returns.

When you compile a program with main, it’s not the first function to be called when executing it. main is actually called by another function called _start. This function is responsible for setting up argv and argc and other initialisation. It then calls main. When main returns, _start calls exit with the return value of main.

There’s no return address on stack in _start as it’s the first function to be called. If it tries to return, an invalid read occurs (ultimately causing a segmentation fault). This is exactly what is happening in our entry point function. Add a call to exit as the last line of your entry function to properly clean up and not crash.

example.c

#include <stdio.h>
#include <stdlib.h>

const char interp_section[] __attribute__((section(".interp"))) = "/path/to/ld-linux";

void entry_point()
{
    printf("Hello, world!\n");
    exit(0);
}

Compile with gcc example.c -shared -fPIC -Wl,-e,entry_point.

Leave a Comment