ARTICLE DETAIL

资讯详情

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

RISC-V交叉编译工具链riscv-gnu-toolchain从零安装与避坑指南

RISC-V交叉编译工具链riscv-gnu-toolchain从零安装与避坑指南 前阵子帮朋友调一块 RISC-V 开发板折腾了大半天riscv-gnu-toolchain发现网上多数教程只写到“git clone 然后 make”就结束了。但真正劝退新人的恰恰是 make 之前的环境准备以及 make 之后的版本混乱、路径不对、死活编不出第一个 hello world。这篇文章把我从零开始下载、编译、安装、验证的全过程整理成一条可以直接照做的路径并把我在 Ubuntu、Windows、WSL 几种环境下踩过的坑一并讲清楚。1. 为什么绕不开 riscv-gnu-toolchain交叉编译的第一课1.1 所谓“交叉编译”到底在交叉什么先聊一个基础但极重要的问题为什么 RISC-V 开发非要装一套专门的 GCC你在 x86 电脑上运行 Ubuntugcc编译出来的程序默认只能在 x86 架构的 CPU 上跑。但 RISC-V 开发板、RISC-V 模拟器、还有那些跑 bare-metal裸机程序的芯片它们的 CPU 指令集和 x86 完全不同。你不可能在 x86 机器上直接执行 RISC-V 的二进制文件正如你没法把一把英制内六角扳手拧进公制螺丝。交叉编译的意思就是在 A 架构的电脑上编译出能在 B 架构 CPU 上运行的程序。这里的“交叉”二字指的就是编译器和目标机架构不一致。riscv-gnu-toolchain就是 RISC-V 官方的 GCC 交叉编译工具链它把 binutils、gcc、glibc/newlib、gdb 等一整套工具打包在一起让你在 x86 电脑上就能产出 RISC-V 指令集的程序。这个工程涉及的东西远不止一个gcc命令它同时包含binutils汇编器as、链接器ld、objcopy、objdump等GCCC/C 编译器一个 C 标准库实现通常是 newlib 或 glibcGDB可选用于调试这也是为什么推荐直接使用官方整合的riscv-gnu-toolchain而不是自己一个个装 binutils、gcc、newlib。自己去配版本组合很容易出现汇编器、编译器、标准库三者版本不匹配的问题。1.2 三个容易混淆的工具链变体很多人第一次装都会懵riscv64-unknown-elf-gcc和riscv64-linux-gnu-gcc到底有什么区别甚至还有riscv64-unknown-linux-gnu-gcc这种变体。简单来说工具链名字里的三段是有含义的命令前缀目标系统典型用途riscv64-unknown-elf-无操作系统裸机单片机、嵌入式固件、RTOSriscv32-unknown-elf-无操作系统裸机32 位 RISC-V 内核比如某些 MCUriscv64-unknown-linux-gnu-Linux 用户态程序跑在 RISC-V Linux 系统上的应用程序riscv64-linux-gnu-Linux 用户态程序同上一行只是前缀写法不同如果你要做的是跑 Linux 的 RISC-V 开发板比如 SiFive 的 Unleashed、VisionFive 系列或者树莓派平替类开发板你需要的是 Linux 版本也就是带linux-gnu的那套。这套工具链编译出的程序依赖 Linux 系统调用接口底层 C 库是 glibc。如果你是做裸机开发、RTOSFreeRTOS、RT-Thread或者搞 RISC-V 内核实验那就用unknown-elf前缀它的 C 库是 newlib一个为嵌入式场景裁剪过的轻量 C 库。配置参数上riscv-gnu-toolchain也给出了对应的目标选项--enable-multilib默认裸机版本会支持多种 ISA或--with-archrv64imafdc --with-abilp64d。1.3 博主建议你的使用场景决定选哪个如果你是第一次接触 RISC-V大概率会同时需要两套东西一套 Linux 版的跑系统测试一套裸机版的用来做启动代码、链接脚本实验。我个人的建议是只是想在 QEMU 里跑 RISC-V Linux装riscv64-linux-gnu-即可玩裸机、写自己的最小内核、调硬件寄存器装riscv64-unknown-elf-不确定装哪个源码编译时两次配置都做出来也不冲突两条工具链前缀不同可以共存。网上热词里我看到有“arm 交叉编译”相关搜索这里顺便说一句ARM 的arm-none-eabi-gcc和 RISC-V 的riscv64-unknown-elf-gcc在使用逻辑上几乎一致只是目标 ISA 和寄存器语义不同。装工具链这一步思路完全可以平移。2. 下载前先想清楚源码编译与预编译二选一2.1 源码编译的完整流程和硬件要求官方推荐的源码编译方式是最稳的但时间长、依赖多。整个流程可以概括成git clone https://github.com/riscv/riscv-gnu-toolchain cd riscv-gnu-toolchain git submodule update --init --recursive ./configure --prefix/opt/riscv sudo make -j$(nproc)看到这里你以为就完了我当时也是这么以为的。后面才发现make默认编的是裸机版 newlib如果你还想要 Linux 版得这样sudo make linux -j$(nproc)对make linux是另一个目标它会重新编译一份带 glibc 的 Linux 工具链。源码编译的建议资源磁盘剩余空间至少 10GB两个目标都编建议 20GB 以上内存 8GB 起步16GB 比较舒服make -j并行任务别拉太满我实测-j$(nproc)在 8 核机器上容易内存占满除非你内存够大编译耗时通常在 30 分钟到一个小时取决于机器性能。编译完成后所有可执行文件都会装到--prefix指定的目录默认是/opt/riscv/bin。你需要把/opt/riscv/bin加进PATH才能正常使用。2.2 预编译工具链怎么选如果不想花一小时去编译官方也提供了预编译版本在 GitHub 的 releases 页面可以找到。这些预编译包通常按riscv64-elf-ubuntu-...、riscv64-linux-ubuntu-...命名你是 Ubuntu x86_64 就选 x86_64 的包解压即用。解压过程wget https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.09.20/riscv64-elf-ubuntu-22.04-gcc.13.2.tar.gz tar -xzf riscv64-elf-ubuntu-22.04-gcc.13.2.tar.gz sudo mv riscv /opt/ export PATH/opt/riscv/bin:$PATH这里有个巨大的坑预编译包的目录结构不一定和源码安装一致。有的包解压后直接是bin/目录有的会多套一层riscv/如果你不查看就直接写export PATH大概率会找不到riscv64-unknown-elf-gcc命令。建议先ls看解压出来的结构再决定export路径。另外预编译包年代跨度大版本较老的可能只支持rv64gc不支持后续新增的扩展指令如果你用的开发板要求特定 ISA 扩展比如向量扩展v还是建议源码编译自己指定--with-arch。2.3 发行版软件包最省事但最落后的选择很多新手第一反应是sudo apt install gcc-riscv64-unknown-elf或者sudo apt install gcc-riscv64-linux-gnuUbuntu 的软件源里确实有 RISC-V 交叉编译器能用但版本老旧。我之前在一个 20.04 的容器里装过riscv64-unknown-elf-gcc --version显示的是 9.x连官方仓库都已经演进到 13、14 了。如果你只是跑个 hello world 那无所谓但如果编译新版 Linux 内核或某些依赖较新 GCC 特性的项目就会遇到莫名其妙的编译错误错误信息还不会直接告诉你“是编译器太老”。顺带回应一个热搜词很多人搜“ubuntu 安装 gcc 失败”其实不是apt装不上gcc而是装完riscv64-linux-gnu-gcc后在 CMake 里配置交叉编译时发现编译器版本太旧或者缺少对应的libgcc、glibc-dev包。这类问题验证一句话交叉编译环境的三方依赖永远比本地 gcc 复杂得多。3. 源码安装完整实录从克隆到 make 全流程3.1 克隆仓库与子模块最容易载跟头的地方riscv-gnu-toolchain不是一个单体仓库它依赖一堆子模块包括 binutils、gcc、newlib、glibc、gdb 等。直接git clone主仓库很快但如果你忽略子模块更新后面configure一定会失败报错信息还特别不直观。我推荐先浅克隆再拉子模块尤其网络不稳定的情况下git clone --depth1 https://github.com/riscv/riscv-gnu-toolchain cd riscv-gnu-toolchain git submodule update --init --recursive --depth1--depth1可以显著减少克隆数据量。但这招也有麻烦后续如果你需要切换到某个历史 tag浅克隆就不方便了需要补全历史。如果要装稳定版可以直接在 GitHub 页面上挑一个 release tag比如2024.04.12然后git checkout 2024.04.12 git submodule update --init --recursive子模块的网络问题也很烦人git submodule update经常挂在某一个仓库上。如果某个子模块一直拉不动可以手动进入对应目录看git config里的 remote URL换成镜像地址再拉但这属于修补方案。更稳妥的做法是直接下载官方的源码 tar 包GitHub 的 release 页面里的源码包是包含了所有子模块的快照解压后直接就能配置省去了submodule update这一步。3.2 configure 参数怎么定进入源码目录后第一步是./configure。这个步骤决定你最后得到什么版本的工具链。最常见的裸机配置./configure --prefix/opt/riscv-elf make -j$(nproc)如果你连 32 位也想要比如编译rv32的裸机程序加一个多架构支持./configure --prefix/opt/riscv-elf --enable-multilib make -j$(nproc)--enable-multilib的意思是一套编译好的 GCC 支持多种指令集组合rv32i、rv32imac、rv64gc 等链接时通过-march、-mabi参数选择。缺点是很费时间会在 multilib 模式下把每种组合的库都编一遍。如果你确定只做 64 位裸机就不用加这个选项。Linux 版工具链配置./configure --prefix/opt/riscv-linux make linux -j$(nproc)注意--prefix最好分开不要把裸机和 Linux 版装到同一个目录不然各种库文件会互相覆盖到最后你分不清riscv64-unknown-elf-gcc是新的还是riscv64-linux-gnu-gcc被覆盖了。还有一个参数--with-arch。比如./configure --prefix/opt/riscv-linux --with-archrv64gc --with-abilp64d这样编译出的工具链默认面向rv64gc指令集。如果你需要向量扩展v要写成rv64gcv同时 ABI 通常保持lp64d。不过我觉得在单目标工具链上手动指定--with-arch意义不大因为你在用gcc编译时通过-march参数完全可以覆盖默认值只有在做 multilib 配置时才需要仔细考虑。3.3 make 编译的长期等待与加速make过程是内存大户。riscv-gnu-toolchain编译 GCC 时单进程占用内存经常超过 1GB并行编译时内存墙会非常明显。8GB 内存机器上跑-j8系统 Swap 会被打满表现为两个多小时还没编完。5 个make -j反而更快因为效率来自不触发 swap。实际经验参数make -j4如果机器是 16 核 32GB 内存可以放心-j16。如果机器一般推荐先nproc看核数然后取一半通常比盲目拉满要快。编译过程中会有多次“看似卡住”的区间尤其是编译 GCC 的libgcc和glibc时窗口期可能持续好几分钟没有任何输出这是正常现象。不要中途打断否则重来会消耗更长时间。3.4 环境变量配置为什么命令找不到编译完成后如果直接输入riscv64-unknown-elf-gcc --version大概率提示command not found。因为make install只是把文件复制到--prefix目录并不会自动修改你的PATH。在~/.bashrc末尾追加export PATH/opt/riscv/bin:$PATH或者如果你在/opt/riscv-elf和/opt/riscv-linux分了两个目录就按需导出其中一个。我更推荐把两个都加进 PATH因为两个前缀不一样一个是unknown-elf一个是linux-gnu不会冲突。验证命令riscv64-unknown-elf-gcc --version riscv64-unknown-elf-gcc -v-v会输出编配置信息里面能看到 target、用于编译 GCC 的 host 平台、线程模型等。如果你发现gcc -v的COLLECT_GCC路径指向的不是你预期的目录说明 PATH 里可能同时存在多个 GCC后面的章节会详细说这个问题。4. 装完之后先别急着跑版本、路径与验证4.1 验证工具链是否可用先写一个最小 C 程序#include stdio.h int main(void) { printf(Hello RISC-V!\n); return 0; }裸机版本编译riscv64-unknown-elf-gcc -marchrv64gc -mabilp64d -o hello hello.cLinux 版本编译riscv64-linux-gnu-gcc -o hello hello.c裸机和 Linux 版本有一个显著区别裸机版printf不能直接跑因为裸机环境下没有标准输出实现printf的输出必须要有自己的驱动或者 semihosting主机调试通道所以就算编译通过直接放到 QEMU 裸机模式下也看不到输出。Linux 版编译出的可执行文件则可以在 QEMU 的 RISC-V Linux 环境下正常执行。检查编译产物file hello你会看到类似输出hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV)看到UCB RISC-V字样就说明目标架构确实对了。4.2 为什么升级后还是旧版本这是很多人在“安装 gcc 升级后为啥还是旧版本”这个搜索里遇到的现象。你按教程装好了新版 RISC-V 工具链运行riscv64-unknown-elf-gcc --version看到的却还是系统自带的旧版本或者干脆是另一个路径下的版本。核心原因就是PATH 环境变量的优先级顺序。which是用来排查这个问题的利器which riscv64-unknown-elf-gcc如果输出的是/usr/bin/riscv64-unknown-elf-gcc而不是你/opt/riscv-elf/bin/下的说明系统原来的软链占了更靠前的 PATH 顺序。解决方案有三个调整 PATH 顺序把你自定义的目录放到/usr/bin前面export PATH/opt/riscv-elf/bin:$PATH删除或覆盖旧软链但不太建议可能影响系统包管理器的依赖关系直接用绝对路径调用最保险/opt/riscv-elf/bin/riscv64-unknown-elf-gcc --version检查是否正确加载你需要的版本riscv64-unknown-elf-gcc -v 21 | grep COLLECT_GCC输出的那行会显示实际调用的 GCC 路径这是确认版本是否生效最直接的方法。4.3 一个最小测试程序的实际运行装好工具链只是第一步更重要的验证是在模拟环境里真正跑起来。我推荐用 QEMU可以省去真机成本。先装qemu-usersudo apt install qemu-user然后运行 Linux 版编译的 helloqemu-riscv64 ./hello如果输出Hello RISC-V!说明整条链路全部打通。如果提示qemu-riscv64: Could not open /lib/ld-linux-riscv64-lp64d.so.1: No such file or directory这个报错非常常见原因是你编译好了 RISC-V 用户态程序但系统里缺少 RISC-V 的 glibc 动态链接器和共享库。解决方式有两个一是用静态编译riscv64-linux-gnu-gcc -static -o hello hello.c qemu-riscv64 ./hello二是给 qemu 指定-L参数指向工具链里的 sysroot 目录qemu-riscv64 -L /opt/riscv-linux/sysroot ./hello如果你是源码编译的 Linux 工具链sysroot 在--prefix目录下的sysroot/子目录里。预编译包不一定带 sysroot所以静态编译是排查环境问题时比较快的方向。5. 实战中的另一些坑从网络问题到依赖缺失5.1 克隆失败与子模块拉取问题前面说了riscv-gnu-toolchain的克隆经常卡在子模块。常见的表现是Cloning into riscv-gcc... remote: Counting objects: 100% (123/123), done. remote: Compressing objects: 100% (110/123), done. Receiving objects: 99% (23456/23457), 45.23 MiB | 320.00 KiB/s然后就不动了。你 CtrlC 再试可能这次能过但下一次又卡在riscv-binutils。我的建议是优先用 GitHub release 的 tar.gz 源码包它不会遇到子模块问题如果一定用 git 方式建议设置一个较长的超时时间git config --global http.lowSpeedLimit 1000、git config --global http.lowSpeedTime 600避免网络抖动直接中断子模块拉取失败的单个目录可以手动进入该子模块目录用git remote set-url origin更换远程地址再执行git fetch git checkout。5.2 依赖库检查与 Python 版本要求源码编译需要的基础依赖Ubuntu 上可以用一个命令装全sudo apt install autoconf automake autotools-dev curl python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build如果你之前只装了gcc和make到configure阶段会报configure: error: Building GCC requires GMP 4.2, MPFR 3.1.0 and MPC 0.8.0。这就是缺libgmp-dev、libmpfr-dev、libmpc-dev的典型症状。另外新版 riscv-gnu-toolchain 的构建脚本用了meson来构建某些组件比如 newlib所以ninja-build、meson最好也装上sudo apt install meson ninja-build这里特别提醒不要用系统自带的 python3 硬凑如果 Ubuntu 版本较老系统 python3 可能版本较低而较新的 riscv-gnu-toolchain 需要 Python 3.8 以上。遇到“Python”相关报错先检查python3 --version。有些环境下系统里只有python2而没有python3那你需要在源码目录把configure里的脚本改成PYTHONpython3再执行。5.3 编译失败合集binutils 与 GCC 的链接报错编译过程中最常见的两个失败点一是 binutils 编译失败报错类似/usr/bin/ld: cannot find crt1.o: No such file or directory看起来像链接器缺文件其实不是。这个问题的根源通常是你的 host GCC 版本过老或过新与当前 binutils 源码不兼容。最直接的解法是换一个更接近官方测试矩阵的 Ubuntu 版本或者用预编译包。二是 GCC 的构建依赖了一个不存在头文件比如error: gmp.h: No such file or directory就是没装libgmp-dev。还有一类比较隐蔽的是--enable-multilib时报缺 32 位库比如ld: cannot find -lgccmultilib 模式下GCC 需要两端rv32 和 rv64的 libgcc 配合容易出现静态库缺失。遇到这种问题检查你的gcc是否支持对应 ABI比如编译 rv32 程序的 host 工具链需要支持-m32如果 Ubuntu 没装gcc-multilib可以补sudo apt install gcc-multilib g-multilib再重新make clean make。5.4 如何与其他工具链共存很多人机器上同时装过 ARM 工具链、RISC-V 工具链甚至还有主机 GCC。只要 PATH 设置得好就不冲突。我习惯把所有第三方工具链统一放到/opt/toolchains/下每个工具链一个独立的目录/opt/toolchains/ ├── riscv-elf/ ├── riscv-linux/ ├── arm-none-eabi/ └── xpack-riscv-none-embed-gcc/这样在~/.bashrc里按需导出export PATH/opt/toolchains/riscv-linux/bin:$PATH export PATH/opt/toolchains/riscv-elf/bin:$PATHriscv64-unknown-elf-和riscv64-linux-gnu-前缀不同不会互相覆盖。有些软件包比如 RT-Thread Studio、MounRiver Studio它们内部其实也集成了自己的 RISC-V GCC通常装在 IDE 的安装目录下的toolchain或类似子目录里。如果你用这些 IDE 开发系统 PATH 里有没有riscv工具链其实并不重要IDE 用自己的编译器反而可能导致 IDE 自动检测混乱。遇到这类情况可以先把 IDE 自带工具链的具体路径找出来避免和系统全局工具链混用。这和 Windows 下 MinGW64 的g.exe路径问题类似本质都是工具位置和 PATH 配置要清楚。6. 工具链装好后还能怎么用6.1 QEMU 模拟运行低成本验证 RISC-V 程序除了前面提到的qemu-riscv64运行单文件还可以用qemu-system-riscv64跑完整系统镜像。这是验证 Linux 工具链是否可靠的冷门但高效的方法。# 先准备一个 RISC-V Linux 内核和设备树 qemu-system-riscv64 -M virt -kernel vmlinux -drive filerootfs.img,formatraw -nographic如果工具链配置正确内核可以正常启动你会看到一串 RISC-V Linux 启动日志最后进入 shell。如果内核启动过程中出现Internal: unaligned instruction之类的指令异常大概率是你的-march和-mabi与内核配置不一致需要回到工具链--with-arch重新配置。6.2 IDE 集成VS Code、Eclipse 与 CMake工具链装好之后真正用得顺手还得靠编辑器/IDE 集成。VS Code 中安装 “C/C” 扩展后在c_cpp_properties.json里指定{ configurations: [ { name: RISC-V, compilerPath: /opt/toolchains/riscv-elf/bin/riscv64-unknown-elf-gcc, cStandard: c17 } ] }注意compilerPath必须是绝对路径不要依赖 PATH否则 VS Code 不知道去哪找编译器。CMake 项目里交叉编译的惯用做法是写一个toolchain.cmakeset(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR riscv64) set(CMAKE_C_COMPILER /opt/toolchains/riscv-elf/bin/riscv64-unknown-elf-gcc) set(CMAKE_CXX_COMPILER /opt/toolchains/riscv-elf/bin/riscv64-unknown-elf-g) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)配置时cmake -DCMAKE_TOOLCHAIN_FILEtoolchain.cmake .. make如果你是拿这套工具链去编 Qt、Boost 这类大型库原理也一样无非是把 CMake 交叉编译文件写好让依赖库的构建系统也走同一套交叉编译器。Qt 5.12 到 5.15 的交叉编译我实测过核心坑点就在于-device-option CROSS_COMPILE/opt/toolchains/riscv-linux/bin/riscv64-linux-gnu-以及-sysroot路径是否正确工具链问题解决后Qt 本身的编译就顺畅很多。6.3 从交叉编译器到 C 库动态链接的暗坑Linux 版工具链编译出的程序默认是动态链接的它依赖目标机器上的ld-linux-riscv64-lp64d.so.1和一堆.so动态库。编译时链接器默认从工具链的 sysroot 里找库运行时目标板或模拟器要从自己的根文件系统里加载库。编译时可以查看依赖riscv64-linux-gnu-readelf -d hello输出里会有NEEDED项列出它运行时需要的共享库。如果程序跑不起来报错总是“找不到某个 .so”先检查readelf的输出再对照qemu-riscv64 -L或目标板的/lib路径是否齐全。一个很实用的技巧在交叉编译的时候加上-Wl,-rpath,/opt/toolchains/riscv-linux/sysroot/lib这样的选项可以在开发阶段临时指定运行时库路径少写很多环境变量。正式部署时再用patchelf或静态链接处理。6.4 Windows 与 WSL 环境还有哪些坑在等你很多人是在 Windows 上用riscv-gnu-toolchain的比如xpack-riscv-none-embed-gcc就是这个社区里比较规范的 Windows 预编译包。这类包解压后可以直接在 cmd 或 PowerShell 里用但有一个总被问到的细节Windows 控制台默认代码页可能跟 UTF-8 不兼容导致输出的调试信息乱码或者命令解析出错。这种情况下先执行chcp 65001把控制台代码页切到 UTF-8再调用 GCC。之前热搜词里有人提到cmd /c chcp 65001nul e:/mingw64/bin/g.exe -fdiagnostics-coloralways ...这种写法本质就是为了让编译报错信息在中文 Windows 下正常显示。RISC-V 工具链在 Windows 上也是一样的道理。更推荐的方式是直接在 WSL 里跑 Linux 版工具链可以避免很多路径分隔符、环境变量继承、长路径限制的问题。WSL2 和 Windows 文件系统互通你可以在 Windows 侧用 VS Code Remote-WSL 插件把整个编译过程放到 Linux 原生环境跑体验顺滑很多。前面提到过mingw64环境里的g.exe那个和 RISC-V 工具链没有关系但有一个共通的教训编译器的完整路径、PATH 优先级、库文件选择永远是交叉编译工具链使用中最大的坑。不管你是 MinGW、ARM 还是 RISC-V解决思路都一样先用which/where确认实际调用的编译器再用-v打印内部配置最后用file/readelf检查产物架构。7. 写在实际安装之后工具链装好之后建议养成一个习惯在项目根目录写一个setenv.sh把 PATH、QEMU_CPUS、SYSROOT 这些环境变量统一放进去避免每次开终端都手动 export。#!/bin/bash export RISCV/opt/toolchains/riscv-elf export PATH$RISCV/bin:$PATH export QEMU_LD_PREFIX/opt/toolchains/riscv-linux/sysroot alias riscv-gccriscv64-unknown-elf-gcc这样换机器、换项目、换环境时一条source setenv.sh就能恢复所有配置。最后再分享一个我觉得很值得做的事工具链编译成功后把/opt/riscv/bin目录下的riscv64-*全部file一下看它们是不是真的 x86-64 主机可执行文件。这一步能帮你快速定位“装完了但跑不起来”的问题到底出在工具链本身还是出在环境配置上。交叉编译的关键从来不只是“会安装”而是安装之后能确定地知道每一个组件是什么、从哪里来、要到哪里去。
返回列表