7

This is theoretical question. I am aware that perhaps best practice would be the use of shared libraries. But I ran into this question and cannot seem to find an answer anywhere.

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

For example if one executable contains implementation of some function int test() and I would like to call this function from my program (and preferably get the result of function), if that is even possible, how would I go about doing that?

In pseudocode I could describe it as follows:

ELF executable 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"
}
Alexey Kamenskiy
  • 2,888
  • 5
  • 36
  • 56
  • I don't think it's possible (without a LOT of hacking) since the executable hasn't be compiled as "shared" (which is quite usual for a executable). – blue112 Mar 09 '15 at 08:49
  • @blue112 so "compiled as shared" is somehow possible? In my question both of executables are compiled by me, so if there is a legal way to do that - how? – Alexey Kamenskiy Mar 09 '15 at 08:51
  • @EmployedRussian I believe those two questions are not duplicates although share same topic. In linked question asker cannot change anyhow program that he/she wants to load as shared library, however in my case (even prior edits) I am asking how to make it loadable. Though I agree that before edit the title of question was not clear about this part, but in the body of the question it sure was clear enough. The final goal is to have instruction set on how to compile such executable. – Alexey Kamenskiy Mar 17 '15 at 10:44
  • It's `int main` not `void main`. You are **not** free to invent your own main() prototype. – Jens Mar 17 '15 at 13:59
  • @Jens I think `pseudocode` is the keyword you've missed. No need to go insulting people. – Alexey Kamenskiy Mar 18 '15 at 04:15
  • @n.m. Appears to be that they are linked on compile time in that question. But without linking I think the only solution will be is RPC. I will play a bit around with that code and if it gives me the output I need, I will gladly accept it as a dup. – Alexey Kamenskiy Mar 18 '15 at 04:23
  • You can dlopen it just fine. – n. m. could be an AI Mar 18 '15 at 04:44
  • @n.m. Please see the answer I just posted here. The complete answer is a compilation of multiple questions and provides complete working code (while each question is also missing little part, e.g. compilation flags). – Alexey Kamenskiy Mar 18 '15 at 04:50
  • @AlexKey: Your answer doesn't have any significant that wasn't in the answer to the question n.m. linked (and the insignificant part, example of a `dlsym` call, isn't even what you asked for in your own question) So yes, it is a duplicate. – Ben Voigt Mar 18 '15 at 04:56
  • @BenVoigt feel like we are going off-topic here, but - I did not say it is not. I just needed a complete answer that works with example. I agree it is a dup and in my answer provided reference to the original question and agree it can be marked as a dup. So I don't see a problem. The only difference is that here also will be a complete answer to this exact question. – Alexey Kamenskiy Mar 18 '15 at 04:59
  • @AlexKey: Ok seems good. I thought you were saying it wasn't a duplicate; that you couldn't get the required help from just that question. BTW, your answer copied code that is known to be problematic (check the answers and comments to the question you got it from) – Ben Voigt Mar 18 '15 at 05:05
  • @AlexKey I'm sorry if my comment came across as an insult. It was meant as a statement of fact to avoid *undefined behavior* of the C code. Since you marked your question with the 'c' tag I assumed it to be C, not pseudo code. – Jens Mar 18 '15 at 09:37

2 Answers2

4

ELF executable are not relocatable and they usually compiled to start at the same start address (0x400000 for x86_64) which means it's technically not possible to load two of them in the same address space.

What you could do is either:

  • Compile the executable you want to dlopen() as an executable shared-library (-pie). Technically this file is an ELF shared object but can be executed. You can check if the program is an ELF executable or an ELF shared object with readelf -h my_program or file my_program. (As a bonus, by compiling your program as a shared object you will be able to benefit from ASLR).

  • By compiling your main program as a shared object (so that it is loaded at another place in the virtual address space) you should be able to dynamically link the other executable. The GNU dynamic linker does not want to dlopen an executable file so you'd have to make the dynamic linking yourself (you probably do not want to do this).

  • Or you can link one of your executables to use another base address by using a linker script. Same as before, you'd have to do the work of the dynamic linker yourself.

Solution 1: compile your dynamically-loaded executable as PIE

The called executable:

// hello.c
#include <string.h>
#include <stdio.h>

void hello()
{
  printf("Hello world\n");
}

int main()
{
  hello();
  return 0;
}

The 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 hello as a PIE, the dynamic linker does not add the hell symbol 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
[...]

In order to fix this, you need to pass the -E flag to ld (see @AlexKey's 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 references

For more information, 4. Dynamically Loaded (DL) Libraries from the Program Library HOWTO is a good place to start reading.

ysdx
  • 8,889
  • 1
  • 38
  • 51
  • Since both of binaries are supposedly compiled by me, then I could follow that way, could you maybe give a simple example for given pseudocode? – Alexey Kamenskiy Mar 09 '15 at 08:53
1

Based on links provided in comments and other answers here is how it can be done without linking these programs compile time:

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 %d d 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:

PS: In order to avoid symbol clashes imported symbols and pointers they assigned to better have different names. See comments here.

Community
  • 1
  • 1
Alexey Kamenskiy
  • 2,888
  • 5
  • 36
  • 56