美文网首页
ELF文件长什么样子(1/2)

ELF文件长什么样子(1/2)

作者: 大猫Kevin | 来源:发表于2020-04-21 23:52 被阅读0次

    和JavaScript这样的解释执行语言不同,编译执行的语言通过编译、链接最终生成可执行文件。

    ELF(Executable and Linkable Format),指.o文件和可执行文件,本文分析.o文件格式。

    1. 示例程序

    demo.s

    #understanding .obj sections and .elf segments
    .section .data
    mydata1:
        .ascii "This is my data1 xxx."
    mydata2:
        .ascii "This is my data2 xxx."
    .section .text
    .globl _start
    _start:
        movl $mydata1, %edi #replace "xxx" with "1st"
        movb $49, 17(%edi)  #"1st"
        movb $115, 18(%edi)
        movb $116, 19(%edi)
     
        movl $mydata2, %edi #replace "xxx" with "2nd"
        movb $50, 17(%edi)  #"2nd"
        movb $110, 18(%edi)
        movb $100, 19(%edi)
     
        movl $1, %eax   #syscall exit
        movl $0, %ebx   #argument 0, ie. exit(0)
        int $0x80
    

    上述程序的功能是将两段字符串中"XXX"分别替换为"1st"和"2nd"
    编译
    as -o demo.o demo.s
    得到demo.o文件

    链接
    ld -o demo demo.o
    得到demo可执行文件。

    2. 调试

    加载到调试器中
    gdb demo

    设置断点
    break *_start
    让程序在_start处暂停。

    查看当前指令
    display/2i $eip
    查看eip指针指向的两条指令。显示格式为I (instruction)

    运行
    r
    运行,显示输出:

    1: x/2i $eip 
    => 0x8048074 <_start>:    mov $0x80490a4,%edi 0x8048079 <_start+5>: 
                         movb $0x31,0x11(%edi)
    

    0x80490a4即为mydata1在内存中的起始地址。

    查看mydata
    display/5s 0x80490a4
    显示地址0x80490a4开始的5条字符串。输出格式为s(string)

    执行下一条指令
    ni
    输入ni,执行next instruction。然后一直按回车,重复执行ni。
    最终显示输出:

    2: x/5s 0x80490a4
    0x80490a4 <mydata1>:     "This is my data1 1st.This is my data2 2nd."
    0x80490cf:   ".symtab"
    0x80490d7:   ".strtab"
    0x80490df:   ".shstrtab"
    0x80490e9:   ".text"
    

    "This is…."存放.data节中,".sysmtab",".strtab"等字符串存放在.shstrtab 节中。最终加载到内存中,.shstrtab刚好位于.data节后,所以将.shstrtab中的内容也打印出来了。(结论:.shstrtab也是会被加载到内存中的。)

    3. 分析.o文件的结构。

    输出elf文件内容
    readelf -a demo.o > elf.demo.o
    用readelf工具将 demo3.o的内容输出到elf.demo3.o中。

    反汇编.text段
    objdump -d demo.o > objdump.demo.o
    用objdump工具将.text段的代码反汇编,输出到objdump.demo.o中

    说明:下面内容中中括[]号内表示文件中的起止位置(包括首尾),单位为byte,计数进制为16进制, 圆括号()表示不包含首尾字节。

    (1) ELF Header [0,33](共52 bytes)

    文件开始的52byte为ELF Header,这52字节按如下结构进行解释
    elf32_hdr结构体

    typedef struct elf32_hdr{
      unsigned char e_ident[EI_NIDENT];
      Elf32_Half    e_type;
      Elf32_Half    e_machine;
      Elf32_Word    e_version;
      Elf32_Addr    e_entry;  /* Entry point */
      Elf32_Off e_phoff;
      Elf32_Off e_shoff;
      Elf32_Word    e_flags;
      Elf32_Half    e_ehsize;
      Elf32_Half    e_phentsize;
      Elf32_Half    e_phnum;
      Elf32_Half    e_shentsize;
      Elf32_Half    e_shnum;
      Elf32_Half    e_shstrndx;
    } Elf32_Ehdr;
    

    本实例显示内容为:
    ELF Header

    (2) .text [34,61](共46 bytes)

    .text存放的是汇编代码,共2e(即十进制的46) 字节。
    .text

       0:   bf 00 00 00 00          mov    $0x0,%edi
       5:   c6 47 11 31             movb   $0x31,0x11(%edi)
       9:   c6 47 12 73             movb   $0x73,0x12(%edi)
       d:   c6 47 13 74             movb   $0x74,0x13(%edi)
      11:   bf 15 00 00 00          mov    $0x15,%edi
      16:   c6 47 11 32             movb   $0x32,0x11(%edi)
      1a:   c6 47 12 6e             movb   $0x6e,0x12(%edi)
      1e:   c6 47 13 64             movb   $0x64,0x13(%edi)
      22:   b8 01 00 00 00          mov    $0x1,%eax
      27:   bb 00 00 00 00          mov    $0x0,%ebx
      2c:   cd 80                   int    $0x80
    

    (3) 填充字符 [62,63]

    用00 00填充。

    (4) .data [64,8D](共42 bytes)

    .data段对应汇编语言中的.data段,存放了两个字符串,共2a (即十进制的42)字节。
    .text段后有两个空字符 00 00,然后才是.data段。
    .data

    This is my data1 xxx.This is my data2 xxx.
    

    (5) 填充字符 [62,63]

    用 00 00填充。

    (6) .bss (90,90)(共0bytes)

    .bss段,只有section header,没有对应section。所以它对应的section大小设置为0字节。.bss段不占用.o文件中的空间,当进程被加载到内存中时,才为.bss分配空间,并用0填充该空间。

    (7) .shstrtab [90,BF](共48 bytes)

    .shstrtab 即 section header string table,存放的是各个section名称字符串。其中第一个字符串为'\0'. 此处为30 (即十进制的48)字节。
    .shstrtab

    ""
    ".symtab"
    ".strtab"
    ".shstrtab"
    ".rel.text"
    ".data"
    ".bss"
    

    上面字符串按照标准C语言格式表示的,即 “.symtab”实际由: '.' 's' 'y' 'm' 't' 'a' 'b' '\0' 字符组成。每个字符串用'\0'结尾,在文件视图中显示为一个点“.”(16进制00)。另外,名称

    注意:这些字符串之间并没有用换行分隔,这里只是为了显示清晰,每个字符串单独占一行。

    (8) Section Headers [C0,1FF](共320 bytes)

    section headers 处存放的是用来描述各个section的section header,每个section header占40 bytes, 这里共有8个section header。
    每个占用40个字节的section header根据如下结构体解释:
    Elf32_Shdr

    typedef struct {
      Elf32_Word    sh_name;
      Elf32_Word    sh_type;
      Elf32_Word    sh_flags;
      Elf32_Addr    sh_addr;
      Elf32_Off sh_offset;
      Elf32_Word    sh_size;
      Elf32_Word    sh_link;
      Elf32_Word    sh_info;
      Elf32_Word    sh_addralign;
      Elf32_Word    sh_entsize;
    } Elf32_Shdr;
    

    本示例中,各个section header的有效信息如下:
    section headers

    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 00002e 00  AX  0   0  4
      [ 2] .rel.text         REL             00000000 000288 000010 08      6   1  4
      [ 3] .data             PROGBITS        00000000 000064 00002a 00  WA  0   0  4
      [ 4] .bss              NOBITS          00000000 000090 000000 00  WA  0   0  4
      [ 5] .shstrtab         STRTAB          00000000 000090 000030 00      0   0  1
      [ 6] .symtab           SYMTAB          00000000 000200 000070 10      7   6  4
      [ 7] .strtab           STRTAB          00000000 000270 000018 00      0   0  1
    

    第0条对应ELF Header.

    (9) .symtab [200,26F](共112 bytes)

    .symtab 存放的是代码中使用到的符号字符串索引以及它对应的地址、属性(local,global,weak global)等信息。在这里共占用0x70(即十进制的112)字节。
    其中每一项占用16个字节,这里共有7项,所以占用112字节。每项都通过如下结构体进行解释
    Elf32_Sym

    typedef struct elf32_sym{
      Elf32_Word    st_name;
      Elf32_Addr    st_value;
      Elf32_Word    st_size;
      unsigned char st_info;
      unsigned char st_other;
      Elf32_Half    st_shndx;
    } Elf32_Sym;
    

    在本示例中,.symtab中7项内容的有效信息如下:

    Symbol table '.symtab' contains 7 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000000     0 SECTION LOCAL  DEFAULT    1 
         2: 00000000     0 SECTION LOCAL  DEFAULT    3 
         3: 00000000     0 SECTION LOCAL  DEFAULT    4 
         4: 00000000     0 NOTYPE  LOCAL  DEFAULT    3 mydata1
         5: 00000015     0 NOTYPE  LOCAL  DEFAULT    3 mydata2
         6: 00000000     0 NOTYPE  GLOBAL DEFAULT    1 _start
    

    1~3条分别表示索引号为1、3、4的section的名称,即.text,.data和.bss。第0条默认为STN_UNDEF(undefined)类型

    其中,后三项对应代码中的三个符号。

    (10) .strtab [270,287](共24 bytes)

    .strtab即 string table 用来存放在代码中定义的符号的字符串。
    和.shstrtab一样,第一个字符串为'\0',每个字符串以'\0'结尾。

    .strtab

    ""�"mydata1"�"mydata2"�"_start"
    

    (11) .rel.text [288,297](共16 bytes)

    .rel.text存放的是.text节的重定位信息,。其中每一项占8个字节,这里共两项所以占16字节。
    PS: .rel.XXX节为.XXX节的重定位表,如:.data段的重定位表为.rel..data

    每一项通过如下结构解释:
    Elf32_Rel

    typedef struct elf32_rel {
      Elf32_Addr    r_offset;
      Elf32_Word    r_info;
    } Elf32_Rel;
    

    本示例中,有效信息如下:
    .rel.text

    Relocation section '.rel.text' at offset 0x288 contains 2 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    00000001  00000201 R_386_32          00000000   .data
    00000012  00000201 R_386_32          00000000   .data
    

    (12) 重定位

    在源码中定义的符号代码一个地址,在.o文件中,该地址指向对应符号所在文件中的相对位置。链接过程中需要将这个相对位置替换为虚拟内存地址,才能使得程序正常运行。
    汇编代码

    movl $mydata1, %edi 
    movb $49, 17(%edi)      
    movb $115, 18(%edi)
    movb $116, 19(%edi)
     
    movl $mydata2, %edi 
    movb $50, 17(%edi)  
    movb $110, 18(%edi)
    movb $100, 19(%edi)
     
    movl $1, %eax   #syscall exit
    movl $0, %ebx   #argument 0, ie. exit(0)
    int $0x80
    

    .o文件中的.text段

    00000000 <_start>:
       0:   bf 00 00 00 00          mov    $0x0,%edi
       5:   c6 47 11 31             movb   $0x31,0x11(%edi)
       9:   c6 47 12 73             movb   $0x73,0x12(%edi)
       d:   c6 47 13 74             movb   $0x74,0x13(%edi)
      11:   bf 15 00 00 00          mov    $0x15,%edi
      16:   c6 47 11 32             movb   $0x32,0x11(%edi)
      1a:   c6 47 12 6e             movb   $0x6e,0x12(%edi)
      1e:   c6 47 13 64             movb   $0x64,0x13(%edi)
      22:   b8 01 00 00 00          mov    $0x1,%eax
      27:   bb 00 00 00 00          mov    $0x0,%ebx
      2c:   cd 80                   int    $0x80
    

    可执行文件中的.text段

    08048074 <_start>:
     8048074:   bf a4 90 04 08          mov    $0x80490a4,%edi
     8048079:   c6 47 11 31             movb   $0x31,0x11(%edi)
     804807d:   c6 47 12 73             movb   $0x73,0x12(%edi)
     8048081:   c6 47 13 74             movb   $0x74,0x13(%edi)
     8048085:   bf b9 90 04 08          mov    $0x80490b9,%edi
     804808a:   c6 47 11 32             movb   $0x32,0x11(%edi)
     804808e:   c6 47 12 6e             movb   $0x6e,0x12(%edi)
     8048092:   c6 47 13 64             movb   $0x64,0x13(%edi)
     8048096:   b8 01 00 00 00          mov    $0x1,%eax
     804809b:   bb 00 00 00 00          mov    $0x0,%ebx
     80480a0:   cd 80                   int    $0x80
            
    

    观察上面代码中绿色阴影标记部分,在.o文件中,使用的是mydata1和mydata2在.data段中的相对位置。在可执行文件中,使用的是mydata1和mydata2的虚拟内存地址。

    在.rel.text中可以看到,.o文件中将汇编源码中使用了mydata1和mydata2的地方标记为需要重定位,重定位类型为:R_386_32
    一个符号就代表一个地址,这两个符号代表的值是它们在.data段中的偏移量,分别为0和15(即十进制 的21)。所以.o中两条mov指令使用的分别是0x1和0x15。

    已经初始化的数据存放在.data section中。这些数据实际上引用的是.data的地址。例如,定义了一个变量static int x = 10. 我们知道符号实际上代表一个地址而已,在这里引用x时,实际上是这样的:.symtab中记录了x在.data section中的偏移量(假设是12),其他地方要用到x变量时,实际上是通过 .data地址+偏移量 来进行引用的。
    对于上面的 movl $mydata1, %edi 指令也是如此。因此,在重定位表中记录的需要重定位的项不是mydata1和mydata2而是.data。

     Offset     Info    Type            Sym.Value  Sym. Name
    00000001  00000201 R_386_32          00000000   .data
    00000012  00000201 R_386_32          00000000   .data
    offset 表示要更改的地方在.text section中起始地址。要更改的内容默认为为该处的4个字节;
    info 
        1. 表示更改的方式为:0X01 即:R_386_32
        2. 被引用的符号在.symtab中的索引为: 0x000002,即.data
    

    需要重定位的地方如上表所示,offset为需要重定位的字段(四字节)在.text section中的偏移量(.rel.text是.text section的重定位信息表)。被引用的symbol为.data(因为它是已经初始化的数据,因此被引用符号为.data而不是mydata1和mydata2). info字段中,低八位表示重定位类型(上面显示的是十六进制),因此这里是01即R_386_32 . 高24位表示的是被引用字符在.symtab中的索引,这里高24位为2,查找上面的.symtab可以发现它对应第三条(粗体显示):

    Symbol table '.symtab' contains 7 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000000     0 SECTION LOCAL  DEFAULT    1 
         2: 00000000     0 SECTION LOCAL  DEFAULT    3  
         3: 00000000     0 SECTION LOCAL  DEFAULT    4 
         4: 00000000     0 NOTYPE  LOCAL  DEFAULT    3 mydata1
         5: 00000015     0 NOTYPE  LOCAL  DEFAULT    3 mydata2
         6: 00000000     0 NOTYPE  GLOBAL DEFAULT    1 _start
    

    1~3条分别表示索引号为1、3、4的section的名称,即.text,.data和.bss。第0条默认为STN_UNDEF(undefined)类型 第三条中的Ndx字段为3,表示它是第三节(即.data)中的符号。

    R_386_32重定位方式:
    .text section中偏移量为0x1和偏移量为0x12处的四字节需要更改为.data section在内存中的起始地址+它原来的值(分别为0x0和0x15)。(生成的.o文件中。汇编器(as命令)已经将我们汇编代码中引用到的mydata1和mydata2分别替换为了这两个符号在.data中的偏移地址了)
    细节情况如下:
    R_386_32:重定位一个使用32位绝对地址的引用。其地址计算方法为.symtab中对应的value值加上原始值。链接过程中先将.symtab中索引为2的项(即.data)的value设置为它的虚拟地址(本示例是:80490a4)。用该虚拟地址加上原值,便得到了新的值,这也就是之前所说的那个过程。

    重定位的方式还有很多种,这里只讨论其中之一,也是最常用的一种。注意:当被引用的符号是未初始化的变量时,它和上面的过程一样,整个过程仅仅是把上面涉及的.data符号改为被引用的符号而已。

    总结:

    编译器将程序的入口地址设置为标号_start的地址。所以汇编程序必须提供_start符号,C程序由glibc自动提供,因此在C程序中不能定义新的_start全局符号。

    image.png

    附录

    输出文件

    elf.demo.o

    ELF Header:
      Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              REL (Relocatable file)
      Machine:                           Intel 80386
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          0 (bytes into file)
      Start of section headers:          192 (bytes into file)
      Flags:                             0x0
      Size of this header:               52 (bytes)
      Size of program headers:           0 (bytes)
      Number of program headers:         0
      Size of section headers:           40 (bytes)
      Number of section headers:         8
      Section header string table index: 5
    
    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 00002e 00  AX  0   0  4
      [ 2] .rel.text         REL             00000000 000288 000010 08      6   1  4
      [ 3] .data             PROGBITS        00000000 000064 00002a 00  WA  0   0  4
      [ 4] .bss              NOBITS          00000000 000090 000000 00  WA  0   0  4
      [ 5] .shstrtab         STRTAB          00000000 000090 000030 00      0   0  1
      [ 6] .symtab           SYMTAB          00000000 000200 000070 10      7   6  4
      [ 7] .strtab           STRTAB          00000000 000270 000018 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)
    
    There are no section groups in this file.
    
    There are no program headers in this file.
    
    Relocation section '.rel.text' at offset 0x288 contains 2 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    00000001  00000201 R_386_32          00000000   .data
    00000012  00000201 R_386_32          00000000   .data
    
    There are no unwind sections in this file.
    
    Symbol table '.symtab' contains 7 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000000     0 SECTION LOCAL  DEFAULT    1 
         2: 00000000     0 SECTION LOCAL  DEFAULT    3 
         3: 00000000     0 SECTION LOCAL  DEFAULT    4 
         4: 00000000     0 NOTYPE  LOCAL  DEFAULT    3 mydata1
         5: 00000015     0 NOTYPE  LOCAL  DEFAULT    3 mydata2
         6: 00000000     0 NOTYPE  GLOBAL DEFAULT    1 _start
    
    No version information found in this file.
    

    objdump.demo.o

    demo.o:     file format elf32-i386
     
    Disassembly of section .text:
     
    00000000 <_start>:
       0:   bf 00 00 00 00          mov    $0x0,%edi
       5:   c6 47 11 31             movb   $0x31,0x11(%edi)
       9:   c6 47 12 73             movb   $0x73,0x12(%edi)
       d:   c6 47 13 74             movb   $0x74,0x13(%edi)
      11:   bf 15 00 00 00          mov    $0x15,%edi
      16:   c6 47 11 32             movb   $0x32,0x11(%edi)
      1a:   c6 47 12 6e             movb   $0x6e,0x12(%edi)
      1e:   c6 47 13 64             movb   $0x64,0x13(%edi)
      22:   b8 01 00 00 00          mov    $0x1,%eax
      27:   bb 00 00 00 00          mov    $0x0,%ebx
      2c:   cd 80                   int    $0x80
    

    文件视图

    image.png image.png

    相关文章

      网友评论

          本文标题:ELF文件长什么样子(1/2)

          本文链接:https://www.haomeiwen.com/subject/tshvihtx.html