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

我在类似于这个线程的autotools C项目中使用自定义elf头: 如何在C(gcc)中获得自定义ELF部分的起始和结束地址? 。 问题是声明自定义部分的c文件链接到静态库,然后链接到最终应用程序。

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

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

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

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

背景

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

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

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

例:

 typedef struct { unsigned long ulStart; unsinged long ulSize; } CustomSectionMeta; __attribute((__section__(".mycustom_meta"))) CustomSectionMeta g_customSectionMeta = { 0, 0 }; 

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

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

  1. 以二进制模式打开ELF文件(.o,.so或可执行文件)并将其读入连续数组。 或者,您可以将其映射到内存中以实现相同的目的。
  2. 使用上面列出的ELF链接中的头结构和指令读取二进制文件。
  3. 找到你的’.mycustom’部分并阅读section.sh_addr和section.sh_size。
  4. 找到你的’.mycustom_meta’部分。 使用步骤3中的start和size值创建CustomSectionMeta的实例.memcpy()将结构放在现有“.mycustom_meta”部分数据的顶部,到目前为止全部为零。
  5. 将ELF数据保存回原始文件。 它现在应该是完全未修改的,除了你写入’.mycustom_meta’部分的几个字节。

我做的是在我的Makefile中执行这个MetaWriter程序作为构建过程的一部分。 因此,您将构建.so或可执行文件,然后在其上运行MetaWriter以填充元部分。 在那之后,它准备好了。

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

例:

 #include  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二进制文件,这将有点过分。 但是,如果您需要帮助,请随时与我联系。

在我的例子中,代码中没有引用该变量,并且该部分在发布模式(-O2)中进行了优化。 添加used属性解决了问题。 例:

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