Ubuntu 9.10上建立ARM-Linux交叉编译环境

这篇文章是我在2010年学习嵌入式的时候,自己动手建立交叉编译环境的一片文章。那时候获取信息没有那么丰富,也没有像现在这么多成熟的已经建立好的交叉编译环境,所以动手自己做的时候着实废了一番功夫。写完这篇文章后我就把它丢到了新浪博客上,再也没有看过它。直到最近整理博客才察觉了他的存在,现在整理一下发到目前的博客上。

文章上的东西离现在的技术已经有些过时,但是其中的方法还是值得看一下的,以此来纪念一下在大学里曾经奋斗过的日子!

序言

学习ARM嵌入式的人都知道,要想使PC上的程序运行在ARM板,必须建立交叉编译环境来对源代码进行编译。对于交叉编译环境的基本概念这里就不多说了,网上多得是。这里主要是写我在建立交叉编译环境时的方法步骤和一些想法。

我的建立过程经历了疯狂的四天。在这四天里,我除了睡觉,吃饭,上厕所,几乎所有的时间都坐在电脑面前,只是为了最后那100多MB的文件。我终于体会到了在面对复杂的问题时的那种无助的心情。也终于知道了想当一名自由软件支持者所要具备的能力与心态(那就是怎样在困难与错误面前寻找解决办法)。

学习嵌入式Linux的道路是辛苦的,尤其是DIY。为了建立一个交叉编译的环境,我便花费了4天的努力才得到结果。就那100多MB的文件,看起来没什么,可是时间成本是巨大的。这几天时间什么都没做,课也全逃掉了,有一位同学甚至还发短信问我是不是消失了?_?嘿嘿,我当然没消失,只是躲在寝室里在电脑上编译那几个文件。这就是自由软件的麻烦之处,文件之间关系依赖很复杂繁琐,初学者根本不知道其文件见的联系是怎么样的,按着网上的步骤出现了错误也不知原因。编译中总是有那些莫名奇怪的错误,有了错误怎么办,上网查,看README文档,看标准,上各个BBS上去问,国内的网站没有,还得去国外网站上去查汗。有的错误还好,是常见的,有解决方案色;有的错误有成百上千种原因,我不可能一一去试晕;甚至有两次我还编译出了连网上都不存在的错误,我更是晕了惊恐。有时候编译总是不过,我便把全部文件删除然后重新来做,这样反反复复不知有多少次吐。怪不得有人说做交叉编译环境是要靠运气的。

不过还好,我在国外的一个网站中找到一个版本较为新的建立步骤,而且通过率很高,没什么大的错误。下面便进入正题,把我的建立过程写下来。

准备文件

我的电脑是装的Ubuntu 9.10 Linux,不是虚拟机。想建立交叉编译环境,首先你的电脑中得有编译环境,就是那些常见的,例如GCC等。这个我就不写了,网上也是多得是,太常见了。

然后,我们要下载建立所需要的文件。这是列表:

源代码文件及其版本与下载地址:

补丁包版本及其下载地址:

注:1. 对于版本的要求:刚开始时很多人都会选用各个源代码最新的版本,其实没必要,也不好。其一,你的板子不一定支持这么新的源代码;其二,因为新的版本中会加入新的东西,当你按着原先的方法进行编译时会出错误,而你又不知道错误在哪,这是很麻烦的,我就吃了这方面的亏,所以当你是新人时,尽量选择网上成功例子的版本,这样会出错的概率会小一些。当然,如说你是高手或者对Linux下各个源代码的编译过程有所了解,那就例外了。2. 打补丁的方法:打补丁的方法很简单,首先进入你想要打补丁的文件夹,然后执行patch -p1 < path\to\your patch document(就是你的补丁文件的路径,还要注意“<”前后是有空格的),例如:

1
2
$cd binutils-2.19.1
$patch -p1 < /home/Yourname/binutils-2.19.1-branch_update-1.patch>

如果你想详细了解请看:http://www.diybl.com/course/6_system/linux/Linuxjs/2008826/137710.html

然后我们开始要建立文件目录:

