0、知识储备
- GCC:是编译工具(编辑器),能将C语言编写的程序转换为处理器能处理的二进制代码。
- Binutils:二进制程序处理工具,包括:ar、as、size、ldd等。
(1)as:主用于汇编。
(2)ld:主用于链接。
(3)ar:主用于创建静态库:
- 若将多个.o目标文件生成一个库文件,则需要静态库和动态库(共享库)。
- windows系统下:静态库是.lib为后缀的文件,动态库是.dll为后缀的文件; linux系统下:静态库是.a为后缀的文件,动态库是.so为后缀的文件。
- 静态库和动态库区别在于代码被载入的时间不同:静态库的代码在编译过程中已被载入可执行程序,所以体积较大。动态库的代码在可执行程序运行时被载入内存,简单引用,体积较小。
- linux系统下,可用ldd命令查看可执行程序的共享库。
- 多个程序同时运行时 ,且包含动态库,则动态库更节省内存。
(4)ldd:可查看可执行程序的动态库。
(5)size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段和总大小等。
(6)objdump:反汇编。
(7)readelf:显示有关ELF文件的信息。
1、准备工作
编写如下代码:
#include<stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
2、编程过程
2.1 预处理
使用gcc进行预处理命令如下:
$ gcc -E hello.c -o hello.i
//将源文件hello.c文件预处理生成hello.i文件
//gcc 的选项-E使gcc在进行完预处理后即停止
部分hello.i代码片段如下:
2.2 编译
使用gcc编译命令如下:
$ gcc -S hello.i -o hello.s
//将预处理生成的hello.i文件编译生成hello.s文件
//gcc的选项-s使gcc在执行完编译后停止,生成汇编程序
hello.s代码片段如下:
2.3 汇编
使用gcc汇编命令如下:
$ gcc -c hello.s -o hello.o
//将编译生成的hello.s文件汇编生成目标文件hello.o
//gcc的选项-c使gcc在执行完汇编后停止,生成目标文件
//或直接调用as进行汇编
$ as -c hello.s -o hello.o
//使用as将hello.s文件汇编生成目标文件
注意:hello.o目标文件为ELF格式的可重定向文件。
2.4 链接
1、链接也分为动态链接和静态链接:
(1)静态链接:程序在编译阶段直接把静态库加入到可执行文件中去,这样的可执行文件会比较大。
(2)动态链接:程序执行时再从系统中将相应的动态库加入到内存中去。
-
Linux系统中,gcc编译链接时动态库搜索路径的顺序通常为:
1)从gcc命令参数-L指定路径寻找;
2)再从环境变量LIBRARY_PATH指定的路径寻址;
3)再从默认路径/lib、/usr/lib、/usr/local/lib寻找。 -
Linux系统中,执行二进制文件时的动态库搜索路径的顺序通常为:
1)先搜索编译目标代码时指定的动态库搜索路径;
2)再从环境变量LD_LIBRARY_PATH指定的路径寻址;
3)再从配置文件/etc/ld.so,sonf中指定的动态库搜索路径;
4)再从默认路径/lib、/usr/lib寻找。 -
再Linux系统下,可用ldd命令查看一个可执行程序依赖的动态库。
注意:链接动态库和静态库有可能重合,所以,当有同名的静态库和动态库文件时,gcc链接默认优先选择动态库,若要让链接选择静态库则需指定-static,强制性使用静态库链接。
2、若命令“gcc hello.c -o hello动态库进行链接,生成ELF可执行文件的大小(用size命令查看)和链接的动态库(用ldd命令查看),示例如下:
$ gcc hello.c -o hello
$ size hello
$ ldd hello
3、若使用命令“gcc -static hello.c -o hello”则会使用静态库进行链接,生成ELF可执行文件的大小(用size命令查看)和链接的动态库(用ldd命令查看),示例如下:
$ gcc -static hello.c -o hello
$ size hello//可见text的代码尺寸极大
$ ldd hello//说明没有动态链接库
注:链接器链接后生成的最终文件为ELF格式可执行文件,其通常被链接为不同的段,常见的有:.text、.rodata、.data和.bss等段。
3、分析ELF文件
3.1 ELF文件的段
一个典型的ELF文件包含下面几个段:
- .text:已编译程序的指令代码段。
- .rodata:ro表示read only,即只读数据。
- .data:已初始化的C程序全局变量和静态局部变量。
- .bss:未初始化的C程序全局变量和静态局部变量。
- .debug:调试符号表,调试器用此段的信息帮助调试。
可以使用readelf -S查看其各个section的信息如下:
$ readelf -S hello
部分代码如下:
3.2 反汇编ELF
因为ELF无法当作普通文件打开,若希望直接查看一个ELF文件包含的指令和数据,则需要用反汇编方法。
使用objdump -D对其进行反汇编,如下所示:
$ objdump -D hello
部分代码如下:
使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:
$ gcc -o hello -g hello.c//要加上-g选项
$ objdump -S hello
部分代码如下:
4、总结
gcc是一个十分强大的编译工具,他的编译工具集能巧妙地将C语言编写的程序转换为处理器能处理的二进制代码。