building a .so that is also an executable building a .so that is also an executable c c

building a .so that is also an executable


Update 2: see Andrew G Morgan's slightly more complicated solution which does work for any GLIBC (that solution is also used in libc.so.6 itself (since forever), which is why you can run it as ./libc.so.6 (it prints version info when invoked that way)).

Update 1: this no longer works with newer GLIBC versions:

./a.out: error while loading shared libraries: ./pie.so: cannot dynamically load position-independent executable

Original answer from 2009:

Building your shared library with -pie option appears to give you everything you want:

/* pie.c */#include <stdio.h>int foo(){  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);  return 42; }int main() {   printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);  return foo(); }/* main.c */#include <stdio.h>extern int foo(void);int main() {   printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);  return foo(); }$ gcc -fPIC -pie -o pie.so pie.c -Wl,-E$ gcc main.c ./pie.so$ ./pie.soin main pie.c:9in foo pie.c:4$ ./a.outin main main.c:6in foo pie.c:4$

P.S. glibc implements write(3) via system call because it doesn't have anywhere else to call (it is the lowest level already). This has nothing to do with being able to execute libc.so.6.


I have been looking to add support for this to pam_cap.so, and found this question. As @EmployedRussian notes in a follow-up to their own post, the accepted answer stopped working at some point. It took a while to figure out how to make this work again, so here is a worked example.

This worked example involves 5 files to show how things work with some corresponding tests.

First, consider this trivial program (call it empty.c):

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

Compiling it, we can see how it resolves the dynamic symbols on my system as follows:

$ gcc -o empty empty.c$ objcopy --dump-section .interp=/dev/stdout empty ; echo/lib64/ld-linux-x86-64.so.2$ DL_LOADER=/lib64/ld-linux-x86-64.so.2

That last line sets a shell variable for use later.

Here are the two files that build my example shared library:

/* multi.h */void multi_main(void);void multi(const char *caller);

and

/* multi.c */#include <stdio.h>#include <stdlib.h>#include "multi.h"void multi(const char *caller) {    printf("called from %s\n", caller);}void multi_main(void) {    multi(__FILE__);    exit(42);}const char dl_loader[] __attribute__((section(".interp"))) =    DL_LOADER ;

We can compile and run it as follows:

$ gcc -fPIC -shared -o multi.so -DDL_LOADER="\"${DL_LOADER}\"" multi.c -Wl,-e,multi_main$ ./multi.socalled from multi.c$ echo $?42

So, this is a .so that can be executed as a stand alone binary. Next, we validate that it can be loaded as shared object.

/* opener.c */#include <dlfcn.h>#include <stdio.h>#include <stdlib.h>int main(int argc, char **argv) {    void *handle = dlopen("./multi.so", RTLD_NOW);    if (handle == NULL) {        perror("no multi.so load");        exit(1);    }    void (*multi)(const char *) = dlsym(handle, "multi");    multi(__FILE__);}

That is we dynamically load the shared-object and run a function from it:

$ gcc -o opener opener.c -ldl$ ./openercalled from opener.c

Finally, we link against this shared object:

/* main.c */#include "multi.h"int main(int argc, char **argv) {    multi(__FILE__);}

Where we compile and run it as follows:

$ gcc main.c -o main multi.so$ LD_LIBRARY_PATH=./ ./maincalled from main.c

(Note, because multi.so isn't in a standard system library location, we need to override where the runtime looks for the shared object file with the LD_LIBRARY_PATH environment variable.)


I suppose you'd have your ld -e point to an entry point which would then use the dlopen() family of functions to find and bootstrap the rest of the dynamic linker. Of course you'd have to ensure that dlopen() itself was either statically linked or you might have to implement enough of your own linker stub to get at it (using system call interfaces such as mmap() just as libc itself is doing.

None of that sounds "nice" to me. In fact just the thought of reading the glibc sources (and the ld-linux source code, as one example) enough to assess the size of the job sounds pretty hoary to me. It might also be a portability nightmare. There may be major differences between how Linux implements ld-linux and how the linkages are done under OpenSolaris, FreeBSD, and so on. (I don't know).