【Raspberry Pi 400】搭建GCC-12.2.0交叉编译环境

前置知识

树莓派已经自带了GCC编译器,为什么还要多此一举搭建交叉编译环境?

  • 树莓派确实自带了GCC编译器,但是自带的GCC编译器版本不够新,导致一些C++新特性和新语法无法使用。(笔者写这边博文的时候,Raspbian的系统版本是 Linux raspberrypi 5.15.76-v71l+ , Raspbian自带的GCC版本是 10.2.1 ,而最新的GCC release版本已经到 12.2.0 了。)
  • 通俗来讲,树莓派的性能跟PC比还是较弱,而编译链接是个耗时耗力的工作,所以我们更希望编程的工作流如下:PC上编码->PC上编译链接->树莓派上运行,这就是交叉编译。交叉编译深度阅读可以参考 Why do I need a Cross Compiler? - OSDev Wiki

    各大Linux发行版的软件源里面有现成的gcc-arm-xxx-guneabi交叉编译工具,arm/linaro等公司也发布了免费的GCC二进制编译套件,直接安装不就行了吗,为什么还要从源码编译Binutils,Glibc和GCC?

    主要原因是各大Linux发行版的软件源里面的gcc-arm-xxx-guneabi编译套件中的glibc版本和树莓派系统自带的glibc的版本不一致,导致在PC上交叉编译出来的可执行文件拷贝到树莓派上后运行报错GLIBC的版本不匹配。

    root@raspberrypi:/home/jtang# ./helloworld
    ./helloworld: /lib/arm-linux-gnueabihf/libc.so.6: version `GLIBC_2.34' not found (required by ./helloworld)
    

    笔者写这边博文的时候,Raspbian自带的GLIBC版本是2.31,而目前GLIBC的最新版本已经是2.36。

    root@raspberrypi:~# ldd --version
    ldd (Debian GLIBC 2.31-13+rpt2+rpi1+deb11u5) 2.31
    

    由于GLIBC直接和Linux Kernal打交道,安全性非常重要,所以像arm/linaro等公司发布的二进制交叉编译套件中的GLIBC版本都是最新的,跟树莓派有严重的配套问题。

    我们要自己构建一套树莓派的交叉编译套件,宿主机是发行版Linux(笔者选用Ubuntu20.04.5 LTS),目标机是树莓派。
    任务细分如下:

  • 交叉编译套件的Binutils版本和树莓派系统自带的一致,为2.35.2
  • root@raspberrypi:~# ld -v
    GNU ld (GNU Binutils for Raspbian) 2.35.2
    
  • 交叉编译套件的GLIBC版本和树莓派系统自带的一致,为2.31
  • root@raspberrypi:~# ldd --version
    ldd (Debian GLIBC 2.31-13+rpt2+rpi1+deb11u5) 2.31
    这是一个自由软件;请见源代码的授权条款。本软件不含任何没有担保;甚至不保证适销性
    或者适合某些特殊目的。
    由 Roland McGrath 和 Ulrich Drepper 编写。
    
  • 交叉编译套件的GCC版本为当前最新的12.2.0
  • PC:Windows Linux 子系统 Ubuntu20.04.5 LTS
    只要是Linux发行版都可以

    root@T-T-Desktop:~# uname -a
    Linux T-T-Desktop 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
    硬件:Raspberry Pi 400
    OS: Raspberry Pi OS(32bit)

    后续步骤可以参考傻瓜教程Building GCC as a cross compiler for Raspberry Pi | Solarian Programmer,笔者补充一下几点可供参考:

  • 原文中安装的是GCC8.3和GCC-10, 我们需要安装的是GCC10.3和GCC12.2,之所以要装2个GCC,原文中的解释是GLIBC和GCC的构建相互依赖,编译GLIBC最好要用同时期发布的GCC,否则会有莫名其妙的编译问题,反之亦然。所以我们先安装GCC10.3和GLIBC2.31,然后再编译出GCC12.2,这样编译出来的GCC12.2就可以依赖GLIBC2.31了。

  • 国内下载Binutils, Glibc, Gcc源码可以从清华大学开源网站下载,没必要从gnu下载。
    wget https://mirrors.tuna.tsinghua.edu.cn/gnu/binutils/binutils-2.35.2.tar.gz
    wget https://mirrors.tuna.tsinghua.edu.cn/gnu/glibc/glibc-2.31.tar.gz
    wget https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-10.3.0/gcc-10.3.0.tar.gz
    wget https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-12.2.0/gcc-12.2.0.tar.gz

  • 下载树莓派的Linux内核代码,没必要用git clone,速度慢,而且github设置代理后在WSL上使用git clone有各种各样奇怪的问题,建议直接在github网站上下载zip包,然后在WSL上解压即可。

    【非常重要】 如果使用了Windows的Linux子系统作为宿主机环境,那么在开始Binutils, GCC和GLIBC构建之前,一定要设置文件名大小写敏感属性。因为Windows对文件名大小写不敏感,stamp.osT和stamp.oST被Windows识别为同一个文件,无法在同一个文件下共存;而Linux是大小写敏感的,所以GLIBC在编译过程中会生成很多仅仅是大小写不一致的多个文件,eg: stamp.osT和stamp.oST;如果不设置文件名大小写敏感属性,那么WSL上构建会报如下错误:
    mv: cannot stat '/mnt/d/JTang_Documents/01_c++/raspberry/gcc_all/build-glibc/dlfcn/stamp.oST': No such file or directory
    undefined reference to rtld_errno

    设置WSL对大小写敏感的方法如下:
    a. 首先在WSL下编辑/etc/wsl.conf文件,添加automount相关选项

    先执行vi /etc/wsl.conf
    然后添加如下内容
    [automount]
    enabled = true
    options = "metadata, mask=22,fmask=11,case=force"
    mountFsTab = false
    

    b. 回到Windows下打开命令行窗口,执行如下命令
    reg.exe add HKLM\SYSTEM\CurrentControlSet\Services\lxss /v DrvFsAllowForceCaseSensitivity /t REG_DWORD /d 1
    附:WSL下设置文件名大小写敏感属性,详情可以参考微软帮助Case Sensitivity | Microsoft Learn

    构建GCC之前,修改源码,添加PATH_MAX定义,否则可能会报错PATH_MAX未定义。(或者也可以先修改,等报错后再改)
    笔者这边构建GCC-10.3.0的sanitizer模块时报错了,于是添加PATH_MAX定义

    vi /mnt/d/JTang_Documents/01_c++/raspberry/gcc_all/gcc-10.3.0/libsanitizer/asan/asan_linux.cpp 找到使用PATH_MAX的地方,在其上方添加 #ifndef PATH_MAX #define PATH_MAX 4096 #endif