参考:https://blog.csdn.net/gt1025814447/article/details/80442673
GCC四步详解
第一步:预处理(也叫预编译)
gcc -E test.c -o test.i
将所有#define删除,并且展开所有的宏定义
处理所有的条件预编译指令,如#if #ifdef #undef #ifndef #endif #elif
处理#include,将包含的文件插入到此处,这是一个递归的过程
删除所有注释 // /* */
添加行号和文件名标识,以便于编译时产生的错误警告能显示行号
保留#pragma编译器指令
第二步:编译
gcc -S test.i -o test.s
将预处理完的.i文件进行一系列的词法分析、语法分析、语义分析及优
化后生成响应的汇编代码文件,这是整个程序构建的最核心的部分,也是最复杂的部分
第三步:汇编
gcc -C test.s -o test.o
汇编是将第二步生成的汇编代码编程机器可执行的指令,每一个汇编语句几乎都对应一条机器指令
第四步:链接
链接动态库和静态库
————————————————
生成的目标文件有什么,什么是目标文件?
目标文件就是源代码经过编译后但未进行链接的那些中间文件。Linux下的 .o文件就是目标文件,目标文件和可执行文件内容和
格式几乎都一样,所以我们可以广义地将目标文件和可执行文件看成一类型文件。他们都是按照ELF文件格式存储的
Linux下有哪些ELF类型的文件?
.o文件、可执行文件、核心转储文件(core dump)、.so文件(动态链链接库)
可执行文件的概貌详解
File Header 、.text section 、.data section 、.bss section
文件头(File Header)
描述了整个文件的文件属性,包括目标文件是否可执行、是静态链接还是动态链接及入口地址、目标硬件、目标操作系统等信息、段表(描述文件中各个段的偏移位置及属性等)
代码段(.text)
存放了程序源代码编译后生成的机器指令
数据段(.data)
存放已初始化的全局静态与非静态变量和已初始化的局部静态变量
.bss段
存放未初始化的全局变量(全局静态和非静态变量)和局部静态变量
但是.bss段只是为这些变量预留位置而已,并没有内容,所以这些变量在.bss段中也不占据空间
—————————————————————————————————
深入挖掘 .o文件
使用命令:
objdump -h xxxx.o
打印主要段的信息
objdump -x xxxx.o
打印更多的详细信息
objdump -s xxx.o
将所有段的内容以16进制方式打印出来
objdump -d xxx.o 或者-S
将所有包含指令的段反汇编
objdump -t xxx.o
查看所有的符号以及他们所在段
readelf -h xxx.o
查看.o文件的文件头详细信息
readelf -S xxx.o
显示.o文件中的所有段,即查看段表
size xxx.o
查看.o文件中各个段所占大小
nm xxx.o
查看.o文件中所有的符号
使用命令gcc -c test.c编译下面这个test.c程序生成test.o文件,然后查看test.o文件结构
test.c
/* this is a test code */
/* test.c */
int printf(const char *format, ...);
int g_var2 = 10;
int g_var2;
void func(int i){
printf("%d\n",i);
}
int main(void){
static int static_var1 = 20;
static int static_var2;
int var3 = 1;
int var4;
func(static_var1 + static_var2 + var3 + var4);
return var3;
}
然后查看生成的test.o文件的结构: objdump -h test.o
行:
.text :代码段(存放函数的二进制机器指令)
.data :数据段(存已初始化的局部/全局静态变量、未初始化的全局静态变量)
.bss :bss段(声明未初始化变量所占大小)
.rodata :只读数据段(存放 " " 引住的只读字符串)
.comment :注释信息段
.node.GUN-stack :堆栈提示段
列:
Size:段的长度
File Off :段的所在位置(即距离文件头的偏移位置)
段的属性:
CONTENTS:表示该段在文件中存在
ALLOC :表示只分配了大小,但没有存内容
关于.bss段
我们说.bss段是存放未初始化的全局变量(静态与非静态)和局部静态变量的
所以我们程序中的g_var2和stactic_var2应该都在.bss段中被预留位置,所以.bss段的size应该是8个字节,但是结果却是4个字节,怎么回事呢?
这就是不同的编译器实现不一样的原因了,有些编译器会将未初始化的全局非静态变量放在.bss段,有些则不放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss段分配空间。而我的编译器是没有将g_var2(全局未初始化的非静态变量)放在任何段
下面让我们真正的查看一下g_var2
首先,我们查看段表
readelf -S test.o
(主要为了查看每个段的段号)
然后我们再看一下符号表 (我们定义的变量名都是符号,包括函数名)
readelf -s test.o
符号表里会显示这个符号所在的位置
我们看到static_var1和g_var1所在段的段号为24(24是.data段)
static_var2和g_var2所在段的段号为25(25是.bss段)
各种变量所在位置总结
全局已初始化非静态变量、局部已初始化静态变量会被放入.data段
全局未初始化静态变量会被放入.bss段