The ELF executable cannot be moved and is usually compiled to run from the same start address (0x400000 for x86_64), which means that it is technically impossible to load two of them in the same address space.
What you can do is either:
Compile the executable file that you want to use dlopen() as an executable shared library ( -pie ). Technically, this file is a common ELF object, but can be executed. You can check if the program is an ELF executable or a shared ELF with readelf -h my_program or file my_program . (As a bonus, compiling your program as a common object, you can use ASLR).
When compiling the main program as a shared object (so that it loads to another location in the virtual address space), you should be able to dynamically link another executable file. The GNU dynamic linker does not want the dlopen executable, so you have to do the dynamic linking yourself (you probably don't want to).
Or you can link one of your executables to use a different base address using the linker script. As before, you will have to do the work of the dynamic linker yourself.
Solution 1: compile your dynamically loaded executable as a PIE
The executable file to be called:
// hello.c #include <string.h> #include <stdio.h> void hello() { printf("Hello world\n"); } int main() { hello(); return 0; }
Caller executable:
// caller.c #include <dlfcn.h> #include <stdio.h> int main(int argc, char** argv) { void* handle = dlopen(argv[1], RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } void (*hello)() = dlsym(handle, "hello"); if (!hello) { fprintf(stderr, "%s\n", dlerror()); return 1; } hello(); return 0; }
Trying to make it work:
$ gcc -fpie -pie hello.c -o hello
$ gcc caller.c -o caller
$ ./caller ./hello
./hello: undefined symbol: hello
The reason is that when you compile hi as a PIE, the dynamic linker does not add the hell character to the dynamic symbol table ( .dynsym ):
$ readelf -s
Symbol table '.dynsym' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000200 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC _2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC _2.2.5 (2)
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
8: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC _2.2.5 (2)
9: 0000000000200bd0 0 NOTYPE GLOBAL DEFAULT 24 _edata
10: 0000000000200bd8 0 NOTYPE GLOBAL DEFAULT 25 _end
11: 0000000000200bd0 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
Symbol table '.symtab' contains 67 entries:
Num: Value Size Type Bind Vis Ndx Name
[...]
52: 0000000000000760 18 FUNC GLOBAL DEFAULT 13 hello
[...]
To fix this, you need to pass the -E flag to ld (see @AlexKey anwser):
$ gcc -fpie -pie hello.c -Wl, -E hello.c -o hello
$ gcc caller.c -o caller
$ ./caller ./hello
Hello world
$ ./hello
Hello world
$ readelf -s ./hello
Symbol table '.dynsym' contains 22 entries:
Num: Value Size Type Bind Vis Ndx Name
[...]
21: 00000000000008d0 18 FUNC GLOBAL DEFAULT 13 hello
[...]
Some links
For more information 4. Dynamically loaded (DL) libraries from the HOWTO Program Library are a good place to start reading.