How to compile ELF binary so that it can be loaded as a dynamic library?

This is a theoretical question. I know that perhaps the best practice would be to use shared libraries. But I came across this question and can not find the answer anywhere.

How to build code and compile a C / C ++ program in ELF format so that it can be loaded using dlopen() ?

For example, if one executable file contains an implementation of some int test() function, and I would like to call this function from my program (and, preferably, get the result of the function), if possible, how would I do it

In pseudo code, I could describe it like this:

Original ELF Source:

 void main() { int i = test(); printf("Returned: %d", i);//Prints "Returned: 5" } int test() { return 5; } 

External program:

 // ... Somehow load executable from above void main() { int i = test(); printf("Returned: %d", i);//Must print "Returned: 5" } 
+7
c linux elf dlopen
source share
2 answers

Based on the links provided in the comments and other answers, here's how to do this without tying the compilation time of these programs:

test1.c

 #include <stdio.h> int a(int b) { return b+1; } int c(int d) { return a(d)+1; } int main() { int b = a(3); printf("Calling a(3) gave %d \n", b); int d = c(3); printf("Calling c(3) gave %d \n", d); } 

test2.c

 #include <dlfcn.h> #include <stdio.h> int (*a_ptr)(int b); int (*c_ptr)(int d); int main() { void* lib=dlopen("./test1",RTLD_LAZY); a_ptr=dlsym(lib,"a"); c_ptr=dlsym(lib,"c"); int d = c_ptr(6); int b = a_ptr(5); printf("b is %dd is %d\n",b,d); return 0; } 

Compilation

 $ gcc -fPIC -pie -o test1 test1.c -Wl,-E $ gcc -o test2 test2.c -ldl 

Execution Results :

 $ ./test1 Calling a(3) gave 4 Calling c(3) gave 5 $ ./test2 b is 6 d is 8 

References

  • creating .so also executable
  • Compile C program with dlopen and dlsym with -fPIC

PS . To avoid character conflicts, the imported characters and the pointers they assigned have better names. See comments here .

+1
source share

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.

+4
source share

All Articles