编译工具链制作-x86_64-linux-uclibc

从源码构建交叉编译工具链-实践!

1 简介

尝试制作编译工具链,编译工具链的所采用的软件包如下:

  • linux kernel : 2.6.32
  • gcc : 4.8.0
  • uclibc-ng : 1.0.11

主机平台为 ubuntu 18.04.3,目标平台为 x86_64-linux-uclibc,过程同制作交叉编译工具链。

2 背景知识

2.1 编译器

编译器通常由多个部分所构成,它们的最终目的是生成能在目标平台运行的字节码。而大多数的编译器则可以划分为以下4个部分。

  • 解析器(Parser),解析器将高级编程语言转换成汇编语言,例如将 C 语言程序转换为汇编语言。因此解析器必须了解目标平台的所支持的汇编命令。
  • 汇编器(Assembler),汇编器将汇编语言转换成能够被目标平台CPU所能运行的字节码。
  • 连接器(Linker),连接器能够将汇编器生成的多个对象文件,如 .o 文件,合并成一个可执行文件。由于不同的操作系统和 CPU 通常会使用不同的封装机制和标准,因此连接器需要知道目标平台的封装格式。
  • 标准C库(Standard C library),标准C库提供核心的 C 语言函数,例如 printf。

通常主机自己的编译器所产生的汇编代码、字节码和可执行程序的格式都遵守主机自身的平台标准。但对于交叉编译器,除了编译器自身可运行于主机平台,但其汇编语言标准、连接器格式、C库都遵守于目标平台。

2.2 GNU 编译器

GNU 编译器包含了 C 编译器,一组工具程序和 C 库。制作 GNU 编译工具链需要构建以下 3 个部分。

  • binutils:binutils 包含了一组基本的二进制实用程序,如汇编器(as),连接器(ld)和一些其他的工具如 size、strip等。这组工具包含了构建目标应用程序的核心组件以及目标平台的应用程序封装格式。例如 strip 应用程序可以从对象文件或目标应用程序中删除符号表、调试信息以及其他“无用”的信息。要完成这项工作 strip 命令就必须知道目标平台的相关格式,这样不会删除错误的信息。

    gcc:gcc是编译过程的主要的部分。gcc 包含了 C 预处理器(c preprocessor, cpp) 和翻译器,翻译器主要将 C 代码转换成目标平台的汇编代码,同时 gcc 也控制着个编译过程,负责有序的调用预处理器,翻译器,汇编器以及连接器。

  • glibc/newlib/uclibc:这些库都是标准 C 库的实现。uclibc 以及 newlib 适用于嵌入式操作系统。glibc 多用于桌面环境。

除了以上 3 个主要部分,还需要目标操作系统的一些头文件,如 linux。通过这些头文件可以访问操作系统相关的函数以及系统调用。

3 构建过程

3.1 设置构建环境以及环境变量

首先创建构建目录 CCT,并下载以下源码包至 CCT/TarFiles目录下。

  • binutils-2.26
  • gcc-4.8.0
  • gmp-4.3.2
  • mpc-1.0.2
  • mpfr-2.4.2
  • linux-2.6.32.27
  • uClibc-ng-1.0.11

然后,并解压源码包。目录结构如下所示。

1
2
3
4
5
6
7
8
9
10
CCT $ tree -L 1 ./
./
├── binutils-2.26
├── gcc-4.8.0
├── gmp-4.3.2
├── linux-2.6.32.27
├── mpc-1.0.2
├── mpfr-2.4.2
├── TarFiles
└── uClibc-ng-1.0.11

最后以下设置环境变量,安装位置为 /opt/cct。

1
2
3
4
5
export ARCH=x86
export TARGET=x86_64-linux-uclibc
export PREFIX=/opt/cct
export TARGET_PREFIX=$PREFIX/$TARGET
export PATH=$PREFIX/bin:$PATH

3.2 配置,编译和安装

第一遍构建 binutils

binutils 是第一个需要编译的分部,因为他所提供的汇编器、连接器等工具会在接下来编译的其他部分时所使用。使用如下命令进行软件包配置。

1
2
3
4
5
6
7
8
9
CCT $ cd binutils-2.26 && mkdir -p $TARGET && cd $TARGET
x86_64-linux-uclibc $ CC=gcc-4.8 CXX=g++-4.8 ../configure \
--prefix=$PREFIX \
--with-lib-path=$PREFIX/lib:$PREFIX/lib64 \
--disable-multilib \
--disable-werror \
--disable-nls \
--target=$TARGET
x86_64-linux-uclibc$ make && make install
  • —disable-multilib,由于只需要生成和编译64位的库程序,因此关闭 multilib 选项;
  • —disable-nls,禁止国际化;
  • —disable-werror,允许警告信息;
  • —target,目标平台为 x86_64-linux-uclibc;
  • —with-lib-path,指定该工具的库搜索路径;

编译完成安装后,运行如下命令进行检查标准连接器的所搜路径:

1
2
$ x86_64-linux-uclibc-ld --verbose | grep SEARCH
SEARCH_DIR("/opt/cct/x86_64-linux-uclibc/lib64"); SEARCH_DIR("/opt/cct/lib"); SEARCH_DIR("/opt/cct/lib64"); SEARCH_DIR("/opt/cct/x86_64-linux-uclibc/lib");

安装内核头文件

编译器通过内核头文件可以知道目标平台和系统支持哪些系统调用。对于 Linux 来说可以从 kernel 源码中获得。

1
2
CCT $ cd linux-2.6.32.27
linux-2.6.32.27 $ make ARCH=$ARCH INSTALL_HDR_PATH=$TARGET_PREFIX headers_install

