文章目录
- 一、背景
- 二、沙箱环境摸底
- 三、下载代码
- 四、踩坑与解决
- 坑 1:缺 Python 包 `kconfiglib` / `pycparser`
- 坑 2:预装 Python 3.12 没有 `_curses`
- 坑 3(核心):aarch64 沙箱跑不动 x86-64 工具链
- (1) 搞到一个 aarch64 宿主、模拟 x86-64 guest 的静态 qemu
- (2) 给 qemu 准备 x86-64 guest 的运行时库
- (3) 用包装脚本替代 binfmt_misc
- 坑 4:qemu 下 `dlopen` 加载 `liblto_plugin.so` 失败
- 坑 5:签名工具也是 x86-64
- 五、正式编译
- 六、编译产物
- 七、通过 AI Shell 下载编译产物
- 7.1 一句话搞定
- 7.2 浏览器下载
- 7.3 手动操作(了解原理)
- 7.4 清理
- 八、方案小结与几点体会
本文记录在华为AI开发者空间(华为云 CodeArts 云端开发沙箱)中,从零下载并成功编译海思
fbb_ws63星闪 SDK 的完整过程。沙箱为 aarch64 架构,而 SDK 自带的 RISC-V 交叉工具链是 x86-64 二进制,无法直接执行。文中给出一套** qemu-user 静态模拟 + 包装脚本 + 链接器插件禁用**的零侵入跨架构方案,最终在 ARM64 云环境里跑通了原本只能在 x86-64 Linux/WSL 上编译的 WS63 固件。
一、背景
WS63是海思推出的 2.4GHz Wi-Fi 6 + 星闪(NearLink)多模 SoC 解决方案,适用于大小家电、电工照明等物联网智能场景。fbb_ws63是基于 FBB(Family Big Box,统一开发框架)构建的 SDK 代码包,开发者可在其上做二次开发,应用易于移植到其他星闪方案。
- 代码仓:
https://gitcode.com/HiSpark/fbb_ws63 - 在线文档:
https://docs.hisilicon.com/repos/fbb_ws63/zh-CN/master/ - 官方推荐在 Windows 上用 WSL 子系统(预配置 x86-64 镜像)或 HiSparkStudio 插件编译烧录。
而华为AI开发者空间提供了一个开箱即用的云端 Linux 沙箱(预装 git / cmake / make / python 等),免本地环境搭建,非常适合做这类 SDK 的云端编译与验证。华为AI开发者空间的体验版提供5小时的使用时间,而持久化版本可以提供免费的8000核时使用时间。唯一的问题是:沙箱是 aarch64 架构,SDK 工具链是 x86-64。下文围绕这一核心矛盾展开。
不过你并不需要手工解决这些问题,只需要在华为AI开发者空间中使用CodeArts让它替你下载WS63的SDK并完成配置与编就可以了,下面的步骤二到六只是告诉你CodeArts都完成了什么工作。
二、沙箱环境摸底
进入开发者空间终端,先摸清家底:
$uname-maarch64 $cat/etc/os-release|head-2NAME="openEuler"VERSION="22.03 LTS"$whichpython3 cmakemakegitgcc /root/runtime/codearts/bin/python3# 预装 Python 3.12/root/runtime/codearts/bin/cmake# cmake 3.22/root/runtime/codearts/bin/make# GNU Make 4.3... $ /usr/bin/python3.9--version# 系统自带 Python 3.9Python3.9.9几个关键点先记下,后面都会踩到:
- 预装
python3是 3.12,但没有_curses模块(menuconfig 依赖)。 - 系统另有
/usr/bin/python3.9,带_curses,但环境变量PYTHONPATH/PYTHONHOME默认指向 3.12,直接跑会崩。 - 沙箱
/proc/self/mounts为空、binfmt_misc无法挂载(权限拒绝),这意味着没法用 binfmt_misc 透明模拟异架构二进制,后面方案要绕开它。 - 根分区是 7.5G tmpfs,但
/usr、/etc实为 80G overlayfs;dnf因读不到挂载表会误报空间不足。
三、下载代码
沙箱自带 git,直接克隆:
cd/workspacegitclone https://gitcode.com/HiSpark/fbb_ws63.git仓库约 1.8 万个文件,几分钟拉完。目录结构:
| 目录 | 说明 |
|---|---|
docs | 软件资料、IO 复用表、用户指南 |
src | SDK 源码包,编译入口在这里 |
tools | 开发工具与环境搭建文档 |
vendor | 各厂商开发板硬件资料与案例 |
编译入口是src/build.py,目标名ws63-liteos-app。官方编译命令就一句:
cdsrc python3 build.py-cws63-liteos-app# -c 表示全量编译看起来很简单,但在 aarch64 沙箱里直接跑会连环报错。下面按踩坑顺序讲。
四、踩坑与解决
坑 1:缺 Python 包kconfiglib/pycparser
build.py导入kconfiglib,NV bin 生成阶段导入pycparser,沙箱里都没有。
解决:用 pip 装上即可。但注意要装到能用的那个 Python 上(见坑 2)。
坑 2:预装 Python 3.12 没有_curses
build.py→usr_config.py→from menuconfig import menuconfig→import curses→import _curses,在预装 3.12 上直接:
ModuleNotFoundError: No module named '_curses'3.12 是预编译环境,没法补 C 扩展。好在系统自带的/usr/bin/python3.9有_curses。但它被 3.12 的环境变量干扰:
$echo$PYTHONPATH$PYTHONHOME/root/runtime/codearts/python3.12/lib/python3.12 /root/runtime/codearts/python3.12直接跑 3.9 会因为去 3.12 的库里找io而崩。解决:运行时剥掉这两个变量,并显式给 3.9 指定 user site:
# 给 python3.9 装包(用干净环境,HOME 指向 /root 以便装到 /root/.local)env-iPATH=/usr/bin:/binHOME=/root /usr/bin/python3.9-mensurepipenv-iPATH=/usr/bin:/binHOME=/root /usr/bin/python3.9-mpipinstallkconfiglib pycparser# 验证env-uPYTHONHOMEPYTHONPATH=/root/.local/lib/python3.9/site-packages\/usr/bin/python3.9-c"import _curses, kconfiglib, pycparser; print('OK')"之后所有编译命令统一用这个前缀:
PY=env-uPYTHONHOMEPYTHONPATH=/root/.local/lib/python3.9/site-packages /usr/bin/python3.9坑 3(核心):aarch64 沙箱跑不动 x86-64 工具链
装好 Python 后编译,刷屏报:
/workspace/fbb_ws63/src/tools/bin/compiler/riscv/cc_riscv32_musl_105/cc_riscv32_musl/bin/riscv32-linux-musl-gcc: cannot execute binary file: Exec format error查一下就明白了:
$uname-maarch64 $file.../cc_riscv32_musl/bin/riscv32-linux-musl-gcc... ELF64-bit LSB pie executable, x86-64,... interpreter /lib64/ld-linux-x86-64.so.2SDK 自带的 RISC-V 交叉工具链(gcc 7.3.0 + musl)是x86-64 宿主二进制,在 ARM64 上根本执行不了。官方文档默认你在 x86-64 WSL 里跑,所以没考虑这个。
思路:用qemu-user-static在 aarch64 上模拟跑 x86-64 二进制。但沙箱有两个限制:
- 没有
qemu-user-static包,openEuler 仓库也只有qemu-system-*(全系统模拟,太重)。 binfmt_misc挂不了,没法做"透明"模拟——即 qemu 模拟的 gcc 去 fork/execcc1时,内核不会自动用 qemu 接管cc1。
解决分三步:
(1) 搞到一个 aarch64 宿主、模拟 x86-64 guest 的静态 qemu
Debian bookworm 的qemu-user-staticarm64 包正好是 aarch64 宿主、静态链接、内含qemu-x86_64-static。从华为云镜像下载并提取单文件:
cd/workspacecurl-sL-oqemu.deb\"https://mirrors.huaweicloud.com/debian/pool/main/q/qemu/qemu-user-static_7.2+dfsg-7+deb12u18+b3_arm64.deb"ar x qemu.deb data.tar.xztar-xJfdata.tar.xz ./usr/bin/qemu-x86_64-staticfileusr/bin/qemu-x86_64-static# ELF 64-bit LSB pie executable, ARM aarch64, ... static-pie linked(2) 给 qemu 准备 x86-64 guest 的运行时库
工具链的 gcc/cc1 是动态链接的 x86-64,依赖 x86-64 的ld-linux、libc、libz。从 Debian amd64 抓最小 sysroot:
mkdir-p/workspace/x86_64-root&&cd/workspace/x86_64-root# glibccurl-sL-o/workspace/libc6.deb\"https://mirrors.huaweicloud.com/debian/pool/main/g/glibc/libc6_2.36-9+deb12u14_amd64.deb"ar x /workspace/libc6.deb data.tar.xz&&tar-xJfdata.tar.xz# zlib(cc1 依赖 libz.so.1)curl-sL-o/workspace/zlib1g.deb\"https://mirrors.huaweicloud.com/debian/pool/main/z/zlib/zlib1g_1.2.13.dfsg-1_amd64.deb"ar x /workspace/zlib1g.deb data.tar.xz&&tar-xJfdata.tar.xz# Debian 的 /lib64/ld-linux-x86-64.so.2 是绝对符号链接,qemu 解析不到,改成真实文件rm-flib64/ld-linux-x86-64.so.2cplib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib64/ld-linux-x86-64.so.2验证 qemu 能跑工具链:
QEMU=/workspace/usr/bin/qemu-x86_64-static$QEMU-L/workspace/x86_64-root\/workspace/fbb_ws63/src/tools/bin/compiler/riscv/cc_riscv32_musl_105/cc_riscv32_musl/bin/riscv32-linux-musl-gcc\--version# riscv32-linux-musl-gcc (build ver105.010 2024-06-18) 7.3.0看到7.3.0输出,说明 qemu + guest glibc 这条路通了。
(3) 用包装脚本替代 binfmt_misc
因为挂不了binfmt_misc,gcc 去 execcc1/as/ld时内核不会自动套 qemu。办法是:把工具链里每一个 x86-64 ELF 重命名为xxx.real,原位置放一个 shell 脚本,脚本里exec qemu xxx.real "$@"。这样无论谁用绝对路径 exec 它,都会先执行脚本→进 qemu。
包装脚本wrap_toolchain.py:
#!/usr/bin/env python3importos,sys,stat QEMU="/workspace/usr/bin/qemu-x86_64-static"SYSROOT="/workspace/x86_64-root"defis_x86_64_elf(path):try:withopen(path,"rb")asf:h=f.read(20)exceptException:returnFalsereturnlen(h)>=20andh[:4]==b"\x7fELF"andh[4]==2andh[18]==0x3eandh[19]==0defwrap_file(path):ifnotis_x86_64_elf(path):returnFalsereal=path+".real"ifos.path.exists(real):returnFalseos.rename(path,real)withopen(path,"w")asf:f.write('#!/bin/sh\nexec "%s" -L "%s" "%s" "$@"\n'%(QEMU,SYSROOT,real))st=os.stat(path)os.chmod(path,st.st_mode|stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH)returnTrueforrootinsys.argv[1:]:fordp,dirs,filesinos.walk(root):fornameinfiles:p=os.path.join(dp,name)ifos.path.islink(p):continuetry:st=os.stat(p)exceptException:continueifstat.S_ISREG(st.st_mode):wrap_file(p)对两套工具链(普通版 + fp 浮点版)以及签名/压缩工具目录都做包装:
python3 wrap_toolchain.py\/workspace/fbb_ws63/src/tools/bin/compiler/riscv/cc_riscv32_musl_105/cc_riscv32_musl\/workspace/fbb_ws63/src/tools/bin/compiler/riscv/cc_riscv32_musl_105/cc_riscv32_musl_fp\/workspace/fbb_ws63/src/tools/bin/sign_tool\/workspace/fbb_ws63/src/tools/bin/derived_key_tool\/workspace/fbb_ws63/src/tools/bin/lzma_tool一共包装了约 100 个 x86-64 二进制。符号链接不动(它指向的真实文件会被包装)。
坑 4:qemu 下dlopen加载liblto_plugin.so失败
包装完跑一个真实编译测试:
echo'int main(){return 0;}'>t.c.../riscv32-linux-musl-gcc-ot.elf t.c报:
ld.real: .../liblto_plugin.so: error loading plugin: invalid ELF header collect2.real: error: ld returned 1 exit status原因:gcc 7.3.0 默认开-fuse-linker-plugin,让ld去dlopen一个 x86-64 的liblto_plugin.so。qemu-user 对 guest 的dlopen支持有限,加载失败。--no-plugins传给 ld 也不顶用(gcc 仍会塞-plugin)。
解决:给 gcc driver 的包装脚本统一注入-fno-use-linker-plugin,从 gcc 这一层就别让 ld 加载插件。先验证该选项对--version/-print-*等查询命令无害(实测 gcc 直接忽略),然后只改 gcc 系 driver(gcc/g++/c++/gcc-7.3.0/cpp),不要改as/ld/ar(它们不认这个选项会报错):
importos drivers=["riscv32-linux-musl-gcc","riscv32-linux-musl-g++","riscv32-linux-musl-c++","riscv32-linux-musl-gcc-7.3.0","riscv32-linux-musl-cpp"]forbasein[cc_riscv32_musl/bin,cc_riscv32_musl_fp/bin]:fordindrivers:p=os.path.join(base,d)ifnotos.path.exists(p)oros.path.islink(p):continuewithopen(p,"rb")asf:raw=f.read()ifnotraw.startswith(b"#!")orb"-fno-use-linker-plugin"inraw:continuewithopen(p,"wb")asf:f.write(raw.replace(b'"$@"',b'-fno-use-linker-plugin "$@"'))再编译t.c,成功产出 RISC-V 32-bit ELF:
t.elf: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, ...坑 5:签名工具也是 x86-64
编译跑到 100% 链接出ws63-liteos-app.elf,最后签名步骤又挂:
OSError: [Errno 8] Exec format error: '.../sign_tool/sign_tool_pltuni'sign_tool_pltuni是 x86-64 静态 PIE。好在坑 3 步骤里已经把sign_tool等目录一起包装了,这步自动解决。验证:
$.../sign_tool/sign_tool_pltuni sign_tool_pltuni - Version0.10(debug)Build2023.06.19五、正式编译
所有坑填完,一行命令开跑(用前面准备好的 python3.9 前缀):
cd/workspace/fbb_ws63/srcenv-uPYTHONHOMEPYTHONPATH=/root/.local/lib/python3.9/site-packages\/usr/bin/python3.9 build.py-cws63-liteos-app关键进度摘录:
[ 98%] Linking C executable flashboot.elf [100%] ws63 image sign [100%] Built target GENERAT_HEX [100%] Linking C executable ws63-liteos-app.elf Memory region Used Size Region Size %age Used ITCM: 12992 B 16 KB 79.30% DTCM: 14900 B 16 KB 90.94% SRAM: 181072 B 548608 B 33.01% PROGRAM: 1294248 B 2357504 B 54.90% [100%] Built target ws63-liteos-app [100%] post_build:gen rom and ram bin file packet success!packet success!即打包完成。
编译过程中
build.py会先编ws63-flashboot等依赖目标,再编主目标ws63-liteos-app。中途若因缺包/缺工具中断,修好后不带-c增量续编即可,不必从头来。
六、编译产物
产物都在src/output/ws63/下:
output/ws63/ ├── acore/ws63-liteos-app/ │ ├── ws63-liteos-app.elf # ELF 可执行文件 │ ├── ws63-liteos-app.bin # 原始 bin │ ├── ws63-liteos-app-sign.bin # 签名 bin │ └── ws63-liteos-app_rom.bin ├── fwpkg/ws63-liteos-app/ │ ├── ws63-liteos-app_all.fwpkg # ★ 烧录包(1.4M) │ └── ws63-liteos-app_load_only.fwpkg └── pktbin/ws63-liteos-app.bin烧录时用ws63-liteos-app_all.fwpkg,配合 BurnTool 选择 WS63 目标即可(具体烧录步骤见仓库tools/WSL子系统编译及烧录.md)。
七、通过 AI Shell 下载编译产物
编译完成后,产物都在云端沙箱里,本地电脑拿不到。华为AI开发者空间的沙箱没有直接的文件下载按钮,但可以通过AI Shell(AI 智能体终端)快速把文件传回本地。
思路很简单:在沙箱里起一个 HTTP 文件服务器 → 通过 DevBridge 开发隧道暴露到公网 → 浏览器打开隧道 URL 直接下载。整个过程只需跟 AI Shell 说一句话,它会自动完成。
7.1 一句话搞定
在 AI Shell 里直接输入:
在沙箱里启动 HTTP 文件服务器,通过 DevBridge 让用户可以在浏览器中访问 src/output/ws63/ 目录下的编译产物AI Shell 会自动完成以下三步:
- 启动 HTTP 文件服务器:在编译产物目录下执行
python3 -m http.server 8080,提供文件浏览与下载服务。 - 启动 DevBridge 隧道:创建或复用一条 DevBridge 开发隧道,将沙箱的 8080 端口通过华为云中继暴露到公网。
- 返回公网 URL:形如
https://<tunnelId>-8080.devbridge-s2.hwtunnel.com,直接在浏览器打开即可。
7.2 浏览器下载
打开 AI Shell 返回的隧道 URL,你会看到编译产物的目录列表:
Directory listing for / ├── acore/ │ ├── ws63-liteos-app/ │ │ ├── ws63-liteos-app.elf # ELF 可执行文件(16M) │ │ ├── ws63-liteos-app.bin # 原始 bin(1.3M) │ │ └── ws63-liteos-app-sign.bin # 签名 bin(1.3M) │ └── boot_bin/ │ ├── flashboot.bin │ └── ssb.bin ├── fwpkg/ws63-liteos-app/ │ ├── ws63-liteos-app_all.fwpkg # ★ 烧录包(1.4M) │ └── ws63-liteos-app_load_only.fwpkg └── pktbin/ ├── ws63-liteos-app.bin └── ...点击任意文件即可下载到本地。最关心的是fwpkg/ws63-liteos-app/ws63-liteos-app_all.fwpkg——这是最终烧录包,配合 BurnTool 即可烧录到 WS63 开发板。
7.3 手动操作(了解原理)
如果想手动操作或脚本化,三步命令如下:
# 1. 在编译产物目录下启动 HTTP 文件服务器cd/workspace/fbb_ws63/src/output/ws63 python3-mhttp.server8080--bind0.0.0.0&# 2. 通过 DevBridge 隧道暴露 8080 端口(AI Shell 中执行)# AI Shell 会调用 DevBridge WebUI API(端口 13000)创建隧道并启动 host 进程source/root/.agents/skills/huawei-cloud-jobenv-devbridge-tunnel/scripts/devbridge_cmd.sh db_init# 确保沙箱运行TUNNEL_ID=$(db_create"ws63-download""下载编译产物"72)# 创建隧道,有效期 72 小时db_port_create"$TUNNEL_ID"8080autotrue# 添加端口,开启匿名访问db_host"$TUNNEL_ID"# 启动 host 进程db_processes# 查看进程及隧道 URL# 3. 浏览器打开返回的 URL,如:# https://mufzmrtt-8080.devbridge-s2.hwtunnel.com7.4 清理
下载完成后,及时关闭服务,避免文件长期暴露在公网:
# 停止 DevBridge host 进程db_process_stop<进程ID># 停止 HTTP 文件服务器kill%1# 删除隧道(可选)db_delete"$TUNNEL_ID"安全提醒:DevBridge 隧道开启匿名访问后,任何拿到 URL 的人都能访问你的文件。建议下载完成后立即关闭 host 进程或删除隧道。隧道有效期默认 72 小时,到期自动失效。
八、方案小结与几点体会
华为AI开发者空间适合做 SDK 云端编译:预装工具链齐全、网络通畅(可拉 GitCode/镜像)、有大磁盘 overlayfs,省去本地装环境的麻烦。唯一要适应的是 aarch64 架构。
aarch64 跑 x86-64 工具链的"三件套":
qemu-x86_64-static(aarch64 宿主)+ x86-64 guest glibc/zlib sysroot + 包装脚本替代 binfmt_misc。这套方案对任何"宿主架构 ≠ 工具链架构"的交叉 SDK 都通用,且不改动 SDK 源码,只动工具链目录(可.gitignore或脚本化复现)。qemu-user 的两个暗坑:
- guest 动态链接器若是绝对符号链接,qemu 解析不到,要改成真实文件;
- qemu-user 下 guest
dlopenguest.so不可靠,遇到 gcc LTO plugin 这类要靠-fno-use-linker-plugin绕开。
Python 双版本坑:预装 3.12 缺
_curses,用系统 3.9 时记得剥掉PYTHONPATH/PYTHONHOME,否则会串到 3.12 的库里崩掉。整个环境复现可以脚本化:下载 qemu/glibc deb → 提取 → 包装工具链 → 注入
-fno-use-linker-plugin→build.py。在开发者空间里开新会话也能一键跑通。
希望这篇踩坑记能帮到在 ARM64 云环境(不只是华为AI开发者空间,也包括树莓派、Ampere 云主机等)上玩 WS63 星闪 SDK 的同学。有问题欢迎在评论区交流。