【问题标题】:How to trigger c'tors of globals in executable shared library (.so)?如何在可执行共享库(.so)中触发全局变量的 c'tors?
【发布时间】:2014-08-10 14:16:20
【问题描述】:

我有一个共享库,我想使其可执行,类似于 libc。当库执行时,我希望它转储向特定抽象工厂(这是 C++)注册的类的名称列表。我使用通过全局变量的初始化/构造向工厂注册类的标准技术。

有几个关于如何使共享库可执行的教程(例如herehere)。这是相对简单的。但是,当我尝试它时,我发现入口点是在调用任何全局构造函数之前 执行的。在我的例子中,这意味着我的工厂没有注册任何课程,所以我没有信息可以打印出来。

我想在调用构造函数之后执行入口点,或者学习如何从我的入口点函数自己触发构造。这可能吗?有谁知道该怎么做?也许有一个内部 libc 函数可以外部调用和调用?

【问题讨论】:

  • 你在控制入口点函数代码吗?究竟是什么阻止了您在其中调用全局对象的构造函数? (无知问。)
  • 我可以控制入口点代码,但我相信我需要一种机制来检索要调用的 c'tors 列表。这种机制一定存在,因为正常程序一直都是这样做的,但我自己不知道如何做到这一点。也许我需要深入研究 ELF 文档。

标签: c++ linker shared-libraries factory elf


【解决方案1】:

我相信我已经找到了一个可行的解决方案。它基于创建-nostdlib 可执行文件(例如操作系统内核)的技术。但是,在这种情况下,我们的共享库仍然链接标准库。我发现this 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() 调用。该库在作为常规共享库加载时“正常”工作。

【讨论】:

  • 一个更简单的解决方案是将您的 DSO 与 -pie 标志链接。 stackoverflow.com/a/1451482/50617
  • 哇。 gcc 手册页中的 nothing 表明 -pie 会给我这种行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多