本博客在 X86-64 系统上运行,实验用例为以下两个 C 语言程序
main.c
#include<stdio.h> int sum(int *a, int n); int array[2] = {1, 2}; int main(){ int val = sum(array, 2); return val; }
sum.c
int sum(int *a, int n){ int i, s = 0; for(i = 0; i < n; i++){ s += a[i]; } return s; }
一、下面是一个典型程序的转换处理过程:
本博客主要讲解可重定位目标文件(.o 文件),所以前面的处理过程就不展开讲解了。
二、使用链接的前提是利用符号表示跳转位置和变量的方法简化了问题(汇编)
左侧全部使用 0 1 来表示程序, 0010 表示“跳转”指令,指令后接的就是跳转的地址,现在跳转的地址是 0101 (即 5),但是如果在 3 的位置插入一条指令的话,那么需要跳转的地址可能就不是 0101 了,从而导致程序的修改。
而右侧使用符号很好地为程序员解决了这个烦恼。在程序中,用助记符表示操作码,用符号表示位置,用助记符表示寄存器。
例如上例,先确定 L0 的地址,再在 jmp 指令中填入 L0 的地址。
三、ELF 可重定位目标文件格式
四、 ELF 头
我们重点了解 ELF 头 和 节头表 的信息。
ELF 头位于 ELF 文件开始,包含文件结构的说明信息,分 32 位系统和 64 位系统,下面分别是 32 位系统和 64 位系统对应的 ELF 头的数据结构。
在Linux 的 /usr/include/elf.h 文件中查看
其中魔数( Magic number) :文件开头几个字节,用来确定文件的类型或格式。(加载或读取文件时,用魔数确认文件类型是否正确)
a.out 的魔数:01H 07H
PE 的魔数:4DH 5AH
ELF 的魔数:7f 45 4c 46 (其中 45 4c 46 是 ELF 的 ASCII 码值)
可重定位目标文件是二进制文件,在linux终端 输入以下命令,就可以看见 ELF头的信息。(不同设备运行结果可能是不一样的)
readelf -h main.o
1. 入口点地址为 0x0 :这是 ELF 文件,所以给出的是链接视图,不是执行视图。
2. 本头的大小为 64 字节,说明在 ELF文件中,下一个块(.text) 的起始位置很有可能为 0x40。
3. 最后方框中的内容的意思是:节头表中每一个表项的大小为 64 个字节,一共有 12 个表项,所以字节表的大小为 64*12 = 768(即 0x300)
五、节头表(Section header)
下面是 32 位系统和 64 位系统的节头表的数据结构
输入以下命令可以查看节头表的信息
readelf -S main.o
根据偏移量和大小,可以得到以下 可重定位目标文件 main.o 的结构‘
其中 .bss 是不分配空间的。