30

TL;博士

我试图把这个问题变成一个简短的问题,但这是一个复杂的问题,所以它最终变得很长。如果您可以回答其中的任何部分或提供任何建议或提示或资源或任何东西,那将非常有帮助(即使您没有直接解决我的所有问题)。我现在正用头撞墙。:)

这是我遇到的具体问题。阅读下文了解更多信息。

  • 我正在寻找有关如何处理重定位条目和更新节数据中未解析符号的指导。我只是不明白如何处理我从搬迁和部分等中提取的所有信息。
  • 我也希望了解链接器遇到重定位时发生了什么。尝试正确实现重定位方程并以正确的方式使用所有正确的值是非常具有挑战性的。
  • 当我遇到操作码和地址和符号等时,我需要了解如何处理它们。我觉得我错过了一些步骤。
  • 我觉得我不太了解符号表条目如何与重定位交互。我应该如何使用符号的绑定、可见性、值和大小信息?
  • 最后,当我用可执行文件使用的解析数据和新的重定位条目输出我的文件时,数据都是不正确的。我不确定如何跟踪所有搬迁并提供所有必要的信息。可执行文件对我有什么期望?

到目前为止我的方法

我正在尝试以严重基于 ELF 的特定 [未记录] 专有格式创建重定位文件。我编写了一个工具,它采用 ELF 文件和部分链接文件 (PLF) 并处理它们以输出完全解析的 rel 文件。此 rel 文件用于根据需要加载/卸载数据以节省内存。该平台是 32 位 PPC。一个问题是该工具是用 c# 为 Windows 编写的,但数据是为 PPC 准备的,因此需要注意一些有趣的字节序问题等。

我一直在尝试了解在用于解析未解析的符号等时如何处理重定位。到目前为止,我所做的是从 PLF 复制相关部分,然后对于每个相应的 .rela 部分,我解析条目并尝试修复部分数据并根据需要生成新的重定位条目。但这就是我的困难所在。我在这里已经脱离了我的元素,这种事情似乎通常由链接器和加载器完成,所以没有很多好的例子可以借鉴。但我发现了一些有帮助的,包括THIS ONE

所以正在发生的事情是:

  1. 从 PLF 复制部分数据以用于 rel 文件。我只对 .init(无数据)、.text、.ctors、.dtors、.rodata、.data、.bss(无数据)和我们正在使用的另一个自定义部分感兴趣。
  2. 遍历 PLF 中的 .rela 部分并读入 Elf32_Rela 条目。
  3. 对于每个条目,我提取 r_offset、r_info 和 r_addend 字段,并从 r_info(符号和 reloc 类型)中提取相关信息。
  4. 从PLF 的符号表中,我可以得到symbolOffset、symbolSection 和symbolValue。
  5. 从 ELF 中,我得到了 symbolSection 的加载地址。
  6. 我计算 int localAddress = ( .relaSection.Offset + r_offset )。
  7. 我从 r_offset 处的 symbolSection 内容中获得了 uint relocValue。
  8. 现在我有了我需要的所有信息,所以我打开 reloc 类型并处理数据。These are the types I support:
    R_PPC_NONE
    R_PPC_ADDR32
    R_PPC_ADDR24
    R_PPC_ADDR16
    R_PPC_ADDR16_LO
    R_PPC_ADDR16_HI
    R_PPC_ADDR16_HA
    R_PPC_ADDR14
    R_PPC_ADDR14_BRTAKEN
    R_PPC_ADDR14_BRNTAKEN
    R_PPC_REL24
    R_PPC_REL14
    R_PPC_REL14_BRTAKEN
    R_PPC_REL14_BRNTAKEN
  9. 怎么办??我需要更新部分数据并构建伴随的重定位条目。但我不明白需要做什么以及如何去做。

我这样做的全部原因是因为有一个旧的过时的不受支持的工具不支持使用自定义部分,这是该项目的关键要求(出于内存原因)。我们有一个自定义部分,其中包含我们要在启动后卸载的一堆初始化代码(总共大约一个兆)。现有工具只是忽略该部分中的所有数据。

因此,虽然制作我们自己的支持自定义部分的工具是理想的,但如果有任何好的想法可以通过另一种方式来实现这一目标,我会全力以赴!我们一直在考虑使用 .dtor 部分来存储我们的数据,因为它几乎是空的。但这很麻烦,如果它阻止了干净的关机,它可能无论如何都不起作用。


重定位加上示例代码

当我处理重定位时,我正在处理 ABI 文档中的方程式和信息大约第 4.13 节,第 80 页)以及我挖掘的许多其他代码示例和博客文章。但这一切都如此令人困惑,并没有真正拼写出来,而且我发现的所有代码的作用都有些不同。

