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

和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 :     "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))