ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中)-阿里云开发者社区

开发者社区> 开发与运维> 正文

ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中)

简介: ELF中的section和segment

ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中)

段(section)的概念

一块内存分配给应用程序之后,从代码的组织上,我们就有将它们分段的需求。
比如,可以分为代码段,数据段,只读数据段,堆栈段,未初始化的数据段等等。

在GAS汇编器中,我们通过.section伪指令来指定段名。ARM编译器我买不起,我就忽略它了。

标准section

段的描述 默认段名
代码段 .text
经过初始化的数据段 .data
未经初始化的数据段 .bss

BSS是Block Started by Symbol的缩写,就是为符号预留一些空间。

为什么要将指令段和数据段分开呢?这要从冯.诺依曼结构和哈佛结构说起。
诺依曼结构是指令和数据都存在同一个存储器上,比如Intel的8086,MIPS,ARM7都是这样的结构。因为指令和数据都要走同一条总线,所以会产生一些竞争。

与此相对,将指令存储和数据存储分开,这就是哈佛结构。从ARM9开始,ARM芯片就是哈佛结构了。它们各走各自的总线,各有各的cache,可以提高命中率。

即使在诺依曼结构下,通常也是将代码和指令存放在同一存储器的不同区域。

section示例

我们找一个gcc -S生成的汇编来看一下:
源码就用俗到不能再俗的hello,world的例子:

#include <cstdio>
int main(){
        printf("Hello,Word!\n");
}

生成的汇编如下:

        .file   "hello.cc"
        .section        .rodata
.LC0:
        .string "Hello,Word!"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $.LC0, %edi
        call    puts
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
        .section        .note.GNU-stack,"",@progbits

像.text,.data和.bss这样的标准段直接就是伪指令,不用再加.section了。
而像本例这样,将字符串放到.rodata这样的区域中,就需要加.section伪指令来声明一下。
.rodata可以对应C++的const关键字,为常量专门区分一个区域。

GCC生成的section知多少

我们通过用objdump -h命令来查看我们刚才的helloworld代码被编译成多少个段。打出来一看,还真不少,一共有26个。

a.out:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     0000001c  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000060  00000000004002b8  00000000004002b8  000002b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       0000003d  0000000000400318  0000000000400318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000008  0000000000400356  0000000000400356  00000356  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  0000000000400360  0000000000400360  00000360  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     00000018  0000000000400380  0000000000400380  00000380  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000030  0000000000400398  0000000000400398  00000398  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         00000018  00000000004003c8  00000000004003c8  000003c8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000030  00000000004003e0  00000000004003e0  000003e0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         000001d8  0000000000400410  0000000000400410  00000410  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini         0000000e  00000000004005e8  00000000004005e8  000005e8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       00000010  00000000004005f8  00000000004005f8  000005f8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr 0000002c  0000000000400608  0000000000400608  00000608  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame     000000a4  0000000000400638  0000000000400638  00000638  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .ctors        00000010  0000000000600e28  0000000000600e28  00000e28  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .dtors        00000010  0000000000600e38  0000000000600e38  00000e38  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 19 .jcr          00000008  0000000000600e48  0000000000600e48  00000e48  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      00000190  0000000000600e50  0000000000600e50  00000e50  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000008  0000000000600fe0  0000000000600fe0  00000fe0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000028  0000000000600fe8  0000000000600fe8  00000fe8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .data         00000010  0000000000601010  0000000000601010  00001010  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000010  0000000000601020  0000000000601020  00001020  2**3
                  ALLOC
 25 .comment      0000002a  0000000000000000  0000000000000000  00001020  2**0
                  CONTENTS, READONLY

好吧,不是每种语言都这么疯狂的,我们看看Go语言生成的段:

除了debug相关的,只有8个段,比GCC还是简单一些的。

main:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000fb00  0000000000400c00  0000000000400c00  00000c00  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       000073b0  0000000000410700  0000000000410700  00010700  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .gosymtab     0000ad5c  0000000000417ab0  0000000000417ab0  00017ab0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gopclntab    00002248  0000000000422810  0000000000422810  00022810  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .noptrdata    00000008  0000000000425000  0000000000425000  00025000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  5 .data         000022b0  0000000000425008  0000000000425008  00025008  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  6 .bss          00011830  00000000004272b8  00000000004272b8  000272b8  2**3
                  ALLOC
  7 .noptrbss     02009a38  0000000000438ae8  0000000000438ae8  00038ae8  2**3
                  ALLOC
  8 .debug_abbrev 000000cc  0000000000000000  0000000000000000  0002c93a  2**0
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_line   00004868  0000000000000000  0000000000000000  0002ca06  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_frame  00003240  0000000000000000  0000000000000000  0003126e  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_info   00008828  0000000000000000  0000000000000000  000344ae  2**0
                  CONTENTS, READONLY, DEBUGGING
 12 .debug_pubnames 00001dd3  0000000000000000  0000000000000000  0003ccd6  2**0
                  CONTENTS, READONLY, DEBUGGING
 13 .debug_pubtypes 00000ae0  0000000000000000  0000000000000000  0003eaa9  2**0
                  CONTENTS, READONLY, DEBUGGING
 14 .debug_aranges 00000630  0000000000000000  0000000000000000  0003f589  2**0
                  CONTENTS, READONLY, DEBUGGING
 15 .debug_gdb_scripts 0000002c  0000000000000000  0000000000000000  0003fbb9  2**0
                  CONTENTS, READONLY, DEBUGGING

