TL; DR
我试图将这个问题作为一个简短的问题,但这是一个复杂的问题所以它最终会变长。如果您可以回答这方面的任何部分或提供任何建议或提示或资源或任何内容,那将非常有帮助(即使您不直接解决我的所有问题)。我现在正撞在墙上。 :)
以下是我遇到的具体问题。请阅读以下内容以获取更多信息。
到目前为止我的方法
我正在尝试以特定的[未记录的]专有格式创建一个重定位文件,该格式主要基于ELF。我编写了一个工具,它接受一个ELF文件和一个部分链接文件(PLF)并处理它们以输出完全解析的rel文件。此rel文件用于根据需要加载/卸载数据以节省内存。该平台是一个32位PPC。一个问题是该工具是用c#编写的,但数据是针对PPC的,因此需要注意有趣的endian问题等。
我一直试图了解在用于解析未解析的符号等时如何处理重定位。到目前为止我所做的是从PLF复制相关部分,然后对于每个相应的.rela部分,我解析条目并尝试修复部分数据并根据需要生成新的重定位条目。但这就是我的困难所在。我在这里摆脱了我的元素,这种事情似乎通常是由链接器和加载器完成的,所以没有很多好的例子可以借鉴。但是我发现了一些有帮助的东西,包括THIS ONE。
所以发生的事情是:
我这样做的全部原因是因为有一个旧的过时不支持的工具不支持使用自定义部分,这是该项目的关键要求(出于内存原因)。我们有一个自定义部分,其中包含一堆初始化代码(总计大约一个兆字节),我们要在启动后卸载它们。现有工具只是忽略该部分中的所有数据。
因此,虽然制作支持自定义部分的自己的工具是理想的,但如果有任何明智的想法以实现这一目标的另一种方式,我全都耳朵!我们已经提出了将.dtor部分用于我们的数据的想法,因为它几乎是空的。但这很麻烦,如果它阻止干净关闭,可能无论如何都不会起作用。
重新定位加示例代码
当我处理重定位时,我正在处理ABI文档HERE中的方程式和信息(第4.13节,第80页)以及其他一些代码示例和博客我挖出的帖子。但这一切都让人感到困惑,而且并没有真正拼写出来,我发现的所有代码都有所不同。
例如,
所以,当我看到这种代码时,我该如何解读呢?
以下是一个例子(来自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;
我真的很想了解这些家伙在这里做的魔术以及为什么他们的代码看起来并不总是一样。我认为一些代码假设数据已被正确掩盖(对于分支等),而某些代码则没有。但我根本不了解这一点。
遵循符号/数据/重定位等
当我在hexeditor中查看数据时,我看到了一堆&#34; 48 00 00 01&#34;遍。我已经发现这是一个操作码,需要使用重定位信息进行更新(这特别适用于&#39; bl&#39;分支和链接),但我的工具并不适用于广大的操作其中大部分和我更新的内容都有错误的值(与过时工具的示例相比)。很明显,我错过了这个过程的某些部分。
除了节数据外,还需要将其他重定位条目添加到rel文件的末尾。这些包括内部和外部的重新安置,但我还没有完全了解这些。 (两者之间的差异以及何时使用其中一种?)
如果您在函数RuntimeDyldELF::processRelocationRef
处查看this file的末尾附近,则会看到正在创建的某些重定位条目。他们还制作存根功能。我怀疑这对我来说是缺失的环节,但是它和泥浆一样清晰,我甚至没有跟踪它。
当我在每个重定位条目中输出符号时,它们每个都具有绑定/可见性[全局/弱/本地] [功能/对象]以及值,大小和部分。我知道该部分是符号所在的部分,值是该部分符号的偏移量(或者是虚拟地址?)。大小是符号的大小,但这很重要吗?也许全局/弱/本地对于确定它是内部还是外部重定位很有用?
也许这个重定位表我在谈论创建实际上是我的rel文件的符号表?也许这个表将符号值从虚拟地址更新为部分偏移量(因为可重定位文件中的值是什么,PLF中的符号表基本上是可执行文件)?
部分资源:
呼!这是一个问题的野兽。恭喜你,如果你做到这一点。 :)提前感谢您提供的任何帮助。
答案 0 :(得分:14)
我偶然发现了这个问题并认为应该得到答案。
有elf.h方便。你可以在互联网上找到它。
如您所知,每个RELA部分都包含一个Elf32_Rela条目数组,但也与某个其他部分相关联。 r_offset是其他部分的偏移量(在本例中为it works differently for shared libraries)。您会发现节标题有一个名为sh_info的成员。这告诉你哪个部分。 (它是您期望的节头表的索引。)
你从r_info获得的'symbol'实际上是一个驻留在另一个部分的符号表的索引。在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;
假设我们正在处理重新安置号码。
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-&gt; 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似乎足够合理。
我也不知道存根函数。在链接时(通常至少动态地),您通常不需要知道这些。
我不确定我是否已回答了您的所有问题,但您应该能够看到您的示例代码现在至少做了什么。
答案 1 :(得分:1)
尝试进入ELF规范。它大约需要60页,并且可以大大澄清事情。特别是第2部分,关于链接。
答案 2 :(得分: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