Как да задействам c'tors на глобали в изпълнима споделена библиотека (.so)?

Имам споделена библиотека, която бих искал да направя изпълнима, подобна на libc. Когато библиотеката се изпълни, бих искал да изхвърли списък с имената на класове, които са регистрирани в определена абстрактна фабрика (това е C++). Използвам стандартната техника за регистриране на класове във фабриката чрез инициализация/конструиране на глобални променливи.

Има няколко урока как да направите споделените библиотеки изпълними (тук и тук, например). Това е сравнително право напред. Въпреки това, когато го изпробвам, установявам, че входната точка се изпълнява преди да бъдат извикани конструктори на глобални елементи. В моя случай това означава, че няма регистрирани класове в моята фабрика, така че нямам информация за разпечатване.

Бих искал или да изпълня входната точка след като конструкторите са били извикани, или да науча как да сам задействам конструирането от моята функция за входна точка. Възможно ли е това? Някой знае ли как да направя това? Може би има вътрешна функция на libc, която мога да изведа и извикам?


person Glenn    schedule 19.06.2014    source източник
comment
Вие контролирате ли функционалния код на входната точка? Какво точно ви спира да извикате конструкторите на вашите глобални обекти там? (Пита от незнание.)   -  person Matt Phillips    schedule 23.06.2014
comment
Мога да контролирам кода на входната точка, но смятам, че ще ми е необходим механизъм за извличане на списък от c'tors за повикване. Този механизъм трябва да съществува, тъй като нормалните програми правят това през цялото време, но аз не знам как да направя това. Може би трябва да се заровя по-дълбоко в документацията на ELF.   -  person Glenn    schedule 24.06.2014


Отговори (1)


Вярвам, че попаднах на работещо решение. Базира се на техники за създаване на -nostdlib изпълними файлове (като OS ядра). Нашата споделена библиотека обаче все още свързва стандартните библиотеки в този случай. Намерих тази тема във форума на RaspberryPi за особено полезна.

Решението е ръчно да се изпълнят указателите на функции, съхранени във вградения init_array на споделената библиотека. Номерът е да използвате скрипт за свързване, за да дефинирате указатели за достъп до този масив. След това extern тези указатели в програмния код. Можем да повторим процеса и за изпълнение на деструктори.

В test.cpp имаме следното:

#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);
}

Трябва ръчно да дефинираме _manual* указателите чрез скрипт за свързване. Трябва да използваме ключовата дума INSERT, за да не заменим изцяло скрипта за свързване по подразбиране на ld. Във файл test.ld имаме:

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 */

Трябва да предоставим два параметъра на линкера: (1) нашия скрипт на линкер и (2) входната точка към нашата библиотека. За да компилираме, правим следното:

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

Получаваме следния изход, когато libtest.so се изпълни в командния ред:

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

Също така се опитах да дефинирам глобални стойности в .cpp файлове, различни от test.cpp, които също са компилирани в споделената библиотека. Извикванията на ctor за тези глобални са включени в init_array, така че те се извикват от manual_init(). Библиотеката работи "като нормално", когато е заредена като обикновена споделена библиотека.

person Glenn    schedule 24.06.2014
comment
Много по-просто решение е да свържете своя DSO с -pie флаг. stackoverflow.com/a/1451482/50617 - person Employed Russian; 28.06.2014
comment
Еха. Няма нищо в gcc man страницата, което да предполага, че -pie ще ми даде това поведение. - person Glenn; 29.06.2014