链接到库时,__start_section和__stop_section符号丢失

时间:2013-12-18 21:50:11

标签: c gcc elf

我在类似于此主题的autotools C项目中使用自定义elf标头:How do you get the start and end addresses of a custom ELF section in C (gcc)?。问题是声明自定义部分的c文件被链接到一个静态库,然后链接到最终的应用程序。

在此配置中,不会生成符号__start_custom_section和__stop_custom_section。我像这样定义精灵部分:

struct mystruct __attribute((__section__("custom_section"))) __attribute((__used__) = {
...
};

如果我链接到目标文件而不是库,则会创建符号并且一切都按预期工作。这不是一个可扩展的解决方案,因为我希望通过将它们编译到模块库中来使用新模块。知道为什么当库中的部分存在于单个目标文件中时,链接器不会创建这些特殊符号吗?

2 个答案:

答案 0 :(得分:1)

我最近做了类似的事情,我的解决方案不依赖于任何编译器特定的实现,内部未记录的符号等。但是,它确实需要更多的工作:)

<强>背景

磁盘上的ELF二进制文件可以通过了解其格式并使用提供给我们的几个结构来轻松加载和解析:http://linux.die.net/man/5/elf。您可以遍历每个段和节(段是段的容器)。如果执行此操作,则可以计算节的相对开始/结束虚拟地址。通过这种逻辑,您可以认为通过迭代加载的内存中版本的ELF二进制文件的段和部分,您可以在运行时执行相同的操作。但是,唉,你只能自己遍历片段(通过http://linux.die.net/man/3/dl_iterate_phdr),并且所有部分元数据都已丢失。

那么,我们如何保留部分元数据?自己存放。

<强>解决方案

如果你有一个名为&#39; .mycustom&#39;的自定义部分,那么定义一个元数据结构,该结构应该至少存储两个数字,这些数字将指示相对的起始地址和&#39; .mycustom&的大小#39;部分。创建此元数据结构的全局实例,该结构将在名为&#39; .mycustom_meta&#39;的另一个自定义部分中自行

示例:

typedef struct
{
    unsigned long ulStart;
    unsinged long ulSize;
} CustomSectionMeta;

__attribute((__section__(".mycustom_meta"))) CustomSectionMeta g_customSectionMeta = { 0, 0 };

您可以看到我们的struct实例初始化为start和size值都为零。编译此代码时,您的目标文件将包含名为&#39; .mycustom_meta&#39;的部分。 32位编译的大小为8字节(64位为16字节),值为全零。在它上面运行 objdump ,你会看到同样多的东西。如果你愿意,可以把它放到静态库(.a)中,在它上面运行 readelf ,你会看到完全相同的东西。如果需要,将其构建为共享对象(.so),在其上运行 readelf ,然后您将看到同样的事情。将其构建为可执行程序,在其上运行 readelf ,并将其保留在那里。

现在诀窍来了。你需要编写一个小的可执行文件(让我们称之为MetaWriter),它将更新磁盘上的ELF文件以填充起始值和大小值。以下是基本步骤:

  1. 以二进制模式打开ELF文件(.o,.so或可执行文件)并将其读入连续数组。或者,您可以将其映射到内存中以实现相同目的。
  2. 使用上面列出的ELF链接中的标题结构和说明读取二进制文件。
  3. 找到你的.mycustom&#39;部分并阅读section.sh_addr和section.sh_size。
  4. 找到您的&#39; .mycustom_meta&#39;部分。使用步骤3中的开始和大小值创建CustomSectionMeta的实例.memcpy()将您的结构放在现有&#39; .mycustom_meta&#39;的顶部。截面数据,到目前为止都是零。
  5. 将ELF数据保存回原始文件。它现在应该完全不加修改,除了你写入你的“.mycustom_meta”中的几个字节。部分。
  6. 我所做的是在我的Makefile中作为构建过程的一部分执行了这个MetaWriter程序。因此,您将构建.so或可执行文件,然后在其上运行MetaWriter以填充元部分。在那之后,它准备好了。

    现在,当你的.so或可执行文件中的代码运行时,它只能从g_customSectionMeta读取,这将填充你的&#39; .mycustom&#39;的起始地址 offset 。当然,它可以用来轻松计算结束部分,以及它的大小。必须将此开始偏移添加到已加载的ELF二进制文件的基址。有几种方法可以实现这一点,但我找到的最简单的方法是在我知道存在于二进制文件中的符号上运行 dladdr (例如g_customSectionMeta!)并使用结果值< em> dli_fbase 知道模块的基地址。

    示例:

    #include <dlfcn.h>
    Dl_info dlInfo;
    if (dladdr(&g_customSectionMeta, &dlInfo) != 0)
    {
        void * vpBase = dlInfo.dli_fbase;
        void * vpMyCustomStart = vpBase + g_customSectionMeta.ulStart;
        void * vpMyCustomEnd = vpMyCustomStart + g_customSectionMeta.ulSize;
    }
    

    发布完成所有这些工作所需的全部代码,特别是在MetaWriter中解析ELF二进制文件,这将有点过分。但是,如果您需要帮助,请随时与我联系。

答案 1 :(得分:1)

就我而言,代码中未引用该变量,并且该部分已在发布模式(-O2)中进行了优化。添加used属性解决了这个问题。例如:

static const unsigned char unused_var[] __attribute__((used, section("foo"))) = {
    0xCA, 0xFE, 0xBA, 0xBE
};