OAT都有些什么section

幸好,OAT的section比C++的还要少一些。主要的段有14个。
一个指令段,7个只读的数据段,6个调试用的段。

base.odex:     file format elf64-littleaarch64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .dynsym       00000060  0000000000000200  0000000000000200  00000200  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .dynstr       00000027  0000000000000260  0000000000000260  00000260  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .hash         00000020  0000000000000288  0000000000000288  00000288  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .rodata       0030f000  0000000000001000  0000000000001000  00001000  2**12
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .text         003c9424  0000000000310000  0000000000310000  00310000  2**12
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  5 .dynamic      00000070  00000000006da000  00000000006da000  006da000  2**12
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .eh_frame     000869b0  00000000006db000  00000000006db000  0084d000  2**12
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .eh_frame_hdr 00016744  00000000007619b0  00000000007619b0  008d39b0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .debug_info   000427a1  0000000000000000  0000000000000000  008ea0f4  2**0
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_info.oat_patches 00006028  0000000000000000  0000000000000000  0092c895  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_abbrev 000055bf  0000000000000000  0000000000000000  009328bd  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_str    001328d8  0000000000000000  0000000000000000  00937e7c  2**0
                  CONTENTS, READONLY, DEBUGGING
 12 .debug_line   00038d42  0000000000000000  0000000000000000  00a6a754  2**0
                  CONTENTS, READONLY, DEBUGGING
 13 .debug_line.oat_patches 00000583  0000000000000000  0000000000000000  00aa3496  2**0
                  CONTENTS, READONLY, DEBUGGING

section的类型

从上面列出的信息可以看到,段是有自己的类型的,而且还有对应的如可写,只读,可执行等属性。

段的类型

常量 含义
SHT_NULL 0 无效段
SHT_PROGBITS 1 程序段
SHT_SYMTAB 2 符号表
SHT_STRTAB 3 字符串表
SHT_RELA 4 重定位表
SHT_HASH 5 符号表的哈希表
SHT_DYNAMIC 6 动态链接信息
SHT_NOTE 7 提示性信息,注释
SHT_NOBITS 8 无内容,如未初始化的.bss段
SHT_REL 9 重定位信息
SHT_SHLIB 10 RFU
SHT_DYNSYM 11 动态链接的符号表

段的属性

段的标志位

常量 含义
SHF_WRITE 1 可写
SHF_ALLOC 2 需要分配空间,有些段并不需要,而.code,.bss,.data这些都需要分配空间
SHF_EXEINSTR 4 可执行,一般只有.code段才有这个属性

下面我们学以致用,通过readelf -S看看OAT文件的段的类型和属性:

带A的是SHF_ALLOC属性,X是可执行属性。连同符号表、字符串表,空字段等字段,一共有18个字段。

There are 18 section headers, starting at offset 0xaa3ae0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .dynsym           DYNSYM           0000000000000200  00000200
       0000000000000060  0000000000000018   A       2     0     8
  [ 2] .dynstr           STRTAB           0000000000000260  00000260
       0000000000000027  0000000000000000   A       0     0     1
  [ 3] .hash             HASH             0000000000000288  00000288
       0000000000000020  0000000000000004   A       1     0     4
  [ 4] .rodata           PROGBITS         0000000000001000  00001000
       000000000030f000  0000000000000000   A       0     0     4096
  [ 5] .text             PROGBITS         0000000000310000  00310000
       00000000003c9424  0000000000000000  AX       0     0     4096
  [ 6] .dynamic          DYNAMIC          00000000006da000  006da000
       0000000000000070  0000000000000010   A       2     0     4096
  [ 7] .symtab           SYMTAB           0000000000000000  006da070
       00000000000435c0  0000000000000018           8     0     8
  [ 8] .strtab           STRTAB           0000000000000000  0071d630
       000000000012f609  0000000000000000           0     0     1
  [ 9] .eh_frame         PROGBITS         00000000006db000  0084d000
       00000000000869b0  0000000000000000   A       0     0     4096
  [10] .eh_frame_hdr     PROGBITS         00000000007619b0  008d39b0
       0000000000016744  0000000000000000   A       0     0     4
  [11] .debug_info       PROGBITS         0000000000000000  008ea0f4
       00000000000427a1  0000000000000000           0     0     1
  [12] .debug_info.oat_p LOUSER+0         0000000000000000  0092c895
       0000000000006028  0000000000000000           0     0     1
  [13] .debug_abbrev     PROGBITS         0000000000000000  009328bd
       00000000000055bf  0000000000000000           0     0     1
  [14] .debug_str        PROGBITS         0000000000000000  00937e7c
       00000000001328d8  0000000000000000           0     0     1
  [15] .debug_line       PROGBITS         0000000000000000  00a6a754
       0000000000038d42  0000000000000000           0     0     1
  [16] .debug_line.oat_p LOUSER+0         0000000000000000  00aa3496
       0000000000000583  0000000000000000           0     0     1
  [17] .shstrtab         STRTAB           0000000000000000  00aa3a19
       00000000000000c1  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute)

