一、交叉编译工具的使用

 在进行嵌入式开发时,通常有宿主机与目标机的角色之分,宿主机是执行编译、链接嵌入式软件的计算机,而目标机是运行嵌入式软件的硬件平台。而这两者之间有时硬件/软件平台可能不同,在宿主机上直接使用编译器的程序在目标机上无法运行,因此就出现了交叉编译工具。在针对ARM架构上运行的Linux目标机来说,其专用的交叉编译工具为arm-linux-gcc、arm-linux-ld等。

04-ARM裸机开发的命令行工具
 一个 C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和连接(linking)等 4 步才能变成可执行文件。其每一步的作用在下表中进行了说明:

步骤 说明
预处理 C/C++源文件中,以“ #”开头的命令被称为预处理命令,如包含命令“ #include”、宏定义命令“ #define”、条件编译命令“ #if”、“ #ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“ .i”文件中等待进一步处理。
编译 编译就是把 C/C++代码(比如上述的“ .i”文件)“ 翻译” 成汇编代码。
汇编 汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在 Linux 系统上一般表现为 ELF 目标文件(OBJ 文件)。
链接 链接就是将上步生成的 OBJ 文件和系统库的 OBJ 文件、 库文件连接起来,最终生成了可以在特定平台运行的可执行文件。

 编译器利用上述四个步骤中的一个或多个来处理输入文件,源文件的后缀表示源文件所用的语言,后缀控制编译器的缺省动作。

后缀名 语言种类
.c C源程序
.i 预处理后的c文件
.s或.S 汇编语言源程序
.h 头文件
.o 汇编后对象文件,包含了对各个函数的入口标记,描述,当程序要执行时还需要链接(link).链接就是把多个.o文件链成一个可执行文件

 在交叉编译工具里经常用到的命令有:

命令 介绍
arm-linux-gcc 编译器
arm-linux-ld 链接器
arm-linux-objdump 反汇编工具
arm-linux-readelf elf文件查看器
arm-linux-objcopy 文件转换工具

1.arm-linux-gcc
常用选项:

 -E:让编译器在预处理后停止编译过程
 -o file:指定输出文件为 file
 -S:只进行编译而不进行汇编,生成汇编代码
 -c:将汇编代码转化为“.o”的二进制目标代码(-c可以一次性完成前三部工作)

2.arm-linux-ld
用于将多个目标文件、库文件连接成可执行文件,常用选项:

 object-file-name:默认是是OBJ 文件或库文件(根据文件内容,连接器能够区分 OBJ 文件和库文件)。如果 GCC 执行连接操作,这些 OBJ 文件将成为连接器的输入文件。
 -llibrary:连接名为 library 的库文件。
 -nostdlib:不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。这个选项常用于编译内核、 bootloader 等程序,它们不需要启动文件、标准库文件。
 -static:在支持动态连接(dynamic linking)的系统上,阻止连接共享库。
 -shared:生成一个共享 OBJ 文件,它可以和其他 OBJ 文件连接产生可执行文件。只有部分系统支持该选项。
 -T:只在连接 Bootloader、内核等没有底层软件支持的软件;连接运行于操作系统之上的应用程序时, 无需指定-T。

3.arm-linux-objcopy
被用来拷贝一个目标文件的内容到另一个文件中, 可以使用不同于源文件的格式来输出目的文件,即可以进行格式转换。

 input-file、outfile:分别表示输入目标文件(源目标文件)和输出目标文件(目的目标文件)
 -I bfdname:用来指明源文件的格式, bfdname 是 BFD 库中描述的标准格式名
 -O bfdname :使用指定的格式来输出文件, bfdname 是 BFD 库中描述的标准格式名
 -F bfdname :同时指明源文件、目的文件的格式
 -R sectionname:从输出文件中删掉所有名为 sectionname 的段。
 -S :不从源文件中拷贝重定位信息和符号信息到目标文件中去
 -g :不从源文件中拷贝调试符号到目标文件中去

在编译 bootloader、 内核时,常用 arm-linux-objcopy 命令将 ELF 格式的生成结果转换为二进制文件,比如:
$ arm-linux-objcopy -O binary -S elf_file bin_file
4.arm-linux-objdump
用于显示二进制文件信息。

 -b bfdname :指定目标码格式。可以使用“ arm-linux-objdump –i”命令查看支持的目标码格式列表。
 -disassemble :反汇编可执行段(executable sections)。
 -disassemble-all :与-d 类似,反汇编所有段。
 -EB :指定字节序。
 --file-headers :显示文件的整体头部摘要信息。
 --section-headers:显示目标文件各个段的头部摘要信息。
 -info :显示支持的目标文件格式和 CPU 架构,它们在“ -b”、“ -m”选项中用到。
 --section=name :仅仅显示指定 section 的信息。
 --architecture=machine :指定反汇编目标文件时使用的架构,当待反汇编文件本身没有描述架构信息的时候(比如S-records),这个选项很有用。

5.arm-linux-readelf

-a 应用程序    可查看文件运行架构、大小端、共享库等信息。针对编译时加上"-static"选项的应用程序。
-d 应用程序    可查看应用程序的动态链接库

