Skip to content

Latest commit

 

History

History
301 lines (237 loc) · 15.5 KB

File metadata and controls

301 lines (237 loc) · 15.5 KB

Termux / Android (aarch64) 适配分析报告

日期: 2026-06-23 范围: mcpp / xlings / tinyhttps / musl-gcc 生态在 Termux(Android 13, aarch64)上的 curl quick_install.sh | bashxlings install mcppmcpp build 全链路适配。 目标: 用户无感体验 —— 零手动步骤(无需 --mirror CN、无需手动 PATH)。

本报告记录本次会话中在 Termux 上逐层暴露并修复的全部问题:网络下载、 工具链解压(hardlink / ld / as)、连接慢、终端"需要回车"、以及最终的 set_robust_list / musl 版本兼容问题。


0. 环境与症状总览

  • 设备: Android 13 (API 33), kernel 5.15.74-android13, aarch64, Termux(bionic libc)。
  • 关键约束: Termux 运行的是 musl-static 的 xlings/mcpp/工具链二进制(非 bionic)。
  • 表现链(修复顺序即暴露顺序):
    1. 所有 HTTPS 资源下载挂(github + gitcode 同时超时)。
    2. 下载通了但 musl-gcc 解压失败aarch64-linux-musl/bin/ld
    3. 解压通了但连接慢 / 卡几分钟 / 需要回车
    4. 工具链装上了但 aarch64-linux-musl-g++ --version SIGSYS(Bad system call)。

每一层都是独立根因。下面逐个记录。


1. musl-static 在 Android 上 DNS 解析失败(所有下载挂)

症状

curl quick_install.sh | bash 装好 xlings 后,任何资源下载失败:xlings self install 探测 https://github.com: timeout https://gitcode.com: timeout(两个都超时), patchelf 卡 0.0%。而系统 curl/gitping 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 同时超时 = 解析层挂,不是某站点。

修复(tinyhttps 0.2.5)

新建 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 里 tinyhttps gitcode.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

2. 连接慢 / "connecting…" 卡几十秒

症状

patchelf(14KB)装了 85 秒;musl-gcc 下载前 "connecting…" 卡 19s+。

根因

  1. tinyhttps 的 connectTimeoutSec = 30,而手写 DNS 解析器把 DNS 查询超时也设成了 connect 超时(30s)。8.8.8.8 从国内偶尔丢 UDP 包时,单个 nameserver 干等 30s, 两个最坏 60s —— 每次连接都付这个代价。DNS 本该 <1s。
  2. 无缓存:一次下载有多次连接(HEAD 探测 + GET + 重定向),每次都从头解析。

修复(tinyhttps 0.2.7 → 0.2.8)

  • 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


3. xlings update 卡 5 分钟(github-only 子索引)

症状

mcpp build 首次在 [VERBOSE] fetcher: resolve 后卡 ~5 分钟无输出。

根因

rm -rf .mcpp 清掉 mcpp 的索引新鲜标记 → mcpp 调 ensure_official_package_index_freshxlings update(quiet,无输出 → 像死 hang)。xlings updategit clone --depth 1 同步索引仓:主索引 xim-pkgindex 有 gitee CN 镜像(通),但子索引 xim-pkgindex-{awesome,scode,d2x} 的 CN 也配成 github.com(无真 CN 镜像)。 git 没有连接超时,连被墙的 github 干等 OS TCP 超时 ~127s/个 × 3 ≈ 5 分钟。

修复(xlings 0.4.58)

src/core/xim/repo.cppmsync_repo:clone/pull 前用 tinyhttps::probe_latency(url, 3000)(返回 inf = 3s 内不可达)探主机可达性, 不可达就跳过。github 子索引在被墙网络上 3s 跳过,不再冻结 5 分钟;主索引走 gitee CN 镜像正常。本地索引仍可用(非致命跳过)。


4. "需要回车" + ^[]11;rgb:... 泄漏

症状

mcpp build 时终端偶尔卡住、需要按回车才继续,日志里出现 ^[]11;rgb:0000/0000/0000^[\

根因(不是 stdout 解析 bug)

是 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 修好不再失败即不再双下载。

修复(xlings 0.4.58)

src/platform/unix.cppmquery_terminal_is_light() 开头:

// 自己的 stdout/stderr 都不是终端(被管道捕获)→ 不戳 /dev/tty,回退 env/默认主题
if (!::isatty(STDOUT_FILENO) && !::isatty(STDERR_FILENO)) return std::nullopt;

5. musl-gcc 解压失败:strip(体积)+ dereference(hardlink)

症状

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/ldhardlink(→ ld.bfd),Termux/Android 的 link() 在此失败(libarchive 对 hardlink entry 调 link(),失败即报 "Can't create"; 跨两次尝试都卡同一个 hardlink 是共同点)。

修复(资产层,无需改索引)

两件事都要做:

  1. 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++.a 45MB 保留)。→ 1.86GB → 344MB。
  2. 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 下载 404 NOT_PATH(127 字节错误页)。
  • aarch64 只有 15.1.0 一个 musl-gcc(13.3/11.5/9.4 是 x86_64-only)。

6. ⭐ set_robust_list / musl 1.2.6 → Android 13 SIGSYS(当前最后阻塞)

症状

工具链装上后,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 驱动特有。

strace 铁证(真机)

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)。

交叉 vs 原生工具链(必须分清)

生态里有两套 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 版本不一样?

都是 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。

修复(✅ 已完成,Plan C)

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 改不了)。

7. 发版级联(本次产物)

每个修复都要跨仓级联发版(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


8. 一句话总结

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 重建原生工具链解决。