例如,

  • R_PPC_ADDR16_LO --> half16: #lo(S + A)
  • R_PPC_ADDR14_BRTAKEN --> low14*: (S + A) >> 2
  • ETC

那么当我看到这种代码时,我该如何破译呢?

这是一个示例(来自此来源

case ELF::R_PPC64_ADDR14 : {
    assert(((Value + Addend) & 3) == 0);
    // Preserve the AA/LK bits in the branch instruction
    uint8_t aalk = *(LocalAddress+3);
    writeInt16BE(LocalAddress + 2, (aalk & 3) | ((Value + Addend) & 0xfffc));
} break;

case ELF::R_PPC64_REL24 : {
    uint64_t FinalAddress = (Section.LoadAddress + Offset);
    int32_t delta = static_cast<int32_t>(Value - FinalAddress + Addend);
    if (SignExtend32<24>(delta) != delta)
        llvm_unreachable("Relocation R_PPC64_REL24 overflow");
    // Generates a 'bl <address>' instruction
    writeInt32BE(LocalAddress, 0x48000001 | (delta & 0x03FFFFFC));
} break;

这是另一个例子中的一些(这里

case R_PPC_ADDR32: /* word32 S + A */
    addr = elf_lookup(lf, symidx, 1);
    if (addr == 0)
        return -1;
    addr += addend;
    *where = addr;
    break;

case R_PPC_ADDR16_LO: /* #lo(S) */
    if (addend != 0) {
        addr = relocbase + addend;
    } else {
        addr = elf_lookup(lf, symidx, 1);
        if (addr == 0)
            return -1;
    }
    *hwhere = addr & 0xffff;
    break;

case R_PPC_ADDR16_HA: /* #ha(S) */
    if (addend != 0) {
        addr = relocbase + addend;
    } else {
        addr = elf_lookup(lf, symidx, 1);
        if (addr == 0)
            return -1;
    }
    *hwhere = ((addr >> 16) + ((addr & 0x8000) ? 1 : 0)) & 0xffff;
    break;

还有另一个例子(来自这里

case R_PPC_ADDR16_HA:
    write_be16 (dso, rela->r_offset, (value + 0x8000) >> 16);
    break;
case R_PPC_ADDR24:
    write_be32 (dso, rela->r_offset, (value & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003));
    break;
case R_PPC_ADDR14:
    write_be32 (dso, rela->r_offset, (value & 0xfffc) | (read_ube32 (dso, rela->r_offset) & 0xffff0003));
    break;
case R_PPC_ADDR14_BRTAKEN:
case R_PPC_ADDR14_BRNTAKEN:
    write_be32 (dso, rela->r_offset, (value & 0xfffc)
                                    | (read_ube32 (dso, rela->r_offset) & 0xffdf0003)
                                    | ((((GELF_R_TYPE (rela->r_info) == R_PPC_ADDR14_BRTAKEN) << 21)
                                    ^ (value >> 10)) & 0x00200000));
    break;
case R_PPC_REL24:
    write_be32 (dso, rela->r_offset, ((value - rela->r_offset) & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003));
    break;
case R_PPC_REL32:
    write_be32 (dso, rela->r_offset, value - rela->r_offset);
    break;

我真的很想了解这些人在这里所做的魔法以及为什么他们的代码看起来并不总是一样的。我认为一些代码假设数据已经被正确屏蔽(用于分支等),而一些代码则没有。但我一点也不明白。


遵循符号/数据/重定位等

当我在 hexeditor 中查看数据时,我看到一堆“48 00 00 01”。我发现这是一个操作码,需要使用重定位信息进行更新(这专门用于 'bl' 分支和链接),但我的工具不能在绝大多数和我所做的那些上运行更新中有错误的值(与过时工具制作的示例相比)。显然,我错过了该过程的某些部分。

除了节数据之外,还有一些额外的重定位条目需要添加到 rel 文件的末尾。这些包括内部和外部搬迁,但我还没有弄清楚这些。(两者有什么区别,什么时候使用其中一个?)

如果您在该文件的末尾附近查看function RuntimeDyldELF::processRelocationRef,您会看到正在创建一些重定位条目。他们还制作存根函数。我怀疑这对我来说是缺失的环节,但它就像泥泞一样清晰,我什至没有关注它。

当我在每个重定位条目中输出符号时,它们每个都有一个绑定/可见性 [Global/Weak/Local] [Function/Object] 和一个值、一个大小和一个部分。我知道该部分是符号所在的位置,并且值是该部分中符号的偏移量(或者它是虚拟地址?)。大小就是符号的大小,但这重要吗?也许全局/弱/本地对于确定它是内部还是外部重定位很有用?

也许我正在谈论创建的这个重定位表实际上是我的 rel 文件的符号表?也许这个表将符号值从虚拟地址更新为节偏移量(因为这是可重定位文件中的值,而 PLF 中的符号表基本上在可执行文件中)?


一些资源:

  1. 关于搬迁的博客: http: //eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
  2. 最后提到操作码:http ://wiki.netbsd.org/examples/elf_executables_for_powerpc/
  3. 我相关的未回答问题:ELF Relocation reverse engineering

哇!这是一个问题。恭喜你做到了这一步。:) 提前感谢您能给我的任何帮助。

4

3 回答 3

20

我偶然发现了这个问题,并认为它值得一个答案。

有 elf.h 方便。你可以在互联网上找到它。

如您所知,每个 RELA 部分都包含一组 Elf32_Rela 条目,但也与某个其他部分相关联。r_offset 是其他部分的偏移量(在这种情况下 -它对共享库的工作方式不同)。您会发现节标题有一个名为 sh_info 的成员。这告诉你那是哪个部分。(正如您所期望的,它是节标题表的索引。)

您从 r_info 获得的“符号”实际上是位于另一个部分的符号表的索引。在 RELA 部分的标题中查找成员 sh_link。

符号表以 Elf32_Sym 的 st_name 成员的形式告诉您要查找的符号的名称。st_name 是字符串部分的偏移量。也就是说,您从符号表的部分标题的 sh_link 成员中获得哪个部分。对不起,如果这变得混乱。

Elf32_Shdr *sh_table = elf_image + ((Elf32_Ehdr *)elf_image)->e_shoff;
Elf32_Rela *relocs = elf_image + sh_table[relocation_section_index]->sh_offset;

unsigned section_to_modify_index = sh_table[relocation_section_index].sh_info;
char *to_modify = elf_image + sh_table[section_to_modify_index].sh_offset;

unsigned symbol_table_index = sh_table[relocation_section_index].sh_link;
Elf32_Sym *symbol_table = elf_image + sh_table[symbol_table_index].sh_offset;

unsigned string_table_index = sh_table[symbol_table].sh_link;
char *string_table = elf_image + sh_table[string_table_index].sh_offset;

假设我们正在使用重定位编号 i。

Elf32_Rela *rel = &relocs[i];
Elf32_Sym *sym = &symbol_table[ELF32_R_SYM(rel->r_info)];
char *symbol_name = string_table + sym->st_name;

找到该符号的地址(假设 symbol_name == "printf")。最终值将进入 (to_modify + rel->r_offset)。

至于您链接的pdf第79-83页上的表格,它告诉我们在该地址放置什么,以及要写入多少字节。显然,我们刚刚得到的地址(在这种情况下是 printf 的)是其中大部分的一部分。它对应于表达式中的 S。

r_addend 只是 A。我猜有时编译器需要向 reloc 添加一个静态常量。

B 是共享对象的基地址,或者 0 表示可执行程序,因为它们没有被移动。

所以如果 ELF32_R_TYPE(rel->r_info) == R_PPC_ADDR32 我们有 S + A,字长是 word32,所以我们会得到:

*(uint32_t *)(to_modify + rel->r_offset) = address_of_printf + rel->r_addend;

...我们已经成功地进行了搬迁。

当涉及到#lo、#hi 等以及像low14 这样的字长时,我无能为力。我对 PPC 一无所知,但链接的 pdf 似乎足够合理。

我也不知道存根函数。在链接时,您通常不需要知道那些(至少是动态的)。

我不确定我是否已经回答了你所有的问题,但你至少应该能够看到你的示例代码现在做了什么。

于 2014-05-30T00:51:01.443 回答
1

只是回答这个问题:什么是搬迁?

当你编写汇编程序时,如果它是位置相关的,则程序将被假定加载到内存的特定部分。当您将程序加载到另一个内存地址时,所有内存相关寻址数据都必须更新,以便从新内存区域正确加载。

例如:

负载 A、B

有时 B 可以是一个地址,有时它只是纯常量数据。所以只有编译器会知道他们何时生成程序集。编译器将构建一个包含偏移量及其原始加载相对地址的所有条目的表,假设加载它的位置是固定的全局地址。这称为重定位表。

它链接到符号表、PLT 表和 GOT 等:

在此处输入图像描述

http://blog.k3170makan.com/2018/10/introduction-to-elf-format-part-vi_18.html

https://reverseengineering.stackexchange.com/questions/1992/what-is-plt-got

搬迁的概念是什么意思?

C++ 链接在实践中是如何工作的?

于 2020-03-01T05:34:38.523 回答
0

尝试进入 ELF 规范。它大约需要 60 页,并且非常清楚地说明了事情。特别是第 2 部分,关于链接的部分。

于 2016-09-15T21:33:57.410 回答