ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ARM架构与交叉编译实战:从指令集、ABI到Qt嵌入式部署

2026/9/12 16:55:01 拓冰建站 浏览量
ARM架构与交叉编译实战:从指令集、ABI到Qt嵌入式部署 1. 项目概述为什么今天还在啃ARM架构和交叉编译这根“硬骨头”你打开终端敲下arm-linux-gnueabihf-gcc --version屏幕上跳出一串带arm字样的版本号心里却没底——这到底是在给谁编译是树莓派4B还是某款国产工控板的RK3399抑或是刚拿到手还没拆封的ARMv9服务器芯片别急这不是玄学这是嵌入式开发绕不开的底层现实我们写的C代码从来不是直接在目标设备上跑起来的它先在x86笔记本里被“翻译”成ARM指令再打包烧进那块只有256MB RAM、没键盘没显示器的板子上。这个“翻译”过程就叫交叉编译而翻译所依据的那套语法规则手册就是ARM架构本身。我带过三届嵌入式方向的毕设学生90%的人第一次把Qt程序交叉编译到ARM板上时都会卡在同一个地方libQt5Core.so: cannot open shared object file: No such file or directory。不是代码写错了是根本没搞懂——交叉编译不是换个gcc命令就行它是整套工具链、头文件路径、库链接顺序、ABI约定、甚至浮点运算模式soft-float vs hard-float的系统性对齐。ARM架构也不是一个静态名词它像一棵不断分叉的树ARMv7-ACortex-A9/A15、ARMv8-ACortex-A53/A72、ARMv9-ACortex-X4/A715每一代都新增SVE2向量扩展、内存标签扩展MTE、指针认证PAC而你的交叉编译器是否支持这些特性直接决定你能不能用上最新的AI加速指令。更现实的问题是你手头的Ubuntu 20.04虚拟机里装的是gcc-arm-linux-gnueabihf但板子厂商SDK文档里明确要求用arm-none-eabi-gcc或者你下载了ARM Compiler 5.06u7却发现它根本不认C17的std::optional——因为AC5是基于C03标准构建的。这些不是配置错误是架构代际、工具链定位、ABI规范三者错位导致的必然结果。本文不讲虚的“ARM有多重要”而是带你亲手拆开arm-linux-gnueabihf-gcc这个命令背后的齿轮组从指令集编码规则、ELF文件段布局到sysroot目录结构、动态链接器ld-linux-armhf.so.3的加载路径再到Qt5.12.10交叉编译时如何绕过OpenSSL的符号冲突。所有内容均来自我过去八年在工业网关、车载T-Box、边缘AI盒子项目中的实操记录每一个参数、每一行报错、每一个--sysroot路径都对应着一块真实焊在PCB上的ARM芯片。2. ARM架构核心解构指令集、异常模型与内存管理不是概念是寄存器里的比特位2.1 指令集架构ISA的本质不是语法书是CPU硬件电路的控制协议很多人把ARM指令集当成汇编语言教材去背这是致命误区。ARM ISA真正的意义在于它定义了CPU内部寄存器、ALU、乘法器、流水线控制单元之间数据流动的电气时序和逻辑约束。举个最直观的例子ARMv7-A的LDR R0, [R1, #4]!指令表面看是“从R14地址读取数据到R0并更新R1R14”但硬件执行时它触发的是以下物理动作地址生成单元AGU将R1值左移2位因ARM是字对齐#4代表4字节16位偏移与立即数4相加输出28位地址总线信号内存控制器根据该地址发出读请求等待DRAM刷新周期tRP和行激活延迟tRCD数据总线返回32位数据同时ALU将R1值加4并写回R1寄存器堆流水线第5级Write Back将数据写入R0。提示如果你在调试时发现LDR指令耗时异常长比如超过200ns不要先怀疑代码先查板载DDR的CLCAS Latency参数是否与SoC内存控制器配置匹配。我曾在一个RK3288项目中因CL值设为7而实际DDR是CL9导致所有内存读取指令多等1个时钟周期整个GUI帧率掉30%。ARM指令集分为三类其设计哲学直接反映在工具链选择上A32ARM指令32位固定长度支持条件执行每个指令前缀4位条件码适合需要高确定性的实时控制如电机PID算法。GCC默认用-marm启用。T32Thumb-2指令混合16/32位长度代码密度比A32高30%但部分复杂操作如长跳转需两条指令。嵌入式Linux内核默认用-mthumb因其节省Flash空间。A64ARM64指令64位纯固定长度取消条件执行引入新的寄存器命名X0-X30代替R0-R15且强制使用64位地址空间。aarch64-linux-gnu-gcc专为此设计不兼容ARMv7。关键参数计算为什么arm-linux-gnueabihf-gcc中的eabihf如此重要eabi Embedded Application Binary Interface定义函数调用时寄存器使用规则R0-R3传参R4-R11保存R12为IPR13为SPR14为LRR15为PChf Hard Float表示浮点运算由VFP/NEON硬件单元完成函数返回浮点值用S0/S1寄存器而非通过内存栈传递。若误用arm-linux-gnueabi-gccsoft-float编译即使代码能链接成功运行时也会因浮点寄存器未初始化而崩溃。2.2 异常模型从复位向量到中断处理全是内存地址映射的游戏ARM芯片上电后第一行执行的代码不是main()而是位于物理地址0x00000000或0xFFFF0000取决于VBAR寄存器设置的复位向量。这个地址必须指向一段能初始化栈指针SP、关闭看门狗、配置时钟的裸机代码。很多初学者以为交叉编译只管C代码却忽略了启动代码startup code才是连接架构与工具链的真正桥梁。ARMv7-A异常向量表Exception Vector Table结构如下每个向量占4字节偏移异常类型典型用途工具链关联0x00Reset跳转到_start初始化SPcrt0.o中定义0x04Undefined Instruction调试断点陷阱GDB远程调试依赖0x08Software Interrupt (SWI)系统调用入口libc的syscall实现0x0CPrefetch Abort指令预取失败如Flash损坏Bootloader校验逻辑0x10Data Abort数据访问异常如空指针解引用sigsegv信号处理基础0x14IRQ外设中断UART、GPIOLinux内核irq_desc注册0x18FIQ快速中断DMA传输实时音频处理专用注意Linux内核启动时会重映射向量表到0xFFFF0000并通过set_irq_handler()动态注册中断服务程序ISR。但裸机程序如U-Boot必须严格按向量表地址放置跳转指令。我曾在一个STM32MP1项目中因链接脚本linker script中.vectors段未指定 FLASH AT FLASH导致向量表被加载到RAM中上电后直接跳到随机地址死机。2.3 内存管理单元MMU与页表没有MMU就没有现代操作系统ARM Cortex-A系列芯片的MMU是理解交叉编译环境的关键。它让0xC0000000这样的虚拟地址通过两级页表L1 PGD L2 PTE映射到物理内存0x80000000。而交叉编译工具链中的--sysroot参数本质就是在告诉编译器“请把所有#include stdio.h头文件从/path/to/sysroot/usr/include找而不是宿主机的/usr/include”。页表项PTE的比特位含义直接决定你的程序能否运行Bit[1]APAccess Permission 0b11 表示用户/内核均可读写若设为0b01则用户态无法访问SIGSEGVBit[4]XNeXecute Never 1 表示此页不可执行用于防御缓冲区溢出攻击Bit[12:10]Domain域 0 表示使用domain 0的权限检查Linux内核强制使用domain 0。实操验证在ARM Linux系统中执行cat /proc/self/maps你会看到类似b6f3d000-b6f5e000 r-xp 00000000 b3:02 12345 /lib/libc-2.31.so的行。其中r-xp对应PTE的AP位和XN位设置b3:02是块设备号指向SD卡分区12345是inode号。交叉编译时若--sysroot指向的libc版本与目标板不一致如编译用glibc 2.31板子运行2.28dlopen()加载动态库时会因符号版本GLIBC_2.28缺失而失败。3. 交叉编译工具链深度解析从源码构建到ABI兼容性避坑3.1 工具链组成不是单个gcc而是一套协同工作的“翻译工厂”一个完整的ARM交叉编译工具链包含5个核心组件缺一不可组件作用典型名称关键参数Binutils汇编、链接、目标文件操作arm-linux-gnueabihf-as,arm-linux-gnueabihf-ld--sysroot,-L,-T链接脚本GCCC/C编译器前端arm-linux-gnueabihf-gcc-marcharmv7-a,-mfpuvfpv3,-mfloat-abihardGlibcC标准库实现libc.so.6,ld-linux-armhf.so.3--with-archarmv7-a,--with-fpuvfpv3Kernel Headers内核API定义asm/unistd.h,linux/input.h必须与目标板内核版本一致如4.19.71Sysroot运行时库与头文件根目录/opt/sysroot--sysroot/opt/sysroot实测心得我曾用Buildroot自动生成工具链但编译Qt5.12.10时仍失败。最终发现是Buildroot默认启用BR2_TOOLCHAIN_HEADERS_AT_LEAST_4_19y而目标板内核为4.14导致linux/usb/ch9.h中缺少USB_DT_BOS定义。解决方案手动修改Buildroot配置或用make linux-menuconfig重新生成headers。3.2 ARM Compiler 5.06u7AC5与GCC的实战对比何时该放弃开源ARM Compiler 5.06u7AC5是ARM官方发布的商用编译器基于ARM自己的Edison C/C前端与GCC有本质差异维度GCC (arm-linux-gnueabihf-gcc)AC5 (armcc)标准支持C11/C17完整支持GCC 10C99/C03不支持auto、lambda、std::thread优化策略-O2侧重通用性能-Os压缩代码尺寸-O3针对ARM微架构深度优化如循环展开、NEON自动向量化调试信息DWARF2格式GDB完美支持ARMDWARF格式需ARM DS-5调试器许可证GPL可自由分发商用授权需购买ARM Development Studio什么场景必须用AC5车规级MCU如NXP S32K144要求ASIL-B功能安全认证AC5提供TÜV认证的编译器资格报告QoI高频电机控制算法需确定性执行时间AC5的#pragma push可精确控制指令调度使用ARM Socrates生成NIC-400互连矩阵时AC5的__attribute__((section(.nic400)))可将配置数据段精准映射到特定地址。AC5安装避坑下载的arm_compiler_5.06_u7_linux.tar.bz2解压后执行./install.sh提示“该版本未安装”是因为缺少32位兼容库。在Ubuntu 20.04上需先运行sudo dpkg --add-architecture i386 sudo apt update sudo apt install libc6:i386 libncurses5:i386 libstdc6:i386否则安装程序无法加载libX11.so.6依赖。3.3 Ubuntu 20.04/24.04构建ARM交叉编译环境从零开始的完整流程步骤1安装基础依赖Ubuntu 20.04sudo apt update sudo apt install -y git wget bison flex gawk build-essential libncurses5-dev \ python3 python3-pip python3-setuptools python3-wheel python3-distutils \ libssl-dev zlib1g-dev libglib2.0-dev libpixman-1-dev步骤2下载并编译crosstool-ng推荐方式git clone https://github.com/crosstool-ng/crosstool-ng.git cd crosstool-ng ./bootstrap ./configure --prefix$HOME/ct-ng make make install export PATH$HOME/ct-ng/bin:$PATH步骤3配置ARMv7-A工具链以Cortex-A9为例ct-ng arm-cortexa9-linux-gnueabihf ct-ng menuconfig关键配置项Paths and misc options→Local tarballs directory:/opt/tarballs存放源码包C compiler→gcc version:9.5.0避免GCC 11的C20 ABI变更C-library→glibc version:2.31匹配Ubuntu 20.04Debug facilities→duma,strace,gdb开启调试支持步骤4构建工具链耗时约45分钟ct-ng build # 完成后工具链位于 $HOME/x-tools/arm-cortexa9-linux-gnueabihf/ export PATH$HOME/x-tools/arm-cortexa9-linux-gnueabihf/bin:$PATH步骤5验证交叉编译能力# 编写测试代码 test.c echo #include stdio.h int main() { printf(Hello ARM!\n); return 0; } test.c # 交叉编译 arm-cortexa9-linux-gnueabihf-gcc -static test.c -o test_arm # 检查ELF属性 file test_arm # 输出: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked readelf -h test_arm | grep -E (Machine|Flags) # Machine: ARM, Flags: 0x5000002, Version5 EABI注意-static参数至关重要。动态链接的二进制在目标板上需ld-linux-armhf.so.3而该文件必须与工具链glibc版本严格匹配。静态链接虽增大体积但规避了90%的运行时库依赖问题。4. Qt5.12.10交叉编译实战从源码配置到OpenSSL集成全链路4.1 环境准备为什么Qt官方不提供ARM预编译包Qt 5.12.10的源码包qt-everywhere-src-5.12.10.tar.xz包含约1200个模块每个模块的编译依赖不同qtbase依赖glibc、X11若启GUI、EGLARM Mali GPUqtwebengine需Chromium源码10GB交叉编译几乎不可能qtconnectivity依赖BlueZ蓝牙协议栈需目标板BlueZ头文件。因此Qt官方只提供x86_64桌面版预编译包ARM版必须自行编译。核心挑战在于Qt configure脚本需识别交叉编译器、sysroot、平台插件platforms/libqxcb.so、图形后端eglfs、linuxfb。准备工作清单目标板系统信息uname -a获取内核版本、cat /proc/cpuinfo确认ARMv7/v8sysroot目录从板子SDK提取/usr/include、/usr/lib、/lib或用Buildroot生成OpenGL ES库若用Mali GPU需libmali.so及/usr/include/mali头文件OpenSSLQt网络模块必需需交叉编译OpenSSL 1.1.1k非3.0因Qt5不支持。4.2 OpenSSL 1.1.1k交叉编译关键前置步骤# 下载OpenSSL 1.1.1k wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz tar -xzf openssl-1.1.1k.tar.gz cd openssl-1.1.1k # 配置交叉编译 ./Configure linux-armv4 \ --prefix$HOME/sysroot/usr \ --openssldir$HOME/sysroot/etc/ssl \ no-async \ no-hw \ no-engine \ CCarm-cortexa9-linux-gnueabihf-gcc \ ARarm-cortexa9-linux-gnueabihf-ar r \ RANLIBarm-cortexa9-linux-gnueabihf-ranlib # 编译安装 make -j$(nproc) make install注意no-async禁用异步IO因ARM嵌入式系统无相应驱动no-hw禁用硬件加速引擎避免链接libcrypto.so时找不到-lcrypto。编译后$HOME/sysroot/usr/lib/libssl.so.1.1即为Qt所需。4.3 Qt5.12.10 configure全流程详解# 解压Qt源码 tar -xJf qt-everywhere-src-5.12.10.tar.xz cd qt-everywhere-src-5.12.10 # 创建构建目录避免污染源码 mkdir build cd build # 执行configure关键参数逐条解释 ../configure \ -xplatform linux-arm-gnueabihf-g \ # 指定ARM平台mkspec -prefix /opt/qt-arm \ # 安装路径目标板上也需此路径 -extprefix $HOME/sysroot/opt/qt-arm \ # sysroot中的安装路径 -sysroot $HOME/sysroot \ # 根文件系统路径 -no-opengl \ # 禁用桌面OpenGL改用EGL -opengl es2 \ # 启用OpenGL ES 2.0 -device-option CROSS_COMPILEarm-cortexa9-linux-gnueabihf- \ # 交叉编译器前缀 -device linux-imx6-g \ # 若为i.MX6板用此mkspec -qt-zlib -qt-libpng -qt-libjpeg \ # 静态链接基础图像库 -ssl -openssl-linked \ # 启用OpenSSL且静态链接 -I $HOME/sysroot/usr/include/openssl \ # OpenSSL头文件路径 -L $HOME/sysroot/usr/lib \ # OpenSSL库路径 -openssl-runtime \ # 运行时动态链接若用-static则改为-linked -nomake examples -nomake tests \ # 跳过示例和测试节省时间 -skip webengine \ # 跳过WebEngine太重 -v # 显示详细配置过程configure输出关键验证点Found pkg-config: /usr/bin/pkg-config→ 确保pkg-config已安装Checking for OpenSSL... yes→ 显示OpenSSL版本和路径Building on: linux-g→ 宿主机编译器Building for: linux-arm-gnueabihf-g→ 目标平台正确Qt is built in release mode.→ 确认构建模式。4.4 编译与部署解决libQt5Core.so找不到的终极方案# 开始编译4核CPU约需3小时 make -j4 # 安装到sysroot make install # 将生成的库复制到目标板假设板子IP为192.168.1.100 rsync -avz --rsync-pathsudo rsync \ $HOME/sysroot/opt/qt-arm/lib/ \ root192.168.1.100:/opt/qt-arm/lib/ # 在目标板上创建符号链接Qt运行时查找libQt5Core.so.5 ssh root192.168.1.100 cd /opt/qt-arm/lib ln -sf libQt5Core.so.5.12.10 libQt5Core.so.5运行时库路径问题终极解决若仍报libQt5Core.so.5: cannot open shared object file执行# 在目标板上 echo /opt/qt-arm/lib /etc/ld.so.conf.d/qt-arm.conf ldconfig # 更新动态链接器缓存实测心得Qt5.12.10交叉编译最大的坑是-openssl-linked与-openssl-runtime的混淆。前者将OpenSSL静态链接进Qt库生成的libQt5Network.so体积达15MB后者仅动态链接但需确保目标板/usr/lib中有libssl.so.1.1。我推荐-openssl-linked因嵌入式系统存储空间可控且避免OpenSSL版本冲突。5. 常见问题与排查技巧实录从链接错误到运行时崩溃的现场还原5.1 典型问题速查表问题现象根本原因排查命令解决方案arm-linux-gnueabihf-gcc: command not foundPATH未包含工具链bin目录echo $PATHexport PATH/opt/gcc-arm/bin:$PATHerror: unknown type name ‘size_t’sysroot中缺少stddef.hls $SYSROOT/usr/include/stddef.h重新提取SDK sysroot或手动复制/usr/include/undefined reference to ‘pthread_create’未链接pthread库arm-linux-gnueabihf-gcc test.c -lpthread添加-lpthread或configure时加-pthreadSegmentation fault (core dumped)ABI不匹配soft/hard floatreadelf -A test_arm检查Tag_ABI_VFP_args: VFP registers确保编译与链接参数一致QStandardPaths: XDG_RUNTIME_DIR not setQt GUI程序缺少运行时环境变量export XDG_RUNTIME_DIR/tmp在目标板启动脚本中添加该行5.2 动态链接器ld-linux-armhf.so.3加载失败深度分析当执行./test_arm报错/lib/ld-linux-armhf.so.3: No such file or directory并非文件真丢失而是动态链接器路径硬编码在ELF文件中。用readelf -l test_arm | grep interpreter查看[Requesting program interpreter: /lib/ld-linux-armhf.so.3]但目标板实际路径可能是/lib/ld-2.31.so。解决方案有三重建工具链时指定链接器路径ct-ng arm-cortexa9-linux-gnueabihf ct-ng menuconfig → C-library → Additional glibc options → Path to ld-linux.so.3: /lib/ld-2.31.so编译时覆盖默认路径arm-cortexa9-linux-gnueabihf-gcc test.c -Wl,--dynamic-linker,/lib/ld-2.31.so -o test_arm目标板创建符号链接临时方案ssh rootboard ln -sf ld-2.31.so /lib/ld-linux-armhf.so.35.3 Qt5.9.9交叉编译OpenSSL问题符号版本冲突的根源Qt5.9.9 configure时若指定-openssl-linked编译会卡在qsslsocket_openssl.cpp报错undefined reference to OPENSSL_init_ssl这是因为OpenSSL 1.1.0废弃了SSL_library_init()改用OPENSSL_init_ssl()而Qt5.9.9源码仍调用旧函数。解决方案降级OpenSSL至1.0.2u推荐./Configure linux-armv4 --prefix$SYSROOT/usr no-shared打补丁修复Qt源码需修改qtbase/src/network/ssl/qsslsocket_openssl.cpp// 替换 SSL_library_init() 为 OPENSSL_init_ssl() #if OPENSSL_VERSION_NUMBER 0x10100000L OPENSSL_init_ssl(0, NULL); #else SSL_library_init(); #endif踩过的坑我在一个海思Hi3516DV300项目中因OpenSSL版本不匹配导致视频流推流时TLS握手失败。抓包发现ClientHello后无ServerHello响应最终定位到libssl.so中SSL_CTX_new()返回NULL根源正是OPENSSL_init_ssl()未被正确调用。5.4 VMware运行ARM系统为什么不能直接装Ubuntu ARM镜像VMware Workstation 17虽支持ARM64虚拟化但其本质是二进制翻译Binary Translation而非原生ARM CPU模拟。当你下载ubuntu-20.04-preinstalled-server-arm64raspi.img.xz并尝试在VMware中运行时会遇到内核panicUnable to handle kernel paging request at virtual address xxxxxxxx原因Raspberry Pi镜像内核Image针对Broadcom BCM2711 SoC编译含特定GPIO、PWM驱动而VMware无对应硬件。正确做法使用QEMU用户态模拟器# 下载ARM64 Ubuntu云镜像 wget https://cloud-images.ubuntu.com/releases/20.04/release/ubuntu-20.04-server-cloudimg-arm64.img # 启动QEMU qemu-system-aarch64 -M virt -cpu cortex-a57 -m 2G -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ -drive ifnone,fileubuntu-20.04-server-cloudimg-arm64.img,formatqcow2,idhd0 \ -device virtio-blk-device,drivehd0 -netdev user,idnet0 -device virtio-net-device,netdevnet0VMware的ARM支持仅适用于ARM64 Windows 10/11子系统WSL2或运行ARM64版Docker容器而非完整Linux发行版。6. 架构演进与工程实践从ARMv7到ARMv9交叉编译的未来在哪里6.1 ARMv9-A带来的范式转变SVE2与内存标签扩展MTE如何重塑工具链ARMv9-A2021年发布引入两大革命性特性直接影响交叉编译决策SVE2Scalable Vector Extension 2向量长度可变128~2048位编译器需支持#pragma omp simd自动向量化。GCC 12通过-marcharmv9-asve2启用但需目标SoC如AWS Graviton3硬件支持。若在不支持SVE2的Cortex-A78上编译运行时会触发SIGILL非法指令异常。MTEMemory Tagging Extension为每个内存分配8位标签CPU自动检查指针标签与内存标签匹配。启用需# 编译时 arm-linux-gnueabihf-gcc -marcharmv8.5-amemtag -fsanitizememtag test.c # 运行时需内核支持CONFIG_ARM64_MTEy这意味着未来的交叉编译不再是“选对架构”就够而是要精确匹配SoC硬件特性集。例如瑞芯微RK3588支持ARMv8.2-Adotprod点积指令编译AI推理库时用-marcharmv8.2-adotprod可提升ResNet50推理速度18%。6.2 为什么还要用GCC-ARM工具链Clang/LLVM的崛起与局限LLVM 15已提供aarch64-linux-gnu-clang其优势在于更快的编译速度LLVM IR中间表示更强的静态分析-fsanitizeaddress对Rust、Swift等新语言的原生支持。但GCC-ARM仍有不可替代性内核开发强制要求Linux内核Makefile硬编码$(CC) $(KBUILD_CFLAGS)而KBUILD_CFLAGS包含GCC特有参数如-fno-delete-null-pointer-checks裸机启动代码依赖GCC内建函数__builtin_arm_dsb(0xF)数据同步屏障在Clang中需用__asm__ volatile (dsb sy ::: memory)替代ARM官方认证车规级功能安全ISO 26262认证报告仅覆盖GCC和AC5LLVM无认证。6.3 我的工程实践建议工具链选型决策树面对arm-linux-gnueabihf-gcc、aarch64-linux-gnu-gcc、armcc、clang我的选择逻辑如下目标板是Linux发行版如Debian ARM64→ 用aarch64-linux-gnu-gccGCC 11因Debian 12默认glibc 2.36需GCC 11 ABI支持。开发裸机固件如Bootloader→ 用arm-none-eabi-gccGNU Arm Embedded Toolchain因其不含glibc依赖纯裸机运行。项目需ASIL-D认证→ 必须用ARM Compiler 5.06u7或6.18且需购买ARM DS-5专业版进行代码覆盖率分析。快速原型验证如树莓派4→ 用Clang因其编译速度快且-fsanitizeundefined能捕获90%的未定义行为。最后分享一个小技巧在Makefile中动态检测工具链能力避免硬编码# 检测是否支持-mfloat-abihard ifeq ($(shell $(CC) -mfloat-abihard -E -x c /dev/null 2/dev/null echo yes || echo no), yes) FLOAT_ABI : hard else FLOAT_ABI : softfp endif CFLAGS -mfloat-abi$(FLOAT_ABI)这个检测在Ubuntu 20.04交叉编译