Linux中gcc的详解⽤法及其可重定位⽬标⽂件
<组成
gcc是⼀组编译⼯具的总称,包括:C编译器、C++编译器、源码预处理程序和库⽂件。
<编译
1.⽣成⼀个程序
gcc hello.c -o hello 把hello.c编译成⼀个可执⾏程序
如果gcc hello.c 不指定输出名,⽣成⼀个a.out⽂件。
可以通过./hello或者./a.out来运⾏程序
<编译步骤(包括预处理preprocessor,编译compiler,汇编assemble,链接linker)
预处理:gcc -E hello.c -o hello.i      完成对代码的预处理
处理源⽂件中以“#”开头的预编译指令,包括:
– 处理所有条件预编译指令,如“#if”,“#ifdef”, “#endif”等
– 插⼊头⽂件到“#include”处,可以递归⽅式进⾏处理
– 删除所有的注释“//”和“/* */”
– 添加⾏号和⽂件名标识,以便编译时编译器产⽣调试⽤的⾏号信息
– 保留所有#pragma编译指令(编译器需要⽤)
· 经过预编译处理后,得到的是预处理⽂件(如,hello.i) ,
它还是⼀个可读的⽂本⽂件 ,但不包含任何宏定义
编译: gcc -S hello.i -o hello.s gcc –S hello.c –o hello.s 将源代码编译成汇编代码
· 编译过程就是将预处理后得到的预处理⽂件(如 hello.i)
进⾏ 词法分析、语法分析、语义分析、优化后,⽣成汇编代码⽂
· 经过编译后,得到的汇编代码⽂件(如 hello.s)还是可读的⽂ 本⽂件,CPU⽆法理解和执⾏它
gcc命令实际上是具体程序(如ccp、cc1、as等)的包装命令,
⽤户通过gcc命令来使⽤具体的预处理程序ccp、编译程序cc1和汇编程序as等
汇编:gcc -c hello.s -o hello.o ,  gcc -c hello.c -o hello.o ,  as hello.s -o hello.o
(as是⼀个汇编程序)将汇编代码转换成可重定位⽬标⽂件(⼆进制)
· 汇编程序(汇编器)⽤来将汇编语⾔源程序转换为机器指令序列 (机器语⾔程序)
· 汇编指令和机器指令⼀⼀对应,前者是后者的符号表⽰,它们都 属于机器级指令,
所构成的程序称为机器级代码
· 汇编结果是⼀个可重定位⽬标⽂件(如,hello.o),其中包含 的是
不可读的⼆进制代码,必须⽤相应的⼯具软件来查看其内容
链接:gcc hello.o -o hello
将⽬标代码和所需要的库链成⼀个完整的应⽤程序。
gcc的结果输出是后缀名不相关的,只与输⼊参数有关
为了构造可执⾏⽂件,连接器必须完成两个主要任务:
符号解析(symbol resolution),重定位(relocation)
⽬标⽂件有三种形式:
· 可重定位⽬标⽂件。包含⼆进制代码和数据,其形式可以在编译时与其他可重定位
⽬标⽂件合并起来,创建⼀个可执⾏⽬标⽂件。
· 可执⾏⽬标⽂件。包括⼆进制代码和数据,其形式可以被直接复制到内存并执⾏。
· 共享⽬标⽂件。⼀种特殊类型的可重定位⽬标⽂件,可以在加载或者运⾏
时被动态的加载进内存并链接。
3.使⽤多个源码的项⽬
⽐如有两个源程序main.c和sum.c
使⽤gcc编译器并链接⽣成可执⾏程序p:gcc -O2 -g -o p main.c sum.c    ./p
-O2:2级优化
-g:⽣成调试信息
-o:⽬标⽂件名
链接过程的本质(以main.c和swap.c为例)
main.c                          swap.c
int buf[2] = {1, 2};                extern int buf[];
void swap();                      int *bufp0 = &buf[0];
int main()                          static int *bufp1;
{ swap();                            void swap() {
return 0; }                          int temp;bufp1 = &buf[1];temp = *bufp0;*bufp0 = *bufp1;*bufp1 = temp; }
关于ELF格式⽂件符号表解析及readelf命令使⽤
1.读取ELF⽂件头  readelf -h main.c
在 readelf 的输出中:
第 1 ⾏,ELF Header: 指名 ELF ⽂件头开始。
linux下gcc编译的四个步骤
第 2 ⾏,Magic 魔数,⽤来指名该⽂件是⼀个 ELF ⽬标⽂件。第⼀个字节 7F 是个固定的数;后⾯的 3 个字节正是 E, L, F 三个字母的ASCII 形式。
第 3 ⾏,CLASS 表⽰⽂件类型,这⾥是 64位的 ELF 格式。
第 4 ⾏,Data 表⽰⽂件中的数据是按照什么格式组织(⼤端或⼩端)的,不同处理器平台数据组织格式可能就不同,如x86平台为⼩端存储格式。
第 5 ⾏,当前 ELF ⽂件头版本号,这⾥版本号为 1 。
第 6 ⾏,OS/ABI ,指出操作系统类型,ABI 是 Application Binary Interface 的缩写。
第 7 ⾏,ABI 版本号,当前为 0 。
第 8 ⾏,Type 表⽰⽂件类型。ELF ⽂件有 3 种类型,⼀种是如上所⽰的 Relocatable file 可重定位⽬标⽂件,⼀种是可执⾏⽂件(Executable),另外⼀种是共享库(Shared Library) 。
第 9 ⾏,机器平台类型。
第 10 ⾏,当前⽬标⽂件的版本号。
第 11 ⾏,程序的虚拟地址⼊⼝点。
第 12 ⾏,与 11 ⾏同理,这个⽬标⽂件没有 Program Headers。
第 13 ⾏,sections 头开始处,这⾥ 856 是⼗进制.。
第 14 ⾏,是⼀个与处理器相关联的标志。
第 15 ⾏,ELF ⽂件头的字节数。
第 16 ⾏,因为这个不是可执⾏程序,故此处⼤⼩为 0。
第 17 ⾏,同理于第 16 ⾏。
第 18 ⾏,sections header 的⼤⼩,这⾥每个 section 头⼤⼩为 64个字节。
第 19 ⾏,⼀共有多少个 section 头,这⾥是 13个。
第 20 ⾏,section 头字符串表索引号,从 Section Headers 输出部分可以看到其内容的偏移在 0xa0 处,从此处开始到0xcf 结束保存着各个 sections 的名字,如 .data,.text,.bss等。
2.读取节头表    readelf -S main.o
可重定位⽬标⽂件
ELF头
.text
.rodata
.data
.bss
.symtab
.
.rel.data
.debug
.line
.strtab
节头部表
ELF 头
包括16字节标识信息、⽂件类型 (.o, exec, .so)、机器类型(如 IA-32)、 节头表的偏移、节头表的表项⼤⼩以及 表项个数 .text 节  编译后的代码部分
.rodata 节  只读数据,如 printf 格式串、switch 跳转表等
.data 节  已初始化的全局变量
.
bss 节  未初始化全局变量,仅是占位符,不占 据任何实际磁盘空间。区分初始化和⾮ 初始化是为了空间效率
.symtab 节  存放函数和全局变量 (符号表)信息 , 它不包括局部变量
. 节  .text节的重定位信息,⽤于重新修改代 码段的指令中的地址信息
.rel.data 节  .data节的重定位信息,⽤于对被模块使 ⽤或定义的全局变量进⾏重定位的信息
.debug 节  调试⽤符号表 (gcc -g)
strtab 节  包含symtab和debug节中符号及节名
Section header table(节头表)  每个节的节名、偏移和⼤⼩
以下以main.o为例⼦解释
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific) 可重定位⽬标⽂件中,每个可装⼊节的起始地址总是0
3.符号表机制    readelf -s main.o
可以看出全局变量,静态全局变量,静态局部变量,
全局函数名都会出现在符号表中,⽽局部变量不会被保存在符号表中。

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