二进制文件(包含预处理、编译、汇编、链接以及ELF文件系统的介绍)
二进制文件(包含预处理、编译、汇编、链接以及ELF文件系统的介绍)
一、源代码到可执行文件
注意:本节内容需要一定C语言基础,最少要会写hello world,最好会一点8086汇编
我们写的C语言“程序”其实是源代码,从源代码到二进制的可执行文件需要经过预处理、编译、汇编、链接这些操作,下面我们简单讲解一下这些过程分别干了什么以及我们怎么上机操作。这里不讲具体的编译过程,有兴趣可以学习一下编译原理,如果师傅感兴趣可以给我留言,人数比较多的话后期我会单独开一期讲解。
1、预处理
顾名思义,是编译前对原文件进行处理,大致会进行以下几个处理:
- 递归处理#include头文件,将头文件内容复制到代码中
- 展开所有的宏定义
- 去掉所有的注释
- 标上行号和文件名表示
图一为源代码,图二为预处理后的节选代码
gcc -E hello.c -o hello.i #这里-o为给生成的程序命名


2.编译
最终生成汇编代码,可以用-masm来指定Intel风格,一般学的都是8086的,就是Intel风格的。
gcc -S hello.i -o hello.s -masm=intel
然后可以cat hello.s查看
.file "hello.c"
.intel_syntax noprefix
.text
.section .rodata
.LC0:
.string "hello world!"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
lea rax, .LC0[rip]
mov rdi, rax
call puts@PLT
mov eax, 0
pop rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
3.汇编
汇编是将汇编代码汇编成二进制文件,但是这里还不能运行,这里还需要进行重定向
可以用这个指令来执行:
gcc -c hello.s -o hello.o
这个时候查看就不能用cat指令了,因为现在hello. o已经是一个二进制的可重定向文件,要用下面的objdump指令查看
objdump -sd hello.o -M intel
Disassembly of section .text:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push rbp
5: 48 89 e5 mov rbp,rsp
8: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # f <main+0xf>
f: 48 89 c7 mov rdi,rax
12: e8 00 00 00 00 call 17 <main+0x17>
17: b8 00 00 00 00 mov eax,0x0
1c: 5d pop rbp
1d: c3 ret
#节选
上面的代码块里面会有很多的0(0x0就是十六进制下的0),还是因为没有重定向
4.链接
链接分为两种:动态链接和静态链接,gcc默认动态链接,可以用-static指定静态链接,静态链接出来的文件会大一些。
gcc hello.o -o hello -static
n0kesyst3m@n0kesyst3m-VMware-Virtual-Platform:~/test$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=79ab9f0e2d7b54e39b96cc13378e88249f06c6dd, for GNU/Linux 3.2.0, not stripped
#用file命令查看,可以看到statically linked表示静态链接
这个代码太长了我就不放了,建议要动手试一下,效果肯定更好。
二、ELF文件格式
注意:这一节需要用到第一节的知识点,第一节学到知道每个指令怎么写以及他是干什么的就行了。
这是这一节里面最核心的点,一定要动手练一下,然后要理解,最好能完完整整得看懂
#include<stdio.h>
int global_init_var = 10; //global全局,local局部,init已初始化,static静态,var变量名。
int global_uninit_var;
void fun(int n)
{
printf("%d\n",n);
}
int main()
{
static int local_static_init_var=20;
static int local_static_uninit_var;
int local_init_var=30;
int local_uninit_var;
fun(global_init_var+local_init_var+local_static_init_var);
return 0;
}
//这是源代码
下面是ELF的文件头表,能看懂就行,不必深究。
n0kesyst3m@n0kesyst3m-VMware-Virtual-Platform:~/test/ELF_learning$ readelf -h elfdemo.rel
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 #0x7f 45 4c 46是魔术字符,可快速在内存中定位程序
类别: ELF64 #对应Magic那一行的02表示64位,01为32位
数据: 2 补码,小端序 (little endian)
#这里注意不是指值为2,而是指二进制补码,值对应Magic那一行的第一个01,01表示小端序,02表示大端序
Version: 1 (current) #定值1,值对应Magic那一行的第二个01
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64#AMD64
版本: 0x1
入口点地址: 0x0 #还未链接,链接后会重定位
程序头起点: 0 (bytes into file)
Start of section headers: 1024 (bytes into file)#节头表的偏移量为1024字节
标志: 0x0
Size of this header: 64 (bytes) #ELF文件头大小为64字节
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14 #节头表的总数
Section header string table index: 13 #节名称字符串表在第13个节,很重要!!!可以看程序里面有哪些函数
#注释解释了重要的几个数据,其他的看名称以此类推即可
下面是节头表
n0kesyst3m@n0kesyst3m-VMware-Virtual-Platform:~/test/ELF_learning$ readelf -S elfdemo.rel
There are 14 section headers, starting at offset 0x400:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 标志 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000005f 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002e0
0000000000000078 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a8
0000000000000008 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000ac
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000d8
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.pr[...] NOTE 0000000000000000 000000d8
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 000000f8
0000000000000058 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000358
0000000000000030 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000150
0000000000000120 0000000000000018 12 7 8
[12] .strtab STRTAB 0000000000000000 00000270
000000000000006f 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000388
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
节头表的结构
每个节头包含以下信息:
- 名称(Name):节的名称,如
.text、.data等。 - 类型(Type):节的类型,例如
PROGBITS、RELA、NOBITS等。 - 地址(Address):节在内存中的加载地址,通常为 0,如果节不需要加载到内存则为 0。
- 偏移量(Offset):节在 ELF 文件中的偏移位置。
- 大小(Size):节的大小,单位是字节。
- 全体大小(Total Size):节的总大小,包括可能的对齐空间。
- 标志(Flags):节的标志,表示节的特性(例如,是否可写、可执行等)。常见的标志有
W(可写),A(分配到内存),X(可执行)等。 - 链接(Link):链接信息,通常指向其他节或者是无意义的,依赖于节的类型。
- 信息(Info):与节的内容相关的信息,具体含义依赖于节的类型。
- 对齐(Alignment):节在内存中的对齐要求。
1. 类型(Type)
节的类型定义了节的内容和目的。不同类型的节在文件中的角色和用途不同。常见的节类型包括:
PROGBITS:包含程序的实际数据或代码。这是最常见的节类型,用于.text(代码段)、.data(已初始化数据段)和.rodata(只读数据段)等节。它可以包含机器指令或数据,并且通常会被加载到内存中。NOBITS:表示该节没有实际内容,仅在内存中分配空间。典型的节有.bss(未初始化数据段),它的大小在文件中为零,但在程序加载时会分配相应的内存空间。RELA:表示重定位节(带有附加信息的重定位节)。它通常用于存放程序中符号地址的修改信息。重定位节中包含了指向需要修改的地址和修改的偏移量。与之类似的还有REL类型,不过RELA相比REL更具扩展性,它包含额外的“addend”字段,用于存储固定的调整值。SYMTAB:符号表节,用于存储程序的符号信息,如变量名、函数名等。符号表用于链接和重定位过程中,帮助连接程序的各个部分。STRTAB:字符串表节,存储字符串数据,通常用来存储符号表中符号的名称或节的名称。NOTE:用于存储注释信息,通常包含一些元数据,如编译器版本、操作系统类型等。HASH:哈希表节,通常与符号表一起使用,用于加速符号查找过程。DYNAMIC:动态链接信息节。它包含了程序需要的动态链接信息,用于支持动态库的加载。REL和RELA:它们用于存储程序的重定位信息。REL是没有附加数据的重定位条目,而RELA会有附加的值用于修改。EH_FRAME:异常处理框架节,用于存储与异常处理相关的信息,如栈展开数据等。SHSTRTAB:节名称字符串表节,存储每个节的名称。
2. 链接(Link)
链接(Link) 字段的值是一个与其它节相关的索引或引用,表示当前节与其他节的关系。其具体含义通常依赖于节的类型。
- 对于 符号表节(
SYMTAB) 和 字符串表节(STRTAB),链接字段通常是指向该节所依赖的另一个节。例如,.symtab节通常会链接到.strtab节,指向符号名称的字符串数据。 - 对于 重定位节(
RELA或REL),链接字段通常指向符号表节(例如.symtab)。重定位节中的每个条目都需要使用符号表来解析和计算相应的地址。 - 对于 动态链接节(
DYNAMIC),链接字段可以指向动态符号表或者其他相关的动态信息。 - 对于 其他类型的节,
链接字段的含义则取决于该节的特定用途,通常是某种关联关系的索引,例如用于合并节或链式存储的特殊节。
3. 信息(Info)
信息(Info) 字段通常包含关于节的一些附加信息,具体的含义依赖于节的类型。不同类型的节,信息 字段的作用和含义是不同的。
- 对于 重定位节(
RELA或REL),信息字段通常包含与当前重定位条目相关的符号索引。通过该符号索引,可以找到符号表中与该条目相关的符号。 - 对于 符号表节(
SYMTAB),信息字段通常包含有关符号类型、符号绑定等信息(如STB_GLOBAL、STB_LOCAL等)。它帮助在链接和重定位时理解符号的特性。 - 对于 动态链接节(
DYNAMIC),信息字段可能包含与动态符号或动态库的索引相关的信息。例如,它可以指向某个动态符号或指定的标志。 - 对于 段标志节(
NOTE),信息字段通常用于存储特定注释或标记的内容,比如编译器版本、目标平台等元数据。
常见的节标志
- W:表示该节可写。
- A:表示该节会被加载到内存。
- X:表示该节是可执行的。
- MS:表示节包含的是注释信息。
- I:表示节为信息性节。
具体节的内容
.text节通常包含程序的机器代码。.data和.bss节包含程序的数据:.data存储已初始化的数据,.bss存储未初始化的数据。.symtab存储符号信息,.strtab存储符号的字符串。.rela.text和.rela.eh_frame存储重定位信息。
各节的解析
- [ 0] NULL:表示空节,没有实际内容。
- [ 1] .text:代码段,通常包含程序的执行代码,
PROGBITS表示存储程序内容的节。它是可执行的(X),并且具有分配(A)的特性。 - [ 2] .rela.text:重定位节,
RELA表示它是一个重定位节,包含符号重定位信息。该节的内容是对.text节的重定位。I表示信息性节,A表示它会被加载到内存。 - [ 3] .data:数据段,包含程序运行时需要的初始化数据,
PROGBITS表示存储数据的节。它是可写的(W)并具有分配(A)的特性。 - [ 4] .bss:未初始化数据段,
NOBITS表示节没有实际的数据,仅仅是内存空间的占位符。它是可写的(W)并具有分配(A)的特性。 - [ 5] .rodata:只读数据段,通常存放常量数据,
PROGBITS表示存储数据的节。它是只读的(A)且不会被修改。 - [ 6] .comment:存放编译器版本等注释信息,
PROGBITS表示存储程序内容的节。MS是“内部注释”的标志。 - [ 7] .note.GNU-stack:GNU 堆栈标记,通常不包含实际数据,标明该文件是否使用可执行堆栈。该节没有实际内容(大小为 0)。
- [ 8] .note.gnu.pr[...]:GNU 使用的注释信息,类型为
NOTE,通常用于存储一些元数据(如架构、编译器信息等)。 - [ 9] .eh_frame:异常处理信息,
PROGBITS类型,包含异常处理的框架信息。 - [ 10] .rela.eh_frame:存放
eh_frame节的重定位信息,RELA类型。 - [ 11] .symtab:符号表,包含程序中使用的符号信息,
SYMTAB类型。它链接到.strtab节,后者包含字符串的实际内容。 - [ 12] .strtab:字符串表,包含符号表中符号的名称。
- [ 13] .shstrtab:节名称字符串表,包含所有节名称(如
.text、.data等)。

浙公网安备 33010602011771号