交叉编译踩坑指南
前言
毕业设计准备做一套ros的sdk。
既然要用ros,那像树莓派那样的arm开发板肯定是必不可少的。
既然要做sdk,那c++是必不可少的。
既然要用c++,那c++20是必不可少的。
需求分析下来,在树莓派上进行c++20开发是必不可少的。
树莓派的系统倒是更的挺勤,最新版本的发布时间是在2022年9月22日(本文编写时间2022年11月30日), 用的Linux内核5.15.76,glibc用的2.31,但是很可惜GCC工具链还停留在10.2.1。
那怎么办?
一般就两种方案:
- 在树莓派上编译gcc-12
- 使用gcc-12交叉编译工具链进行开发
考虑到树莓派那羸弱的armv7处理器,以及4Gb内存,还是果断选择了交叉编译。
最方便的解决方案
Ubuntu22.04上能够非常方便的通过apt获取arm-linux-gnueabihf-gcc-12
apt install之后开箱即用,非常方便。
编译一个Hello world程序,拷贝到树莓派上运行,报错glibc版本不匹配
小问题,只要我们静态链接就行
这是最简单、最方便的,为树莓派进行c++20开发的方式。
当然,这种方法也有它的局限性: 只能-static静态编译
这意味着:
- 你的程序不能链接任何动态库
2. libc极难静态链接,部分函数不能静态链接
3. 编译后体积极大
tips: gcc 有提供
-static-libgcc
和
-static-libstdc++
编译选项,但是,上面也说了,libc极难静态链接,很难单独将libc静态编译,而其它库动态链接。
综上,这个交叉编译工具链不能满足我的需求。
但如果它能满足你的需求,无脑用它,就没必要自己做工具链了(一般流行的发行版都有提供的)。
踩坑指南正式开始
首先要明确需求,我需要一套gcc工具链,它要:
- 运行在x86-linux平台上 (host=x86_64-linux-gnu)
- 编译目标运行在arm-linux平台上 (target=arm-linux-gnueabihf)
- 支持c++20
- glibc版本版本<=2.31
需求明确后,就可以开始做了
制作在x86主机下运行的arm交叉编译工具链_星空下的夜行的博客-CSDN博客_arm交叉编译x86
诶~我直接贴一篇教程给你,指南结束~~~~~
哈哈哈,开个玩笑
我基本就是照着这套教程做的,基本上没什么大问题。
本文主要目的也不是教你如何制作交叉编译,本文主要讲述,我按照这篇教程,以及结合其它资料,成功制作出交叉编译工具链后,回忆整个过程,发现整个过程中的一些坑,以及可以优化的地方。
我的主要观点是,网上大部分交叉编译教程都是叫你如何从0开始,在仅有一套普通的、低级的本地gcc编译器的情况下,制作出高级的、可跨平台的工具链。
基本流程如下
- 准备好目标linux内核头文件
- 编译binutils工具集
- 编译最基础的、丐版的gcc (不支持libc)
- 用丐版gcc编译glibc
- 编译功能完整的gcc
但是,很多时候我们并不需要从0开始啊 u(~ ̄▽ ̄)~
优化点一:编译binutils的必要性
binutils里的东西,挺好编译的,也没有什么依赖,一般都能顺利编译。一般包管理里面,交叉编译工具链的binutils是可以拿来复用的。
当然,考虑到binutils是容易编译的,按照教程来自己编译一遍,可能比自己一个一个在sysroot里创建软链接来得更方便、更靠谱。
优化点二:编译丐版gcc的必要性
这个,我觉得真没什么必要,因为编译丐版gcc的目的,就是为了编译glibc。
假设包管理器里的工具链可以编译出目标版本的glibc,甚至你直接有了这个glibc的二进制文件,(比如在其它机器上编译好的、从目标机器中拷贝过来的),甚至你不是在做交叉编译,你只是想升级一下gcc。
那这种情况下,丐版gcc就完全没有必要。
像我这种情况就更特殊了,要用高版本gcc搭配低版本glibc,而gcc-12还不能编译glibc2.31(各种语法错误),编译丐版gcc-12就是编译了个寂寞。为了编译这个glibc,我还专门下了个arm-linux-gnueabihf-gcc-9去编译,就顺利编译过了。
除非glibc需要用 你正在编译的编译器 才能编译出来,否则丐版gcc是没有必要的。
优化点三: 编译glibc的必要性
这个上面也说了,如果你能从其它地方,搞到编译好的glibc二进制文件,也是没有必要编译的。
优化点四:编译完整gcc的必要性
这个是必要的,没法优化。
在树莓派上进行c++20开发的其它方案:升级glibc
在制作交叉编译工具链时,由于glibc一时间编译不出来,所以中间转战过其它方案: 升级树莓派的glibc 。
这种方法虽然没那么优雅,但也是一个可选的方案
我选择从glibc2.35源码编译安装。
编译过程挺顺利的,无非是久了一点。
回想起来安装路径一定要注意,建议安装在用户自己新建的一个目录,不建议直接安装在根目录下,直接替换现有glibc。若编译出来的glibc不可用,你的系统就直接寄了。可能得借助另一台电脑恢复glibc
C库,属于操作系统的核心组件(我觉得?),其中,libc.so是C库的核心,C库的实现基本都在里面,是极其重要的文件,绝大部分程序都要链接它。
但是在libc.so前面,还有一个更重要的文件:
ld.so
这个文件是用来帮助程序加载动态库的, 包括libc.so
上图中,run程序需要动态链接libstc++,libc.so, libm.so , ld-linux-x86-64.so, libgcc_s.so
可以发现,除ld.so外,其它库都是只留一个名字,然后 => 会指向这些动态库的绝对路径。
找这些路径就是ld.so的工作。
那ld.so怎么找?ld.so就没法找,它只能在编译的时候填一个绝对路径,或者是由操作系统指定一个绝对路径。
所以个人感觉ld.so是比libc.so更加特殊、更加重要的。
我在树莓派上编译安装好glibc2.35后,把安装路径放在LD_LIBRARY_PATH的最前面,尝试运行一个程序,失败了。因为ld.so还是用的glibc2.31的版本,用老版本的ld.so,链接新版本的libc.so, 不兼容很正常。
要使用新版本的ld.so,很简单,只需要把ld.so当成可执行文件,把执行程序当成参数即可
基于ld.so的这种特性,使得不同版本的glibc可以在一台机子上共存,只要在启动的时候用ld.so启动即可。
但是这样有点麻烦,而且ld.so不会搜索path路径,执行的程序必须是个有效路径(比如ld.so ls是不行的,只能ld.so /bin/ls这样执行)
优雅一点的方式是在编译链接时指定ld的路径
虽然ldd分析程序依赖的ld.so还是原来的路径,但是程序已经可以正常运行啦~