日期: 2026-06-23
范围: mcpp / xlings / tinyhttps / musl-gcc 生态在 Termux(Android 13, aarch64)上的
curl quick_install.sh | bash → xlings install mcpp → mcpp build 全链路适配。
目标: 用户无感体验 —— 零手动步骤(无需 --mirror CN、无需手动 PATH)。
本报告记录本次会话中在 Termux 上逐层暴露并修复的全部问题:网络下载、
工具链解压(hardlink / ld / as)、连接慢、终端"需要回车"、以及最终的
set_robust_list / musl 版本兼容问题。
- 设备: Android 13 (API 33), kernel 5.15.74-android13, aarch64, Termux(bionic libc)。
- 关键约束: Termux 运行的是 musl-static 的 xlings/mcpp/工具链二进制(非 bionic)。
- 表现链(修复顺序即暴露顺序):
- 所有 HTTPS 资源下载挂(github + gitcode 同时超时)。
- 下载通了但 musl-gcc 解压失败在
aarch64-linux-musl/bin/ld。 - 解压通了但连接慢 / 卡几分钟 / 需要回车。
- 工具链装上了但
aarch64-linux-musl-g++ --versionSIGSYS(Bad system call)。
每一层都是独立根因。下面逐个记录。
curl quick_install.sh | bash 装好 xlings 后,任何资源下载失败:xlings self install
探测 https://github.com: timeout 且 https://gitcode.com: timeout(两个都超时),
patchelf 卡 0.0%。而系统 curl/git、ping github.com 都正常。
先后排查 CA bundle、TLS 1.2、熵源,全错:
- mbedtls 用
MBEDTLS_SSL_VERIFY_OPTIONAL→ CA 缺失非致命(排除 CA)。 - gitcode 接受 TLS 1.2(排除 TLS 版本)。
/dev/urandom可读(排除熵源)。
musl-static 二进制在 Android 上 getaddrinfo() 解析不了主机名:
- musl 读
/etc/resolv.conf,但 Android 的/etc只读且无该文件。 - Termux 把 nameserver 放在
$PREFIX/etc/resolv.conf(实测nameserver 8.8.8.8/8.8.4.4), libc 从不看这里 → getaddrinfo 卡死在127.0.0.1:53→ 所有下载 "Connection failed"。 - 系统 curl/git 能用是因为它们 bionic 编译、走 Android DNS;musl-static 走不了。
- 判据: github 和 gitcode 同时超时 = 解析层挂,不是某站点。
新建 mcpplibs.tinyhttps:platform 分区(对齐 xlings/mcpp 的 platform 模块布局):
getaddrinfo 失败时读 $PREFIX/etc/resolv.conf(候选 /data/data/com.termux/.../resolv.conf、
/etc/resolv.conf)拿 nameserver,手写最小 UDP DNS A 查询拿 IP,再按 IP 连接。
纯 musl-static 自包含,不兜 curl/git。调用点用 if constexpr (platform::is_windows),
平台分歧集中在 platform 模块。新增 test_resolver 做真实 UDP DNS 查询验证。
- 文件:
mcpplibs/tinyhttps/src/platform.cppm,src/socket.cppm - 实测生效:
xlings self install里 tinyhttpsgitcode.com: 235 ms,patchelf 装上。
- 接口分区(
export module x:platform;)必须被主模块单元export import(GCC 强制, 否则 "interface partition is not exported")。 - 非模板
if constexpr仍会编译被弃分支 → Windows 分支引用#ifndef _WIN32的 POSIX-only helper 会编译失败 → platform 模块实现体用#ifdef,调用点才用if constexpr。
patchelf(14KB)装了 85 秒;musl-gcc 下载前 "connecting…" 卡 19s+。
- tinyhttps 的
connectTimeoutSec = 30,而手写 DNS 解析器把 DNS 查询超时也设成了 connect 超时(30s)。8.8.8.8 从国内偶尔丢 UDP 包时,单个 nameserver 干等 30s, 两个最坏 60s —— 每次连接都付这个代价。DNS 本该 <1s。 - 无缓存:一次下载有多次连接(HEAD 探测 + GET + 重定向),每次都从头解析。
-
DNS 查询超时硬上限 2.5s(与 connect 超时解耦):
int dnsTimeout = (timeoutMs > 0 && timeoutMs < 2500) ? timeoutMs : 2500;注意不能用std::min——<winsock2.h>定义min宏,会 mangle Windows 分支 (0.2.7 因此挂 Windows 构建,0.2.8 改三元表达式修)。 -
resolve_fallback加进程内缓存(std::map<host, ips>+std::mutex),成功解析后复用。 -
文件:
mcpplibs/tinyhttps/src/socket.cppm,src/platform.cppm
mcpp build 首次在 [VERBOSE] fetcher: resolve 后卡 ~5 分钟无输出。
rm -rf .mcpp 清掉 mcpp 的索引新鲜标记 → mcpp 调 ensure_official_package_index_fresh
→ xlings update(quiet,无输出 → 像死 hang)。xlings update 用 git clone --depth 1
同步索引仓:主索引 xim-pkgindex 有 gitee CN 镜像(通),但子索引
xim-pkgindex-{awesome,scode,d2x} 的 CN 也配成 github.com(无真 CN 镜像)。
git 没有连接超时,连被墙的 github 干等 OS TCP 超时 ~127s/个 × 3 ≈ 5 分钟。
src/core/xim/repo.cppm 的 sync_repo:clone/pull 前用
tinyhttps::probe_latency(url, 3000)(返回 inf = 3s 内不可达)探主机可达性,
不可达就跳过。github 子索引在被墙网络上 3s 跳过,不再冻结 5 分钟;主索引走 gitee
CN 镜像正常。本地索引仍可用(非致命跳过)。
mcpp build 时终端偶尔卡住、需要按回车才继续,日志里出现 ^[]11;rgb:0000/0000/0000^[\。
是 xlings 的终端明暗主题探测:platform::query_terminal_is_light() 往 /dev/tty
发 OSC-11 查询(\e]11;?),select 等 50ms 读回复。当 xlings 在 mcpp 下运行时
(stdout 是管道,被 mcpp 的 NDJSON 解析捕获),它仍然去戳 /dev/tty。Termux 上
终端回复慢(>50ms)→ select 超时放弃 → 回复晚到、落进终端输入缓冲没人读 → 泄漏成
^[]11;rgb:... 污染输入行 → 用户得按回车清掉。
数据流分工(供参考,排除"解析 bug"):
mcpp → 起子进程 `xlings interface install_packages`(NDJSON,带 "yes":true)
xlings → tinyhttps 真连接/下载/解压,吐 NDJSON 进度
mcpp ui.cppm → 解析 NDJSON → 画 "Downloading … connecting…"
"connect 慢"是 xlings 的 tinyhttps 连接(§2),不是 mcpp 解析。mcpp 设计上 install
失败会 interface → install_direct 跑两遍(下载两次);extract 修好不再失败即不再双下载。
src/platform/unix.cppm 的 query_terminal_is_light() 开头:
// 自己的 stdout/stderr 都不是终端(被管道捕获)→ 不戳 /dev/tty,回退 env/默认主题
if (!::isatty(STDOUT_FILENO) && !::isatty(STDERR_FILENO)) return std::nullopt;mcpp build 装 musl-gcc 时:
[error] extract failed for musl-gcc:
write_header(.../aarch64-linux-musl/bin/ld): Can't create '.../bin/ld'
原生 musl-gcc-15.1.0-linux-aarch64 当初未 strip = 828MB 压缩 / 1.86GB 解压
(cc1plus418MB + cc1381 + lto1370 + lto-dump388 ≈ 1.5GB 调试信息)。
- 先误判 ENOSPC:
bin/ld在解压 189MB 处,以为 790MB 包占盘 + 解压把手机空间撑爆。 - strip 到 116MB/337MB 后仍卡同一个
bin/ld→ 推翻 ENOSPC。 - 真因:
aarch64-linux-musl/bin/ld是 hardlink(→ld.bfd),Termux/Android 的link()在此失败(libarchive 对 hardlink entry 调link(),失败即报 "Can't create"; 跨两次尝试都卡同一个 hardlink 是共同点)。
两件事都要做:
- strip:用
aarch64-linux-musl-strip(交叉 strip,在~/.mcpp/.../xpkgs/xim-x-aarch64-linux-musl-gcc/15.1.0/bin/,只需交叉 strip 工具, 不需 aarch64 构建环境——当初"deferred 需 aarch64 env"判断错了)。按file判类型: 可执行全 strip、.so用--strip-unneeded、跳过.a/.o(strip 破坏静态库链接, 如libstdc++.a45MB 保留)。→ 1.86GB → 344MB。 tar --hard-dereference重打包:把 hardlink 全展开成真实文件(解压不再调link())。 → 最终 132MB 压缩 / 337MB 解压。
qemu-aarch64 跑 C + C++ 冒烟验证(gcc 是 musl-static,可直接 qemu/binfmt)。重传 xlings-res:
github gh release upload --clobber;gitcode 同名资产不能覆盖(gtc obs_callback 400),
要先删旧再传。musl-gcc.lua 无 sha pin → 资产透明替换免改索引。
- gtc 上传文件名 = 索引 URL 期望名:传成
th-0.2.8.tar.gz而非tinyhttps-0.2.8.tar.gz→ CN 下载 404NOT_PATH(127 字节错误页)。 - aarch64 只有 15.1.0 一个 musl-gcc(13.3/11.5/9.4 是 x86_64-only)。
工具链装上后,mcpp 探测编译器:
error: '<...>/aarch64-linux-musl-g++ --version' exited with status 159
直接跑:Bad system call,exit=159(= 128 + 31 = SIGSYS)。as / gcc 同样 exit=159
→ 整个 musl 工具链在 Android 13 上都跑不了,非 gcc 驱动特有。
set_tid_address(...) = <tid>
set_robust_list(0x.., 24)
--- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP,
si_syscall=__NR_set_robust_list, si_arch=AUDIT_ARCH_AARCH64} ---
+++ killed by SIGSYS +++
被拦的是 set_robust_list(musl 启动时 __init_tp 为主线程注册 robust mutex 链表)。
Android 13 app 沙盒的 seccomp 把它 TRAP 成 SIGSYS(而非返回 ENOSYS)。
生态里有两套 aarch64 相关的 musl-gcc,极易混淆:
| 交叉工具链 | 原生工具链 = "原生包" | |
|---|---|---|
| xim 包名 | xim:aarch64-linux-musl-gcc |
xim:musl-gcc(aarch64 上) |
| 资产名 | aarch64-linux-musl-gcc-15.1.0-**linux-x86_64**.tar.gz |
musl-gcc-15.1.0-**linux-aarch64**.tar.gz |
| gcc/g++ 二进制本身 | x86_64 ELF(在 x86_64 机器上跑) | aarch64 ELF(在 aarch64 设备上跑) |
| 编译出的程序 | aarch64 | aarch64 |
| 用途 | 在 x86_64 上交叉编译 xlings/mcpp 的 aarch64 版(release CI) | 在 aarch64 Termux 上 mcpp build 本地编译 |
| musl | 1.2.5 ✅ | 1.2.6 ❌ |
| §5 strip+deref 的 | 否 | 就是它 |
- "交叉"的 gcc 跑在 x86_64(host=x86_64, target=aarch64)→ xlings/mcpp 的 aarch64 版是它编的, 所以那些二进制链的是 musl 1.2.5,能跑。
- "原生"的 gcc 跑在 aarch64(host=target=aarch64)→ Termux 上
mcpp build装的xim:musl-gcc@15.1.0就是它,二进制自身链了 musl 1.2.6 → 启动调set_robust_list→ SIGSYS。 - 用户报错路径
.mcpp/.../xpkgs/xim-x-musl-gcc/15.1.0/bin/aarch64-linux-musl-g++= 原生包里的 g++。
| 工具链 | musl | set_robust_list | Android 13 |
|---|---|---|---|
| 交叉(构建 xlings/mcpp 用) | 1.2.5 | ❌ 不调 | ✅ 能跑 |
| 原生 musl-gcc 包(Termux 编译用) | 1.2.6 | ✅ 启动就调 | ❌ SIGSYS |
都是 musl-cross-make 构建,但 musl-cross-make.lua 的 config.mak 模板没 pin MUSL_VER
(只有 GCC_VER / TARGET / OUTPUT)→ 每次构建吃"当时那个 musl-cross-make 的默认 musl":
- musl-cross-make 当前
Makefile默认MUSL_VER = 1.2.5;config.mak.dist有注释行# MUSL_VER = git-master。 - 交叉工具链:用了默认稳定版 1.2.5(不调 set_robust_list)。
- 原生包:吃到 1.2.6 —— 这是 musl 1.2.5 之后的开发版(git-master 报告的下一个
版本号即 1.2.6),它新增了主线程启动期
set_robust_list注册。说明构建原生包时显式选了MUSL_VER = git-master、或用了不同时间点/配置的 musl-cross-make。 - 没 pin 版本 → 两次构建吃到不同 musl = 根本原因。
- 根治:config.mak 模板显式 pin
MUSL_VER = 1.2.5,锁定所有工具链(交叉 + 原生)到同一个 Android 兼容 musl,杜绝默认版本漂移。
实测:
- 交叉
aarch64-linux-musl-gcc(musl 1.2.5)产出的 hello + xlings 二进制,strace 只有set_tid_address,无set_robust_list→ Android 13 能跑。 - 原生包(musl 1.2.6)的 gcc/g++/as 二进制自身调
set_robust_list→ 全挂。 - musl 版本用
qemu-aarch64 <libc.so>打印确认:交叉 =Version 1.2.5,原生 =Version 1.2.6。
musl 1.2.6 改成主线程启动即注册 robust list(set_robust_list),触发 Android 13 seccomp。
原生包的 gcc/g++/as 二进制是用 musl 1.2.6 静态链接的 → 换 libc 文件无用,必须重建。
差异来源:musl-cross-make.lua 的 config.mak 模板没 pin MUSL_VER → 用 musl-cross-make
默认版本;建交叉工具链时默认 1.2.5,后来建原生包时默认涨到 1.2.6。
用 musl 1.2.5 重建原生 aarch64 musl-gcc。三方案:A. 真 aarch64 机器;B. qemu 模拟 (10+ 小时);C. canadian-cross(已采用,~15 分钟)——本机 x86_64 用现成交叉工具链 (musl 1.2.5)编 host=aarch64 的 gcc/g++,产物链 musl 1.2.5。
完整可复现步骤、踩坑(gawk PMA segfault→setarch -R、损坏 options-urls.cc、
--disable-decimal-float 炸 libgcc、usr/ sysroot 软链)、验证、打包、上传,
见独立文档 2026-06-23-aarch64-musl-gcc-canadian-cross-rebuild.md。
产物已上架:musl-gcc-15.1.0-linux-aarch64.tar.gz,80MB/194MB,
sha256 2277f07cd4fff2111f37182ad49dc331ccc13b6aef7dde31c704bf7dbdb0f326,
musl 1.2.5、strace 0 次 set_robust_list、静态、dereferenced。github + gitcode
xlings-res 两端字节一致。
根治已落地:xim-pkgindex/pkgs/m/musl-cross-make.lua 的 config.mak 模板已 显式 pin
MUSL_VER = 1.2.5,所有后续工具链锁定 1.2.5,杜绝默认版本漂移再引入 1.2.6+。
- 任何"在 Android 上运行"的 musl-static 二进制都必须避开 musl ≥1.2.6 的启动期
set_robust_list(Android app seccomp 拦截)。统一全生态 musl = 1.2.5(或打 patch 去掉该调用)。 - 若将来需 musl 1.2.6+,改为patch musl 去掉
__init_tp里的set_robust_list调用 (robust mutex 极少用),而非靠 seccomp(无 root 改不了)。
每个修复都要跨仓级联发版(tinyhttps 是 xlings 依赖,xlings 被 mcpp 打包):
| 组件 | 版本 | 关键内容 |
|---|---|---|
| tinyhttps | 0.2.5 | musl DNS 兜底(§1) |
| tinyhttps | 0.2.6 | Windows 构建修复(if constexpr 弃分支编译) |
| tinyhttps | 0.2.7 | DNS 超时上限 + 缓存(§2) |
| tinyhttps | 0.2.8 | Windows min 宏修复 |
| xlings | 0.4.57 | bundle tinyhttps DNS 修复 |
| xlings | 0.4.58 | tinyhttps 0.2.8 + 跳过不可达索引(§3)+ 跳过管道下 OSC 查询(§4) |
| mcpp | 0.0.59 / 0.0.60 | bundle 对应 xlings;aarch64 release job 两段式修复 |
| musl-gcc aarch64 | 15.1.0 | strip + dereference(§5);待:musl 1.2.5 重建(§6) |
发版闭环细节见 memory release-publish-pipeline / termux-musl-dns-resolv-conf /
aarch64-musl-static-ninja-closure。
Termux/Android 适配踩的是**"musl-static 二进制在 bionic + seccomp 环境下"**的一连串坑:
DNS(读不到 resolv.conf)、hardlink(link() 失败)、终端 OSC 查询泄漏、以及最致命的
musl 1.2.6 set_robust_list 被 Android 13 seccomp SIGSYS。前几个已修并发版;最后一个
正用 canadian-cross 以 musl 1.2.5 重建原生工具链解决。