添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
这个帖子主要目的是分享我制作交叉编译工具链的过程,它是在参考了网上大量的资料以及我的实践后,修改、整理而成的。之所以以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平台上执行的。