segment

上面我们了解了section,这是针对ELF文件中的组织单元。下面我们讨论另一个概念叫做segment。

将section映射到内存中时,为了节省空间,我们可以将相同类型和属性的section放在一起。因为section很多,每个都占用整个页面的话会导致页的浪费,而合到一起则只需要每个segment占满整页就可以了。

我们通过readelf -l参数可以查看segment的分配情况,如下例:

Elf file type is DYN (Shared object file)
Entry point 0x0
There are 7 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x0000000000000188 0x0000000000000188  R      8
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000310000 0x0000000000310000  R      1000
  LOAD           0x0000000000310000 0x0000000000310000 0x0000000000310000
                 0x00000000003c9424 0x00000000003c9424  R E    1000
  LOAD           0x00000000006da000 0x00000000006da000 0x00000000006da000
                 0x0000000000000070 0x0000000000000070  RW     1000
  DYNAMIC        0x00000000006da000 0x00000000006da000 0x00000000006da000
                 0x0000000000000070 0x0000000000000070  RW     1000
  LOAD           0x000000000084d000 0x00000000006db000 0x00000000006db000
                 0x000000000009d0f4 0x000000000009d0f4  R      1000
  GNU_EH_FRAME   0x00000000008d39b0 0x00000000007619b0 0x00000000007619b0
                 0x0000000000016744 0x0000000000016744  R      4

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .dynsym .dynstr .hash .rodata 
   02     .text 
   03     .dynamic 
   04     .dynamic 
   05     .eh_frame .eh_frame_hdr 
   06     .eh_frame_hdr 

segment类型

常量 描述
PT_NULL 0 空值
PT_LOAD 1 加载到内存中
PT_DYNAMIC 2 动态链接
PT_INTERP 3 动态链接的辅助信息
PT_NOTE 4 暂时用不到
PT_SHLIB 5 RFU
PT_PHDR 6 程序表头的位置和大小
GNU_EH_FRAME .eh_frame_hdr专用

总结起来,过程就是,先装载数据,再装载代码,然后是动态链接相关的,最后是系统的一些辅助信息。

ELF文件头(下)

积累了前面这么多基础知识之后,我们终于可以向ELF文件头的后半部分前进了。

做为坚定的64位的爱好者,我以64位为例来讲解后面的文件头,兼顾32位。

偏移量 字段 长度 描述
0x18 e_entry 8 程序的入口点,32位下为4字节
0x20 e_phoff 8 程序头表的地址,其实就是segment表,对应segment与section的关系的,32位下为4字节
0x28 e_shoff 8 段头表的地址,指向section的表,32位下为4字节
0x30 e_flags 4 根据架构不同的标志位
0x34 e_ehsize 2 ELF头的长度,在64位下是64字节,在32位下是52字节。差的12个字节就是上面的三个字段e_entry,e_phoff,e_shoff给闹的
0x36 e_phentsize 2 程序头表项的长度
0x38 e_phnum 2 程序头表的记录数
0x3A e_shentsize 2 section表记录的长度
0x3C e_shnum 2 section表的记录数
0x3E e_shstrndx 2 section表中的哪一项是名字项

好,我们还把上章的那个例子拿出来:

7F 45 4C 46  02 01 01 03  00 00 00 00  00 00 00 00  
03 00 B7 00  01 00 00 00  00 00 00 00  00 00 00 00  
40 00 00 00  00 00 00 00  E0 3A AA 00  00 00 00 00  
00 00 00 00  40 00 38 00  07 00 40 00  12 00 11 00  
06 00 00 00  04 00 00 00  40 00 00 00  00 00 00 00  
40 00 00 00  00 00 00 00

还记得我们上次分析的头0x18个字节吗?

  • 7F 45 4C 46: Magic Number,0x7f-E-L-F
  • 02:64位
  • 01:Little Endian,小端
  • 01:版本号1
  • 03:Linux
  • 8个0:没用到
  • 03 00:动态链接库
  • B7 00: AArch64
  • 01 00 00 00: 版本号1

温故而知新,我们开始这节学到的内容:

  • 00 00 00 00 00 00 00 00:e_entry入口地址,0
  • 40 00 00 00 00 00 00 00:程序表头地址:本文件0x40开始
  • E0 3A AA 00 00 00 00 00:section表的地址:0xaa3ae0
  • 00 00 00 00: flag,全0
  • 40 00:ELF头的长度,0x40,64字节。
  • 38 00:每个segment表项的长度,0x38,56字节
  • 07 00:segment一共多少项:7项
  • 40 00:section表记录的长度,0x40,64字节
  • 12 00:section共有多少个,18个
  • 11 00:名字的index,17

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章