最近笔者看论文烦得慌,便又重新拾起之前没有完全完成的交叉编译,准备在网上找资料,好好研究一下。 讲道理,笔者其实对编译链接的过程都不是很明白,所以如果想要了解交叉编译,还是先从编译链接的基本概念看起吧。
本文参考链接: http://blog.csdn.net/shenjianxz/article/details/52130111 http://blog.csdn.net/koudaidai/article/details/8092647 http://blog.163.com/gene_lu/blog/static/6402542120138181597392/
编译过程又可以被分为两个阶段:编译、汇编。
编译是指编译器读取字符流的源程序,对其进行词法与语法的分析,将高级语言指令转换为功能等效的汇编代码。 编译主要分为两个过程:预处理过程、编译过程。
预处理过程将.c文件转换为.i文件,当编译器为gcc时,使用的命令是gcc -E,对应于预处理命名cpp。即进行预处理的命令如下:
gcc -E hello.c -o hello.i
其中的参数-E代表只进行预处理。或:
cpp hello.c > hello.i
预处理过程,主要是以下几部分:
关于头文件的搜索规则:
-I
开始;C_INCLUDE_PATH
, CPLUS_INCLUDE_PATH
, OBJC_INCLUDE_PATH
等);/usr/include
, /usr/local/include
, /usr/lib/gcc-lib/i386-linux/2.95.2/include
等);编译是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
指令如下:
gcc -S hello.i -o hello.s
或:
/usr/lib/gcc/x86_64-linux-gnu/4.8/cc1 hello.c
注:
1、现在版本的GCC把预处理和编译两个步骤合成一个步骤,用cc1工具来完成;
2、gcc其实是后台程序的一些包装,根据不同参数去调用其他的实际处理程序,比如:预编译编译程序cc1、汇编器as、连接器ld
汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
指令如下:
gcc -c hello.c -o hello.o
或:
as hello.s -o hello.o
汇编生成的目标文件中,存放的是与源程序等效的机器语言代码。生成的目标文件由段组成,通常至少有两个段:
生成的目标文件一般为下列三种:
库从本质上来说,都是一种可执行代码的二进制格式,可以被载入内存中执行,可分为静态库与动态库两种。静态函数库与动态函数库相同之处在于,都是由*.o目标文件生成。
静态函数库的名字一般是libxxx.a
。利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中。
动态函数库的名字一般是libxxx.so
。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,只有程序执行到相关函数时,才调用该动态函数库里的相应函数,因此,动态函数库产生的可执行文件较小。
链接的主要工作是把各个模块之间相互引用的部分处理好,使各个模块之间可以正确衔接。 链接的作用,一方面在于使得分离编译成为可能;另一方面在于动态绑定的实现,即定义、实现、使用分离(笔者理解成头文件*.h、源文件*.cpp文件、目标文件的分离)
静态库的搜索由静态链接器负责,搜索路径如下:
/lib
, /usr/lib
, /usr/local/lib
等)动态库的搜索由动态链接器负责,搜索路径如下:
/etc/ld.so.conf
中指定的动态库搜索路径;/lib
, /usr/lib
, /usr/local/lib
等)链接可以分为静态链接与动态链接。
静态链接是指在编译阶段直接把静态库加入到可执行文件中去。一般静态链接生成的可执行文件较大。静态链接过程的流程如下图所示:
链接器将函数的代码从其所在地(目标文件或静态链接库中)拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。 为了创建可执行文件,链接器必须完成的任务是:
动态链接指链接阶段仅仅加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中。 在此种方式下,函数的定义在动态链接库或共享对象的目标文件中。在编译的链接阶段,动态链接库只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。动态链接器(ld-linux.so)链接程序在运行过程中,根据记录的共享对象符号定义来动态加载共享库,然后完成重定位。在该可执行文件被执行时,动态链接库的全部内容被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
加载器将可执行文件从外存加载到内存中,并执行。加载过程如下: 加载器首先创建内存映像。Linux进程运行时的内存映像如下:
根据上面的内存映像,加载器跳转到程序入口点(即_start符号的地址),执行启动代码(startup code)。启动代码的调用顺序如下:
0x080480c0 <_start>:
call __libc_init_first
call _init
call atexit
call main
call _exit
在执行完初始化任务,即_init之后,启动代码调用atexit例程,该例程注册了一系列调用exit函数时必须的例程。随后,启动代码调用应用程序的main例程,执行用户程序代码。当用户程序返回后,启动代码调用_exit例程,将控制权交还给操作系统。