Mach-O 静态符号绑定

485 阅读4分钟

Mach-O 静态符号绑定

什么是符号

我们在编写代码时书写的函数名、变量名都是符号。正是有符号的存在,在使用函数和变量时,我们无需关心其真实的地址,使用符号便可以完成定义和引用。并且符号也更有助于我们阅读代码。可以简单总结下符号的作用

  • 符号让目标文件可以相互引用,一起链接生成可执行文件。
  • 符号可以帮助我们阅读代码、定位问题。

想对符号有更多了解的,可以阅读下关于 Mac/iOS 平台的符号

符号绑定

代码在完成预处理、编译、汇编后生成目标文件,此时的符号还未进行绑定,也就是说还无法通过符号找到函数和变量的地址。当多个目标文件链接生成可执行文件后,这时符号绑定的工作才完成。简单说符号起到了延时绑定的作用。延时绑定的含义就是不要过早的进行符号绑定,而是推迟到最后的链接阶段。因为链接是整合多个目标文件的过程,只有在整合完成后符号对应的地址才能确定。

静态符号的绑定

准备源代码main.c如下:

// main.c文件

int test() {
	return 1;
}

int main(int argc, const char * argv[]) {
    test();
    return 0;
}

Clang 编译生成目标文件

clang -c main.c -o main.o

目标文件属于Mach-O格式,使用MachOView工具来解析main.o。

关于 Mach-O 的具体格式信息可以参考这一篇:Parsing Macho-O files

MachOView解析main.o,先观察代码段__TEXT,在偏移代码段0x28处,正是在调用test函数,此时函数地址是0x000000,符号还未绑定。

TEXT.png

再观察Relocations重定位表,此处记录了所有需要在链接时进行绑定的符号。它记录着符号在内存中的位置信息和在Symbol Table符号表中的位置。下图,2处Address字段0x28表示符号在所在段内的偏移。3处Symbol字段0x0表示在符号表0x0处记录着该符号的其它信息。

Relocations.png

再看Symbol Table符号表,此处记录了.o文件的所有符号。它记录着符号对应的地址和符号对应在字符串表中的位置。下图,3处Value字段表示符号的地址,此时未绑定,所以值为0。2处String Table Index字段值为1,表示字符在字符串表中的起始位置为1。MachOView已经帮助我们查到在字符串表中起始1处就是_test。

Symbols.png

再看String Table字符表,此处记录了所有符号对应的字符串。下图2处,通过上面符号表中存储的字符串表起始位置1,查到该符号确实是_test。

StringTable.png

Clang 链接目标文件生成可执行

clang main.o -o main

MachOView再解析main可执行文件,发现Relocations重定位表不存在了,说明链接完成符号绑定后,已经不需要再记录符号的位置。再看Symbol Table符号表,value字段值变为0x100003F74,此时符号对应的地址已确定。

SymbolTable2.png

再看代码段__TEXT,在main函数中调用test的函数地址和符号表中地址一致都是0x100003F74。

这里通过汇编代码:bl FFFFF6,计算出函数目标地址。

先求得 0xFFFFF6 = -0x00000A,-0x00000A再左移2位-0x000028。

目标地址 = 当前指令地址 - 0x000028 = 0x00003F9C - 0x000028 = 0x00003F74

这里计算结果和0x100003F74相差0x100000000,是因为我在使用MachOView时,选择的是RAW展示的是相对文件的偏移地址,选择RVA就可以展示载入内存后的偏移地址。

TEXT2.png

总结

静态符号的绑定过程:

  • 编译:生成目标文件,会先生成Relocations重定位表,记录重定位符号的位置信息。
  • 链接:在分段合并所有.o文件的内容后,确定Symbol Table符号表中每个符号的地址。
  • 重定位:再根据重定位表找到符号位置,将符号表中对应的地址绑定到该符号。