把你的嵌入式软件的源代码表述转换为可执行的二进制映像的过程, 包括三个截然不同的步骤。首先,每一个源文件都必须被编译或汇编到一个目标文件( object file)。然后.第一步产生的所有目标文件要被链接成一个目标文件,它叫做可重定位程序 ( relocatable program)。最后, 在一个称为重定址 ( relocation)的过程中,要把物理存储器地址指定给可重定位程序里的每个相对偏移处。第三步的结果就是一个可以运行在嵌入式系统上的包含可执行二进制映像的文件。
用 GNU 工具(编译器、汇编器、链接器和定址器)作为示范
编译
编译器的工作主要是把用人可读的语言所书写的程序, 翻译为特定的处理器上等效的一系列操作码。
链接
在程序能被执行前, 所有第一步产生的目标文件都要以一种特殊的方式组合起来。链接器的工作就是把这些目标文件组合到一起,同时解决所有未解决的符号问题。
链接器的输出是同样格式的一个目标文件, 其中包含了来自输人目标文件的
所有代码和数据。它通过合对输人文件里的 text、 data 和 bss 段来完成这一点。
这样,当链接器运行结束以后,所有输入目标文件里的机器语言代码将出现在
新文件的 text 段里, 所有初始化变量和未初始化变量分别在 data 和 bss 段里面。
在链接器台并各段内容的过程中,它也监视没解决的符号。例如,如果一个
目标文件包含一个对变量 foo 的未解决的引用同时一个叫 foo 的变量在另外的一
个目标文件里被声明,那么链接器将匹配它们。那个没解决的引用就会被一个
到实际变量的引用所取代。换句话说,如果 foo 位于输出的数据段的偏移 14 的
位置,它在符号表中的人口将包含这个地址。
>启动代码
传统软件开发工具自动做的一件事是插入启动代码( startup code)。启动
代码是用来位高级语言写的软件做好运行前准备的一小段汇编语言。每一种
高级语言都有其希望的运行环境。比如 C 和 C++都使用了一个固定的堆栈。
在任何用此两种语言写的软件可以正确运行之前,必须为堆栈分配空间并进
行初始化。这还只是 C/C++程序启动代码的一个职责而已。
大多数供嵌入式系统使用的交叉编译器包括一个叫 startup.asm, crt0.s
(“ C 运行时”的所写)或者类似的一个汇编语言文件。随编译器提供的文档
通常会说明该文件的位置和内容。
C/C++程序的启动代码通常包含以下行为,并且按照所列的次序执行:
1、禁止所有中断。
2、从 ROM 里复制所有初始化数据到 RAM 里。
3、把未初始化数据区清零。
4、未堆栈分配空间并初始化。
5、初始化处理器堆栈指针。
6、创建并初始化堆。
7、(只对 C++有效)对所有全局变量执行构造函数和初始化函数。
8、允许中断。
9、调用 main。
典型地,启动代码在调用 main 之后也包含一些指令。这些指令只在高级
语言程序退出地情况下运行(即:从对 main 的调用返回)。根据嵌入式系统
的种类,你也许会希望利用这些指令来暂停处理器,复位整个系统或者把控
制传到一个调试工具。
因为启动代码不是自动插入的,程序员通常必须亲自汇编这段代码并把
得到的目标文件包括在链接器的输入文件列表里。他甚至需要给链接器一个
特殊的命令行选项以阻止它插入通常的启动代码。适用于多种目标处理器的
定址
把可重定址程序转换到可执行二进制映像的工具叫定址器。实际上,这一步你将不得不自己做大部分工作来为定址器提
供关于目标电路板上的存储器的信息。定址器将用这个信息来为可重定址程序里的每一个代码和数据段指定物理内存地址。然后它将产生一个包含二进制内存映像的输出文件。这个文件就可以被调人目标 ROM 中执行。