概述
1.什么是段?
目标文件中的内容至少有编译后的机器指令代码、数据。没错,除了这些内容以外,目标文件中还包括了链接时所须要的一些信息,比如符号表、调试信息、字符串等。一般目标文件将这些信息按不同的属性,以“节”(Section)的形式存储,有时候也叫“段”(Segment),我们一般都称为“段”。
2.常用的段(以SimpleSection.o为例)
SimpleSection程序:
int printf( const char* format, ... );
int global_init_var = 84;
int global_uninit_var;
void func1( int i )
{
printf( "%dn", i );
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1( static_var + static_var2 + a + b );
return a;
}
SimpleSection目标文件:
根据目标文件可分析目标文件包含:头文件,代码段,数据段,只读数据段,公共块和其他数据(BSS不存放在目标文件中,但是会被装载到内存中)。下面逐个分析:
(1)代码段
代码段,存放的是目标文件的机器指令。
$ objdump -s -d SimpleSection.o
……
Contents of section .text:
0000 5589e583 ec088b45 08894424 04c70424 U......E..D$...$
0010 00000000 e8fcffff ffc9c38d 4c240483 ............L$..
0020 e4f0ff71 fc5589e5 5183ec14 c745f401 ...q.U..Q....E..
0030 0000008b 15040000 00a10000 00008d04 ................
0040 020345f4 0345f889 0424e8fc ffffff8b ..E..E...$......
0050 45f483c4 14595d8d 61fcc3 E....Y].a..
……
00000000 <func1>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 8b 45 08 mov 0x8(%ebp),%eax
9: 89 44 24 04 mov %eax,0x4(%esp)
d: c7 04 24 00 00 00 00 movl $0x0,(%esp)
14: e8 fc ff ff ff call 15 <func1+0x15>
19: c9 leave
1a: c3 ret
0000001b <main>:
1b: 8d 4c 24 04 lea 0x4(%esp),%ecx
1f: 83 e4 f0 and $0xfffffff0,%esp
22: ff 71 fc pushl -0x4(%ecx)
25: 55 push %ebp
26: 89 e5 mov %esp,%ebp
28: 51 push %ecx
29: 83 ec 14 sub $0x14,%esp
2c: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp)
33: 8b 15 04 00 00 00 mov 0x4,%edx
39: a1 00 00 00 00 mov 0x0,%eax
3e: 8d 04 02 lea (%edx,%eax,1),%eax
41: 03 45 f4 add -0xc(%ebp),%eax
44: 03 45 f8 add -0x8(%ebp),%eax
47: 89 04 24 mov %eax,(%esp)
4a: e8 fc ff ff ff call 4b <main+0x30>
4f: 8b 45 f4 mov -0xc(%ebp),%eax
52: 83 c4 14 add $0x14,%esp
55: 59 pop %ecx
56: 5d pop %ebp
57: 8d 61 fc lea -0x4(%ecx),%esp
5a: c3 ret
“Contents of section .text”就是.text的数据以十六进制方式打印出来的内容,总共0x5b字节,跟前面我们了解到的“.text”段长度相符合,最左面一列是偏移量,中间4列是十六进制内容,最右面一列是.text段的ASCII码形式。对照下面的反汇编结果,可以很明显地看到,.text段里所包含的正是SimpleSection.c里两个函数func1()和main()的指令。.text段的第一个字节“0x55”就是“func1()”函数的第一条“push %ebp”指令,而最后一个字节0xc3正是main()函数的最后一条指令“ret”。
(2)数据段
.data段保存的是那些已经初始化了的全局静态变量和局部静态变量。前面的SimpleSection.c代码里面一共有两个这样的变量,分别是global_init_varabal与static_var。这两个变量每个4个字节,一共刚好8个字节,所以“.data”这个段的大小为8个字节。
(3)只读数据段
“.rodata”段存放的是只读数据,一般是程序里面的只读变量(如const修饰的变量)和字符串常量。单独设立“.rodata”段有很多好处,不光是在语义上支持了C++的const关键字,而且操作系统在加载的时候可以将“.rodata”段的属性映射成只读,这样对于这个段的任何修改操作都会作为非法操作处理,保证了程序的安全性。另外在某些嵌入式平台下,有些存储区域是采用只读存储器的,如ROM,这样将“.rodata”段放在该存储区域中就可以保证程序访问存储器的正
确性。
$ objdump -x -s -d SimpleSection.o
……
Sections:
Idx Name Size VMA LMA File off Algn
1 .data 00000008 00000000 00000000 00000090 2**2
CONTENTS, ALLOC, LOAD, DATA
3 .rodata 00000004 00000000 00000000 00000098 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
……
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..
……
我们看到“.data”段里的前4个字节,从低到高分别为0x54、0x00、0x00、0x00。这个值刚好是global_init_varabal,即十进制的84。global_init_varabal是个4字节长度的int类型,为什么存放的次序为0x54、0x00、0x00、0x00而不是0x00、0x00、0x00、0x54?这涉及CPU的字节序(Byte Order)的问题,也就是所谓的大端(Big-endian)和小端(Little-endian)的问题。关于字节序的问题本书的附录有详细的介绍。而最后4个字节刚好是static_init_var的值,即85。
(4).BSS段
.bss段存放的是未初始化的全局变量和局部静态变量,如上述代码中global_uninit_var和static_var2就是被存放在.bss段,其实更准确的说法是.bss段为它们预留了空间。但是我们可以看到该段的大小只有4个字节,这与global_uninit_var和static_var2的大小的8个字节不符。其实我们可以通过符号表(Symbol Table)看到,只有static_var2被存放在了.bss段,而global_uninit_var却没有被存放在任何段,只是一个未定义的“COMMON符号”。这其实是跟不同的语言与不同的编译器实现有关,有些编译器会将全局的未初始化变量存放在目标文件.bss段,有些则不存放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss段分配空间。
.bss并没有存放变量,只是为变量预留了空间。我们只要记录变量要使用的空间的大小就可以了。COMMON是公共块,它的基本的功能就是记录弱符号(未初始化的全局变量),根据COMMON的机制,当不同的目标文件需要的COMMON块空间大小不一致时,以最大的块为准。比如,int类型和double类型会按照double类型分配空间。
(5)其他段
最后
以上就是粗犷网络为你收集整理的目标文件--以ELF文件为例对段简介的全部内容,希望文章能够帮你解决目标文件--以ELF文件为例对段简介所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复