您的位置:首页 > 运维架构 > Linux

Linux系统--ELF文件之可执行文件(Executable file)解析

2017-06-26 09:36 639 查看
    Linux下ELF文件类型分为以下几种:

    1、可重定位文件,例如SimpleSection.o;

    2、可执行文件,例如/bin/bash;

    3、共享目标文件,例如/lib/libc.so。

    在Linux 可重定位文件 ELF结构一文中,我们已经分析了可重定位文件ELF结构。本文分析可执行文件的ELF结构。

    首先附上源代码:

    SectionMapping.c 

[cpp] view
plain copy

#include <stdlib.h>  

  

int main()  

{  

    while(1)  

    {  

        sleep(1000);  

    }  

    return 0;  

}  

    使用命令gcc -static SectionMapping.c -o SectionMapping.elf,静态链接为可执行文件。

    接着使用命令readelf -S SectionMapping.elf得到Section Table。如下:

[plain] view
plain copy

There are 33 section headers, starting at offset 0xc3878:  

  

Section Headers:  

  [Nr] Name              Type             Address           Offset  

       Size              EntSize          Flags  Link  Info  Align  

  [ 0]                   NULL             0000000000000000  00000000  

       0000000000000000  0000000000000000           0     0     0  

  [ 1] .note.ABI-tag     NOTE             0000000000400190  00000190  

       0000000000000020  0000000000000000   A       0     0     4  

  [ 2] .note.gnu.build-i NOTE             00000000004001b0  000001b0  

       0000000000000024  0000000000000000   A       0     0     4  

  [ 3] .rela.plt         RELA             00000000004001d8  000001d8  

       0000000000000120  0000000000000018   A       0     5     8  

  [ 4] .init             PROGBITS         00000000004002f8  000002f8  

       0000000000000018  0000000000000000  AX       0     0     4  

  [ 5] .plt              PROGBITS         0000000000400310  00000310  

       00000000000000c0  0000000000000000  AX       0     0     16  

  [ 6] .text             PROGBITS         00000000004003d0  000003d0  

       0000000000094988  0000000000000000  AX       0     0     16  

  [ 7] __libc_thread_fre PROGBITS         0000000000494d60  00094d60  

       00000000000000a8  0000000000000000  AX       0     0     16  

  [ 8] __libc_freeres_fn PROGBITS         0000000000494e10  00094e10  

       000000000000181c  0000000000000000  AX       0     0     16  

  [ 9] .fini             PROGBITS         000000000049662c  0009662c  

       000000000000000e  0000000000000000  AX       0     0     4  

  [10] .rodata           PROGBITS         0000000000496640  00096640  

       000000000001d344  0000000000000000   A       0     0     32  

  [11] __libc_thread_sub PROGBITS         00000000004b3988  000b3988  

       0000000000000008  0000000000000000   A       0     0     8  

  [12] __libc_subfreeres PROGBITS         00000000004b3990  000b3990  

       0000000000000058  0000000000000000   A       0     0     8  

  [13] __libc_atexit     PROGBITS         00000000004b39e8  000b39e8  

       0000000000000008  0000000000000000   A       0     0     8  

  [14] .eh_frame         PROGBITS         00000000004b39f0  000b39f0  

       000000000000d4c4  0000000000000000   A       0     0     8  

  [15] .gcc_except_table PROGBITS         00000000004c0eb4  000c0eb4  

       0000000000000172  0000000000000000   A       0     0     1  

  [16] .tdata            PROGBITS         00000000006c1ef0  000c1ef0  

       0000000000000020  0000000000000000 WAT       0     0     16  

  [17] .tbss             NOBITS           00000000006c1f10  000c1f10  

       0000000000000038  0000000000000000 WAT       0     0     16  

  [18] .init_array       INIT_ARRAY       00000000006c1f10  000c1f10  

       0000000000000008  0000000000000000  WA       0     0     8  

  [19] .fini_array       FINI_ARRAY       00000000006c1f18  000c1f18  

       0000000000000008  0000000000000000  WA       0     0     8  

  [20] .ctors            PROGBITS         00000000006c1f20  000c1f20  

       0000000000000010  0000000000000000  WA       0     0     8  

  [21] .dtors            PROGBITS         00000000006c1f30  000c1f30  

       0000000000000010  0000000000000000  WA       0     0     8  

  [22] .jcr              PROGBITS         00000000006c1f40  000c1f40  

       0000000000000008  0000000000000000  WA       0     0     8  

  [23] .data.rel.ro      PROGBITS         00000000006c1f50  000c1f50  

       0000000000000080  0000000000000000  WA       0     0     16  

  [24] .got              PROGBITS         00000000006c1fd0  000c1fd0  

       0000000000000010  0000000000000008  WA       0     0     8  

  [25] .got.plt          PROGBITS         00000000006c1fe8  000c1fe8  

       0000000000000078  0000000000000008  WA       0     0     8  

  [26] .data             PROGBITS         00000000006c2060  000c2060  

       0000000000001690  0000000000000000  WA       0     0     32  

  [27] .bss              NOBITS           00000000006c3700  000c36f0  

       0000000000002ba8  0000000000000000  WA       0     0     32  

  [28] __libc_freeres_pt NOBITS           00000000006c62b0  000c36f0  

       0000000000000048  0000000000000000  WA       0     0     16  

  [29] .comment          PROGBITS         0000000000000000  000c36f0  

       000000000000002a  0000000000000001  MS       0     0     1  

  [30] .shstrtab         STRTAB           0000000000000000  000c371a  

       000000000000015b  0000000000000000           0     0     1  

  [31] .symtab           SYMTAB           0000000000000000  000c40b8  

       000000000000c168  0000000000000018          32   870     8  

  [32] .strtab           STRTAB           0000000000000000  000d0220  

       0000000000007a26  0000000000000000           0     0     1  

                                     表 1

    这个可执行文件共有33个Section。

    接着我们使用readelf -h SectionMapping.elf,读取elf可执行文件头部信息。如下图:



                                     图 1

    可以对比,Linux 可重定位文件 ELF结构,这里多了program header。

    Entry point address:程序的入口地址是0x401058,使用objdump -d SectionMapping.elf | less,可以查看到程序的入口地址是<_start>。如下图:



                                           图 2

    Start of program headers:program headers的偏移,由于头文件大小为64,所以program headers紧挨着头文件存放。

    Size of program headers:program headers的大小。为56个字节。

    Number of section headers:program headers的数量。为6个。

    在表1中,第一个section在文件中的偏移是0x190,头文件大小为64 + program header大小为56 * program header数量6 = 400 = 0x190。

    然后,我们使用命令readelf -l SectionMapping.elf,我们会得到program header部分。如下图:



                                 图  3

    从图中可见,分为6个Segment。注意表1中每个段叫Section。

    Offset:这个Segment在文件中偏移。

    VirtAddr:这个Segment在虚拟地址的偏移。

    FileSiz:在ELF文件中所占的长度。

    MemSiz:在进程虚拟空间所占的长度。

    我们发现第二个Segment,MemSiz > FileSiz,表示在内存中分配的空间大小超过文件实际大小。超过的部分全部初始化为0,作为BSS段。因为数据段和BSS段的唯一区别是,数据段从文件中初始化内容,BSS段内容全部初始化为0。

    我们主要关心前两个Segment,第一个是代码段,虚拟地址从0x00400000到0x004c1026。文件偏移从0x00000000到0x000c1026。

    第二个是数据段,虚拟地址为从0x006c1ef0到0x006c1ef0+0x4408=0x6c62f8。文件偏移从0x000c1ef0到0x000c1ef0+0x1800=0x000C36f0。

    结合表1和两个Segment的文件偏移,可以得出:

    第一个Segment从第0个Section到第15个Section。(0x00000000-0x000c1026)

