9

There is a large number of questions on SO about how to execute a library or dynamically load an executable. As far as I can tell, all the answers come down to: compile your executable as position-independent code and load it with dlopen. This worked great --- and still works great on macOS --- until a recent change in glibc, which explicitly disabled dlopening PIEs. This change is now in the current version of glibc (2.30) on ArchLinux, for example, and trying to dlopen a position-independent executable gives an error: "cannot dynamically load position-independent executable".

It's difficult to guess what prompted such a radical change that breaks so much code and useful use cases. (The explanations on Patchwork and Bugzilla don't make much sense to me.) But there is now a question: what to do if you want to create an executable that's also a dynamic library, or vice versa?

A solution was linked from one of the comments. Reproducing it here for posterity:

#include <stdio.h>
#include <unistd.h>

const char service_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux-x86-64.so.2";

extern "C" {

void lib_entry(void)
{
  printf("Entry point of the service library\n");    
  _exit(0);
}

}

Compiling with g++ -shared test-no-pie.cpp -o test-no-pie -Wl,-e,lib_entry produces a shared object (dynamic library) that can also be executed on Linux.

I have two questions:

  1. What if I want to pass command-line arguments? How to modify this solution so it accepts arc,argv?
  2. Are there other alternatives?
foxcub
  • 2,517
  • 2
  • 27
  • 27
  • Possible duplicate of [How to change interpreter path and pass command line arguments to an "executable" shared library on Linux?](https://stackoverflow.com/questions/29652446/how-to-change-interpreter-path-and-pass-command-line-arguments-to-an-executable) – foxcub Nov 28 '19 at 03:05

2 Answers2

6

It's difficult to guess what prompted such a radical change

Not really: it never worked correctly.

that breaks so much code

That code was broken already in subtle ways. Now you get a clear indication that it will not work.

Are there other alternatives?

Don't do that?

What problem does dlopening an executable solve?

If it's a real problem, open a GLIBC bugzilla feature request, explaining that problem and requesting a supported mechanism to achieve desired result.

Update:

at least say why "it never worked correctly". Is it some triviality like potentially clashing globals between the executables, or something real?

Thread-local variables is an example that doesn't work correctly. Whether you think they are "real" or not I have no idea.

Here is the code:

// foo.c
#include <stdio.h>

__thread int var;

__attribute__((constructor))
static void init()
{
  var = 42;
  printf("foo.c init: %d %p\n", var, &var);
}

int bar() {
  printf("foo.c bar:  %d %p\n", var, &var);
  return var;
}

int main()
{
  printf("foo.c main: %d %p bar()=%d\n", var, &var, bar());
  return 0;
}
gcc -g foo.c -o foo -Wl,-E -fpie -pie && ./foo
foo.c init: 42 0x7fb5dfd7d4fc
foo.c bar:  42 0x7fb5dfd7d4fc
foo.c main: 42 0x7fb5dfd7d4fc bar()=42
// main.c
// Error checking omitted for brevity
#include <dlfcn.h>
#include <stdio.h>

int main()
{
  void *h1 = dlopen("./foo", RTLD_LOCAL|RTLD_LAZY);
  int (*bar)(void) = dlsym(h1, "bar");

  printf("main.c: %d\n", bar());
  return 0;
}
gcc -g main.c -ldl && ./a.out
foo.c init: 42 0x7fb7305da73c
foo.c bar:  0 0x7fb7305da73c    <<< what?
main.c: 0                       <<< what?

This is using GNU C Library (Debian GLIBC 2.28-10) stable release version 2.28.

Bottom line: this was never designed to work, and you just happened to not step on many of the land-mines, so you thought it is working, when in fact you were exercising undefined behavior.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Thanks for the example. So globals are the problem. Still seems like throwing the baby out with the bathwater. Also, what's the reason this works if I compile `foo.c` as a shared library? I.e., how are the two situations different? – foxcub Nov 28 '19 at 02:42
  • I agree...we've used executable shared libraries for the convenience in having a shared library that is debugable by running it as standlone with GDB. (instead of compiling another executable that `dlopen` and run main) However, we saw many subtle problems including global variables... – Itay Marom Jul 22 '20 at 08:57
  • The example code would work correctly provided that it's compiled with `-fPIC`( as already demonstrated in this [question](https://stackoverflow.com/questions/64659713/why-does-dynamically-loading-of-pies-no-longer-work-in-glibc?noredirect=1&lq=1)). As such, it doesn't prove anything, and until more evidence is brought out, everything in this answer should be considered only opinion and baseless claims. –  Nov 08 '20 at 15:19
  • @foxcub globals are not the problem. –  Nov 08 '20 at 15:31
  • "What problem does dlopening an executable solve?" Usually the question is not about dlopening an executable, but about executing a shared library. – Alex Che Nov 04 '22 at 10:16
3

Please see this answer:

https://stackoverflow.com/a/68339111/14760867

The argc, argv question is not answered there, but when I found I needed one, I hacked something together to parse /proc/self/cmdline at runtime for pam_cap.so use.

Andrew G Morgan
  • 141
  • 1
  • 6