可执行程序工作原理
程序编译
Linux系统中c语言源程序的编译过程主要分为四个步骤:预处理、编译、汇编、链接。
预处理
预处理阶段的工作内容如下:
- 删除所有注释;
- 删除所有#define,并展开所有宏定义;
- 处理所有条件预编译指令;
- 处理#include预编译指令,将被包含的文件插入预编译指令所在位置;
- 添加行号和文件名标识。
其在shell中的执行命令为gcc -E hello.c -o hello.i
编译
编译阶段的主要功能是将预处理后的高级语言代码编译成汇编语言。
其在shell中的执行命令为gcc -S hello.c -o hello.s -m32
汇编
汇编阶段的主要功能是将汇编语言代码汇编成二进制机器码。
其在shell中的执行命令为gcc -c hello.c -o hello.o -m32
链接
链接阶段的主要功能是将各种代码和数据部分收集起来并组合成一个单一文件,这个文件可以被加载到内存中执行。
其在shell中的执行命令为gcc hello.o -o hello -m32 -static
可执行程序——ELF文件
在学习本章内容前,我一直很好奇Linux系统中使用gcc编译成.o格式的二进制文件后为什么无法直接运行,而是还需要进一步执行链接过程,在本章学习了ELF可执行文件后得到了答案。
ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。
ELF文件的类型
ELF文件可分为三类:
- 可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)
- 可执行文件:文件保存着一个用来执行的程序。(例如bash,gcc等)
- 共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件。)
ELF文件格式
ELF文件的主体结构如下图所示:
-
ELF Header
ELF Header部分长64字节,包括了可执行文件类型(32位/64位),程序入口地址,其他结构部分起始地址、大小、数目等ELF文件的基本数据。
在shell中通过readelf -h hello
命令,查看hello可执行ELF文件的ELF Header部分内容:
-
Section Header Table
Sections是在ELF文件中,用以装载内容数据的最小容器。在ELF文件里,每一个 sections内都装载了性质属性都一样的内容,如:
- .text section 里装载了可执行代码;
- .data section 里面装载了被初始化的数据;
- .bss section 里面装载了未被初始化的数据;
- 以 .rec 打头的 sections 里面装载了重定位条目;
- .symtab 或者 .dynsym section 里面装载了符号信息;
- .strtab 或者 .dynstr section 里面装载了字符串信息;
- 其他还有为满足不同目的所设置的section,比方满足调试的目的、满足动态链接与加载的目的等等。
一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的Section Head Table(SHT)决定。在SHT中,针对每一个section,都设置有一个条目,用以描述对应的这个section,其内容主要包括该section的名称、类型、大小以及在整个ELF文件中的字节偏移位置等等。
在shell中通过readelf -S hello
命令,查看hello可执行ELF文件的Section Head Table部分内容:
可以看到,正如Header中说明的,Section Head Table共有31个Section项。
其中,.symtab
section项存储了程序的字符串表,用readelf -S hello
查看:
Type 列表示符号的种类,Bind 列表示符号的绑定类型,两者共同构成了 st_info 字段。
- Program Header Table
在编译器进行链接步骤时,可重定位对象文件中的sections是作为输入,给链接器那去做链接用的,所以这些 sections 也经常被称做输入 section。
而链接器在链接可执行文件或动态库的过程中,会把来自不同可重定位对象文件中的相同名称的 section 合并起来构成同名的section。接着,其又会把带有相同属性(比方都是只读并可加载的)的 section 都合并成所谓 segments(段)。segments 作为链接器的输出,常被称为输出section。
一个单独的 segment 通常会包含几个不同的 sections,比方一个可被加载的、只读的segment 通常就会包括可执行代码section .text、只读的数据section .rodata以及给动态链接器使用的符号section .dymsym等等。section 是被链接器使用的,但是 segments 是被加载器所使用的。加载器会将所需要的 segment 加载到内存空间中运行。和用 sections header table 来指定一个可重定位文件中到底有哪些 sections 一样。在一个可执行文件或者动态库中,也需要有一种信息结构来指出包含有哪些 segments。这种信息结构就是 Program Header Table。
在shell中通过readelf -l hello
命令,查看hello可执行ELF文件的Program Header Table部分内容: