It happens that I am currently working on this type of thing.
One of the main differences between executables and shared object under linux, is that an executable has an interpreter and a (valid) entry point.
For example, on a minimal program :
$ echo 'int main;' | gcc -xc -
If you look at it's elf program headers:
$ readelf --program-headers a.out
...
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
...
The interpreter program is responsible of the execution of the program, to achieve this, it will perform some initializations like loading the needed shared objects. In fact, it is quite analogous to a script shebang, but for elf files.
In this case, /lib64/ld-linux-x86-64.so.2 is the loader for amd64. You can have multiples loaders: e.g., one for 32bits, one for 64.
Now the entry point :
$ readelf --file-header a.out
ELF Header:
...
Entry point address: 0x4003c0
...
$ readelf -a a.out | grep -w _start
57: 00000000004003c0 0 FUNC GLOBAL DEFAULT 13 _start
By default, you can see that _start is defined as the entry point.
So if you consider the following minimal example :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef INTERPRETER
const char interp[] __attribute__((section(".interp"))) = INTERPRETER;
#endif /* INTERPRETER */
void entry_point(void) {
fprintf(stderr, "hello executable shared object world !\n");
_exit(EXIT_SUCCESS);
}
If you compile it as a "normal" shared object and execute it :
$ gcc libexecutable.c -Wall -Wextra -fPIC -shared -o libexecutable.so
$ ./libexecutable.so
Erreur de segmentation
You can see it segfaults. But now if you define an interpreter (adapt it's path to what readelf --program-headers gave you before) and tell to the linker what is your entry point :
$ gcc libexecutable.c -Wall -Wextra -fPIC -shared -o libexecutable.so -DINTERPRETER=\"/lib64/ld-linux-x86-64.so.2\" -Wl,-e,entry_point
$ ./libexecutable.so hello executable shared object world !
now it works. Please note that the _exit() call is necessary to avoid a segfault at the end of the execution.
But in the end, remember that because you specify a custom entry point, you will bypass libc initialization steps which could be needed or not, depending on your needs.