二、Makefile工程管理器

 当一个项目有上百个文件的代码构成的项目,如果其中只有一个或少数几个文件进行了修改,使用Gcc 编译工具,就不得不把这所有的文件重新编译一遍,因为编译器并不知道哪些文件是最近更新的,只有全部重新才能得到可执行文件。
 但是编译过程是分为编译、汇编、链接不同阶段的,其中编译阶段仅检查语法错误以及函数与变量的声明是否正确声明了,在链接阶段则主要完成是函数链接和全局变量的链接。因此,那些没有改动的源代码根本不需要重新编译,而只要把它们重新链接进去就可以了。而Make工程管理器能够自动识别更新了的文件代码,同时又不需要重复输入冗长的命令行。
 Make 工程管理器也就是个“自动编译管理器” ,它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入 Makefile 文件的内容来执行大量的编译工作。
1.Makefile的构成
 Makefile 是 Make 读入的惟一配置文件,在一个 Makefile 中通常包含如下内容:
· 需要由 make 工具创建的目标体(target) ,通常是目标文件或可执行文件;
· 要创建的目标体所依赖的文件(dependency_file) ;
· 创建每个目标体时需要运行的命令(command) 。

target: dependency_files
    command
如:
hello.o: hello.c hello.h
    gcc –c hello.c –o hello.o

2.Makefile的变量
 变量是在 Makefile 中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标体、依赖文件、命令以及 makefile 文件中其他部分。在Makefile 中的变量定义有两种方式:一种是递归展开方式,另一种是简单方式。
 递归展开方式定义的变量是在引用在该变量时进行替换的,即如果该变量包含了对其他变量的应用,则在引用该变量时一次性将内嵌的变量全部展开,缺点是不能在变量后追加内容(因为语句:CFLAGS = $(CFLAGS) -O 在变量扩展过程中可能导致无穷循环) 。简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量的引用,从而消除变量的嵌套引用。
递归展开方式的定义格式为:VAR=var。
简单扩展方式的定义格式为:VAR:=var。
Make 中的变量使用均使用格式为:$(VAR)。
 预定义变量包含了常见编译器、汇编器的名称及其编译选项:

命令格式 含义
AR 库文件维护程序的名称,默认值为 ar
AS 汇编程序的名称,默认值为 as
CC C 编译器的名称,默认值为 cc
CPP C 预编译器的名称,默认值为$(CC) –E
CXX C++编译器的名称,默认值为 g++
FC FORTRAN 编译器的名称,默认值为 f77
RM 文件删除程序的名称,默认值为 rm –f
ARFLAGS 库文件维护程序的选项,无默认值
ASFLAGS 汇编程序的选项,无默认值
CFLAGS C 编译器的选项,无默认值
CPPFLAGS C 预编译的选项,无默认值
CXXFLAGS C++编译器的选项,无默认值
FFLAGS FORTRAN 编译器的选项,无默认值

Makefile 中常见自动变量

命 令 格 式 含 义
$* 不包含扩展名的目标文件名称
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$< 第一个依赖文件的名称
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分开
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
$? 所有时间戳比目标文件晚的依赖文件,并以空格分开

3.Makefile 规则
 Makefile 的规则是 Make 进行处理的依据,它包括了目标体、依赖文件及其之间的命令语句。一般的,Makefile 中的一条语句就是一个规则。但为了简化 Makefile 的编写,make 还定义了隐式规则和模式规则。
 隐含规则能够告诉 make 怎样使用传统的技术完成任务,这样,当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可。下面的Makefile中kang.o与yul.o的生成过程即可以省略。

OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
    $(CC) $^ -o $@

Makefile 中常见隐式规则目录

对应语言后缀名 规 则
C 编译:.c 变为.o $(CC) –c $(CPPFLAGS) $(CFLAGS)
C++编译:.cc 或.C 变为.o $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
Pascal 编译:.p 变为.o $(PC) -c $(PFLAGS)
Fortran 编译:.r 变为-o $(FC) -c $(FFLAGS)

 模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则,隐式规则仅仅能够用 make 默认的变量来进行操作,而模式规则还能引入用户自定义变量,为多个文件建立相同的规则,从而简化 Makefile 的编写。模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明。

OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
    $(CC) $^ -o $@
%.o : %.c
    $(CC) $(CFLAGS) -c $< -o $@

4.Make 管理器的使用
 make 有丰富的命令行选项,可以完成各种不同的功能。

命 令 格 式 含 义
-C dir 读入指定目录下的 Makefile
-f file 读入当前目录下的 file 文件作为 Makefile
-i 忽略所有的命令执行错误
-I dir 指定被包含的 Makefile 所在目录
-n 只打印要执行的命令,但不执行这些命令
-p 显示 make 变量数据库和隐含规则
-s 在执行命令时不显示命令
-w 如果 make 在执行过程中改变目录,则打印当前目录名

5.makefile中常用函数
 函数调用的格式为:$(function arguments)
 这里function 是函数名, arguments是该函数的参数。参数和函数名之间是用空格或 Tab 隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。
字符串替换和分析函数:

函数 含义
$(subst from,to,text) 在文本text中使用to 替换每一处from.
$(patsubst pattern,replacement,text) 寻找text中符合格式pattern的字,用replacement替换它们.pattern和replacement 中可以使用通配符。
$(strip string) 去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
$(findstring find,in) 在字符串in 中搜寻find,如果找到,则返回值是find ,否则返回值为空。
$(filter pattern...,text) 返 回 在 text 中 由 空 格 隔 开 且 匹 配 格 式 pattern... 的 字 , 去 除 不 符 合 格 式pattern... 的字。
$(filter-out pattern...,text) 返 回 在 text中 由 空 格 隔 开 且 不 匹 配 格 式 pattern...的 字 , 去 除 符 合 格 式pattern... 的字。
$(sort list) 将 list中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。

文件名函数:

函数 含义
$(dir names...) 抽取names...中每一个文件名的路径部分,文件名的路径部分包括从文件名的首字符到最后一个斜杠(含斜杠)之前的一切字符。
$(notdir names...) 抽取names...中每一个文件名中除路径部分外一切字符(真正的文件名)。
$(suffix names...) 抽取names...中每一个文件名的后缀。
$(basename names...) 抽取names...中每一个文件名中除后缀外一切字符。
$(addsuffix suffix,names...) 参数names...是一系列的文件名,文件名之间用空格隔开; suffix 是一个后缀名。将 suffix(后缀)的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间用单个空格隔开。
$(addprefix prefix,names...) 参数 names是一系列的文件名,文件名之间用空格隔开; prefix 是一个前缀名。将preffix(前缀)的值附加在每一个独立文件名的前面,完成后将文件名串联起来,它们之间用单个空格隔开。
$(wildcard pattern) 参数pattern是一个文件名格式,包含有通配符(通配符和 shell 中的用法一样)。函数 wildcard 的结果是一列和格式匹配的且真实存在的文件的名称,文件名之间用一个空格隔开。

6.使用 autotools
 编写 Makefile 不是一件轻松的事,尤其对于一个较大的项目而言更是如此。autotools 系列工具只需用户输入简单的目标文件、依赖文件、文件目录等就可以轻松地生成 Makefile 。另外,这些工具还可以完成系统配置信息的收集,从而可以方便地处理各种移植性的问题。autotools 是系列工具主要包含:aclocal、autoscan、autoconf、autoheader、automake。
 使用 autotools 主要就是利用各个工具的脚本文件以生成最后的 Makefile。其总体流程是:
· 使用 aclocal 生成一个“aclocal.m4”文件,该文件主要处理本地的宏定义;
· 改写“configure.scan”文件,并将其重命名为“configure.in” ,并使用 autoconf 文件生成 configure 文件。

命令 执行过程
autoscan 它会在给定目录及其子目录树中检查源文件,若没有给出目录,就在当前目录及其子目录树中进行检查。它会搜索源文件以寻找一般的移植性问题并创建一个文件“configure.scan” 。
autoconf configure.in 是 autoconf 的脚本配置文件,它的原型文件“configure.scan”。
autoheader 接着使用 autoheader 命令,它负责生成 config.h.in 文件。
automake automake 要用的脚本配置文件是 Makefile.am,用户需要自己创建相应的文件。之后,automake 工具转换成Makefile.in。
configure 在这一步中,通过运行自动配置设置文件 configure,把 Makefile.in 变成了最终的Makefile。

04-ARM裸机开发的命令行工具

三、链接器器脚本

 链接器主要有两个作用,一是将若干输入文件(.o文件)根据一定规则合并为一个输出文件(例如ELF格式的可执行文件);二是将符号与地址绑定(当然加载器也要完成这一部分工作)。
 如果没有为程序提供一个链接器脚本,链接器将会使用默认的编译在链接器执行文件内部的脚本。可以使用命令’–verbose’显示默认的链接脚本。通过在命令行使用’-T’命令使用自己的脚本。如果使用此命令,你的链接脚本将会替代默认链接脚本。
 一个可执行的程序通常是由:代码段,数据段,bss段构成的。同样,在用于链接这个程序的连接器脚本中,就会反映出这几个段的信息。最简单的可能的脚本只有一个命令:’SECTIONS’。

SECTIONS {
    . = 0x30000000;           //起始链接地址
    .text :                            //代码段
    {
        head.o(.text)              //保证head.o首先被链接,也就是程序执行的时候先执行这部代码  
        init.o(.text)
        nand.o(.text)
        *(.text) 
    }
    .rodata ALIGN(4) :      //只读数据段,字节对齐
        {*(.rodata*)} 
    .data   ALIGN(4) :        //可读写且需要初始化数据,字节对齐
        { *(.data) }
    __bss_start = .;          //把当前地址存入bss_start这个变量中,可读写的置零初始化数据bss段
        .bss ALIGN(4)  :
        { *(.bss)  *(COMMON) }
    __bss_end = .;           //把当前地址存入bss_end这个变量中
}

参考文献:
嵌入式Linux系统开发完全手册_基于4412_上册
Linux下的C编程基础
LD说明文档--3.LD链接脚本