[plain] view
plain copy

[Nr] Name              Type             Address           Offset  

     Size              EntSize          Flags  Link  Info  Align  

[ 0]                   NULL             0000000000000000  00000000  

     0000000000000000  0000000000000000           0     0     0  

[ 1] .note.ABI-tag     NOTE             0000000000400190  00000190  

     0000000000000020  0000000000000000   A       0     0     4  

[ 2] .note.gnu.build-i NOTE             00000000004001b0  000001b0  

     0000000000000024  0000000000000000   A       0     0     4  

[ 3] .rela.plt         RELA             00000000004001d8  000001d8  

     0000000000000120  0000000000000018   A       0     5     8  

[ 4] .init             PROGBITS         00000000004002f8  000002f8  

     0000000000000018  0000000000000000  AX       0     0     4  

[ 5] .plt              PROGBITS         0000000000400310  00000310  

     00000000000000c0  0000000000000000  AX       0     0     16  

[ 6] .text             PROGBITS         00000000004003d0  000003d0  

     0000000000094988  0000000000000000  AX       0     0     16  

[ 7] __libc_thread_fre PROGBITS         0000000000494d60  00094d60  

     00000000000000a8  0000000000000000  AX       0     0     16  

[ 8] __libc_freeres_fn PROGBITS         0000000000494e10  00094e10  

     000000000000181c  0000000000000000  AX       0     0     16  

