预处理、编译、汇编和链接。
汇编阶段会生成.o目标文件(待重定位文件)。重定位是指文件里面的符号没有安排地址,这些符号的地址需要将来与其他目标文件组成一个可执行文件时在重新安排地址
nm test.o//可以看到符号地址都是0
00000000 T main
U puts
使用其他命令读取到的地址也是0,因为还没有安排地址
链接阶段则是将多个目标文件链接成一个二进制可执行文件。在链接时我们得到了所有的目标文件,这个时候就能够对各个符号的地址进行编排了。经过了链接,可执行的文件里面的符号就有了地址
任何程序都需要被加载到内存里面才能运行。那我们应该如何去解析程序呢?elf就提供了一种解析程序的方式。elf文件里面有程序头,描述了程序的布局信息 。我们按照一定的规则去加载该elf文件就能将程序跑起来。
linux下可执行文件格式是elf(executable and linkable format).elf有三种文件类型
程序中的段和节是真正的程序体。段是由节组成了,多个节经过链接之后就合并成一个段了(elf文件里面的section header和program header其实都是描述的同一个东西)
摘抄自https://www.cnblogs.com/0xHack/p/11575444.html
section和segment的区别:
- section称为节,是指在汇编源码中经由关键字section或segment修饰、逻辑划分的指令或数据区域。
- segment称为段,是根据目标文件中属性相同的多个section合并后的section集合,这个集合称为segment。我们平时所说的可执行程序内存空间中的代码段和数据段就是指的segment。
- section主要提供给Linker使用, 而segment提供给Loader用。Linker需要关心.text、.rel.text、.data、.rodata等,因为Linker需要做relocation,而Loader只需要知道Read/Write/Execute的属性
- executable的ELF文件可以没有section,但必须有segment。ELF文件中间部分是共用的(也就是代码段、数据段等),如shared objects就可以同时拥有Program header table和Section Header Table,这样load完后还可以relocate。
- 这样设定之后,使得Loader需要做的工作大大减少了,一定程度上提高了程序加载的效率。
由于段和节的数量不确定。因此就产生了程序头表以及节头表来描述所有的段和节。这些表就相当于一个数组,程序头表里面的元素就对应了一个段,节头表的原因对应了一个节。
由于段和节的数量不固定,自然程序头表以及节头表的大小也不固定了。因此需要在一个固定的位置,用一个固定大小的数据去描述这些表的大小以及位置。这个数据结构便是elf header
上图就是elf文件的布局。从上图可以看到,段其实就是多个section组成的。
elf header的信息如下:从elf header中能够看到程序头表以及节头表的起始位置,以及对应的元素个数等信息。(该elf文件是静态编译的)
ELF Header:
Magic: 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048d2a
Start of program headers: 52 (bytes into file)
Start of section headers: 663836 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 31
Section header string table index: 28
elf header的数据结构用如下数据结构描述
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
以下是section信息。可以看到确实是32个section
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.ABI-tag NOTE 080480f4 0000f4 000020 00 A 0 0 4
[ 2] .note.gnu.build-i NOTE 08048114 000114 000024 00 A 0 0 4
[ 3] .rel.plt REL 08048138 000138 000070 08 A 0 5 4
[ 4] .init PROGBITS 080481a8 0001a8 000023 00 AX 0 0 4
[ 5] .plt PROGBITS 080481d0 0001d0 0000e0 00 AX 0 0 16
[ 6] .text PROGBITS 080482b0 0002b0 075c64 00 AX 0 0 16
[ 7] __libc_freeres_fn PROGBITS 080bdf20 075f20 000b26 00 AX 0 0 16
[ 8] __libc_thread_fre PROGBITS 080bea50 076a50 000076 00 AX 0 0 16
[ 9] .fini PROGBITS 080beac8 076ac8 000014 00 AX 0 0 4
[10] .rodata PROGBITS 080beae0 076ae0 01bff0 00 A 0 0 32
[11] __libc_subfreeres PROGBITS 080daad0 092ad0 00002c 00 A 0 0 4
[12] __libc_atexit PROGBITS 080daafc 092afc 000004 00 A 0 0 4
[13] __libc_thread_sub PROGBITS 080dab00 092b00 000004 00 A 0 0 4
[14] .eh_frame PROGBITS 080dab04 092b04 00e0a0 00 A 0 0 4
[15] .gcc_except_table PROGBITS 080e8ba4 0a0ba4 0000b3 00 A 0 0 1
[16] .tdata PROGBITS 080e9f40 0a0f40 000010 00 WAT 0 0 4
[17] .tbss NOBITS 080e9f50 0a0f50 000018 00 WAT 0 0 4
[18] .init_array INIT_ARRAY 080e9f50 0a0f50 000008 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 080e9f58 0a0f58 000008 00 WA 0 0 4
[20] .jcr PROGBITS 080e9f60 0a0f60 000004 00 WA 0 0 4
[21] .data.rel.ro PROGBITS 080e9f80 0a0f80 000070 00 WA 0 0 32
[22] .got PROGBITS 080e9ff0 0a0ff0 000008 04 WA 0 0 4
[23] .got.plt PROGBITS 080ea000 0a1000 000044 04 WA 0 0 4
[24] .data PROGBITS 080ea060 0a1060 000f20 00 WA 0 0 32
[25] .bss NOBITS 080eaf80 0a1f80 00136c 00 WA 0 0 32
[26] __libc_freeres_pt NOBITS 080ec2ec 0a1f80 000018 00 WA 0 0 4
[27] .comment PROGBITS 00000000 0a1f80 00004f 01 MS 0 0 1
[28] .shstrtab STRTAB 00000000 0a1fcf 00014c 00 0 0 1
[29] .symtab SYMTAB 00000000 0a25f4 008bd0 10 30 1063 4
[30] .strtab STRTAB 00000000 0ab1c4 007f1c 00 0 0 1
section用以下数据结构进行描述
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
程序头表信息,6个段。以及这6个段是由哪些section组成的。
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0xa0c57 0xa0c57 R E 0x1000
LOAD 0x0a0f40 0x080e9f40 0x080e9f40 0x01040 0x023c4 RW 0x1000
NOTE 0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R 0x4
TLS 0x0a0f40 0x080e9f40 0x080e9f40 0x00010 0x00028 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x0a0f40 0x080e9f40 0x080e9f40 0x000c0 0x000c0 R 0x1
Section to Segment mapping:
Segment Sections...
00 .note.ABI-tag .note.gnu.build-id .rel.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table
01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs
02 .note.ABI-tag .note.gnu.build-id
03 .tdata .tbss
04
05 .tdata .init_array .fini_array .jcr .data.rel.ro .got
可以看到,段和节其实就是同一个东西。只不过段是由连续多个的节组成。他们只是描述同一个事物的两种方式而已。
那之前我们说的数据段,代码段这些东西去哪里了啊。我怎么没有在程序头表里面看到呢?
可执行程序有至少有以下几个段(看网上说的,那看这个意思是还可以存在其他的段了)
名称 | 内容 |
---|---|
代码段.text | 可执行代码、字符串常量 |
数据段.data | 已初始化全局变量、已初始化全局静态变量、局部静态变量、常量数据 |
BSS段.bss | 未初始化全局变量,未初始化全局静态变量 |
栈 | 局部变量、函数参数 |
堆 | 动态内存分配 |
感觉代码段、数据段、bss段就是分别对应了.text、.data、.bss这几个section呢?(可能还有其他的section)。至于栈区感觉是加载器给程序分配的,堆区就不是很清楚了
#include
long global1; // 未初始化的全局变量 存放bss段
long global2 = 10; //已初始化的全局变量 存放数据段data
long sum_func(long a, long b)//存放text段
{
static long local_static1; // 存放bss段
static long local_static2 = 123;//存放数据段data
static long local_static3 = 456;//存放数据段data
return a + b;
}
int main(void)//存放text段
{
long sum = sum_func(global1, global2);
printf("sum=%ld\n", sum);
return 0;
}
gcc main.c -o main
readelf -a main
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[13] .text PROGBITS 08048320 000320 0001c2 00 AX 0 0 16
[24] .data PROGBITS 0804a018 001018 000014 00 WA 0 0 4
[25] .bss NOBITS 0804a02c 00102c 00000c 00 WA 0 0 4
可以看到section.text的范围是0x8048320-0x80484E2;.data:0x804a018-0x804A02C
.bss: 0x0804a02c-0x804A038
理论上global2 、local_static2 、local_static3存放在section .data。global1和local_static1放在section.bss,main和sum_func在.text.
通过读取出来的符号地址可以看到上述的几个符号的地址,也确实落在了对应的section中
0x804a018 < global2 、local_static2 、local_static3<0x804A02C
0x0804a02c
0x8048320
37: 0804a024 4 OBJECT LOCAL DEFAULT 24 local_static3.1831
38: 0804a028 4 OBJECT LOCAL DEFAULT 24 local_static2.1830
39: 0804a030 4 OBJECT LOCAL DEFAULT 25 local_static1.1829
55: 0804a034 4 OBJECT GLOBAL DEFAULT 25 global1
66: 0804842a 63 FUNC GLOBAL DEFAULT 13 main
70: 0804841d 13 FUNC GLOBAL DEFAULT 13 sum_func
72: 0804a020 4 OBJECT GLOBAL DEFAULT 24 global2