gcc 第一次编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CCT $ cd gcc-4.8.0 
gcc-4.8.0 $ ln -s ../gmp-4.3.2 gmp
gcc-4.8.0 $ ln -s ../mpc-1.0.2 mpc
gcc-4.8.0 $ ln -s ../mpfr-2.4.2 mpfr
gcc-4.8.0 $ mkdir -p $TARGET && cd $TARGET
x86_64-linux-uclibc $ CC=gcc-4.8 CXX=g++-4.8 ../configure \
--prefix=$PREFIX \
--with-sysroot=$PREFIX \
--with-local-prefix=$PREFIX \
--with-native-system-header-dir=/$TARGET/include \
--enable-languages=c,c++ \
--enable-linker-build-id \
--disable-multilib \
--disable-werror \
--without-headers \
--with-newlib \
--disable-shared \
--disable-nls \
--target=$TARGET
x86_64-linux-uclibc $ make all-gcc
x86_64-linux-uclibc $ make install-gcc
  • gmp、mpc 以及 mpfr 为 gcc 的依赖包。
  • 由于 gcc 是完全自包含的,因此使用 —with-sysroot 指向欲安装目录可以使得主机的 gcc 不会搜索根目录下的任何文件。—with-local-prefix 的默认值为 /usr/local,这里也指向一个安装目录。—with-native-system-header 指向包含具体系统文件的目录路径,默认为 /usr,该路径在 —with-sysroot 中。
  • 由于没有 libc,通过 —without-headers,—with-newlib,—disable-shared 可以在编译 all-gcc,all-target-libgcc 时避免引用 libc。

安装 uclibc startfiles and headers

首先使用 make menuconfig 命令配置 uclibc,选中或填写以下信息,其他保持默认。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Target Architecture
--> x86_64
Target Architecture Features and Options
--> [*] Enable C99 Floating-point environment
--> Linux kernel header location (/opt/cct/x86_64-linux-uclibc/include)
General Library Settings
--> [*] Dynamic linker stand-alone mode support
--> Thread support (Native POSIX Threading(NPTL))
--> [*] Enable SuSv3 LEGACY functions
--> [*] Enable SuSv3 LEGACY macros
--> [*] Enable SuSv4 or obsolescent functions
--> [*] utmpx based support for tracking login/logouts to/from the system
--> [*] utmp support (XPG2 compat, SVr4 compat)
Networking Support
--> [*] IP version 6 support
--> [*] DNS resolver functions
--> [*] Provide libresolv stub
String and Stdio Support
--> [*] Wide Charater Support
Libray Installation Options
--> uClibc runtime library directory(/opt/cct/x86_64-linux-uclibc)
--> uClibc development envirment directory(/opt/cct/x86_64-linux-uclibc)
Development/debugging options
--> Cross-compiling toolchain prefix (x86_64-linux-uclibc-)
--> (-D__NR_setns -D__NR_syncfs) Extra CFLAGS

安装 startfiles 以及 headers

1
2
3
4
CCT $ cd uClibc-ng-1.0.11
uClibc-ng-1.0.11 $ make startfiles
uClibc-ng-1.0.11 $ PREIFX="" make install_startfiles
uClibc-ng-1.0.11 $ PREIFX="" make install_headers

编译 libgcc

1
2
3
CCT $ cd gcc-4.8.0/$TARGET
x86_64-linux-uclibc $ make all-target-libgcc
x86_64-linux-uclibc $ make install-target-libgcc

编译 uclibc

1
2
3
CCT $ cd uClibc-ng-1.0.11
uClibc-ng-1.0.11 $ make
uClibc-ng-1.0.11 $ PREIFX="" make install

完整编译 gcc

目前系统中存在一个基本的 gcc,这里需要关闭一些选项构建完成的 GCC,并编译所有目标。这里与 LFS 中第二次构建的不同在于,这里的第二次完整的 GCC 任然是运行于主机平台的而不是目标平台,所以依然是用 gcc-4.8 来编译,而不是用第一次编译好的那个基本的 gcc。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CCT $ cd gcc-4.8.0/$TARGET
x86_64-linux-uclibc $ CC=gcc-4.8 CXX=g++-4.8 ../configure \
--prefix=$PREFIX \
--with-sysroot=/ \
--with-local-prefix=$PREFIX \
--with-native-system-header-dir=$TARGET_PREFIX/include \
--enable-languages=c,c++ \
--enable-linker-build-id \
--disable-multilib \
--disable-werror \
--disable-nls \
--target=$TARGET
x86_64-linux-uclibc $ make all
x86_64-linux-uclibc $ make install

4 测试

测试代码 hello.c 的内容如下:

1
2
3
4
5
6
7
#include <stdio.h>

int main(int argc, char *argv[])
{
printf("Hello\n");
return 0;
}

编译命令如下:

1
$ x86_64-linux-uclibc-gcc hello.c -o hello

在主机环境上无法运行:

1
2
$ ./hello
bash: ./hello: No such file or directory

在目标环境上可以运行:
1
2
3
$ /opt/cct/x86_64-linux-uclibc/lib/ld64-uClibc-1.0.11.so --library-path /opt/cct/x86_64-linux-uclibc/lib:/opt/cct/x86_64-linux-uclibc/lib64 ./hello
Hello
$

5 参考资料

  1. Installing GCC: Configuration
  2. Linux From Scratch Version 7.7-systemd (简体中文版)
  3. Stack Overflow | MIPS GCC cross-compiler build fails: “cannot find -lc”