[ 9] .fini             PROGBITS         000000000049662c  0009662c  

     000000000000000e  0000000000000000  AX       0     0     4  

[10] .rodata           PROGBITS         0000000000496640  00096640  

     000000000001d344  0000000000000000   A       0     0     32  

[11] __libc_thread_sub PROGBITS         00000000004b3988  000b3988  

     0000000000000008  0000000000000000   A       0     0     8  

[12] __libc_subfreeres PROGBITS         00000000004b3990  000b3990  

     0000000000000058  0000000000000000   A       0     0     8  

[13] __libc_atexit     PROGBITS         00000000004b39e8  000b39e8  

     0000000000000008  0000000000000000   A       0     0     8  

[14] .eh_frame         PROGBITS         00000000004b39f0  000b39f0  

     000000000000d4c4  0000000000000000   A       0     0     8  

[15] .gcc_except_table PROGBITS         00000000004c0eb4  000c0eb4  

     0000000000000172  0000000000000000   A       0     0     1  

    第二个Segment从第16个Section到26个Section。(0x000c1ef0-0x000C36f0)

[plain] view
plain copy

[16] .tdata            PROGBITS         00000000006c1ef0  000c1ef0  

     0000000000000020  0000000000000000 WAT       0     0     16  

[17] .tbss             NOBITS           00000000006c1f10  000c1f10  

     0000000000000038  0000000000000000 WAT       0     0     16  

[18] .init_array       INIT_ARRAY       00000000006c1f10  000c1f10  

     0000000000000008  0000000000000000  WA       0     0     8  

[19] .fini_array       FINI_ARRAY       00000000006c1f18  000c1f18  

     0000000000000008  0000000000000000  WA       0     0     8  

[20] .ctors            PROGBITS         00000000006c1f20  000c1f20  

     0000000000000010  0000000000000000  WA       0     0     8  

[21] .dtors            PROGBITS         00000000006c1f30  000c1f30  

     0000000000000010  0000000000000000  WA       0     0     8  

[22] .jcr              PROGBITS         00000000006c1f40  000c1f40  

     0000000000000008  0000000000000000  WA       0     0     8  

[23] .data.rel.ro      PROGBITS         00000000006c1f50  000c1f50  

     0000000000000080  0000000000000000  WA       0     0     16  

[24] .got              PROGBITS         00000000006c1fd0  000c1fd0  

     0000000000000010  0000000000000008  WA       0     0     8  

[25] .got.plt          PROGBITS         00000000006c1fe8  000c1fe8  

     0000000000000078  0000000000000008  WA       0     0     8  

[26] .data             PROGBITS         00000000006c2060  000c2060  

     0000000000001690  0000000000000000  WA       0     0     32  

    以上分析的都是静态状态下的程序,下面我们看看动态下的进程的空间是怎么分配的。

    首先使用命令, ./SectionMapping.elf &,输出如下:

    


    然后使用命令:cat /proc/2184/maps,输出如下:



                                    图 4

    

    静态时,我们计算出的两个Segment的虚拟空间的偏移分别为:

    第一个是代码段,虚拟地址从0x00400000到0x004c1026。在图4中,因为要页面对齐,所以分配了0x400000到0x4c2000。

    第二个是数据段,虚拟地址为从0x006c1ef0到0x006c1ef0+0x4408=0x6c62f8。在图4中,因为要页面对齐,所以分配了0x6c1000到0x6c4000。注意,0x6c62f8大于0x6c4000,具体原因以后再分析。

    第三个紧接着是堆。用于动态分配内存。

    第四个是栈。用于存放局部变量。

    整体的结构如下图:



    程序运行的过程:建立虚拟空间(分配一个页目录)-> 建立虚拟空间与可执行文件映射(页目录项指向磁盘的程序) -> 跳到程序入口 -> 缺页异常-> 在内存中寻找空闲页,将对应的页换入 -> 建立映射 -> 开始执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux ELF 可执行文件