Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2565982
  • 博文数量: 315
  • 博客积分: 3901
  • 博客等级: 少校
  • 技术积分: 3640
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-08 15:32
个人简介

知乎:https://www.zhihu.com/people/monkey.d.luffy Android高级开发交流群2: 752871516

文章分类

全部博文(315)

文章存档

2019年(2)

2018年(1)

2016年(7)

2015年(32)

2014年(39)

2013年(109)

2012年(81)

2011年(44)

分类: LINUX

2011-08-18 13:20:33

                                                          ELF 文件的初识
一、先介绍以下Elf文件的三种类型
   可重定位目标文件(Object File).o 可执行文件(Executable)  共享库(Shared Object) .so
二、首先介绍以下一个汇编,反汇编的命令
   a 首先是编译汇编到.o文件的命令
   1.1 as max.s -o max.o
   b 其次是从.o编译到test的命令
   1.2 ld mx.o -o max
   c 以下是读elf文件和反汇编的命令(不会可以网上了解下命令,应该可以看懂)
   1.3 readelf -a max.o  readelf -a max
   1.4 hexdump -C max.o  hexdump -C max
   1.5 objdump -d max.o   objdump -d max
   1.6 readelf -h max  //查看表头信息
   1.7 readelf –S test  //查看程序头信息
   1.8 strip max.o   strip max //去除符号,这样在查看的时候就更简洁了
 
三、下面我们开始今天的旅程(昨天老师将了,今天早上走了一遍,现在总结下)
   对了这里先了解下今天要认识的几个字段名字(目标文件的布局)
    .text   文本段,其实叫代码段更好,认为
    .data  数据段
    .bss   未初始化数据的存放的.bss段,不是很了解
    .shstrtab  section header string table 字面意思就是section header 的名字存储表--Ascii
    Section Header Table  这个就是section headers table(叫索引更贴切)
    (这里我要申明一个方法,其实后面说好点,Section Header Table中存放了8个section header,每40个字节分一段,每一段中的16个字节是一行(两行半),然后第一行用来找名字,第二行用来找每个section 的地址。
      先说找名字,第一行,你看到第一个是0x25 == 37 所以去.
shstrtab中去数偏移37(相对0而言,后面讲的也是类似)的那个地方,是一个0x74之类的数字,Ascii码值是一个 .  ,后面的就是字母, 所以就是.data,很神奇。
      再说找地址  第二行第一个就是地址偏移了,如98 00 00 00  后面紧跟的是size 如30 00 00 00 就是 0x30,去找0x98的地方,开始的直到0x98 +0x30 的地方,这么一段就是整个.data段)

    .symtab  程序用到的符号的索引表
    .strtab  存放用到的符号的名字--Ascii
    .rel.text  连接器要用到的重定位的东西(差不多就是规定哪些地方要重定位)
    1.当然我是从第一页开始的。就将了以下Elf文件的格式,还有图示布局(左边是从linker角度来看的,右边是从loader角度来看)
   
     从上到下,依次是ELF header -> program header table -> sections -> section header table
     当然在编译生成的.o文件中是没有program header table 阶段的
     其次链接阶段会将所有的sections 合并为Segment,所以链接生成的可执文件,是没有sections说法,而是多了Segment。所以总体来说就是对.o文件做一些合并,改变,删除,添加等工作。当然还有很多去掉的部分(比如相对地址转变为绝对地址(虽然还是虚拟地址),去除.bss段,去除.rel.text段,新增加两个Program Headers,少了两个section header),总体思路就是这样吧,似乎。
  这段文字不错:(我认为) 目标文件需要连接器做进一步处理,所以一定有Section Header Table;可执行文件需要加载运行,所以一定要有Program Header Table;而共享库既要加载运行,又要加载时做动态链接,所以既有Section Header Table又有Program Header Table.
  下面我们来看看。(以上都是针对max.s文件来说的)代码如下:
   
#Get the max number form array
#use register eax, ebx, edi
#use command movl, je, jle, incl, int, cmpl, jmp

.section .data
data_items:
    .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

.section .text
.globl _start
_start:
    movl $0, %edi
    movl data_items(,%edi,4), %eax
    movl %eax, %ebx

start_loop:
    cmpl $0, %eax
    je loop_exit
    incl %edi
    movl data_items(,%edi,4), %eax
    cmpl %ebx, %eax
    jle start_loop

    movl %eax, %ebx
    jmp start_loop

loop_exit:
    movl $1, %eax
    int $0x80


四、下面开始分析代码,最后在总结以下各个要点
      首先 readelf -a max.o
     


 
  分析:
 表头信息可以用 readelf -h max查看:
    

 结构体就是如下:
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* 魔数和相关信息 */
Elf32_Half e_type; /* 目标文件类型 */
Elf32_Half e_machine; /* 硬件体系 */
Elf32_Word e_version; /* 目标文件版本 */
Elf32_Addr e_entry; /* 程序进入点 */
Elf32_Off e_phoff; /* 程序头部偏移量 */
Elf32_Off e_shoff; /* 节头部偏移量 */
Elf32_Word e_flags; /* 处理器特定标志 */
Elf32_Half e_ehsize; /* ELF头部长度 */
Elf32_Half e_phentsize; /* 程序头部中一个条目的长度 */
Elf32_Half e_phnum; /* 程序头部条目个数 */
Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */
Elf32_Half e_shnum; /* 节头部条目个数 */
Elf32_Half e_shstrndx; /* 节头部字符表索引 */
} Elf32_Ehdr

 一开始:Magic对应结构体Elf32_Ehdr中的 e_ident[EI_NIDENT],e_ident[0] 到e_ident[3]分别为7f 45 4c 46 把这几个数据转换为字符7f是不可打印字符 45 = ‘E’ ,4c = ‘L’, 46 = ‘F’这里就说这是一个ELF文件,e_ident[4] 表示硬件系统的位数,1代表32位,2代表64位。e_ident[5]表示数据的字节序1表示小端排序,2表示大端排序。e_ident[6]表示 ELF头的版本。其余段的意义都可以从Elf32_ehdr的定义查看。

在ELF头部是程序表头,他是一个结构体数组,它包含ELF头表中字段e_phnum所表示的字段。他的结构如下(/usr/include/elf.h可以查看)

typedef struct {
Elf32_Word p_type; /* 段类型 */
Elf32_Off p_offset; /* 段位置相对于文件开始处的偏移量 */
Elf32_Addr p_vaddr; /* 段在内存中的地址 */
Elf32_Addr p_paddr; /* 段的物理地址 */
Elf32_Word p_filesz; /* 段在文件中的长度 */
Elf32_Word p_memsz; /* 段在内存中的长度 */
Elf32_Word p_flags; /* 段的标记 */
Elf32_Word p_align; /* 段在内存中对齐标记 */
} Elf32_Phdr;

可以通过readelf查看这些程序表头:

 

那么好的,我们对表头信息的数字来进行分析,看下如何去对应 hexdump -C max.o生成的十六进制的目标文件的字节信息从第一副图的Section Headers:可以看出,.text, .rel.text, .data, .bss, .shstrtab, symtab, strtab 的真实顺序应该是 :
  
      图中可以看到一个很大的Section Header Table,在readelf -a max.o 生成的那副图中,可以看到start of section headers: 200(bytes into file)
   因为是从0x0开始,所以200 就是0xc8 ,从地址0xc8开始都是section header,总共是8个,每个40字节,构成了一个section table。好的,那么我们就从0xc8开始去划分它吧:
   

