目标文件--以ELF文件为例对段简介

 

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( "%d\n",  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目标文件:

wpsE84E.tmp

          根据目标文件可分析目标文件包含:头文件,代码段,数据段,只读数据段,公共块和其他数据(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)其他段

wpsE86E.tmp

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值