4

I build a position-independent executable (puppet below), dynamically load via dlopen, get its main via dlsym, and call it. This works fine, as long as this PIE doesn't use iostream, specifically cout. If it does, the code segfaults with the stack trace below.

If I do roughly the same thing, but compile the puppet as a shared library, not a PIE (renaming main to something else), everything works fine.

What could be causing this problem? It exists only on Linux; on a Mac this works fine.

puppet.cpp:

#include <iostream>

int main(int argc, char** argv)
{
    std::cout << "Hello from puppet" << std::endl;
}

main.cpp:

#include <iostream>

#include <unistd.h>
#include <errno.h>
#include <dlfcn.h>

int main()
{
    std::cout << "Hello from main" << std::endl;

    typedef     int  (*MainType)(int argc, char *argv[]);

    void* lib = dlopen("./puppet", RTLD_LAZY);
    if (lib == NULL)
    {
        std::cerr << "Unable to open the library" << std::endl;
        return 1;
    }

    MainType f = (MainType) dlsym(lib, "main");
    if (f == NULL)
    {
        std::cerr << "Unable to get the function" << std::endl;
        return 1;
    }

    int argc = 0; char** argv = 0;
    f(argc, argv);
}

Makefile:

all: main puppet

puppet: puppet.cpp
        g++ puppet.cpp -o puppet -fPIE -Wl,-pie -Wl,--export-dynamic

main: main.cpp
        g++ main.cpp -o main -ldl

Backtrace from the segfault:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff795da56 in std::ostream::sentry::sentry (this=0x7fffffffe080, __os=...) at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream.tcc:46
46      /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream.tcc: No such file or directory.
(gdb) back
#0  0x00007ffff795da56 in std::ostream::sentry::sentry (this=0x7fffffffe080, __os=...) at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream.tcc:46
#1  0x00007ffff795e109 in std::__ostream_insert<char, std::char_traits<char> > (__out=..., __s=__s@entry=0x7ffff6ce0b95 "Hello from puppet", __n=17) at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/lib
stdc++-v3/include/bits/ostream_insert.h:76
#2  0x00007ffff795e5a9 in std::operator<< <std::char_traits<char> > (__out=..., __s=0x7ffff6ce0b95 "Hello from puppet") at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/char_t
raits.h:320
#3  0x00007ffff6ce0a87 in main () from ./puppet
#4  0x0000555555554a7e in main ()
foxcub
  • 2,517
  • 2
  • 27
  • 27
  • Some sort of ODR violation? Are those `cout` objects the same in both main calls? – user7860670 May 08 '18 at 18:11
  • Please compile with `-g`. – 273K May 08 '18 at 18:12
  • @VTT Adding `printf("%p\n", &std::cout)` to both main.cpp and puppet.cpp shows two different addresses. But I think that's expected. – foxcub May 08 '18 at 18:15
  • @S.M. Adding `-g` didn't change the backtrace since almost all of it is in libstdc++ – foxcub May 08 '18 at 18:16
  • 1
    `main` is not an entry point of an executable. Many initializing things must be done before it. What happens if you call `_start`? https://stackoverflow.com/questions/29694564/what-is-the-use-of-start-in-c – 273K May 08 '18 at 18:20
  • @S.M. Calling `_start` produces the same segfault. – foxcub May 08 '18 at 18:24
  • @VTT Looking into it more, you are right. If I build not a PIE, but a normal shared library, there the address of `std::cout` is the same as in `main.cpp`. Any idea how to fix this in the PIE case? – foxcub May 08 '18 at 18:55
  • Basically, executable programs aren't meant to be run this way. You can use the exec-family for that. – Lorinczy Zsigmond May 09 '18 at 07:21
  • @LorinczyZsigmond Unfortunately, I can't. Without going into details, I have good reasons for "launching" an executable this way. With C, this works perfectly. On a Mac, this works perfectly. It's C++ under Linux that's causing these headaches. – foxcub May 09 '18 at 16:38
  • `main` is magic: it has multiple permitted signatures and isn’t mangled. Combine that with dynamic initialization of variables with static duration and it would be surprising if it did work. – Davis Herring May 10 '18 at 20:12
  • @DavisHerring But it works fine on a Mac. And if I compile `puppet.cpp` as a shared library, it also works fine on Linux. Somehow in the latter case initialization is done correctly by `dlopen`, whereas in the case of a PIE it's not. It's the discrepancy between PIE and normal shared libraries that bothers me, and that I'd like to fix. – foxcub May 10 '18 at 21:11
  • I am having the same problem. I think this is a result of updating distributions. – LUser Mar 06 '23 at 09:15

1 Answers1

0

I was having this same issue, but the issue appears to be compiler related.

I switched from g++-13 to the older g++-11 and it works fine.

LUser
  • 1,127
  • 4
  • 23
  • 39