1
2
3
4
5
/home/Yourname/ARM/--Build /*存放源代码的目录/--binutils/*二进制文件
| | --gcc/*编译器
| | --glibc/*C库文件
|--Linux-Kernel /*存放内核的目录
|--Cross-Compile /*工具安装目录

建立好了目录之后,将各个源代码文件放入对应的文件夹中,这时../Build/binutils中应该有

  • binutils-2.19.1.tar.bz2
  • binutils-2.19.1-branch_update-1.patch
  • binutils-2.19.1-posix-1.patch

/Build/gcc中应该有

  • gcc-4.3.3.tar.bz2
  • gmp-4.2.4.tar.bz2
  • mpfr-2.4.1.tar.bz2
  • gcc-4.3.3-branch_update-5.patch
  • gcc-4.3.3-posix-1.patch
  • mpfr-2.4.1-branch_update-1.patch
  • mpfr-2.4.1-branch_update-2.patch

Build/glibc中应该有

  • glibc-2.11.tar.bz2
  • glibc-ports-2.11.tar.bz2
  • gcc_eh.patch.cross

Linux-Kernel中应该有

  • linux-2.6.30.1.tar.bz2

紧接着,解压缩各个文件,对应打好补丁。然后,将gmp-4.2.4改名gmpmpfr-2.4.1改名mpfr放入gcc-4.3.3文件夹中。将glibc-ports-2.11改名为ports放入glibc-2.11中。

对于Ubuntu的用户,有一工具叫做mawk,我不知道是干什么用的,但是对于建立交叉编译环境是不利的,要换成gawk,具体方法可以google。

到目前为止,文件准备工作已全部完毕。

建立二进制工具(binutils)

建立环境变量:
$vim ~/.bashrc在最先面加入如下的代码:

1
2
3
4
5
export PRJROOT=/home/Yourname/ARM
export PREFIX=$PRJROOT/Cross-Compile
export TARGET=arm-linux
export PATH=$PREFIX/tools/bin:$PATH
export host=i486-cross-linux-gnu

然后在终端执行

1
2
3
4
5
6
7
8
9
10
$cd /home/Youname/ARM/Build/binutils
$mkdir b
$cd b
$AR=ar AS=as ../binutils-2.19.1/configure \
--build=i486-cross-linux-gnu \
--host=i486-cross-linux-gnu \
--target=$TARGET \
--prefix=$PREFIX/tools \
--with-sysroot=$PREFIX \
--disable-nls --enable-shared --disable-multilib --disable-werror

注意:“\”不能省且前有空格

1
2
$make all
$make install

看一下$PREFIX/tools/bin下的生成的文件都是用来干什么的

  • add2line - 将你要找的地址转成文件和行号,它要使用 debug 信息。
  • Ar-产生、修改和解开一个存档文件
  • As-gnu 的汇编器C++filt-C++ 和 java 中有一种重载函数,所用的重载函数最后会被编译转化成汇编的标号,c++filt 就是实现这种反向的转化,根据标号得到函数名。
  • Gasp-gnu 汇编器预编译器。
  • Ld-gnu 的连接器Nm-列出目标文件的符号和对应的地址Objcopy-将某种格式的目标文件转化成另外格式的目标文件Objdump-显示目标文件的信息
  • Ranlib-为一个存档文件产生一个索引,并将这个索引存入存档文件中
  • Readelf-显示 elf 格式的目标文件的信息
  • Size-显示目标文件各个节的大小和目标文件的大小
  • Strings-打印出目标文件中可以打印的字符串,有个默认的长度,为4
  • Strip-剥掉目标文件的所有的符号信息

建立内核头文件(Linux Kernel)

1
2
3
4
5
6
7
$cd $PRJROOT/Linux-Kernel/linux-2.6.30.1
$mkdir -p $PREFIX/usr/include
$make mrproper
$make ARCH=arm headers_check
$make ARCH=arm INSTALL_HDR_PATH=dest headers_install
$cp -rv dest/include/* $PREFIX/usr/include
$find $PREFIX/usr/include -name .install -or -name ..install.cmd | xargs rm -fv

建立初始编译器(bootstrap gcc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$cd $PRJROOT/Build/gcc
$mkdir g
$cd g
$AR=ar LDFLAGS="-Wl,-rpath,$PREFIX/lib" \
/home/polaris/ARM/build/gcc/gcc-4.3.3/configure \
--build=i486-cross-linux-gnu \
--host=i486-cross-linux-gnu \
--target=arm-linux \
--prefix=$PREFIX/tools \
--enable-languages=c \
--disable-nls --disable-shared --disable-threads \
--disable-libmudflap --disable-libssp --disable-libgomp \
--disable-decimal-float --without-headers --with-newlib \
--with-sysroot=$PREFIX
$make
$make install

我们来看看$PREFIX/bin里面多了哪些东西,你会发现多了arm-linux-gccarm-linux-unprotoizecppgcov几个文件。

  • Gcc-gnu 的 C 语言编译器
  • Unprotoize-将 ANSI C 的源码转化为 K&R C 的形式,去掉函数原型中的参数类型。
  • Cpp-gnu的 C 的预编译器
  • Gcov-gcc 的辅助测试工具,可以用它来分析和优程序。

建立 c 库(glibc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$cd $PRJROOT/Build/glibc
$mkdir g
$cd g
$echo "libc_cv_forced_unwind=yes" > config.cache \
echo "libc_cv_c_cleanup=yes" >> config.cache \
echo "libc_cv_arm_tls=yes" >> config.cache
$BUILD_CC="gcc" CC=arm-linux-gcc AR=arm-linux-ar RANLIB=arm-linux-ranlib
$$PRJROOT/build/glibc/glibc-2.11/configure \
--build=i486-cross-linux-gnu \
--host=arm-linux \
--target=arm-linux \
--prefix=$PREFIX/usr \
--with-tls --disable-profile --enable-add-ons \
--enable-kernel=2.6.0 --with-__thread \
--with-binutils=$PREFIX/tools/bin \
--with-headers=$PREFIX/usr/include \
--cache-file=config.cache
$make all
$make install

然后你还要修改libc.so文件,将GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a)改为
GROUP ( libc.so.6 libc_nonshared.a)这样连接程序ld就会在libc.so所在的目录查找它需要的库,因为你的机子的/lib目录可能已经装了一个相同名字的库,一个为编译可以在你的宿主机上运行的程序的库,而不是用于交叉编译的。

建立全套编译器(full gcc)

在建立boot-gcc 的时候,我们只支持了C。到这里,我们就要建立全套编译器,来支持C和C++。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$cd $PRJROOT/Build/gcc
$mkdir g1
$cd g1
$/home/polaris/ARM/build/gcc/gcc-4.3.3/configure \
--build=i486-cross-linux-gnu \
--host=i486-cross-linux-gnu \
--target=arm-linux \
--prefix=$PREFIX/tools \
--enable-languages=c,c++ --enable-c99 \
--enable-threads=posix --enable-long-long --enable-shared \
--enable-__cxa_atexit --disable-multilib --disable-nls --disable-libgomp \
--with-sysroot=$PREFIX
$make
$make install

我们再来看看$PREFIX/bin里面多了哪些东西。你会发现多了arm-linux-g++arm-linux-protoizearm-linux-c++几个文件。

  • G++-gnu的 c++ 编译器。
  • Protoize-与Unprotoize相反,将K&R C的源码转化为ANSI C的形式,函数原型中加入参数类型。
  • C++-gnu 的 c++ 编译器。

至此,整个交叉编译环境就建立完成了。

测试(Test)

写一个Hello World程序来测试你的交叉编译环境
$vim hello.c

1
2
3
4
5
6
#include<stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}

1
2
3
$arm-linux-gcc hello.c -o hello
$file hello
hello: ELF 32-bit LSB executable, ARM, version 1, dynamically linked (uses shared libs), not stripped

上面的输出说明你编译了一个能在 arm 体系结构下运行的 helloworld,证明你的编译工具做成功了!

后序

以上是我的建立过程方法,因为每个人手中的电脑配置不同且Linux内部环境设置也不一样,按此步骤建立不一定能够正确建立。因此本文只作为参考,在建立过程中要多用网络来寻找解决问题的方法,这样才能建立一个完整的交叉编译环境。如若本文有错误,敬请原谅!

参考资料

  1. 自己实验建立交叉编译工具链
  2. 如何为嵌入式开发建立交叉编译环境