ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

riscv-gnu-toolchain从零搭建:RISC-V交叉编译环境完整实战

riscv-gnu-toolchain从零搭建:RISC-V交叉编译环境完整实战 身边不少做嵌入式开发的朋友第一次拿到RISC-V开发板时最头疼的往往不是写代码而是怎么把riscv-gnu-toolchain这套交叉编译环境跑起来。这东西说简单也简单无非是下载、配置、编译、安装几个步骤但真操作起来光是依赖缺失、configure报错、编译几个小时突然失败这些状况就能劝退一大半人。这篇文章是我自己从零搭建riscv-gnu-toolchain交叉编译环境的完整记录。我会把每一步为什么要这么做、参数怎么选、踩了哪些坑都讲清楚而不是简单丢给你几条命令。无论你是刚接触RISC-V的新手还是从ARM转向RISC-V的老手这篇文章应该都能帮你少折腾几天。1. 动手之前先搞清楚riscv-gnu-toolchain到底是个什么东西1.1 交叉编译为什么电脑上的gcc编译不了RISC-V程序先解决一个最基本的问题为什么不能直接用Ubuntu自带的gcc原因很简单——你的电脑是x86架构而RISC-V开发板是RISC-V架构两者指令集完全不同。用生活里的事打个比方你在国内写了一份中文文档把它发给只会读英文的同事对方肯定看不懂。交叉编译也一样普通gcc生成的机器码是给x86 CPU“读”的放到RISC-V CPU上对方完全不认识。交叉编译工具链干的事情就是在一台x86电脑上生成RISC-V架构CPU能识别的机器码。所以riscv-gnu-toolchain本质上是把整套GNU工具链gcc编译器、binutils二进制工具、glibc或newlib运行库、gdb调试器针对RISC-V目标平台做了一次完整移植和配置让你能在x86主机上安心开发RISC-V程序。1.2 riscv-gnu-toolchain项目里到底装了些什么很多人误以为riscv-gnu-toolchain就是一个gcc其实它是一个工具链全家桶。官方仓库里的核心组件包括gcc编译器前端和后端负责把C/C代码编译成RISC-V汇编和机器码binutils包含汇编器as、链接器ld、反汇编器objdump等一系列二进制处理工具glibc面向Linux系统的C标准库提供printf、malloc这些基础函数newlib面向裸机bare-metal和嵌入式RTOS的轻量级C库gdbRISC-V架构的调试器配合JTAG或QEMU使用这一点特别重要因为你后面编译时选哪个库直接决定生成的是“裸机程序”还是“Linux应用程序”。我见过不少人在这一步搞混结果折腾半天编出来的程序在目标板上跑不起来原因就是工具链类型选错了。1.3 先想清楚你要的是裸机工具链还是Linux工具链riscv-gnu-toolchain通过不同的make目标来区分两种工具链这点非常关键newlib工具链默认编译产物不带操作系统适合跑在裸机环境、RTOS、裸机固件等场景。生成的编译器前缀是riscv64-unknown-elf-gcc这里的elf表示生成的是ELF格式的可执行文件不依赖操作系统加载。Linux工具链编译产物带glibc依赖Linux内核适合跑在完整的Linux系统上。生成的编译器前缀是riscv64-unknown-linux-gnu-gcc通过make linux来构建。我在实际项目中一般是各编一套两边场景都会用到。但如果你是纯新手不确定该装哪个先看你的目标板板子上跑的是Linux系统就选Linux工具链板上是裸机、RTOS或者你只是写固件就选newlib工具链。这个决定要在编译之前做因为后面切换的成本很高。2. 编译前的环境准备依赖、源码和版本选择2.1 宿主机依赖安装一步都不能省riscv-gnu-toolchain是从源码编译的所以宿主机的软件环境和依赖必须齐全。这一步偷懒后面configure阶段大概率会报错。我自己用的是Ubuntu 20.04/22.04系统依赖安装命令如下sudo apt update sudo apt install -y autoconf automake autotools-dev curl python3 \ libmpc-dev libmpfr-dev libgmp-dev gawk build-essential \ bison flex texinfo gperf libtool patchutils bc \ zlib1g-dev libexpat-dev这里有三个依赖要特别说明也是gcc编译绕不开的三兄弟libgmp-dev大整数运算库、libmpfr-dev多精度浮点运算库、libmpc-dev复数运算库。gcc在编译过程中要做大量的数学运算和优化计算这三者是硬依赖。如果你看到configure报错类似Building GCC requires GMP 4.2, MPFR 2.4.0 and MPC 0.8.0不用怀疑就是这三兄弟没装好。另外texinfo也很容易被忽略。它负责处理gcc文档的生成版本太旧或者缺失编译会在文档阶段直接卡死。flex和bison则是生成词法语法解析器用的binutils和gcc的构建都离不开。2.2 获取源码几种方式的选择与对比获取riscv-gnu-toolchain源码的主流方式有两种我推荐优先使用官方仓库的归档包省时省心。第一种方式是用git克隆仓库注意一定要带--recursive参数因为项目依赖多个子模块如gcc、binutils、glibc等都是以submodule形式引入的git clone --recursive https://github.com/riscv-collab/riscv-gnu-toolchain克隆完成后务必检查一下子模块是否完整git submodule update --init --recursive第二种方式是直接从GitHub Releases页面下载归档包。相比git方式这种方式更适合网络不太稳定、或者不想等子模块慢慢拉取的场景。下载的是release归档包不是某个开发中的中间状态稳定性更有保障。版本选择上我的建议是除非你要调试最新特性否则别用master分支。master分支时刻在变可能昨天还能编译通过今天因为某个子模块更新就开始报错。我踩过一次这个大坑后来就学聪明了用release版本或者固定的tag版本比如2023.10.21这类带日期的tag至少保证整个团队用的版本是一致的排查问题时也好对齐。2.3 磁盘和内存要求别等到编译到一半才后悔编译这套工具链对硬件资源是有要求的提前准备好能省去很多麻烦磁盘空间源码解压后大约占2-3GB编译产生的中间文件和安装产物加起来建议预留20GB以上的空闲空间。我一开始以为10GB够用结果编译到gcc阶段磁盘就爆了白等了一个多小时。内存和Swapmake -j高并发编译时内存不够容易触发OOM内存耗尽被杀掉。我建议8GB内存的机器用-j416GB内存的用-j8不要盲目拉满。如果物理内存偏小提前配置好Swap空间比如4GB的Swap能救急。提示编译过程中如果系统突然卡死优先检查是不是内存满了。CtrlZ暂停任务用free -h看一下内存和Swap状态再决定要不要调整并发数。3. 从源码编译到安装的完整实录3.1 configure配置参数逐个说清楚源码和环境都准备好后进入源码目录先创建一个构建目录我习惯把编译产物和源码分开方便清理cd riscv-gnu-toolchain mkdir build cd build然后执行configure配置。这里有几个参数需要根据你的目标平台仔细选我把最常用的几个讲清楚../configure --prefix/opt/riscv --with-archrv64gc --with-abilp64d--prefix指定工具的安装路径。我习惯装在/opt/riscv这样所有RISC-V工具都在一个目录下后续配PATH和查找都很方便。你也可以用$HOME/riscv区别不大但路径别带中文和空格。--with-arch指定默认的目标架构。rv64gc表示64位RISC-V其中g是通用扩展的缩写包含IMAFDc表示压缩指令扩展。如果你的芯片是RV32的比如某些MCU就需要改成--with-archrv32imac。--with-abi指定二进制接口即函数调用时参数怎么传、浮点数怎么处理。lp64d表示64位长整型指针浮点数用硬件浮点寄存器传参。千万不要把arch和abi配得不匹配比如rv32配lp64d这种组合编译出来的工具链根本没法正常工作。configure执行成功后屏幕上会打印出配置摘要告诉你将要构建哪些组件、默认架构是什么、sysroot路径在哪里。建议花十秒钟仔细看一下确认没有配错。3.2 make编译两个不同的目标两条不同的路configure完成后正式进入编译。这个环节有两个目标可选理解它们再动手# 方式一编译newlib裸机工具链 make -j$(nproc) # 方式二编译Linux工具链带glibc make linux -j$(nproc)方式一的make不带参数默认构建newlib工具链生成的编译器是riscv64-unknown-elf-gcc。方式二则是make linux构建带glibc的工具链生成riscv64-unknown-linux-gnu-gcc。我刚才说过这两个工具链用途完全不同前者面向裸机后者面向Linux系统。这里提醒一下make linux比make的编译时间更长因为glibc的编译要分两轮第一轮用临时编译器编出glibc库第二轮再用完整编译器重新编一遍gcc的libgcc部分。我就是在这个环节踩过坑第二轮编译时因为之前的configure参数没配对整个报废重来。编译时长方面以我机器的实际经验8核16线程32GB内存newlib工具链大约20分钟Linux工具链需要40-50分钟。如果你的机器配置低一些编译时间翻倍也很正常。这段时间我建议你该干嘛干嘛去不要频繁打断编译进程也尽量不要同时跑大负载的任务。编译通过后安装只需要一条命令sudo make install如果是Linux工具链就执行sudo make install-linux别搞混了。3.3 安装完成后的目录结构解析安装完成后看一下/opt/riscv/bin目录会发现一大批工具。我整理了一份对照表方便你理解每个工具是干什么用的工具名作用类似x86下的对应工具riscv64-unknown-elf-gccRISC-V编译器gccriscv64-unknown-elf-asRISC-V汇编器asriscv64-unknown-elf-ldRISC-V链接器ldriscv64-unknown-elf-objdump反汇编、查看目标文件信息objdumpriscv64-unknown-elf-objcopy格式转换如ELF转bin/hexobjcopyriscv64-unknown-elf-readelf查看ELF文件头信息readelfriscv64-unknown-elf-gdbRISC-V调试器gdbriscv64-unknown-elf-run在主机上模拟运行RISC-V程序qemu-riscv64如果安装的是Linux工具链前缀会变成riscv64-unknown-linux-gnu-工具功能一一对应只是运行时依赖glibc。注意安装完成后务必运行riscv64-unknown-elf-gcc -v确认版本。如果提示command not found说明你的PATH没有配置好看下面这一节。4. 环境变量配置与工具链功能验证4.1 PATH配置与常见小坑安装完成不等于结束还得让系统能找到这些工具。编辑~/.bashrc在文件末尾加上一行export PATH$PATH:/opt/riscv/bin然后执行source ~/.bashrc让配置立即生效。这里有个坑要提醒你如果你用的是zsh就要改~/.zshrc如果用的是fish配置方式又不一样。别机械照搬先看看自己当前shell是什么。还有个小技巧配置完PATH后如果还是提示找不到命令执行一下hash -r清空shell的命令哈希缓存。因为shell会把命令路径缓存起来新装的工具可能识别不到清一下缓存就正常了。4.2 写一个C程序进行交叉编译验证工具链能不能用最直接的验证方式就是编一个hello world。新建一个hello.c#include stdio.h int main() { printf(Hello RISC-V!\n); return 0; }然后执行编译以newlib工具链为例riscv64-unknown-elf-gcc -o hello hello.c如果命令没有任何输出说明编译成功了Unix哲学没有消息就是好消息。这时用file命令检查生成的可执行文件file hello输出应该是类似这样的hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, not stripped注意这里的关键信息UCB RISC-V说明这确实是一个RISC-V架构的ELF文件不是x86的。用readelf -h hello还可以看到更详细的架构信息。然后还可以用objdump反汇编看一下生成的机器码riscv64-unknown-elf-objdump -d hello | head -30你会看到RISC-V的汇编指令比如addi、lui、jal这些跟x86的汇编完全是两套风格。到这里至少证明工具链本身工作正常。4.3 为什么编译出来的程序不能直接跑新手经常问我把这个hello直接拷贝到开发板上能跑吗如果是裸机工具链编译的还真不能直接跑。因为裸机环境下没有操作系统加载ELF文件、初始化运行环境你需要把它烧录到芯片里配合启动代码运行。如果你想先在电脑上验证程序的功能可以用QEMU的用户态模拟user-modesudo apt install qemu-user qemu-riscv64 ./hello输出Hello RISC-V!就说明这个交叉编译产物在逻辑上是正确的。这个方法在做算法验证、单元测试时特别实用不用每次都在开发板上跑。如果你编的是Linux工具链riscv64-unknown-linux-gnu-gcc编译出的程序在qemu-riscv64里跑的话还需要指定动态链接器的路径命令会多几个参数。这里有个小经验裸机工具链编译的程序默认是静态链接的qemu直接跑就行Linux工具链默认动态链接需要把sysroot里的库路径指给qemu。为了避免麻烦我经常在编译时加-static参数临时验证时省心很多。5. 常见问题与排查技巧实录5.1 编译工具链自身时报错怎么办报错1configure时提示缺少GMP/MPFR/MPC这个在前面依赖部分已经说过本质是系统缺库。如果你确认已经安装了libmpc-dev等包还报错可以检查一下是否配置了不干净的CFLAGS环境变量或者系统里有没有老旧版本的gcc残留。用echo $CFLAGS看一下有内容就先清空再试。报错2编译到gcc阶段报错日志显示incompatible pointer type或internal compiler error这种情况大概率是宿主机的gcc版本太老或太新和riscv-gnu-toolchain某个子模块版本不兼容。建议先检查宿主机gcc版本gcc --version。如果是老旧的gcc 5.x建议先升级宿主机gcc到7以上的版本。如果版本正常还报错那就换个release tag再试。报错3编译过程中磁盘空间不足前面我建议预留20GB这是有教训的。如果已经发生了先删掉build目录重新来过同时用df -h检查一下你的/opt目录有没有独立分区。我见过有人把/分区根目录只分了15GB结果/opt和/home挤在一起编译到一半就爆掉。这种情况最稳妥的做法是把--prefix改到空间充足的路径比如/home/yourname/riscv。5.2 编译目标程序时报错的常见原因报错1fatal error: stdio.h: No such file or directory这个报错太典型了原因几乎都是你用了宿主机的gcc而不是交叉编译工具链来编译。检查一下命令开头是不是riscv64-unknown-elf-gcc以及PATH顺序是不是被宿主机的某个脚本改乱了。用which riscv64-unknown-elf-gcc看看实际解析到哪个路径。报错2cannot find crt0.o: No such file or directory出现这个报错说明链接器找不到C运行时启动文件。crt0.o是程序入口的启动代码newlib工具链会把它放在sysroot的lib目录下。如果你configure时自定义了--with-sysroot但指向了错误路径就会这样。检查方法riscv64-unknown-elf-gcc -print-sysroot看看打印的路径是否真的存在且里面有lib和include目录。报错3编译出来的程序在目标板上运行就段错误Segmentation fault这种情况很大概率是ABI不匹配。比如你编译时用了-mabiilp32生成了32位代码但目标板上的Linux内核或固件是64位的两者不兼容。或者反过来目标芯片不支持浮点单元而你默认用了lp64d硬浮点。解决方法是确认目标板的架构和浮点单元情况编译时显式指定-march和-mabiriscv64-unknown-elf-gcc -marchrv64gc -mabilp64d -o hello hello.c riscv64-unknown-elf-gcc -marchrv32imac -mabiilp32 -o hello hello.c5.3 多版本工具链共存与切换实际开发中你机器上很可能同时存在好几个RISC-V工具链系统包管理器装的、自己编译的、IDE自带的。它们之间冲突起来很烦人我这里的经验是经验一装完后检查头文件路径用riscv64-unknown-elf-gcc -E -x c - -v /dev/null这个命令可以打印出工具链的搜索路径包括头文件路径和库路径。如果发现路径指向了别的版本工具链的目录说明环境变量里混入了一些奇怪的配置。重点检查CPATH、LIBRARY_PATH、C_INCLUDE_PATH这几个环境变量很多时候就是它们惹的祸。经验二用版本前缀区分工具链不同工具链的前缀不同riscv64-unknown-elf-、riscv64-unknown-linux-gnu-、riscv64-linux-gnu-、riscv-none-embed-这些都是不同的工具链。用命令ls /opt/riscv/bin/riscv*看一下你手上有哪些心里有数。然后用绝对路径调用永远不会错。经验三小心系统升级把工具链搞挂Ubuntu的apt upgrade有时候会升级glibc或者其他系统库导致你自己编译的交叉工具链在运行时出现version GLIBC_X.XX not found之类的报错。这个问题的根源是工具链内部的某些工具依赖宿主机的库升级后被破坏了。我的习惯是重要项目锁定一个工具链版本并且放在/opt下不轻易动它。万一真的被破坏重新make install一次通常能恢复。5.4 编译很慢先优化并发和磁盘IO如果你编译工具链的时间特别长除了换更好的机器还有两个实操优化点一是合理设置make并发数。-j$(nproc)是全部核心参与编译但实际上gcc编译阶段会同时启动多个cc1进程每个进程都吃大量内存。8GB内存的机器用-j4比-j8更稳定16GB内存以上再考虑拉满。二是把build目录放到SSD上。工具链编译涉及大量小文件的创建和读写在机械硬盘上编译时间可能比SSD多出将近一倍。如果你有条件把整个构建放在SSD分区体验会好很多。6. 再补几句悄悄话一些更省事的替代方案自己从头编译riscv-gnu-toolchain这套流程写出来洋洋洒洒几千字实际操作起来也确实不是一件轻松的事。如果你只是想快速搭好开发环境开始写代码其实还有更省事的路子可以走。比如很多操作系统发行版的软件源里直接就有RISC-V工具链的预编译包。以Ubuntu为例sudo apt install gcc-riscv64-unknown-elf一条命令装完省去了源码编译的所有麻烦。这套预编译包虽然版本不一定是最新的但它稳定、干净用来学习刚好合适。如果你用的是Arch Linux可以用riscv64-unknown-elf-gcc相关的AUR包macOS下用Homebrew也有现成的配方。另外如果项目对工具链版本有特定要求也可以去看看各大芯片原厂或厂商提供的SDK。很多RISC-V芯片厂商在SDK里会带一套完整可用、经过验证的工具链比自己从头折腾要省心得多。不过需要注意版权和授权特别是商用项目提前确认好许可协议。当然自己编译这套工具链也并非没有价值。编译过程中你对整个工具链的构成、依赖关系、版本匹配逻辑都会理解得更深入。我身边不少资深工程师搞了多年开发也不一定清楚gcc的编译还依赖GMP、MPFR这些库而如果你完整地走完这一遍以后遇到这类问题基本可以秒定位。我个人在实际操作中的体会是如果是初次接触RISC-V先用现成的预编译工具链把整个开发流程跑通建立信心之后再找时间完整编译一遍源码把原理搞清楚比一上来就死磕编译要高效得多。两者结合才是最快的学习路径。
返回列表