TL;博士
我试图把这个问题变成一个简短的问题,但这是一个复杂的问题,所以它最终变得很长。如果您可以回答其中的任何部分或提供任何建议或提示或资源或任何东西,那将非常有帮助(即使您没有直接解决我的所有问题)。我现在正用头撞墙。:)
这是我遇到的具体问题。阅读下文了解更多信息。
- 我正在寻找有关如何处理重定位条目和更新节数据中未解析符号的指导。我只是不明白如何处理我从搬迁和部分等中提取的所有信息。
- 我也希望了解链接器遇到重定位时发生了什么。尝试正确实现重定位方程并以正确的方式使用所有正确的值是非常具有挑战性的。
- 当我遇到操作码和地址和符号等时,我需要了解如何处理它们。我觉得我错过了一些步骤。
- 我觉得我不太了解符号表条目如何与重定位交互。我应该如何使用符号的绑定、可见性、值和大小信息?
- 最后,当我用可执行文件使用的解析数据和新的重定位条目输出我的文件时,数据都是不正确的。我不确定如何跟踪所有搬迁并提供所有必要的信息。可执行文件对我有什么期望?
到目前为止我的方法
我正在尝试以严重基于 ELF 的特定 [未记录] 专有格式创建重定位文件。我编写了一个工具,它采用 ELF 文件和部分链接文件 (PLF) 并处理它们以输出完全解析的 rel 文件。此 rel 文件用于根据需要加载/卸载数据以节省内存。该平台是 32 位 PPC。一个问题是该工具是用 c# 为 Windows 编写的,但数据是为 PPC 准备的,因此需要注意一些有趣的字节序问题等。
我一直在尝试了解在用于解析未解析的符号等时如何处理重定位。到目前为止,我所做的是从 PLF 复制相关部分,然后对于每个相应的 .rela 部分,我解析条目并尝试修复部分数据并根据需要生成新的重定位条目。但这就是我的困难所在。我在这里已经脱离了我的元素,这种事情似乎通常由链接器和加载器完成,所以没有很多好的例子可以借鉴。但我发现了一些有帮助的,包括THIS ONE。
所以正在发生的事情是:
- 从 PLF 复制部分数据以用于 rel 文件。我只对 .init(无数据)、.text、.ctors、.dtors、.rodata、.data、.bss(无数据)和我们正在使用的另一个自定义部分感兴趣。
- 遍历 PLF 中的 .rela 部分并读入 Elf32_Rela 条目。
- 对于每个条目,我提取 r_offset、r_info 和 r_addend 字段,并从 r_info(符号和 reloc 类型)中提取相关信息。
- 从PLF 的符号表中,我可以得到symbolOffset、symbolSection 和symbolValue。
- 从 ELF 中,我得到了 symbolSection 的加载地址。
- 我计算 int localAddress = ( .relaSection.Offset + r_offset )。
- 我从 r_offset 处的 symbolSection 内容中获得了 uint relocValue。
- 现在我有了我需要的所有信息,所以我打开 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 - 怎么办??我需要更新部分数据并构建伴随的重定位条目。但我不明白需要做什么以及如何去做。
我这样做的全部原因是因为有一个旧的过时的不受支持的工具不支持使用自定义部分,这是该项目的关键要求(出于内存原因)。我们有一个自定义部分,其中包含我们要在启动后卸载的一堆初始化代码(总共大约一个兆)。现有工具只是忽略该部分中的所有数据。
因此,虽然制作我们自己的支持自定义部分的工具是理想的,但如果有任何好的想法可以通过另一种方式来实现这一目标,我会全力以赴!我们一直在考虑使用 .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 中的符号表基本上在可执行文件中)?
一些资源:
- 关于搬迁的博客: http: //eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
- 最后提到操作码:http ://wiki.netbsd.org/examples/elf_executables_for_powerpc/
- 我相关的未回答问题:ELF Relocation reverse engineering
哇!这是一个问题。恭喜你做到了这一步。:) 提前感谢您能给我的任何帮助。