本书为韦东山老师《嵌入式Linux应用开发》中交叉工具篇章的总结
1.编译工具链
PC端:GCC ld objcopy objdump
交叉编译工具链: arm-linux-gcc arm-linux-ld arm-linux-objcopy arm-linux-objdump
2.编译流程
预处理: #开头的都是预处理命令,只做简单的转换。 由源文件到 .i 文件 (所用工具:cpp / arm-linux-cpp)
编译:进行语法检查, 将 .i 文件编译为 汇编代码 (所用工具:ccl)
汇编:将汇编代码汇编为.obj 目标文件 (elf 目标文件) (所用工具:as / arm-linux-as)
链接:生成可执行文件(将汇编生成的.o 和系统库中需要用到的.oy文件进行链接) (所用工具: ld/ arm-linux-ld)
3.编译过程常见的文件后缀名
.c : C语言
.C .cpp .cc .cxx : C++ 文件, 此处.cc常用于编写只有纯函数的文件
.i : 预处理后的C文件
.ii : 预处理后的C++文件
.s .S : 汇编文件
预处理是进行简单的替换 , 编译阶段只检查语法正确性,(所以只有声明也可以通过编译) ,但是链接阶段会查找程序中使用的所有定义symbol,如果找不到,也会报错。
4.GCC编译选项
(1)前提说明:除非使用 -c -S -E 选项,否则最后的步骤总是链接
示例: arm-linux-gcc -o hello hello.c
arm-linux-gcc -v -o hello hello.c -v选项用于查看编译详细过程
(2)总体选项:
-c : 预处理、编译和汇编, 不进行链接, 生成.o文件,默认用源文件名+.o后缀
-S :编译后停止, 生成的是.s后缀的汇编文件
-E : 预处理后即停止
-o file : 注意 -o 是与输出文件名一起出现的, 预处理、编译、汇编、链接阶段均可以使用这个选项
-v : 显示编译的详细信息
(3)警告选项: -Wall , 打开所有需要的警告信息
(4)调试选项: -g , 产生调试信息, GDB 工具可以使用这些信息
此选项还有其它格式: -gstabs+ -gstabs -gxcoff+ -gxcoff -gdwarf+ -gdwarf (选项对应不同系统的格式)
(5)优化选项:
-O 或 -O1 : 进行优化
-O2, -O3
-O0 : 不进行优化
指定多个优化选项, 生效的总是最后一个-O选项
(6)链接器选项:
-llibrary :搜索真正名字为liblibrary.a的库文件并进行连接, 即使不指定-library, 一些默认的库也会被链接进去。连接器在标准搜索目录和 “-L”选项指定的路径中搜索这个 库文件
-nostartfiles :不连接系统标准启动文件 (ctl1.o ctli.o crtend.o crtn.o)。在连接内核、bootloader时会用到这个选项,而对于一般应用程序,这些启动文件是必须的。
-nostdlib :不连接的标准启动文件和标准库文件,只连接指定的文件。 在编译内核、bootloader时会用到这个选项。
-static : 在支持动态连接的系统上阻止连接共享库
-shared : 生成一个obj文件,只有部分系统支持这个选项 。
用法: gcc -shared -o sub.a sub1.o sub2.o , 可以用来连接多个文件而生成一个库文件
gcc -shared -o sub.a sub.o —> gcc -o test main.o ./sub.a
-Xlinker operation : 用来传递系统特定的选项, 将GCC无法识别的这些选项传递给连接器。 若传递的选项带参数,则需要两次-Xlinker,一次传选项,一次传参数。
示例 : -Xlinker -assert -Xlinker definitions
完成的传递是 “ -assert definitions”
-Wl ,operation : 把选项operation传递给连接器。 如果有多个选项, 则operation间用逗号分隔成多个选项。
-u symbol : 使连接器认为取消了symbol定义, 从而连接库模块以取得定义。 可以使用多个 -u选项,各自跟不同的符号。
(7)目录选项
-Idir : 在头文件的搜索路径中添加dir目录
以 “#include <>”包含的文件,只在标准库目录中搜索, 也包括 -Idir 指定的目录
以 " #include “” " 包含的文件,先从用户工作目录中搜索 ,再搜索标准库目录
-I- : 在 -I- 选项前面用 -I选项指定的搜索路径只适用于 " #Include “file” " ,不能搜索 “#include <>”
在 -I- 后面用 -I选项指定的目录可搜索所有 #include 文件 。 (一般来说 -I选项就是这么用的)
-I- 阻止当前目录成为搜索"#include “file”" 的第一选择 , 要克服这个选项, 要指定 -I. 搜索当前目录。
-Ldir : 在-I 选项的搜索路径列表中添加 dir 目录 例:gcc -L. -o test main.o -lsub
-Bprefix : 指出在何处寻找可执行文件, 库文件,以及编译器自己的数据文件。
编译器执行子程序 (cpp ccl cclplus(for C ++) as ld)时把prefilx当作欲执行的程序的前缀。
如果没有指定 -B 选项 ,或没有在该前缀中找到文件, 则 会试验两个标准前缀,
/usr/lib/gcc /usr/local/lib/gcc-lib/ 如果仍没有找到,就在‘PATH’ 环境变量指定的路径中寻找没有增加任何前缀的文件名。
也可以指定 GCC_EXEC_PREFIX 变量来替代-B选项, 如果两个都有,优先使用 -B 选项。
5.ld / arm-linux-ld
-T 直接用它来指定代码段、数据段、bss段起始地址, 也可以用来指定一个链接脚本 ,在其中完成复杂设置。
这个选项只用于连接Bootloader 内核等没有底层支持的软件。连接应用于操作系统上的程序,无需-T选项。
(1)直接指定:
-Ttext startaddr
-Tdata startaddr
-Tbss startaddr
eg: arm-linux-ld -Ttext 0x00000000 -g led_on.o -o led_on_elf
额外说明: link.s 中的b跳转指令说明
.text
.global _start
_start:
b step1
step1:
ldr pc,=step2
step2:
b step2
编译: arm-linux-ld -Ttext 0x00000000 link.o -o link_elf
b step1 是一条相对跳转指令 , b跳转指令的机器码格式如下 :
31:28 | 27:24 | 23:0 |
---|---|---|
Cond | 101L | Offset |
条件码 | L 为0表示b跳转指令, 1表示BL跳转指令 | 表示偏移地址 |
bl 跳转指令会保存当前PC寄存器的值到LR寄存器中
b/bl跳转目标地址计算:
将24位补码地址扩展为32位, 将此32位数左移两位,将得到的值加到PC寄存器中,得到跳转的目标地址。
(ARM结构中, PC中存的是当前指令下两条指令的地址)
0: eaffffff b 0x4
4: e59ff000 ldr pc, [pc,#0]
8: eafffffe b 0x8
c: 00000008 andeq r0,r0,r8
ffffff —> ffffffff —> fffffffc (左移两位后的值), 其值为 -4
PC 中存放的值是当前指令下两条指令地址, 再-4, 即为当前指令下一条指令。
ldr pc , [pc,#0] , PC 中的值在编译时由-Ttext 指定为 0x00000000, 加偏移值 0 , 还是0x00000000
执行 b step2 , PC 中的值 为当前指令地址下两条, 变为 0x00000008 .
而 fffffe —> fffffffe --> fffffff8 , 其值为 -8, 则目标跳转地址为 0x00000008 - 8 = 0x00000000, 即为运行地址。
b bl 等指令为位置无关指令。
bootloader 内核等刚开始执行所处的地址通常不等于运行地址,所以程度开头先使用 b bl mov 等位置无关指令将代码从 flash等设备复制到内存的运行地址处,再跳转到内存的运行地址处执行。
(2)使用链接脚本设置地址
eg: arm-linux-ld -Ttimer.lds -o timer_elf $^
timer.lds
SECTIONS {
. = 0x300000000;
.text : {*(.text)}
.rodata ALIGN(4) : {*(.rodata)}
.data ALIGN(4) : {*(.data)}
.bss ALIGN(4) : {*(.bss) *(COMMON)}
}
SECTIONS 是基本命令,描述输出文件的“映射图”: 各段、各文件如何设置 。 可以含一个或多个段。 核心是段
格式:
secname start ALIGN(align) (NOLOAD) : AT(ldaddr) {contents} > region :phdr = fill
secname 和 contents 是必须的, secname确定段名, contents确定将什么内容放在这个段中。
start 是这个段的重定位地址,也称运行地址,如果代码中有位置相关的指令,则程序在运行时,这个段必须放在这个地址上。
ALIGN ,对齐指令, 对齐后的地址才是真正的运行地址。
(NOLOAD) 告诉加载器,运行时不用加载这个段,这选项只有在有操作系统时才有意义
AT : 指定这个段在映象文件中的地址。 也是加载地址。 不使用它,则加载地址等于运行地址。
6.objcopy / arm-linux-objcopy
用来复制一个目标文件的内容到另一个文件中, 可使用不同于源文件的格式来输出目标文件。
(例如,可用来将ELF格式的可执行文件转换为二进制文件。
arm-linux-objcopy -O binary -S elf_file bin_file), elf_file 是 input-file , bin_file 是outfile,可省略,若未指定,则会覆写输入文件
7.objdump/ arm-linux-objdump
显示二进制文件信息, 可用来查看汇编代码。
arm-linux-objdump -D elf_file > dis_file , 将ELF文件反汇编
arm-linux-ojbdump -D -bbinary -m arm bin_file > dis_file , 将二进制文件反汇编
-m arm , 用来指定反汇编时使用的架构。 ( 可用-i选项列出支持的架构)
机器码和汇编码的关系 , 最后程序中看到的都为机器码(机器码有各种指令格式 , 如arm 指令集的用户手册)。 汇编码也是为了阅读。
8. nm -C test.o
可用来查看目标文件