这个帖子主要目的是分享我制作交叉编译工具链的过程,它是在参考了网上大量的资料以及我的实践后,修改、整理而成的。之所以以powerpc为例,是因为项目使用的是Freescalse的MPC8315E处理器,实际上稍作修改就可以用在所有的处理器类型上(如ARM,MIPS等)。
这个帖子主要目的是分享我制作交叉编译工具链的过程,它是在参考了网上大量的资料以及我的实践后,修改、整理而成的。之所以以powerpc为例,是因为项目使用的是Freescalse的MPC8315E处理器,实际上稍作修改就可以用在所有的处理器类型上(如ARM,MIPS等)。
1. 基本概念
2.准备工作
3.制作过程
3.1 安装内核头文件
3.2 交叉编译Binutils软件包
3.3 交叉编译临时的GCC
3.4 交叉编译Glibc
3.5 交叉编译GCC
4. 总结
1.基本概念
什么是交叉编译工具链,这是许多第一次接触嵌入式开发的童鞋需要理解的首要问题。通常我们已经习惯在X86平台上运行gcc,对源程序进行编译,编译得到的目标程序,仍然是在X86平台上跑的。而交叉编译工具链就是,需要在某个平台上,对源程序进行编译,但是得到的目标程序却是在另外一个平台上运行的。
我们已经知道,在某个平台对程序进行编译后,得到的目标程序,默认也是在该平台运行的(例如X86)。所以我们通常需要在现有平台(通常我们把这个平台称为 Host)的基础上,制作出一个交叉编译工具链(包括gcc、binutils、glibc),得到新的gcc仍然是在该平台(通常我们把该平台称为Host)上运行的,但是当利用新的gcc去对某个源程序进行编译时,得到的目标程序是在目标平台上运行的(通常我们把该平台称为Target)。
现在我们知道,制作交叉编译工具链少不了要先编译一个gcc和 binutils(包括链接器ld strip等工具),但是仅仅是这两个还不够的。我们知道,在对源程序进行编译时,少不了要依赖一些库,例如C运行时库(glibc),而你的Host中的库,它是针对Host体系结构的。例如你的X86中的库,其机器指令一定是X86的。这样,你的交叉编译工具链中,必须有目标平台的库。你肯定已经想到了,先编译好gcc和binutils,然后用这个gcc编译目标平台的库,然后就可以在这个库的基础上,编译目标平台的程序了。
现在你已经可以想到制作一个交叉编译工具链的步骤了,但是很快你会看到,在gcc的编译过程中,我们需要编译2遍,这是为什么呢? 一个全面的gcc(支持各种语言的),需要目标平台的C库(glibc)的一些头文件,但是这个新的gcc编译出来之前,我们又没有安装目标平台的库 (glibc)。所以我们先编译一个基本的gcc(仅仅支持C语言),然后用这个gcc编译目标平台的glibc,注意此时得到的glibc是目标平台的。最后,再在这个库的基础上,重新编译一个全面的gcc。除此之外,我们还要准备好内核头文件,这样我们就可以直接使用内核的一些宏,数据结构定义,数据类型,等等。
在有了这些概念的基础上,下面的操作就相对比较简单了。这里需要提醒的是,同样的编译参数,不同的编译环境,或者不同的gcc binutilsglibc版本,都可能编译不成功。根据我的经验,制作交叉编译工具链,一帆风顺就成功是很少见的。因此在编译过程中,如果遇到失败,耐心+细心的分析config.log,Makefile,可以帮助你定位问题。尤其对于新手来说,千万不要急于求成,妄想直接复制一下命令行,一步步编译就成功。我建议只是先看完一遍,对自己要做什么,和每一步的目的有个大概的了解,然后再开始。欲速则不达,这个道理很简单,恐怕只有多品位几次才能体会。另外,千万不要以超级用户(root)的身份来制作交叉编译工具链,否则一不小心用target平台的库,把Host平台上的库给覆盖了,后果可是很严重哦!
2. 准备工作
cd $HOME
mkdir ppc #工具链的顶层目录,你也可以命名为ARM
cd ppc
mkdir sources
cd sources
wget http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.bz2
wget http://ftp.gnu.org/gnu/glibc/glibc-2.14.tar.bz2
wget http://ftp.gnu.org/gnu/glibc/glibc-linuxthreads-2.5.tar.bz2
wget http://ftp.gnu.org/gnu/gcc/gcc-4.6.2/gcc-4.6.2.tar.bz2
wget ftp://ftp.kernel.org/pub/linux/kernel/v3.x/linux-3.1.5.tar.bz2
wget ftp://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.bz2
wget http://www.mpfr.org/mpfr-current/mpfr-3.1.0.tar.bz2
mkdir ../tools
export TARGET=powerpc-linux #如果是ARM平台,则为arm-linux
export TOOLS=~/ppc/tools
export SOURCES=~/ppc/sources
export PATH=$TOOLS/bin:$PATH #这一步是必需的,只有将带target alias 即powerpc-linux-前缀的工具放到PATH中,才能使用整个交叉编译环境。
export LANGUAGE=C #下面两个参数编译glibc时默认使用的locale
export LC_ALL=C
3.制作过程
3.1 安装内核头文件(编译glibc时用到)
cd $SOURCES
tar jvxf linux-3.1.5.tar.bz2
#把内核头文件安装到$TOOLS/$TARGET/usr/include中。
make ARCH=powerpc INSTALL_HDR_PATH=$TOOLS/$TARGET/usr headers_install
#安装后目录中的内容:
ls -p $TOOLS/$TARGET/usr/include
asm/ asm-generic/ drm/ linux/ mtd/ rdma/ scsi/ sound/ video/ xen/
3.2 交叉编译Binutils软件包
配置源码:
cd $SOURCES
tar jvxf binutils-2.22.tar.bz2
mkdir binutils-build
cd binutils-build
../binutils-2.22/configure \
--prefix=$TOOLS \
--target=$TARGET #生成的工具可以处理target平台的二进制代码,但是这些工具仍然运行在编译这些工具的平台上(编译平台类型由--build参数指定,但是一般不用指定,而是由源代码中的config.guess来猜测。编译后生成的工具的运行平台由--host参数指定,默认与--build值相同)。
编译并安装:
make install
mkdir $TOOLS/include
复制相关的头文件,以后如果要交叉编译gdb时会用到。
cp ../binutils-xxx/include/libiberty.h $TOOLS/include
安装完成后,$TOOLS目录如下:
ls -p $TOOLS
bin/ info/ lib/ man/ powerpc-linux/ share/
此处需要注意的是:$TOOLS/bin/ 和 $TOOLS/$TARGET/bin(TARGET就是powerpc-linux)的内容
ls -p $TOOLS/bin
powerpc-linux-addr2line powerpc-linux-c++filt powerpc-linux-ld powerpc-linux-objdump powerpc-linux-size
powerpc-linux-ar powerpc-linux-embedspu powerpc-linux-nm powerpc-linux-ranlib powerpc-linux-strings
powerpc-linux-as powerpc-linux-gprof powerpc-linux-objcopy powerpc-linux-readelf powerpc-linux-strip
ls -p $TOOLS/$TARGET/bin
ar as ld nm objcopy objdump ranlib strip
这些文件虽然在不同的目录,有不同的名字,但其实是一个文件:
md5sum $TOOLS/bin/powerpc-linux-as
3f77cbaaa417e2f59059114457d7d074 bin/powerpc-linux-as
md5sum $TOOLS/$TARGET/bin/as
3f77cbaaa417e2f59059114457d7d074 powerpc-linux/bin/as
除此之外,binutils把用于生成目标平台的代码的链接脚本安装到$TOOLS/$TARGET/ldscripts目录下。
3.3 交叉编译临时的GCC
由于编译gcc依赖 gmpmpfr这两个库,因此在编译gcc之前,首先安装这两个库到系统中(如果你的系统已经安装了这两个库,则下面的安装步骤可省略。).
tar jvxf gmp-5.0.2.tar.bz2
cd gmp-5.0.2
./configure --enable-cxx --enable-mpbsd --prefix=/usr
make check
sudo make install
如果没有指定--prefix=/usr,那么gmp默认安装到/usr/local目录下,而mpfr默认到/usr目录下搜索这个库。因此这里通过--prefix=/usr,把他们都安装到/usr下。见mpfr的FAQ:
http://www.mpfr.org/faq.html
cd $SOURCES
tar jvxf mpfr-3.1.0.tar.bz2
./configure --enable-thread-safe --prefix=/usr \
make check
sudo make install
现在第一次编译GCC:
cd $SOURCES
tar jvxf gcc-4.6.2.tar.bz2
mkdir gcc-bootstrap-build
cd gcc-bootstrap-build
../gcc-4.6.2/configure \
--target=$TARGET \ #编译后生成的gcc可以生成的目标代码的执行平台(即编译生成的gcc可以生成target对应的平台的代码)。
--prefix=$TOOLS --disable-nls --disable-shared \
--disable-multilib --disable-decimal-float --disable-threads --disable-libmudflap \
--disable-libssp --disable-libgomp --without-headers --with-newlib \
--enable-languages=c
这一步编译的是一个bootstarp类型的gcc(一个在当前主机上运行的、使用自带的newlib库的gcc,其实是为了解决gcc和glibc间的 “鸡、蛋问题”,:) 。),因此不会使用上一步生成的binutils工具使用的当前系统的binutils,但是当我们编译glibc 最后一次编译gcc时,需要使用上面生成的binutils。
这里的--with-package 的含义是drop newlib into the tree and do a combined build of both at once which breaks the circular dependency. 所以我们编译的gcc被成为bootstrap 即编译的是一个能引导整个编译环境的基本功能编译器。
编译安装gcc库:
make all-gcc
make all-target-libgcc
make install-gcc #把编译好的gcc分别安装到$TOOLS/bin/ 和 $TOOLS/$TARGET/bin
make install-target-libgcc #把gcc的库文件及头文件安装到$TOOLS/lib/$TARGET/$GCC_VERSION/目录中。这里$GCC_VERSION表示gcc版本号。
3.4 交叉编译Glibc
下面用刚才编译出的临时gcc交叉编译glibc,这个glibc可运行在目标平台的(由host参数指定)。
cd $SOURCES
tar jvxf glibc-2.14.tar.bz2
tar jvxf glibc-linuxthreads-2.5.tar.bz2 --directory=glibc-2.14
编译之前,修改glibc-2.9的Makeconfig,否则会出错。
vim glibc-2.14/Makeconfig
把下面两行:
gnulib := -lgcc $(libgcc_eh) #libgcc_eh是用于处理C++异常的代码
static-gnulib := -lgcc -lgcc_eh $(libunwind)
gnulib := -lgcc
static-gnulib := -lgcc
配置glibc:
mkdir glibc-build
cd glibc-build
CC=$TOOLS/bin/${TARGET}-gcc ../glibc-2.9/configure --prefix=/ \
--host=$TARGET \ #生成的软件的运行平台
--build=$(../glibc-2.9/scripts/config.guess) \ #编译该软件的平台类型,当host和build不同时,即为交叉编译: 在build对应的平台上编译可运行于host平台上的软件。
--with-headers=$TOOLS/$TARGET/usr/include/ \ #编译glibc时需要使用kernel的头文件,其中有关于powerpc相关的头文件
--with-binutils=$TOOLS/$TARGET/bin \ #注意,一定要是$TARGET下的bin目录,这里面的程序没有target-alias
--disable-profile libc_cv_forced_unwind=yes libc_cv_c_cleanup=yes
编译并安装:
make install_root=$TOOLS/$TARGET prefix="" install
用这种方法(--prefix=/usr 然后用install_root指定安装位置)编译安装的glibc可以直接拷贝到目标机上运行。
首先,CC=...,binutils=... 指定了用我们新编译好的gcc和binutils,因此得到的glibc是目标平台的。(CC在下一步应该unset)
最后,打开$TOOLS/$TARGET/lib/libc.so
把 GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a ) 改为:
GROUP ( libc.so.6 libc_nonshared.a )
3.5 交叉编译GCC
现在,库已经准备好了,编译一个全面的gcc
cd $SOURCES
mkdir gcc-full-build
cd gcc-full-build
../gcc-4.6.2/configure --target=$TARGET --host=$(../gcc-4.6.2/config.guess)
--prefix=$TOOLS
--with-headers=$TOOLS/$TARGET/usr/include/ #这是必需的,因为powerpc-linux-gcc 会到--with-headers指定的目录查找头文件。这个选项会将相关目录复制到$TOOLS/$TARGET/sys-include目录下。
--enable-languages=cc++ --disable-libgomp
--disable-multilib --disable-nls --enable-shared
这里没有指定--with-newlib,所以使用的是$TOOL/$TARGET下的glibc库,以及$TOOL/$TARGET/bin中的binutils工具。这里使用的是系统的gcc,而不是第一遍生成的powerpc-linux-gcc(因为最终的gcc是运行在本机上的。) 第一遍生成的gcc只用于编译glibc。
交叉编译gcc(powerpc-linux-gcc)被安装在$TOOL目录下,所以不能直接使用$TOOL/$TARGET/bin/gcc,因为交叉编译gcc所调用的一些辅助程序如cc1是放在$TOOL/libexec目录下的。
make all
make install
现在整个交叉编译环境就建立起来了,可以将$TOOL/bin目录放到PATH变量中,这样就可以使用powerpc-linux-gcc即目录中的其它工具为目标板编译任何程序了。
4. 总结
上面的几个步骤中只有编译glibc时是交叉编译过程,需要用到交叉编译器powerpc-linux-gcc和相应的带target alias的binutils。
交叉编译是指在本机上本次编译生成的代码运行在目标主机上,这个过程才叫交叉编译过程。上面的编译bintuils和gcc的过程都不是交叉编译过程,因为它们生成的程序是在当前主机上运行。非交叉编译过程使用的都是本机上的gcc和binutils。但是上面编译后获得的binutils和gcc是交叉编译环境中必不可少的组件(当然,还包括glibc)。一旦使用交叉编译环境中的gcc,则它会自动使用相应的binutils和glibc(因为它们都放在交叉编译环境的目录中$TOOL/$TARGET)。
现在,你就可以在X86平台上运行powerpc-linux-gcc,编译一个C程序,注意得到的目标程序是在 PowerPC平台上执行的。