TL; DR
我试图将这个问题简短化,但是这是一个复杂的问题,因此最终导致问题冗长。如果您可以回答其中的任何部分,或者给出任何建议,技巧,资源或任何其他内容,那将非常有帮助(即使您没有直接解决我的所有问题)。我现在把头撞在墙上。 :)
这是我遇到的具体问题。请在下面阅读以获取更多信息。
我到目前为止的方法
我正在尝试以很大程度上基于ELF的特定[未记录]专有格式创建重定位文件。我编写了一个工具,该工具接受ELF文件和部分链接的文件(PLF),并对其进行处理以输出完全解析的rel文件。该rel文件用于根据需要加载/卸载数据,以节省内存。该平台是32位PPC。一种麻烦之处在于该工具是使用C#为Windows编写的,但是数据是为PPC设计的,因此需要注意有趣的字节序问题等。
我一直试图了解当用于解析未解析的符号等时如何处理重定位。到目前为止,我所做的是从PLF复制相关的节,然后为每个对应的.rela节,我解析条目并尝试修正节数据并根据需要生成新的重定位条目。但这就是我的困难所在。我不在这里,这种事情似乎通常是由链接器和加载器完成的,因此没有很多好的例子可以借鉴。但是我发现一些有用的东西,包括THIS ONE。
因此,正在发生的是:
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文档HERE(在第4.13节,第80ish页左右)以及我挖掘出的许多其他代码示例和博客文章中找到的方程式和信息。但这一切都令人困惑,而且没有真正阐明,我发现的所有代码在功能上都有些不同。
例如,
因此,当我看到这种代码时,该如何解密呢?
这是一个示例(来自this source)
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;
这是另一个示例(here)中的一些
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;
还有另一个示例(from here)
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
函数的this file末尾附近查看,则会看到一些正在创建的重定位条目。它们还具有存根功能。我怀疑这是我所缺少的链接,但它像泥泞一样清晰,我什至没有一点关注。当我在每个重定位条目中输出符号时,它们每个都有一个绑定(bind)/可见性[Global / Weak / Local] [Function / Object]以及一个值,一个大小和一个部分。我知道该部分位于符号所在的位置,值是该部分中符号的偏移量(或者它是虚拟地址吗?)。大小是符号的大小,但这重要吗?也许全局/弱/本地对于确定内部还是外部重定位很有用?
也许我正在谈论创建的重定位表实际上是rel文件的符号表?也许此表将符号值从虚拟地址更新为节偏移量(因为这是可重定位文件中的值,而PLF中的符号表基本上在可执行文件中)?
一些资源:
ew!那真是个问题。恭喜您取得了如此远的成就。 :)预先感谢您可以给我的任何帮助。
最佳答案
我偶然发现了这个问题,并认为它应该得到答案。
方便使用elf.h。您可以在互联网上找到它。
如您所知,每个RELA部分都包含Elf32_Rela条目的数组,但也与其他特定部分绑定(bind)。 r_offset是该其他部分的偏移量(在本例中为it works differently for shared libraries)。您会发现节标题具有一个名为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。我猜有时候编译器需要向重定位添加静态常量。
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似乎足够合理。
我也不知道存根函数。通常,链接时(至少动态地)不需要了解这些内容。
我不确定是否已经回答了您所有的问题,但是您至少应该能够看到示例代码现在所做的事情。
关于reverse-engineering - 处理ELF重定位-了解重定位,符号,剖面数据以及它们如何协同工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16847741/