一、引言
目标代码(Object Code) :指编译器或汇编器处理源代码后所生成的机器语言目标代码;
目标文件(Object File) :存放目标代码的文件
目标文件分为:.o文件(代码、数据的地址从0开始)、可执行目标文件(地址为虚拟地址)、共享的目标文件(共享库文件,在Windows里称为DLLs)。
Linux的目标文件格式是ELF格式,即可执行可链接格式。
二、ELF格式
两种视图:链接视图/执行视图(具体解释看下面)。链接视图的程序头表可选,节头表必须;而执行视图的程序头表必须,节头表可选。
【注】节头表里有各个“节”的信息,如名字、大小等,在ELF文件末;程序头表描述节怎样映射到存储器映像,在ELF头的紧下方。
1、链接视图(重点关注ELF头和节头表)
【重点1:ELF头】
1、用readelf -h a.o
就可以用来在linux下读取a.o的ELF头文件
2、32位下,占52字节。
3、一个ELF头文件例子(了解)
ehsize是节头表大小,phentsize是程序(program)头表大小。
解析完成之后,得到下图:
【考试题】已知start of section headers = 516, size of this header = 52,求从.text到.line所有节所占大小。
解:start of section headers = 516意思是从节头表往前是516字节,而EIF头占了52,所以剩余部分占了516-52个字节。(注意区分header和section header)
【考试题】有下图数据。求节头表所占字节数
节头表里有15个表项,每个表项占40个字节(实际上,32位下,所有.o文件的表项都是占40字节)。故节头表占600字节。
【注】一般最后最后还会给出string table是第几个表项等等
①可以“被用作链接”,也就是说它们是.o文件。它们还可能是.so文件。
②.o文件里包括代码、数据等等。
如图:代码在.text节,已初始化的全局变量在.data节,未初始化的全局变量在.bss(回顾上节课知识:.data和.bss最后会映射到数据读写区),非静态局部变量放在栈帧中,不会放在ELF文件中。
【重点2:以一个完整的链接视图ELF文件为例】
【补充】
1、字符串、跳转表(见switch-case)是只读数据
2、未初始化的变量不占空间,只是占位符!
3、.symtab节是符号表(把全局函数名、变量名放入其中);
.rel.txt节是和.text相关的可重定位信息(如函数的调用关系等);
.rel.data节是和.data相关的可重定位信息;
.debug用作调试;.
.strtab节是字符串表;
.line节就是C语言中的行号和.text机器指令中的对应。
【精髓:节头表】(链接视图下,节头表是精髓)
1、查看节头表的指令:
$ readelf -S test.o
2、不必记格式,仅需要理解
3、节头表里有若干表项,每个表项描述对应节在文件中的偏移、大小、访问属性、对齐方式等
4、【例子:一个真实的节头表】注意:节头表描述了各个节的属性、信息
There are 11 section headers, starting at offset 0x120:
Section Headers:
[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 00005b 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000498 000028 08 9 1 4
[ 3] .data PROGBITS 00000000 000090 00000c 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 00009c 00000c 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 00009c 000004 00 A 0 0 1
[ 6] .comment PROGBITS 00000000 0000a0 00002e 00 0 0 1
[ 7] .note.GNU-stack PROGBITS 00000000 0000ce 000000 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0000ce 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 0002d8 000120 10 10 13 4
[10] .strtab STRTAB 00000000 0003f8 00009e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
(从下往上偏移量从大到小)
【解读】1、此节头表起始位置位120(ELF和所有前面的节共占了120字节),共11个节
2、.o文件中,每个可装入节的起始地址总是0(Addr),但是每个节的偏移量不同,如.data的偏移量为000090.只有装入内存时才会分配
3、AX WA WA A是A空间,此表中有4个节分配(A)空间,这个分配空间指的将来在存储器分配空间。
4、特殊地,.bss和.rodata的偏移都是9c,这说明对于.bss节,在ELF文件并未给它分配空间。
【注意】.bss里全是未初始化全局变量。.bss的大小和符号,分别由节头表和符号表(.symtab)描述,且.bss里面变量的默认初值是0;.bss里在文件(如.o/可执行文件)中不占空间,但在存储器映像中占空间。这样做的目的是节省磁盘空间。
5、必要的节一般放前面,其他的一些节可以放在节头表后面
2、执行视图
【重点1:ELF头】
查看ELF头文件的指令
$ readelf -h main(还是它)
解读:
①程序头表的起始地址:0x8048580+52
②程序头表占32*8个字节
③节头表之前的数据,共占了3232个字节
③节头表占40*29个字节
【注意】所有代码位置连续,所有只读数据位置连续,所有可读可写数据位置连续。
【重点2:以一个完整的执行视图ELF文件为例】
查看程序头表的指令:readelf -l(L的小写) main
执行视图图解
①注意:在链接视图里的节,在执行视图中依旧存在,这是多个.o文件的相同节合并后的产物,而且,在可执行目标文件中有段的概念(相同访问属性的节构成)。.o文件虽然也有段,但很少用。
(从下往上地址从高到低)
在可执行文件中,多了.init和.fini节,这俩分别做初始化工作和结束工作。少了两个.rel节,因为无需再重定位。
后面还有许多节和一个节头表,它们无需在存储器中存储(映射)。
执行视图中,所有符号(变量和函数)、指令等等都有了确定的地址(虚拟地址)。
②可执行目标文件,相当于Windows的.exe文件
【精髓:程序头表】(执行视图下,程序头表是精髓)
【解读】(关注虚拟地址【可执行文件有确定的虚拟地址】、两个Siz、Flg和Align)
1、Flg表示权限,R E表示只读,R W表示可读可写。如红LOAD是只读,蓝LOAD是读写
2、Align表示映射到虚拟地址后的对齐方式。占据的空间看MemSiz,而不看Filesiz(这是在可执行文件中占的空间,有些节在可执行文件中没占空间,但映射到存储器中需要占字节,如.bss节)
【再次注意】.bss里全是未初始化全局变量。.bss的大小和符号,分别由节头表和符号表(.symtab)描述,且.bss里面变量的默认初值是0;.bss里在文件(如.o/可执行文件)中不占空间,但在存储器映像中占空间。这样做的目的是节省磁盘空间
3、物理地址 与 虚拟地址一般一样
4、LOAD的FileSiz和MemSiz不同原因是:可读写数据段包括data和bss,在文件中,bss是空的,不占空间。在存储器里面要分配空间。这110-108 = 8个字节,是要给bss分配的空间!这也解释了为什么光第2个LOAD段的两个Size出现不同!
【上述例子的可执行文件(32位系统)】
1、可执行文件执行时,必须把可装入段映射到存储空间。程序头表描述这种映射关系。特定系统平台中的每个可执行目标文件都采用统一的存储器映像,映射到一个统一的虚拟地址空间(上图右)
2、如图:ELF头文件到.rodata都映射到存储器的只读代码段(内存中,其起始地址是0x08048000)
蓝色区域映射到读写数据段(内存中,其起始地址是0x08049000)
3、对于本例,在存储器中,被装入数据的只读代码段,是从:0x08048000到0x080484d3,剩余空间未使用。
被装入数据的数据读写区要特殊注意!由刚才的程序头表可以知道,存储器中,可读写数据段从0x08049f0c开始(不是0x08049000),连续110(16进制)个字节,给.bss节也分配了空间【对.bss而言,它在文件中不占空间(.o/可执行),但在存储器映像中占空间】。
而在原可执行文件中,我们可以看出,f0c+108就正好到1014(.bss节末端),可见在可执行文件中,未给bss分配空间。