构建GCC作为Raspberry-Pi的交叉编译器
在本文中,展示如何构建GCC10作为Raspberry Pi的交叉编译器。交叉编译器是在一个操作系统上运行并为另一个操作系统生成可执行文件的编译器。当你想用你的健壮的计算机为树莓派构建一个库或其他大型代码段时,这是非常有用的。作为一个实际的例子,在本文的最后,我将向您展示如何使用交叉编译器将GCC本身构建为一个本地Raspberry Pi应用程序。
本文介绍的方法的一个优点是,您可以使用交叉编译器构建RPi Zero和可执行文件。有了Debian官方的crossbuild-essential-armhf,你只能构建Raspberry Pi 2及以上的二进制文件。
这篇文章的一部分是我从别人的帖子中学到的。以下是我使用过的一些资源:
• http://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/
• https://www.raspberrypi.org/documentation/linux/kernel/building.md
• https://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler%3F
• https://wiki.osdev.org/GCC_Cross-Compiler
• https://wiki.osdev.org/Building_GCC
• http://www.ifp.illinois.edu/~nakazato/tips/xgcc.html
从上面的列表中,第一篇文章是最完整的,如果您遵循它,您将得到一个部分工作的交叉编译器。公平地说,这篇文章不是为树莓派写的。我建议你阅读它,如果你想看到一个更深入的解释,某些步骤的过程。
为了构建和托管交叉编译器,我使用了Debian 10,但是在其他Linux发行版上也应该可以使用类似的过程。
首先,请确保系统已更新并安装所需的必备组件:
1 sudo apt update
2 sudo apt upgrade
3 sudo apt install build-essential gawk git texinfo bison file wget
Raspbian附带了GCC 8.3.0、Binutils 2.31和Glibc 2.28。使用与Raspbian版本相同的Glibc版本构建交叉编译器非常重要。这将使我们能够很好地与操作系统集成。如果您来自未来并阅读了本文,可以使用以下命令检查上述软件的版本:
1 gcc --version
2 ld -v
3 ldd --version
这是我在树莓派上看到的:
1 pi@raspberrypi:~ $ gcc --version
2 gcc (Raspbian 8.3.0-6+rpi1) 8.3.0
3 Copyright (C) 2018 Free Software Foundation, Inc.
4 This is free software; see the source for copying conditions. There is NO
5 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
6
7 pi@raspberrypi:~ $ ld -v
8 GNU ld (GNU Binutils for Raspbian) 2.31.1
9 pi@raspberrypi:~ $ ldd --version
10 ldd (Debian GLIBC 2.28-10+rpi1) 2.28
11 Copyright (C) 2018 Free Software Foundation, Inc.
12 This is free software; see the source for copying conditions. There is NO
13 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 Written by Roland McGrath and Ulrich Drepper.
15 pi@raspberrypi:~ $
如果您试图使用更现代的GCC构建glibc2.28,比如9.x或10.x,那么您将得到很多错误。最简单的方法是使用GCC 8.3.0构建Glibc,并在最新、最好的GCC(现在是10.1)中使用它。
在下一个说明中,我假设您正在单独的文件夹中执行所有步骤,并且在完成所有操作之前,您将保持相同的终端会话打开。例如,可以在家中创建工作文件夹:
1 cd ~
2 mkdir gcc_all && cd gcc_all
让我们下载用于构建交叉编译器的软件:
1 wget https://ftpmirror.gnu.org/binutils/binutils-2.31.tar.bz2
2 wget https://ftpmirror.gnu.org/glibc/glibc-2.28.tar.bz2
3 wget https://ftpmirror.gnu.org/gcc/gcc-8.3.0/gcc-8.3.0.tar.gz
4 wget https://ftpmirror.gnu.org/gcc/gcc-10.1.0/gcc-10.1.0.tar.gz
5 git clone --depth=1 https://github.com/raspberrypi/linux
接下来,提取归档文件并将其删除:
1 tar xf binutils-2.31.tar.bz2
2 tar xf glibc-2.28.tar.bz2
3 tar xf gcc-8.3.0.tar.gz
4 tar xf gcc-10.1.0.tar.gz
5 rm *.tar.*
GCC也需要一些先决条件,我们可以在源文件夹中下载:
1 cd gcc-8.3.0
2 contrib/download_prerequisites
3 rm *.tar.*
4 cd ..
5 cd gcc-10.1.0
6 contrib/download_prerequisites
7 rm *.tar.*
8 cd ..
接下来,创建一个文件夹,放置交叉编译器,并将其添加到路径中:
1 cd ~/gcc_all
2 sudo mkdir -p /opt/cross-pi-gcc
3 sudo chown $USER /opt/cross-pi-gcc
4 export PATH=/opt/cross-pi-gcc/bin:$PATH
复制上面文件夹中的内核头文件,请参阅Raspbian文档了解更多关于内核的信息:
1 cd ~/gcc_all
2 cd linux
3 KERNEL=kernel7
4 make ARCH=arm INSTALL_HDR_PATH=/opt/cross-pi-gcc/arm-linux-gnueabihf headers_install
接下来,让我们构建Binutils:
1 cd ~/gcc_all
2 mkdir build-binutils && cd build-binutils
3 ../binutils-2.31/configure --prefix=/opt/cross-pi-gcc --target=arm-linux-gnueabihf --with-arch=armv6 --with-fpu=vfp --with-float=hard --disable-multilib
4 make -j 8
5 make install
GCC和Glibc是相互依赖的,你不可能完全构建一个而没有另一个,所以我们要做GCC的部分构建,Glibc的部分构建,最后构建GCC和Glibc。你可以在Preshing的文章中了解更多。
1 cd ~/gcc_all
2 mkdir build-gcc && cd build-gcc
3 ../gcc-8.3.0/configure --prefix=/opt/cross-pi-gcc --target=arm-linux-gnueabihf --enable-languages=c,c++,fortran --with-arch=armv6 --with-fpu=vfp --with-float=hard --disable-multilib
4 make -j8 all-gcc
5 make install-gcc
现在,让我们部分构建Glibc:
1 cd ~/gcc_all
2 mkdir build-glibc && cd build-glibc
3 ../glibc-2.28/configure --prefix=/opt/cross-pi-gcc/arm-linux-gnueabihf --build=$MACHTYPE --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf --with-arch=armv6 --with-fpu=vfp --with-float=hard --with-headers=/opt/cross-pi-gcc/arm-linux-gnueabihf/include --disable-multilib libc_cv_forced_unwind=yes
4 make install-bootstrap-headers=yes install-headers
5 make -j8 csu/subdir_lib
6 install csu/crt1.o csu/crti.o csu/crtn.o /opt/cross-pi-gcc/arm-linux-gnueabihf/lib
7 arm-linux-gnueabihf-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o /opt/cross-pi-gcc/arm-linux-gnueabihf/lib/libc.so
8 touch /opt/cross-pi-gcc/arm-linux-gnueabihf/include/gnu/stubs.h
回到GCC:
1 cd ..
2 cd build-gcc
3 make -j8 all-target-libgcc
4 make install-target-libgcc
完成Glibc:
1 cd ..
2 cd build-glibc
3 make -j8
4 make install
完成GCC 8.3.0的构建:
1 cd ..
2 cd build-gcc
3 make -j8
4 make install
5 cd ..
可选地,写一个小的C或c++测试程序,你可以构建代码:
1 arm-linux-gnueabihf-g++ test.cpp -o test
上面的可执行文件test是用我们的第一个交叉编译器构建的,将在您的Pi上运行。您可以使用file命令检查:
1 $ file test
2 test: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
更好的测试是将上面创建的二进制文件传输到RPi,看看能否运行它。
现在,您已经在GCC 8.3.0中拥有了一个完整的交叉编译器工具链。在进行下一步之前,做一个备份,以防出错:
1 sudo cp -r /opt/cross-pi-gcc /opt/cross-pi-gcc-8.3.0
1 cd ~/gcc_all
编辑gcc-10.1.0/libsanitizer/asan/asan_linux.cpp。如果没有定义PATH_MAX,则需要定义它。在大约第66行之后添加下一段代码:
1 #ifndef PATH_MAX
2 #define PATH_MAX 4096
3 #endif
保存并关闭文件。
接下来,我们将使用上面构建的Glibc来构建一个更现代的交叉编译器,它将覆盖GCC 8.3:
1 cd ~/gcc_all
2 mkdir build-gcc10 && cd build-gcc10
3 ../gcc-10.1.0/configure --prefix=/opt/cross-pi-gcc --target=arm-linux-gnueabihf --enable-languages=c,c++,fortran --with-arch=armv6 --with-fpu=vfp --with-float=hard --disable-multilib
4 make -j8
5 make install
现在,您可以使用GCC 10.1为您的Raspberry Pi交叉编译任何C、c++或Fortran代码。你可以通过使用前缀来调用任何交叉编译器:
1 arm-linux-gnueabihf-
examples: arm-linux-gnueabihf-gcc, arm-linux-gnueabihf-g++, arm-linux-gnueabihf-gfortran.
为了对交叉编译器进行压力测试,让我们使用它来对Pi进行交叉编译:
1 sudo mkdir -p /opt/gcc-10.1.0
2 sudo chown $USER /opt/gcc-10.1.0
3
4 cd ~/gcc_all
5 mkdir build-native-gcc10 && cd build-native-gcc10
6 ../gcc-10.1.0/configure --prefix=/opt/gcc-10.1.0 --build=$MACHTYPE --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf --enable-languages=c,c++,fortran --with-arch=armv6 --with-fpu=vfp --with-float=hard --disable-multilib --program-suffix=-10.1
7 make -j 8
8 make install-strip
您应该在 /opt/gcc-10.1.0文件夹中得到一个本机ARM GCC。
顺便说一下,在Debian 10 x86-64机器上,使用上面的交叉编译器构建GCC 10.1需要大约30分钟。与我过去在Pi 3上直接构建GCC 8.1的5个小时相比,你会发现在你的主机器上有一个交叉编译器的优势。有人告诉我在Raspberry Pi Zero上编译GCC 7需要5天!
如果你想永久地将交叉编译器添加到你的路径中,可以使用以下方法:
1 echo 'export PATH=/opt/cross-pi-gcc/bin:$PATH' >> ~/.bashrc
2 source ~/.bashrc
现在,您可以选择安全地删除生成文件夹。假设您遵循了我的建议,请在主文件夹中使用下一个命令:
1 cd ~
2 rm -rf gcc_all
让我们把本机GCC ARM编译器存档,并保存到我们的主文件夹:
1 cd /opt
2 tar -cjvf ~/gcc-10.1.0-armhf-raspbian.tar.bz2 gcc-10.1.0
3 cd ~
将gcc-10.1.0-armhf-raspbian.tar.bz2复制到您的RPi。对于本文的其余部分,我将假设您在您的RPi上,并且上面的存档位于您的主文件夹中:
1 cd ~
2 tar xvf gcc-10.1.0-armhf-raspbian.tar.bz2
3 rm gcc-10.1.0-armhf-raspbian.tar.bz2
4 sudo mv gcc-10.1.0 /opt
接下来,我们将把新的编译器添加到路径中,并创建一些符号链接:
1 echo 'export PATH=/opt/gcc-10.1.0/bin:$PATH' >> ~/.bashrc
2 echo 'export LD_LIBRARY_PATH=/opt/gcc-10.1.0/lib:$LD_LIBRARY_PATH' >> ~/.bashrc
3 source ~/.bashrc
4 sudo ln -s /usr/include/arm-linux-gnueabihf/sys /usr/include/sys
5 sudo ln -s /usr/include/arm-linux-gnueabihf/bits /usr/include/bits
6 sudo ln -s /usr/include/arm-linux-gnueabihf/gnu /usr/include/gnu
7 sudo ln -s /usr/include/arm-linux-gnueabihf/asm /usr/include/asm
8 sudo ln -s /usr/lib/arm-linux-gnueabihf/crti.o /usr/lib/crti.o
9 sudo ln -s /usr/lib/arm-linux-gnueabihf/crt1.o /usr/lib/crt1.o
10 sudo ln -s /usr/lib/arm-linux-gnueabihf/crtn.o /usr/lib/crtn.o
此时,您应该能够使用gcc-10.1、g+±10.1或gfortran-10.1调用编译器。
让我们试着编译并运行一个c++ 17代码,它使用了带有init-statement的if块(这个例子有点傻,但它会告诉你如何编译c++ 17程序):
1 #include
2
3 int main() {
4 // if block with init-statement:
5 if(int a = 5; a < 8) {
6 std::cout << "Local variable a is < 8\n";
7 } else {
8 std::cout << "Local variable a is >= 8\n";
9 }
10 return 0;
11 }
将上述代码保存在一个名为if_test.cpp的文件中,并使用它进行编译
1 g++-10.1 -std=c++17 -Wall -pedantic if_test.cpp -o if_test
这是我在RPi上看到的:
1 pi@raspberrypi:~ $ g++-10.1 -std=c++17 -Wall -pedantic if_test.cpp -o if_test
2 pi@raspberrypi:~ $ ./if_test
3 Local variable a is < 8
4 pi@raspberrypi:~ $
让我们尝试使用c++ 17文件系统:
1 #include
2 #include
3
4 int main() {
5 for(auto &file : std::filesystem::recursive_directory_iterator("./")) {
6 std::cout << file.path() << '\n';
7 }
8 }
将上述代码保存在一个名为fs_test.cpp的文件中,并使用以下命令编译:
1 g++-10.1 -std=c++17 -Wall -pedantic fs_test.cpp -o fs_test
这是我在RPi上看到的(如果您有很多文件,请不要在主文件夹中运行下一个代码,因为它将递归地打印所有文件,例如,您可以将其移动到文件数较少的文件夹中):
1 pi@raspberrypi:~ $ g++-10.1 -std=c++17 -Wall -pedantic fs_test.cpp -o fs_test
2 pi@raspberrypi:~ $ ./fs_test
3 "./.nano"
4 "./.profile"
5 "./.bash_logout"
6 "./fs_test.cpp"
7 "./.bashrc"
8 "./if_test.cpp"
9 "./if_test"
10 "./fs_test"
11 "./.bash_history"
12 pi@raspberrypi:~ $
如果你想了解更多关于树莓派编程的知识,一本非常好的书是《探索树莓派》,作者是Derek Molloy:
如果你想了解更多关于现代c++的知识,我推荐Bjarne Stroustroup的《c++之旅》: