1. 交叉编译目的
一般来说 x86 的性能比较强劲,且工具成熟,生态强大。而 arm 嵌入式平台的性能比较弱,但功耗低。很多嵌入式产品都是运行在 arm 平台上的。而编译是一个比较费性能的工作,直接在 arm 平台上编译代码不太现实,因此有了交叉编译。交叉编译即在 A 平台(一般是 x86)上编译出能在 B 平台(一般是 arm 平台)上运行的代码。
2. 交叉编译步骤
一般需要交叉编译的工具的源码都是 C 代码,且一般都是用 makefile 构建的。一般的步骤为:(1) 下载源码;(2) 运行源码中的 configure
脚本文件进行配置;(3) make 编译。
2.1. 下载源码
此步骤不再详细说明,直接从网上找源码下载即可。
2.2. configure 配置
一般这些源码的目录下都有一个 configure
脚本文件,目的是用来配置编译参数,编译的工具链等。
查看所有支持的参数以及说明:
./configure -h
这里以 bash 为例,查看所有支持的参数:
[01:05:48 bash-5.2.9]$ ./configure -h
`configure' configures bash 5.2-release to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Configuration:
-h, --help display this help and exit
--help=short display options specific to this package
--help=recursive display the short help of all the included packages
-V, --version display version information and exit
-q, --quiet, --silent do not print `checking ...' messages
--cache-file=FILE cache test results in FILE [disabled]
-C, --config-cache alias for `--cache-file=config.cache'
-n, --no-create do not create output files
--srcdir=DIR find the sources in DIR [configure dir or `..']
Installation directories:
--prefix=PREFIX install architecture-independent files in PREFIX
[/usr/local]
--exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
[PREFIX]
By default, `make install' will install all the files in
`/usr/local/bin', `/usr/local/lib' etc. You can specify
an installation prefix other than `/usr/local' using `--prefix',
for instance `--prefix=$HOME'.
For better control, use the options below.
Fine tuning of the installation directories:
--bindir=DIR user executables [EPREFIX/bin]
--sbindir=DIR system admin executables [EPREFIX/sbin]
--libexecdir=DIR program executables [EPREFIX/libexec]
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
--datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
--datadir=DIR read-only architecture-independent data [DATAROOTDIR]
--infodir=DIR info documentation [DATAROOTDIR/info]
--localedir=DIR locale-dependent data [DATAROOTDIR/locale]
--mandir=DIR man documentation [DATAROOTDIR/man]
--docdir=DIR documentation root [DATAROOTDIR/doc/bash]
--htmldir=DIR html documentation [DOCDIR]
--dvidir=DIR dvi documentation [DOCDIR]
--pdfdir=DIR pdf documentation [DOCDIR]
--psdir=DIR ps documentation [DOCDIR]
System types:
--build=BUILD configure for building on BUILD [guessed]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-minimal-config a minimal sh-like configuration
--enable-alias enable shell aliases
--enable-alt-array-implementation
enable an alternate array implementation that
optimizes speed at the cost of space
--enable-arith-for-command
enable arithmetic for command
--enable-array-variables
include shell array variables
--enable-bang-history turn on csh-style history substitution
--enable-brace-expansion
include brace expansion
--enable-casemod-attributes
include case-modifying variable attributes
--enable-casemod-expansions
include case-modifying word expansions
--enable-command-timing enable the time reserved word and command timing
--enable-cond-command enable the conditional command
--enable-cond-regexp enable extended regular expression matching in
conditional commands
--enable-coprocesses enable coprocess support and the coproc reserved
word
--enable-debugger enable support for bash debugger
--enable-dev-fd-stat-broken
enable this option if stat on /dev/fd/N and fstat on
file descriptor N don't return the same results
--enable-direxpand-default
enable the direxpand shell option by default
--enable-directory-stack
enable builtins pushd/popd/dirs
--enable-disabled-builtins
allow disabled builtins to still be invoked
--enable-dparen-arithmetic
include ((...)) command
--enable-extended-glob include ksh-style extended pattern matching
--enable-extended-glob-default
force extended pattern matching to be enabled by
default
--enable-function-import
allow bash to import exported function definitions
by default
--enable-glob-asciiranges-default
force bracket range expressions in pattern matching
to use the C locale by default
--enable-help-builtin include the help builtin
--enable-history turn on command history
--enable-job-control enable job control features
--enable-multibyte enable multibyte characters if OS supports them
--enable-net-redirections
enable /dev/tcp/host/port redirection
--enable-process-substitution
enable process substitution
--enable-progcomp enable programmable completion and the complete
builtin
--enable-prompt-string-decoding
turn on escape character decoding in prompts
--enable-readline turn on command line editing
--enable-restricted enable a restricted shell
--enable-select include select command
--enable-separate-helpfiles
use external files for help builtin documentation
--enable-single-help-strings
store help documentation as a single string to ease
translation
--enable-strict-posix-default
configure bash to be posix-conformant by default
--enable-translatable-strings
include support for $"..." translatable strings
--enable-usg-echo-default
a synonym for --enable-xpg-echo-default
--enable-xpg-echo-default
make the echo builtin expand escape sequences by
default
--enable-mem-scramble scramble memory on calls to malloc and free
--enable-profiling allow profiling with gprof
--enable-static-link link bash statically, for use as a root shell
--disable-largefile omit support for large files
--disable-nls do not use Native Language Support
--enable-threads={posix|solaris|pth|windows}
specify multithreading API
--disable-threads build without multithread safety
--disable-rpath do not hardcode runtime library paths
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-afs if you are running AFS
--with-bash-malloc use the Bash version of malloc
--with-curses use the curses library instead of the termcap
library
--with-gnu-malloc synonym for --with-bash-malloc
--with-installed-readline
use a version of the readline library that is
already installed
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
--with-libpth-prefix[=DIR] search for libpth in DIR/include and DIR/lib
--without-libpth-prefix don't search for libpth in includedir and libdir
--with-libiconv-prefix[=DIR] search for libiconv in DIR/include and DIR/lib
--without-libiconv-prefix don't search for libiconv in includedir and libdir
--with-included-gettext use the GNU gettext library included here
--with-libintl-prefix[=DIR] search for libintl in DIR/include and DIR/lib
--without-libintl-prefix don't search for libintl in includedir and libdir
Some influential environment variables:
DEBUGGER_START_FILE
location of bash debugger initialization file
CC_FOR_BUILD
C compiler used when compiling binaries used only at build time
CFLAGS_FOR_BUILD
Compilation options (CFLAGS) used when compiling binaries used
only at build time
LDFLAGS_FOR_BUILD
Linker options (LDFLAGS) used when compiling binaries used only
at build time
CPPFLAGS_FOR_BUILD
C preprocessor options (CPPFLAGS) used when compiling binaries
used only at build time
CC C compiler command
CFLAGS C compiler flags
LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
nonstandard directory <lib dir>
LIBS libraries to pass to the linker, e.g. -l<library>
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
you have headers in a nonstandard directory <include dir>
CPP C preprocessor
YACC The `Yet Another Compiler Compiler' implementation to use.
Defaults to the first program found out of: `bison -y', `byacc',
`yacc'.
YFLAGS The list of arguments that will be passed by default to $YACC.
This script will default YFLAGS to the empty string to avoid a
default value of `-d' given by some make applications.
Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.
Report bugs to <bug-bash@gnu.org>.
而一般的源码,大家约定俗称,一般都支持下列参数。
2.2.1. 安装选项
- --prefix:编译好的二进制安装的路径
2.2.2. 平台选项
- --build:The platform on which the compilation tools are executed。(编译此代码的平台,或者是说交叉编译器运行的平台,一般都是 x86)
- --host:The platform on which the code will run。(编译好的代码要运行在哪种平台上,一般是 arm。注:--build 和 --host 不同的时候就被配置文件认定为交叉编译方式)
- --target:Only when building a compiler, this is the platform for which the compiler will generate code。(这个参数一般不用,只有我们编译的软件是编译器时才可能用到)
例如:
假设在 x86 下编译一个运行于 arm 上的编译器 gcc,这个 gcc 产生 mips 平台的目标代码。那么我们的配置方式是:
--build=x86 --host=aarch64 --target=mips
一般只有在这种情况下才需要配置 --target
参数。我们编译的时候,一般仅仅需要指定 --host
参数即可。--build
参数一般都能推导出来,因为代码知道我们是在什么平台上进行的编译活动。
关于这几个参数的取值,其实就是个字符串,没有统一规定。一般来说 --build
参数一般指定 x86
,x86-linux
, x86-unknown-none
即可。--host
参数一般指定 aarch64
,aarch64-linux
,aarch64-unknown-none
,arm64
即可。但根据经验,有些 configure
只识别特定的名字,例如有的传递 aarch64
不识别,只识别 arm64
;而有的更奇怪,传递 aarch64
不编译动态库,传递 aarch64-linux
才编译动态库,这个需要根据实际情况识别。
2.2.3. 环境变量
一个完整的编译需要经过 "预处理 > 编译 > 汇编 > 链接" 步骤才能完成。我们通常运行 gcc main.cpp -o main
一条指令就把上述步骤涵盖完毕了。但其实每个步骤有特定的编译工具负责。
一般一个完整的交叉编译工具都包含如下类似工具:
程序命令 | 程序功能 |
---|---|
addr2line | 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻译成源代码文件名和行号。 |
ar | 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理连接程序使用的目标库文档。 |
as | GNU 汇编器。实际上它是一族汇编器,因为它可以被编译或能够在各种不同平台上工作。 |
c++filt | 程序接受被 C++ 编译程序转换过的名字(不是被重载的),而且将该名字翻译成初始形式。 |
elfedit | 更新 ELF 文件的 ELF 头。 |
gprof | 该程序会监督编译程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供的配置文件来优化程序。 |
ld | GNU 连接程序。该程序将目标文件的集合组合成可执行程序。 |
ld.bfd | 到 ld 的硬链接。 |
libbfd | 二进制文件描述器库。该程序是 binutils 包的一部分 |
libiberty | 包含多个 GNU 程序会使用的途径,包括 getopt、obstack、strerror、strtol 和 strtoul。 |
libopcodes | 一个库,用于处理 opcodes——处理器指令的 "可读文本" 版本;用于编制 objdump 这样的工具。 |
nlmconv | 将可重定位的目标文件转换成 NetWare 可加载模块(NetWare Loadable Module, NLM)。 |
nm | 列出目标文件中定义的符号。 |
objcopy | 将目标文件从一种二进制格式复制和翻译到另外一种。 |
objdump | 显示一个或多个目标文件中保存的多种不同信息。 |
ranlib | 创建和添加到 ar 文档的索引。该索引被 ld 使用来定位库中的模块。 |
readelf | 从 ELF 格式的目标文件显示信息 |
size | 列出目标文件中每个部分的名字和尺寸。 |
strings | 浏览所有类型的文件,析取出用于显示的字符串。 |
strip | 从目标文件或文档库中去掉符号表,以及其他调试所需的信息。 |
windres | Window 资源文件编译程序。 |
一般需要指定以下环境变量:
- CC:C compiler command。
- AR:打包库使用。
- CFLAGS:C compiler flags。一般交叉编译为了移植方便,都要指定静态编译,这样运行时不依赖 so 库。大部分工具都不会提供静态编译参数,因此为了静态编译一般都要指定
CFLAGS=-static
。bash 的源码中提供了静态编译选项--enable-static-link
,这为用户提供了方便,因此在交叉编译 bash 时直接指定这个参数就能静态编译,而不需要指定CFLAGS
参数。因此,我们拿到一个源码时,运行./config -h | grep static
,通过关键字查找一下是否提供了静态编译选项。
2.2.4. 完整配置
因此一般在运行 configure 配置时,需要传递以下参数:
./configure \
--prefix=YOU_INSTALL_PATH \
--host=aarch64 \
CC=/xxx/aarch64-linux-gnu-cc \
CFLAGS=-static
2.2.5. make 编译
一般这些工具的源码都是用 makefile 构建的,对应的工具就是 mark。
一般运行如下指令编译:
make -j16
- -j:指定用多少个线程同时编译,越多越快,根据电脑实际情况配置。
但有的时候,需要给 makefile 传递变量,即有的时候 makefile 中定义了诸如 ARCH
和 CROSS_COMPILE
等变量,但不是每个 makefile 都定义了这些变量,这是通用做法,而不是必须做法。具体定义了哪些变量,需要打开 makefile 具体看一看。一般工程都自带 一个 Makefile.in
文件,然后运行 ./configure
配置时根据参数与 Makefile.in
生成真正的 Makefile
文件供 make 指令使用。因此这里查看的是运行 ./configure
生成的 Makefile
文件。
-
ARCH:即 architecture,就是编译出的代码运行的平台架构,一般指定
ARCH=aarch64
或ARCH=arm64
即可。 -
CROSS_COMPILE:即交叉编译器的前缀(prefix),如指定
CROSS_COMPILE=/xxx/aarch64-linux-gnu-
就是使用/xxx/aarch64-linux-gnu-cc
,/xxx/aarch64-linux-gnu-ar
,/xxx/aarch64-linux-gnu-ld
等工具将代码编译成 arm 的可执行指令。
此时编译指令一般如下:
make ARCH=aarch64 CROSS_COMPILE=/xxx/aarch64-linux-gnu- -j16
编译完整后,运行如下指令把编译好的二进制安装到 --prefix=YOU_INSTALL_PATH
指定的目录下:
make install
3. 交叉编译器说明
3.1. 命名规则
交叉编译工具链的命名规则为:arch [-vendor] [-os] [-(gnu)eabi]
- arch -体系架构,如 ARM,MIPS
- vendor -工具链提供商
- os -目标操作系统
- eabi -嵌入式应用二进制接口(Embedded Application Binary Interface)
根据对操作系统的支持与否,ARM GCC 可分为支持和不支持操作系统,如:
- arm-none-eabi:这个是没有操作系统的,自然不可能支持那些跟操作系统关系密切的函数,比如 fork(2)。它使用的是 newlib 这个专用于嵌入式系统的 C 库。
- arm-none-linux-eabi:用于 Linux 的,使用 Glibc
3.2. 实例
1、arm-none-eabi-gcc
(ARM architecture,no vendor,not target an operating system,complies with the ARM EABI)
用于编译 ARM 架构的裸机系统(包括 ARM Linux 的 boot、kernel,不适用编译 Linux 应用 Application),一般适合 ARM7、Cortex-M 和 Cortex-R 内核的芯片使用,所以不支持那些跟操作系统关系密切的函数,比如 fork(2),它使用的是 newlib 这个专用于嵌入式系统的 C 库。
2、arm-none-linux-gnueabi-gcc
(ARM architecture, no vendor, creates binaries that run on the Linux operating system, and uses the GNU EABI)
主要用于基于 ARM 架构的 Linux 系统,可用于编译 ARM 架构的 u-boot、Linux 内核、Linux 应用等。arm-none-linux-gnueabi 基于 GCC,使用 Glibc 库,经过 Codesourcery 公司优化过推出的编译器。arm-none-linux-gnueabi-xxx 交叉编译工具的浮点运算非常优秀。一般 ARM9、ARM11、Cortex-A 内核,带有 Linux 操作系统的会用到。
3、arm-eabi-gcc
Android ARM 编译器。
4、armcc
ARM 公司推出的编译工具,功能和 arm-none-eabi 类似,可以编译裸机程序(u-boot、kernel),但是不能编译 Linux 应用程序。armcc 一般和 ARM 开发工具一起,Keil MDK、ADS、RVDS 和 DS-5 中的编译器都是 armcc,所以 armcc 编译器都是收费的。
5、arm-none-uclinuxeabi-gcc 和 arm-none-symbianelf-gcc
arm-none-uclinuxeabi 用于 uCLinux,使用 Glibc。
arm-none-symbianelf 用于 symbian,不知道 C 库是什么。
3.3. Codesourcery
Codesourcery 推出的产品叫 Sourcery G++ Lite Edition,其中基于 command-line 的编译器是免费的,在官网上可以下载,而其中包含的 IDE 和 debug 工具是收费的,当然也有 30 天试用版本的。
目前 Codesourcery 已经由明导国际(Mentor Graphics)收购,所以原本的网站风格已经全部变为 Mentor 样式,但是 Sourcery G++ Lite Edition 同样可以注册后免费下载。
Codesourcery 一直是在做 ARM 目标 GCC 的开发和优化,它的 ARM GCC 在目前在市场上非常优秀,很多 patch 可能还没被 gcc 接受,所以还是应该直接用它的(而且它提供 Windows 下[mingw 交叉编译的]和 Linux 下的二进制版本,比较方便;如果不是很有时间和兴趣,不建议下载 src 源码包自己编译,很麻烦,Codesourcery 给的 shell 脚本很多时候根本没办法直接用,得自行提取关键的部分手工执行,又费精力又费时间,如果想知道细节,其实不用自己编译一遍,看看它是用什么步骤构建的即可,如果你对交叉编译器感兴趣的话。
3.4. ABI 和 EABI
ABI:二进制应用程序接口(Application Binary Interface (ABI)for the ARM Architecture)。在计算机中,应用二进制接口描述了应用程序(或者其它类型)和操作系统之间或其它应用程序的低级接口。
EABI:嵌入式 ABI。嵌入式应用二进制接口指定了文件格式、数据类型、寄存器使用、堆积组织优化和在一个嵌入式软件中的参数的标准约定。开发者使用自己的汇编语言也可以使用 EABI 作为与兼容的编译器生成的汇编语言的接口。
两者主要区别是,ABI 是计算机上的,EABI 是嵌入式平台上(如 ARM,MIPS 等)。
3.5. arm-linux-gnueabi-gcc 和 arm-linux-gnueabihf-gcc
两个交叉编译器分别适用于 armel 和 armhf 两个不同的架构,armel 和 armhf 这两种架构在对待浮点运算采取了不同的策略(有 fpu 的 ARM 才能支持这两种浮点运算策略)。
其实这两个交叉编译器只不过是 gcc 的选项-mfloat-abi 的默认值不同。gcc 的选项-mfloat-abi 有三种值 soft、softfp、hard(其中后两者都要求 ARM 里有 fpu 浮点运算单元,soft 与后两者是兼容的,但 softfp 和 hard 两种模式互不兼容):
- soft:不用 fpu 进行浮点计算,即使有 fpu 浮点运算单元也不用,而是使用软件模式。
- softfp:armel 架构(对应的编译器为 arm-linux-gnueabi-gcc)采用的默认值,用 fpu 计算,但是传参数用普通寄存器传,这样中断的时候,只需要保存普通寄存器,中断负荷小,但是参数需要转换成浮点的再计算。
- hard:armhf 架构(对应的编译器 arm-linux-gnueabihf-gcc)采用的默认值,用 fpu 计算,传参数也用 fpu 中的浮点寄存器传,省去了转换,性能最好,但是中断负荷高。
把以下测试使用的 C 文件内容保存成 mfloat.c:
#include <stdio.h>
int main(void)
{
double a,b,c;
a = 23.543;
b = 323.234;
c = b/a;
printf("the 13/2 = %f\n", c);
printf("hello world !\n");
return 0;
}
1、使用 arm-linux-gnueabihf-gcc 编译,使用 -v 选项以获取更详细的信息:
# arm-linux-gnueabihf-gcc -v mfloat.c
COLLECT_GCC_OPTIONS='-v' '-march=armv7-a' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb'
-mfloat-abi=hard
可看出使用 hard 硬件浮点模式。
2、使用 arm-linux-gnueabi-gcc 编译:
# arm-linux-gnueabi-gcc -v mfloat.c
COLLECT_GCC_OPTIONS='-v' '-march=armv7-a' '-mfloat-abi=softfp' '-mfpu=vfpv3-d16' '-mthumb'
-mfloat-abi=softfp
可看出使用 softfp 模式。