ELF文件(基于Arm+Linux)

1 ELF文件

    ELF(Executable and Linkable Format, ELF):可执行与链接文件,它描述了整个文件的组织结构,操作系统据此来将文件加载到内存并执行。

1.1 ELF文件的头部结构

    ELF文件的头部信息如下图,总共52个字节
在这里插入图片描述
     ELF ID信息中,第一个字节为0x7f,第2,3,4字符分别为’E’,‘L’,‘F’,表明该文件为ELF文件。

     type 它标识的是该文件的类型。
     machine 表明运行该程序需要的体系结构。
     version 表示文件的版本。
     entry 程序的入口地址。
     ProgramHeaderTableOffset: 表示Program header table 在文件中的偏移量(以字节计数)
     SectionHeaderTableOffset: 表示Section header table 在文件中的偏移量(以字节计数)
     processor-specific flags:对IA32而言,此项为0。
    ELF header size 表示ELF header大小(以字节计数)。
    program header entry size表示Program header table中每一个条目的大小。
    number of program header entries:表示Program header table中有多少个条目。
     Section header string table index表示Section header table中的每一个条目的大小。

1.2 节区(Sections)

    节区中包含目标文件中的所有信息,除了:ELF 头部、程序头部表格、节区头部表格。节区满足以下条件:
(1). 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
(2). 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。

root@orangepilite2:/home/orangepi/arm_asm# readelf disassable  -S
There are 29(节区的数量) section headers, starting at offset 0x1f10(节区的偏移位置):

Section Headers:
  [Nr] Name              Type             Address(地址)           Offset(偏移与对齐)
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001b  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000003c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002a0  000002a0
       00000000000000c0  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400360  00000360
       0000000000000084  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004003e4  000003e4
       0000000000000010  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004003f8  000003f8
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             0000000000400438  00000438
       0000000000000030  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400468  00000468
       0000000000000090  0000000000000018  AI       5    22     8
  [11] .init             PROGBITS         00000000004004f8  000004f8
       0000000000000014  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400510  00000510
       0000000000000080  0000000000000010  AX       0     0     16
  [13] .text(代码段)             PROGBITS         0000000000400590  00000590
       0000000000000364  0000000000000000  AX       0     0     8
  [14] .fini             PROGBITS         00000000004008f4  000008f4
       0000000000000010  0000000000000000  AX       0     0     4
  [15] .rodata(只读数据段)           PROGBITS         0000000000400908  00000908
       000000000000004b  0000000000000000   A       0     0     8
  [16] .eh_frame         PROGBITS         0000000000400954  00000954
       0000000000000004  0000000000000000   A       0     0     4
  [17] .init_array       INIT_ARRAY       0000000000410de0  00000de0
       0000000000000008  0000000000000000  WA       0     0     8
  [18] .fini_array       FINI_ARRAY       0000000000410de8  00000de8
       0000000000000008  0000000000000000  WA       0     0     8
  [19] .jcr              PROGBITS         0000000000410df0  00000df0
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .dynamic          DYNAMIC          0000000000410df8  00000df8
       00000000000001e0  0000000000000010  WA       6     0     8
  [21] .got              PROGBITS         0000000000410fd8  00000fd8
       0000000000000010  0000000000000008  WA       0     0     8
  [22] .got.plt          PROGBITS         0000000000410fe8  00000fe8
       0000000000000048  0000000000000008  WA       0     0     8
  [23] .data(数据段)              PROGBITS         0000000000411030  00001030
       0000000000000010  0000000000000000  WA       0     0     8
  [24] .bss(bss数据段)               NOBITS           0000000000411040  00001040
       0000000000000010  0000000000000000  WA       0     0     8
  [25] .comment          PROGBITS         0000000000000000  00001040
       000000000000003c  0000000000000001  MS       0     0     1
  [26] .shstrtab         STRTAB           0000000000000000  00001e12
       00000000000000fa  0000000000000000           0     0     1
  [27] .symtab           SYMTAB           0000000000000000  00001080
       0000000000000a08  0000000000000018          28    76     8
  [28] .strtab           STRTAB           0000000000000000  00001a88
       000000000000038a  0000000000000000           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)

     程序运行时的代码段及数据段,在节区里面都描述了,代码中的变量在编译以后,存放于.text,.bss,.rodata,.data中。
    .需要注意的是:
    .data数据段,data指那些初始化过(非零)的非const的全局变量,占用文件空间
     .bssbss是指那些没有初始化的和初始化为0的全局变量,不占文件空间

     .symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
    .debug:一个调试符号表,其有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表
    .init:此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码。
    .fini:此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。
    .dynamic: 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器。

1.3 Program Headers

    .;section header用于描述section的特性,而program header用于描述segment的特性,目标文件(也就是文件名以.o结尾的文件)不存在program header,因为它不能运行。一个segment包含一个或多个现有的section,相当于从程序执行的角度来看待这些section。
    .;一个program header可能包含多个section,通过section到segment的映射关系可以获取

    .;记载ELF文件时,会根据program header信息读取section到内存。

root@orangepilite2:/home/orangepi/arm_asm# readelf disassable  -l

Elf file type is EXEC (Executable file)
Entry point 0x400590
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001c0 0x00000000000001c0  R E    8
  INTERP         0x0000000000000200 0x0000000000400200 0x0000000000400200
                 0x000000000000001b 0x000000000000001b  R      1
      [Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000958 0x0000000000000958  R E    10000
  LOAD           0x0000000000000de0 0x0000000000410de0 0x0000000000410de0
                 0x0000000000000260 0x0000000000000270  RW     10000
  DYNAMIC        0x0000000000000df8 0x0000000000410df8 0x0000000000410df8
                 0x00000000000001e0 0x00000000000001e0  RW     8
  NOTE           0x000000000000021c 0x000000000040021c 0x000000000040021c
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000de0 0x0000000000410de0 0x0000000000410de0
                 0x0000000000000220 0x0000000000000220  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id    06     
   07     .init_array .fini_array .jcr .dynamic .got 

1.4 ELF文件与二级制文件

     ELF文件并不是一种可以被机器直接执行的文件,里面包含有符号表等信息,这些信息是供操作系统使用的,操作系统利用ELF文件信息最终解析成二级制文件来执行,当然Boot中也可以识别ELF文件,但也得转化成二级制文件才能执行。而二级制文件可以被机器直接指向。
     通过objcopy命令可以将ELF文件转换为二级制文件

arm-linux-gnueabi-objcopy  -O binary  boot0_sdcard.axf boot0_sdcard.bin

     Linux并不支持直接执行二进制文件。通常Boot文件是二进制文件,因为设备启动时不存在操作系统,只能识别二进制文件。Boot本身也支持允许ELF文件和BIN文件

1.5 内存映像

1.6 ELF文件的加载

     ELF文件的加载:解析ELF文件的内容,将代码段、数据段放到内存中,跳转到入口entry中执行程序。
     以下为Uboot下加载ELF文件的例子:遍历每个program header的信息,复制program header中segment offset位置的内容到physical address,内容不足,填充0。由于program header描述的是包含那几个section文件,这样ELF文件的section最终组成了BIN文件。

static unsigned long load_elf_image_phdr(unsigned long addr)
{
	Elf32_Ehdr *ehdr;		/* Elf header structure pointer     */
	Elf32_Phdr *phdr;		/* Program header structure pointer */
	int i;

	ehdr = (Elf32_Ehdr *) addr;
	phdr = (Elf32_Phdr *) (addr + ehdr->e_phoff);

	/* Load each program header */
	for (i = 0; i < ehdr->e_phnum; ++i) {
		void *dst = (void *)(uintptr_t) phdr->p_paddr;
		void *src = (void *) addr + phdr->p_offset;
		debug("Loading phdr %i to 0x%p (%i bytes)\n",
			i, dst, phdr->p_filesz);
		if (phdr->p_filesz)
			memcpy(dst, src, phdr->p_filesz);
		if (phdr->p_filesz != phdr->p_memsz)
			memset(dst + phdr->p_filesz, 0x00,
				phdr->p_memsz - phdr->p_filesz);
		flush_cache((unsigned long)dst, phdr->p_filesz);
		++phdr;
	}

	return ehdr->e_entry;
}
举例:program headers的内容如下
orangepi@orangepilite2:~/arm_asm$ readelf disassable -l

Elf file type is EXEC (Executable file)
Entry point 0x400590
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001c0 0x00000000000001c0  R E    8
  INTERP         0x0000000000000200 0x0000000000400200 0x0000000000400200
                 0x000000000000001b 0x000000000000001b  R      1
      [Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000958 0x0000000000000958  R E    10000
  LOAD           0x0000000000000de0 0x0000000000410de0 0x0000000000410de0
                 0x0000000000000260 0x0000000000000270  RW     10000
  DYNAMIC        0x0000000000000df8 0x0000000000410df8 0x0000000000410df8
                 0x00000000000001e0 0x00000000000001e0  RW     8
  NOTE           0x000000000000021c 0x000000000040021c 0x000000000040021c
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000de0 0x0000000000410de0 0x0000000000410de0
                 0x0000000000000220 0x0000000000000220  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     
   07     .init_array .fini_array .jcr .dynamic .got 
    
   第1个Segment 包含的section为:  .interp 
   第2个Segment 包含的section为:  interp、.text代码段、 .rodata 
   第3个Segment 包含的section为: .dynamic、.data 、.bss 
 

     ELF文件的加载:解析ELF文件的内容,将代码段、数据段放到内存中,跳转到入口entry中执行程序。

1.7 ELF文件执行

     加载完毕以后,跳转到程序的entry,entry的值是 .text代码段的地址

orangepi@orangepilite2:~/arm_asm$ readelf disassable -h
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0x400590
  Start of program headers:          64 (bytes into file)
  Start of section headers:          7952 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 26

orangepi@orangepilite2:~/arm_asm$ readelf disassable -S        
There are 29 section headers, starting at offset 0x1f10:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001b  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000003c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002a0  000002a0
       00000000000000c0  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400360  00000360
       0000000000000084  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004003e4  000003e4
       0000000000000010  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004003f8  000003f8
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             0000000000400438  00000438
       0000000000000030  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400468  00000468
       0000000000000090  0000000000000018  AI       5    22     8
  [11] .init             PROGBITS         00000000004004f8  000004f8
       0000000000000014  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400510  00000510
       0000000000000080  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         0000000000400590  00000590
       0000000000000364  0000000000000000  AX       0     0     8

     Entry point address:与 .text的地址都为0x400590

2 BIN文件

     根据第1章的加载过程及对.bin文件的分析,可以大概知道BIN的结构,这是一个可以执行的内存映像
在这里插入图片描述

2.1  代码段的指令如何访问全局变量?

     对于局部变量,存在于栈中,随着栈顶栈底的移动而为局部变量分配内存,函数使用完以后,局部变量也就销毁了。
     对于全局变量,.text入和访问呢?。
    
adrp指令
adrp就是address page 的简写,这里的page指的是大小为4KB的连续内存,和操作系统中的页不是一回事。该指令的作用是将label所在页且4KB对其的页基地址放入寄存器Xd中。Labe表示的地址肯定在这个页基地址确定的页内。
adrp指令通常用于获取静态存储区的内容,隐含的条件是pc指针,它是不断变化的寄存器

1 
c语言源代码如下,定义了一个全局变量Id和字符串指针str,该字符串执行静态存储区
#include<stdio.h>
int Id=0xffff-1;
char *str="77777";
void main()
{
   int tmp=Id;
   char *tmpStr=str;
   tmp+=10;
}
对该代码编译,并加入gdb信息。
gcc -g disassable.c -o disassable          
2 使用gdb查看函数的反汇编
 gdb disassable
(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000400570 <+0>:     sub     sp, sp, #0x10
   0x0000000000400574 <+4>:     adrp    x0, 0x411000
   0x0000000000400578 <+8>:     add     x0, x0, #0x28
   0x000000000040057c <+12>:    ldr     w0, [x0]
   0x0000000000400580 <+16>:    str     w0, [sp,#4]
   0x0000000000400584 <+20>:    adrp    x0, 0x411000
   0x0000000000400588 <+24>:    add     x0, x0, #0x30
   0x000000000040058c <+28>:    ldr     x0, [x0]
   0x0000000000400590 <+32>:    str     x0, [sp,#8]
   0x0000000000400594 <+36>:    ldr     w0, [sp,#4]
   0x0000000000400598 <+40>:    add     w0, w0, #0xa
   0x000000000040059c <+44>:    str     w0, [sp,#4]
   0x00000000004005a0 <+48>:    nop
   0x00000000004005a4 <+52>:    add     sp, sp, #0x10
   0x00000000004005a8 <+56>:    ret
End of assembler dump.
(gdb) 
3 分析汇编代码
sub     sp, sp, #0x10 ->sp为栈顶,栈顶下移,腾出0x10字节的地址空间,arm体系的栈低在高地址,栈顶在低地址,所以栈顶使用减法,
adrp    x0, 0x411000->获取标号所在的页基址,0x411000。实际允运行结果x0为0x411000(根据readelf disassable -S的结果.text与.data不在一个页中,.text和.rofata在同个页中)
add     x0, x0, #0x28->获取内存x0+ #0x28的内容,根据.data的内容,x0 + #0x28即为全局变量Id的值(对.rodata和.data的访问,在编译阶段已经确定,前提是数据段和代码段挨在一起。)

root@orangepilite2:/home/orangepi/arm_asm# readelf disassable  -x .data  

Hex dump of section '.data':
  0x00411018 00000000 00000000 00000000 00000000 ................
  0x00411028 feff0000 00000000 48064000 00000000 ........H.@.....

2.2  数据段、代码段的大小和BIN文件有哪些关系

     ELF文件主要有代码段和数据段组成,代码段.text是指令的集合,是程序运行时顺序执行的指令,.text的大小与代码量相关。
     数据相关的 .bss段、.data段、.rodata十分影响可执行文件的大小
    **前面已经提到过,.data数据段,存放那些初始化过(非零)的非const的全局变量,占用文件空间。bss是指那些没有初始化的和初始化为0的全局变量,不占文件空间。.rodata存放只读数据,如只读数据

#include<stdio.h>
int Id=0xffff-1;
char *str="77777";
char array[128];
void main()
{
   int tmp=Id;
   char *tmpStr=str;
   tmp+=10;
}
array是为初始化的数组,它的存放位置在.bss
,不占用可执行文件的内容.```

```c
#include<stdio.h>
int Id=0xffff-1;
char *str="77777";
char array[128];
void main()
{
   int tmp=Id;
   char *tmpStr=str;
   tmp+=10;
}
array是未初始化的数组,它的存放位置在.bss
,不占用可执行文件的内容.```

```c
#include<stdio.h>
int Id=0xffff-1;
char *str="77777";
int array[128]={'a','b','a','b','a','b','a','b','a','b','a','b','a','b','a','b'};
int array11[128];
int array16[128];
void main()
{
   int tmp=Id;
   array[0]=100;
   char *tmpStr=str;
   tmp+=10;
}
array是初始化的数组,它的存放位置在.data

     从这一点来讲,代码中未初始化的变量和初始化的变量的存储位置不一样,所以好的习惯是:变量声明的时候同时也将其初始化,可以避免以后访问了未知的值,对于指针,更是如此,访问野指针是很危险的。

2.3  栈在哪里

     栈的位置由SP指针决定,与代码段、数据段分离。SP的值在运行时确定,在代码段中修改SP。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值