其实就是找对应,hexdump -C max.o生成的文件,大体来说分为四个部分:
   第一个部分: ELF Header 大小是 52个字节,所以从 00000000 - 0x34 ,.text就是从0x34开始,这下你就看到第4副图就是这样布局的了。
  第二部分:Section Header ,从0xc8开始,大小是200(0xc8)字节。里面是8个section header(section 的索引的说法更好)
  第三部分: section header table 索引对应的8个真正地址的地方,从这张表中可以查到。
  第四部分: 就是.symtab 里面还可以分为几个部分,这里没有画出,就是第二副图的最后一个部分.symtab 的8个entries。总体来说差不多就是这样。至于里面数据的具体细节,在以后的对于硬件的学习,编程的学习都是会有用的。

       总结下这个小节。在找来找去的过程中,发现.bss是与.shstrtab 重合的,都是000098,但是这里.bss段保存的未初始化的变量是需要占用空间的,所以,只是占了一个section header(就是section header table里面8个之一),而没有对应的Section 。而程序加载的时候,占用多少空间有Sectino Header来描述。
      当然.symtab 符号表中还有一些细节,比如,标明了哪些符号是全局的,哪些符号是局部的。总体来讲,.globl申明过的符号会成为全局数据,否则是局部数据。

  四.(一)
    下一步我们来看下反汇编的max.o文件, objdump -d max.o
     这样你会看得更清晰:
max.o:     file format elf32-i386


Disassembly of section .text:

00000000 <_start>:
   0:    bf 00 00 00 00           mov    $0x0,%edi
   5:    8b 04 bd 00 00 00 00     mov    0x0(,%edi,4),%eax
   c:    89 c3                    mov    %eax,%ebx

0000000e :
   e:    83 f8 00                 cmp    $0x0,%eax
  11:    74 10                    je     23
  13:    47                       inc    %edi
  14:    8b 04 bd 00 00 00 00     mov    0x0(,%edi,4),%eax
  1b:    39 d8                    cmp    %ebx,%eax
  1d:    7e ef                    jle    e
  1f:    89 c3                    mov    %eax,%ebx
  21:    eb eb                    jmp    e

00000023 :
  23:    b8 01 00 00 00           mov    $0x1,%eax
  28:    cd 80                    int    $0x80
左边是机器指令的字节,右边是反汇编结果。所有符号都变成了字节。加$的表示内存地址。目前的地址都是相对地址,只有经过连接器将这些地址改成加载到内存的地址,这样指令才可以正确的执行。

   四.(二)
     readelf -a max
    
    


 与max.o文件相比,你会发现很多地方都不一样,主要来讲,文件类型   
  REL(Relocatable file) => EXEC(Executable file)
  Entry point address 0x0 => 0x8048074
  还有一些字节大小也改变了。

  其次。比max.o少了两个section header (.bss 和 .rel.text)
                        多了两个Program Header
            它标识的应该是文本段,和数据段加载的地址。从上图中看出最后就只有.text和.data段是被加载的。此外多了三个符号。ABS  _bss_start ABS _edata
ABS _end

  下面解释下,为什么是这种现象:
    首先 .bss段没有用到,所以删除,然后.rel.text段就是在链接过程中会用到,所以,在生成可执行文件以后也删掉。
    多出来的Program Header Table 描述了两个Segment(分别是Segment 1 和 Segment 2)
   Segment 1 由.text段和ELF header 、Program Header Table 组成 size 0x9e
   Segment 2 由.data段组成 size 0x38

   具体的加载地址和情况如下图所示:
  



   为什么是这样加载的呢?有如下解释
    因为MMU的权限保护机制是以页为单位的,一个页面只能设置一种权限,所以,Text Segment加载到0x08048000~0x8048fff(4K的页面,占不满也没办法),代码段类似。还有就是text 加载页面从首地址开始(0x08048000),但是它包括文件名,所以.text段,不是从
0x08048000开始的,而是0x08048074开始的,也是_start的开始。


总的来说发生变化的有以下几点:
  1. type relocatable file => excutable
  2. Entry point address
  3. section header
  4. program  header
  5. .rel.text .bss
  6. .symtab

阅读(3648) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~