GCC全过程详解+剖析⽣成的.o⽂件使⽤GCC编译⼀个.c⽂件影藏了哪些过程?
GCC四步详解
第⼀步:预处理(也叫预编译)
gcc -E  hello.c  -o hello.i
或者 cpp hello.c > hello.i    【cpp是预编译器】
将所有#define删除,并且展开所有的宏定义
处理所有的条件预编译指令,如#if #ifdef  #undef  #ifndef  #endif #elif
处理#include,将包含的⽂件插⼊到此处,这是⼀个递归的过程
删除所有注释  //  /* */
添加⾏号和⽂件名标识,以便于编译时产⽣的错误警告能显⽰⾏号
保留#pragma编译器指令
第⼆步:编译
gcc  -S  hello.i  -o  hello.s
将预处理完的.i⽂件进⾏⼀系列的词法分析、语法分析、语义分析及优
化后⽣成响应的汇编代码⽂件,这是整个程序构建的最核⼼的部分,也是最复杂的部分
第三步:汇编
gcc  -c  hello.s  -o  hello.o或者 as  hello.s -o  hello.o
汇编是将第⼆步⽣成的汇编代码编程机器可执⾏的指令,每⼀个汇编语句⼏乎都对应⼀条机器指令
第四步:链接
链接动态库和静态库
⽣成的⽬标⽂件有什么,什么是⽬标⽂件?
⽬标⽂件就是源代码经过编译后但未进⾏链接的那些中间⽂件
Linux下的 .o⽂件就是⽬标⽂件,⽬标⽂件和可执⾏⽂件内容和
格式⼏乎都⼀样,所以我们可以⼴义地将⽬标⽂件和可执⾏⽂化
看成⼀类型⽂件。他们都是按照ELF⽂件格式存储的
Linux下有哪些ELF类型的⽂件?
.o⽂件、可执⾏⽂件、核⼼转储⽂件(core dump)、.so⽂件(动态链
linux下gcc编译的四个步骤
链接库)
可执⾏⽂件的概貌详解
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 :只读数据段(存放 " " 引住的只读字符串)
ment :注释信息段
.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看⼀下符号表(我们定义的变量名都是符号,包括函数名)
符号表⾥会显⽰这个符号所在的位置

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。