二进制文件
计算机文件基本上分为两种:二进制文件和ASCII(也称纯文本文件),图形文件及文字处理程序等计算机程序都属于二进制文件.
可重定位文件:ELF格式的二进制文件(可重定位即可以重新定位内存地址信息)
GCC编译过程(在ubntu18.04环境下完成)
说一下GCC具体的过程(文件是如何变化又经历了什么)
Hello world.c文件——>Hello world.i文件——>Hello world.s文件——>Hello world.o对象文件——>Hello world.exe可执行文件
注:.c文件可以直接转化为.s和.o文件和.exe文件
例如将.c文件直接转化为.s文件所需要的指令为
gcc -S 'Hello world.c' -o 'Hello world.s'
依次经历的事件为:
首先给出一个C语言程序文件
- 预处理 从.c文件转变为.i文件
打开‘Hello world.i’文件(一下为部分截取代码)
# 1 "Hello world.c"
#1 "<built -in>"
#1 "<command-line>"
...
extern int printf(const char *_restrict_format,...);
...
int main()
{
printf("Hello world");
return 0;
}
通过观察我们可以得知预处理的一些处理规则;
-
递归处理”#include“ 预处理指令,将对应文件的内容复制到该指令的位置
-
删除所有“#define”指令,并且在其被引用的位置递归地展开所有的宏定义
-
处理所有条件预处理指令:“#if”、“#ifdef”、“#elif”、“#else”、“#endif”等
-
删除所有注释
-
添加行号和文件名标识
-
编译阶段 从.i文件转变为.s文件
GCC默认使用AT&T格式(大部分Linux内核都是)的汇编语言,添加编译选项“-masm=intel”可以将其指定为我们熟悉的intel格式。编译选项"-fno-asynchrnous-unwind-tables"则用于生成没有cfi宏的汇编指令,以提高可读性。
打开文件所见内容
值得一提的是,生成的汇编代码中函数printf()被替换成了puts(),这是因为当printf()只有一个参数时,与puts()是十分类似的,与是GCC的优化策略就将其替换以提高性能
- 汇编阶段 从.s文件转变为.o对象文件
此时的目标文件’Hello world.o’是一个可重定位文件(Relocatable file),可以使用objdump命令(liunx自带命令不需要安装)来查看其内容
此时由于还未进行链接,对象文件中符号的虚拟地址无法确定,于是我们看到字符串hello world的地址被设置为0x0000。
- 链接阶段 将.o文件转变为.exe可执行文件
可分为静态链接和动态链接。GCC链接默认成为动态链接,这一阶段将目标文件及其依赖库进行链接,生成可执行文件,主要包括地址和空间分配、符号绑定和重定位等操作。
链接操作由链接器(ld.so)完成,结果就得到了hello文件,这是一个静态链接的可执行文件(加了编译选项-static)
同样file 文件 objdump命令 - sd +文件名 -M intel 执行
因为可执行文件含有大量的库文件,没有截取到相应的结果
理论上的结果应该是
call …<链接> 此链接(实际的符号地址)对应一段汇编代码,程序也就可以被加载到内存了