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。