3

I have a shared library that I would like to make executable, similar to libc. When the library executes, I would like it to dump a list of the names of classes that are registered with a particular abstract factory (this is C++). I use the standard technique of registering classes with the factory through the initialization/construction of global variables.

There are several tutorials on how to make shared libraries executable (here and here, for example). It's relatively straight forward. However, when I try it out, I find that the entry point is executed before any constructors of globals are called. In my case, this means that no classes have registered with my factory, so I have no information to print out.

I would like to either execute the entry point after the constructors have been called, or learn how to trigger construction myself from my entry-point function. Is this possible? Does anyone know how to go about doing this? Perhaps there is an internal libc function that I can extern and call?

Community
  • 1
  • 1
Glenn
  • 386
  • 3
  • 12
  • Are you in control of the entry point function code? What exactly stops you from invoking the constructors of your global objects therein? (Asking from ignorance.) – Matt Phillips Jun 23 '14 at 17:45
  • I can control the entry point code, but I believe that I would need a mechanism to retrieve a list of c'tors to call. This mechanism must exist since normal programs do this all the time, but I don't know how to do this myself. Perhaps I need to dig deeper into the ELF documentation. – Glenn Jun 24 '14 at 12:24

1 Answers1

3

I believe that I have come across a workable solution. It is based upon techniques for creating -nostdlib executables (such as OS kernels). However, our shared library still links the standard libraries in this case. I found this RaspberryPi forum thread especially useful.

The solution is to manually execute the function pointers stored in the shared library's embedded init_array. The trick is to use a linker script to define pointers for accessing this array. We then extern these pointers in the program code. We can repeat the process for executing destructors as well.

In test.cpp, we have the following:

#include <cstdio>
#include <unistd.h>

class Test
{
public:
    Test() { printf("Hello world!\n"); }
    ~Test() { printf("Goodbye world!\n"); }
};

Test myGlobal;  // a global class instance

extern "C"
{
// ensures linker generates executable .so (assuming x86_64)
extern const char test_interp[] __attribute__((section(".interp"))) =
    "/lib64/ld-linux-x86-64.so.2";

// function walks the init_array and executes constructors
void manual_init(void)
{
    typedef void (*constructor_t)(void);
    // _manual_init_array_start and _manual_init_array_end
    // are created by linker script.
    extern constructor_t _manual_init_array_start[];
    extern constructor_t _manual_init_array_end[];

    constructor_t *fn = _manual_init_array_start;
    while(fn < _manual_init_array_end)
    {
        (*fn++)();
    }
}

// function walks the fini_array and executes destructors
void manual_fini(void)
{
    typedef void (*destructor_t)(void);
    // _manual_init_array_start and _manual_init_array_end
    // are created by linker script.
    extern destructor_t _manual_fini_array_start[];
    extern destructor_t _manual_fini_array_end[];

    destructor_t *fn = _manual_fini_array_start;
    while(fn < _manual_fini_array_end)
    {
        (*fn++)();
    }
}

// entry point for libtest.so when it is executed
void lib_entry(void)
{
    manual_init();  // call ctors
    printf("Entry point of the library!\n");
    manual_fini();  // call dtors

    _exit(0);
}

We need to manually define the _manual* pointers through a linker script. We must use the INSERT keyword so that don't entirely replace ld's default linker script. In a file test.ld, we have:

SECTIONS
{
    .init_array : ALIGN(4)
    {
        _manual_init_array_start = .;
        *(.init_array)
        *(SORT_BY_INIT_PRIORITY(.init_array.*))
        _manual_init_array_end = .;
    }
}
INSERT AFTER .init; /* use INSERT so we don't override default linker script */

SECTIONS
{
    .fini_array : ALIGN(4)
    {
        _manual_fini_array_start = .;
        *(.fini_array)
        *(SORT_BY_INIT_PRIORITY(.fini_array.*))
        _manual_fini_array_end = .;
    }
}
INSERT AFTER .fini; /* use INSERT so we don't override default linker script */

We must provide two parameters to the linker: (1) our linker script and (2) the entry point to our library. To compile, we do the following:

 g++ test.cpp -fPIC -Wl,-T,test.ld -Wl,-e,lib_entry -shared -o libtest.so

We get the following output when libtest.so is executed at the commandline:

$ ./libtest.so 
Hello world!
Entry point of the library!
Goodbye world!

I've also tried defining globals in .cpp files other than test.cpp that are also compiled into the shared library. The ctor calls for these globals are included in init_array, so they are called by manual_init(). The library works "like normal" when it is loaded as a regular shared library.

Glenn
  • 386
  • 3
  • 12