简而言之
我试图把这个问题简化,但它是一个复杂的问题,所以最终变得很长。如果你能回答其中任何一部分,或者给出任何建议、提示、资源或任何有用的信息,都将非常有帮助(即使你不能直接解决我的所有问题)。我现在正在头痛难忍。
以下是我遇到的具体问题。请继续阅读下面的内容了解更多信息。
我正在寻求关于如何处理重定位条目并更新节数据中未解析符号的指导。我对从重定位和节等提取的所有信息应该怎样处理感到困惑。当链接器遇到重定位时,我也希望了解到具体发生了什么。尝试正确实现重定位方程式并以正确的方式使用所有正确的值是非常具有挑战性的。
当我遇到操作码、地址和符号等内容时,我需要了解如何处理它们。我感觉我漏掉了一些步骤。
我感觉我没有很好地掌握符号表条目与重定位之间的交互。我应该如何使用符号的绑定、可见性、值和大小信息呢?
最后,当我输出我的文件时,包括可执行文件使用的已解析数据和新的重定位条目时,数据都不正确。我不确定如何跟踪所有的重定位并提供所有必要的信息。可执行文件对我期望什么呢?
我的做法
我正在尝试创建一个特定的[未公开]专有格式的重定位文件,该文件严重基于ELF。我编写了一个工具,它接收一个ELF文件和一个部分链接文件(PLF),并将它们处理成输出完全解析的rel文件。这个rel文件用于根据需要加载/卸载数据以节省内存。平台是32位PPC。其中一个麻烦之处在于该工具是用c#在Windows上编写的,但数据是针对PPC的,因此需要注意字节序等问题。
我一直在尝试理解重定位是如何处理未解析的符号等问题的。到目前为止,我已经从PLF中复制了相关部分,并且对于每个相应的.rela部分,我解析条目并尝试修复部分数据并根据需要生成新的重定位条目。但这就是我的困难所在。我在这方面完全不擅长,这种事情通常由链接器和装载程序完成,因此没有太多好的示例可供参考。但我找到了一些有帮助的资料,包括这个。所以正在发生的是:
- 从PLF中复制部分数据以用于rel文件。我只关心.init(无数据)、.text、.ctors、.dtors、.rodata、.data、.bss(无数据)和另一个我们正在使用的自定义部分。
- 遍历PLF中的.rela部分并读取Elf32_Rela条目。
- 对于每个条目,我提取r_offset、r_info和r_addend字段,并从r_info中提取相关信息(符号和重定位类型)。
- 从PLF的符号表中,我可以获取symbolOffset、symbolSection和symbolValue。
- 从ELF中,我获取symbolSection的加载地址。
- 我计算int localAddress =(.relaSection.Offset + r_offset)。
- 我从symbolSection的内容在r_offset处获取uint relocValue。
- 现在我有了所有需要的信息,所以我根据重定位类型进行switch并处理数据。这些是我支持的类型:
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 文档 此处链接 中的方程和信息,以及我挖掘出来的许多其他代码示例和博客文章。但这一切都很令人困惑,没有很好地解释清楚,而我找到的所有代码都有些不同。
例如:
- R_PPC_ADDR16_LO --> half16: #lo(S + A)
- R_PPC_ADDR14_BRTAKEN --> low14*: (S + A) >> 2
- 等等
所以,当我看到这种代码时,我该如何解读它?
以下是一个示例(来自 此源码)
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;
我真的很想了解这些人在做什么以及为什么他们的代码看起来并不总是相同。我认为一些代码假设数据已经正确地屏蔽(对于分支等),而另一些代码则没有。但我完全不理解这一切。
跟随符号/数据/重定位等操作,当我在十六进制编辑器中查看数据时,到处都是“48 00 00 01”。我已经弄清楚这是一个操作码,并且需要更新重定位信息(这具体是针对'bl'分支和链接的),但我的工具并不适用于其中的绝大部分,而我更新的那些则有错误的值(与过时工具创建的示例相比)。显然我漏掉了某个步骤。
除了节数据之外,还需要将其他重定位条目添加到rel文件的末尾。这些包括内部和外部重定位,但我还没有弄清楚它们之间的区别以及何时使用其中之一。
如果你查看这个文件末尾的函数
RuntimeDyldELF :: processRelocationRef
,你会看到一些重定位条目被创建。它们还创建了存根函数。我怀疑这是我缺失的链接,但它就像泥一样浑浊,我一点也不明白。当我输出每个重定位条目中的符号时,它们都有一个绑定/可见性[全局/弱/本地][函数/对象]和一个值、大小和一个部分。我知道部分是符号所在的位置,值是该部分中符号的偏移量(或者是虚拟地址吗?)。大小是符号的大小,但这很重要吗?也许全局/弱/本地对于确定它是内部重定位还是外部重定位有用?
也许我正在谈论创建的这个重定位表实际上是我的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
哇!那是一个非常棘手的问题。如果你看到这里了,恭喜你!:) 非常感谢您能给我任何帮助。