采用ELF文件格式 以以下c程序为例
/* main.c */ /* $begin main */ #include<stdio.h> int sum(int *a, int n); int array[2] = {1, 2}; int main() { int val = sum(array, 2); printf("val=%d",val); return val; } /* $end main */ /* sum.c */ /* $begin sum */ int sum(int *a, int n) { int i, s = 0; for (i = 0; i < n; i++) { s += a[i]; } return s; } /* $end sum */分别执行 $cpp main.c main.i $gcc -S main.i -o main.s $as main.s -o main.o 即可得到可重定位目标文件mian.o
其代码和数据可和其他可重定位文件合并为可执行文件 •每个.o 文件由对应的.c文件生成 •每个.o文件代码和数据地址都从0开始
文件格式如下:
ELF头包括16字节标识信息、文件类型(.o, exec, .so)、机器类型(如x86-64、节头表的偏移、节头表的表项大小以及表项个数.text节编译后的代码部分.rodata节只读数据,如printf 格式串、switch 跳转表等.data节已初始化的全局变量.bss节未初始化全局变量,仅是占位符,不占 据任何实际磁盘空间。区分初始化和非 初始化是为了空间效率.symtab节存放函数和全局变量(符号表)信息,它不包括局部变量.rel.text节text节的重定位信息,用于重新修改代码段的指令中的地址信息.rel.data节.data节的重定位信息,用于对被模块使 用或定义的全局变量进行重定位的信息.debug节调试用符号表(gcc -g).line节原始c程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到。.strtab节包含symtab和debug节中符号及节名节头部表每个节的节名、偏移和大小执行指令readelf -h main.o 可查看main.o的二进制存储的ELF头信息
ELF 头: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 类别: ELF32 数据: 2 补码,小端序 (little endian) 版本: 1 (current) OS/ABI: UNIX - System V ABI 版本: 0 类型: REL (可重定位文件) 系统架构: Intel 80386 版本: 0x1 入口点地址: 0x0 程序头起点: 0 (bytes into file) Start of section headers: 636 (bytes into file) 标志: 0x0 本头的大小: 52 (字节) 程序头大小: 0 (字节) Number of program headers: 0 节头大小: 40 (字节) 节头数量: 13 字符串表索引节头: 10 Magic,魔数:通过对魔数的判断可以确定文件的格式和类型。如:ELF 的可执行文件格式的头 4 个字节为0x7F、e、l、f。类型:这里是32位的ELF格式数据:负数用二进制补码形式表示,小端法存放版本:当前ELF文件头版本号,此处为1OS/ABI:指出操作系统类型ABI版本类型:指出ELF文件的类型(共有三种类型:可重定位目标文件,可执行文件,共享库文件),此处为REL(可重定位目标文件)系统架构:此处为80386版本: 当前目标文件的版本号入口点地址:程序的虚拟地址入口,因为此处是一个可重定位文件,还未链接生成可执行文件,故入口地址为0.程序头起点:可重定问目标文件没有程序头部表。Start of section headers: 节头部表的文件偏移,即节头部表的起始地址标志:一个与处理器相关联的标志本头的大小:ELF文件头的字节数。 52 (字节)程序头大小:此文件中无程序头,0 (字节)Number of program headers: 0节头大小:节头部表中每个section的大小。40 (字节)节头数量:节头表中section的数量。 13字符串表索引节头:.strtab在节头表中的索引 。10$readelf -S main.o
共有 13 个节头,从偏移量 0x27c 开始: 节头: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00000000 000034 000044 00 AX 0 0 1 [ 2] .rel.text REL 00000000 0001f4 000020 08 I 11 1 4 [ 3] .data PROGBITS 00000000 000078 000008 00 WA 0 0 4 [ 4] .bss NOBITS 00000000 000080 000000 00 WA 0 0 1 [ 5] .rodata PROGBITS 00000000 000080 000007 00 A 0 0 1 [ 6] .comment PROGBITS 00000000 000087 000036 01 MS 0 0 1 [ 7] .note.GNU-stack PROGBITS 00000000 0000bd 000000 00 0 0 1 [ 8] .eh_frame PROGBITS 00000000 0000c0 000044 00 A 0 0 4 [ 9] .rel.eh_frame REL 00000000 000214 000008 08 I 11 8 4 [10] .shstrtab STRTAB 00000000 00021c 00005f 00 0 0 1 [11] .symtab SYMTAB 00000000 000104 0000d0 10 12 9 4 [12] .strtab STRTAB 00000000 0001d4 00001e 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) bss节只有装入内存时才会分配。可重定位目标文件中,每个可装入节的起始地址总是0off:每个节的起始地址size:每个节的大小在可重定位文件main.o中的结构如下:
$readelf -s main.o
Symbol table '.symtab' contains 13 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS main.c 2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 SECTION LOCAL DEFAULT 4 5: 00000000 0 SECTION LOCAL DEFAULT 5 6: 00000000 0 SECTION LOCAL DEFAULT 7 7: 00000000 0 SECTION LOCAL DEFAULT 8 8: 00000000 0 SECTION LOCAL DEFAULT 6 9: 00000000 8 OBJECT GLOBAL DEFAULT 3 array 10: 00000000 68 FUNC GLOBAL DEFAULT 1 main 11: 00000000 0 NOTYPE GLOBAL DEFAULT UND sum 12: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf• 文件格式与可重定位文件稍有不同:
ELF头中字段e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段 为0多一个程序头表,也称段头表 (segment header table)多一个.init节,用于定义 _init函数,该函数用来进行可执行目标文件开始执行时的初始化工作少两个.rel节(无需重定位)从得到的信息可以看到:此时,入口点地址不再是0,而是而是执行代码的第一条指令的地址;程序头的起点和大小不再是0;节头大小、数量和strtab在节头表中的索引均更新了。
$ readelf -l myproc
Elf 文件类型为 EXEC (可执行文件) 入口点 0x8048736 共有 6 个程序头,开始于偏移量 52 程序头: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0xa0cd1 0xa0cd1 R E 0x1000 LOAD 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x01024 0x01e48 RW 0x1000 NOTE 0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R 0x4 TLS 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x00010 0x00028 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x000a4 0x000a4 R 0x1 Section to Segment mapping: 段节... 00 .note.ABI-tag .note.gnu.build-id .rel.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres .stapsdt.base __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table 01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 02 .note.ABI-tag .note.gnu.build-id 03 .tdata .tbss 04 05 .tdata .init_array .fini_array .jcr .data.rel.ro .got 第一可装入段:第0x00000~0x73880字节(包括ELF头、程序头表、.init、 .text和.rodata节),映射到虚拟地址0x8048000开始长度为0xa0cd1字节的区域 ,按0x1000=212=4KB对齐,具有只读/执行权限(Flg=RE),是只读代码段。第二可装入段:第0x0a1060开始长度为0xf20字节的.data节,映射到虚拟地址 0x80e9f5c开始长度为0x01024字节的存储区域,在0xf1024=4132B存储区中,前 0x108=3872B用.data节内容初始化,后面4132-38724=260B对应.bss节,初始化为0 ,按0x1000=4KB对齐,具有可读可写权限(Flg=RW),是可读写数据段。-特殊的可重定位目标文件,能在装入或运行时被装入到内 存并自动被链接,称为共享库文件
Windows 中称其为 Dynamic Link Libraries (DLLs)1)合并相关.o文件 2)确定每个符号的地址 3)在指令中填入新地址