2026-09-04 那一轮生态推进(mcpp 2026.9.4.3、openarch 0.9.0、三个板级包、两个 C 库源码包)之后剩下的四条。都不是回归,都是没做完或没定因的;开一个汇总备忘,避免它们只活在设计文档里。
四条分属不同仓库,汇总在这里只是为了不丢;真正动手时应各自开 PR。
设计与实测记录:
.agents/docs/2026-09-04-four-gaps-after-the-ecosystem-batch.md(方案 + §7 实测回填)
.agents/docs/2026-09-04-named-runners-and-the-universal-command-surface.md §17
1. armv7a 没有 C 库路线,也没有板级包
归属:mcpplibs/picolibc + 一个新的板级包仓
已有的部分:
- mcpp 目标表有
armv7a-none-eabi / armv7a-none-eabihf 两行,libdir 是三元组
- openarch
0.9.0 有 armv7a 后端(cpu / pte / context / trap 四组)
- e2e
336_armv7a_builds_and_boots.sh 与 openarch 的 armv7a job 每次 CI 都启动一个真实镜像并读退出码
缺的部分:
picolibc.picolibc 的 [target.'cfg(arch = ...)'] 块只有五个 M-profile 拼写(thumbv6m / thumbv7m / thumbv7em / thumbv8m.base / thumbv8m.main),没有 armv7a
xim:picolibc-arm 预编译载荷只有 7 个 thumb 目录,同样不覆盖
- 没有 armv7a 的板级包(链接脚本、启动、控制台、runner)
⇒ 今天 armv7a 只能零 libc:能编、能启动、能退出,但 printf 用不了。
闭合它需要:picolibc 加一个 cfg(arch = "armv7a") 块(machine 目录与 thumb 共用 libc/machine/arm,主要是选文件与排除项),外加一个 armv7a-virt-rt 之类的板级包,目标机器是 qemu -M virt -cpu cortex-a15——那台机器 CI 已经在用了。
判据:一个 printf 程序在 armv7a 上打印并退 0,与 338_cortex_m_picolibc_sysroot.sh 同形。
2. riscv32 没有 openarch 后端;x86_64-none-elf 没有板级包
归属:mcpplibs/openarch;板级包另议
backend-auto 的 target 行覆盖 riscv64、aarch64、x86_64、五个 thumb 拼写、armv7a。riscv32 不在其中——riscv-virt-rt 自己是服务 rv32 的(CI 有 rv32 自测),但那上面用不了 openarch。
x86_64-none-elf 反过来:openarch 后端四组齐全,却没有板级包,所以没有「一行就能启动」的入口。
两条都不紧急,记下来是因为它们让「支持矩阵」有洞而矩阵本身看不出来。
3. ninja 空转,未定因
归属:mcpp(或上游 ninja)
签名已刻画且稳定:99.9% 占一个核、0 个子进程、系统时间为 0(纯用户态循环)、8 条边的图产物齐全无缺失输入。三次全新沙箱都复现,都发生在 mcpp build 之后那次 mcpp run。
不是图的问题:宿主上同一工程、同样两条 stage_file 边,2 秒完成;手工对同一目录跑同一个 ninja 二进制立刻退 0;事后在同一沙箱按同样顺序重跑两次都成功 ⇒ 非确定性。
取不到栈,三条路都堵死:
gdb -p 被 ptrace_scope=1 拒(空转的 ninja 不是任何能 attach 的 shell 的后代)
perf record -p 被 perf_event_paranoid=4 拒
- 把 gdb 变成 ninja 的父进程(yama level 1 下合法)之后,空转就不再发生——这一条最有信息量:触发条件与时序/环境有关,不是这份 manifest 的函数
嫌疑最窄的描述(推断,非测量):输出不变的 restat 边 → phony → 被当 order-only 依赖消费。这张图里唯一「不跑命令就完成」的边就是那条 phony,而 stage_file 是唯一一条命令经常被跳过却成功的规则。三段全是 mcpp 自己构造的,可改。
下次复现时的取证手段已经就位:MCPP_NINJA_DEBUG=explain 会给两处 ninja 启动追加 -d explain,空转时会反复打同一条边的判定理由。或者在开发机上放开 ptrace_scope=0,一次就能定。
已修的那一半(确定的):杀掉 mcpp 不再留下孤儿 ninja(进程组 / job object + SIGKILL,mcpp 2026.9.4.3),回归测试 340_no_orphan_survives_a_killed_mcpp.sh 对 2026.9.4.2 会红。
4. openarch:preemption 在 armv7a 上被扣住
归属:mcpplibs/openarch
arch_trap_switch 要求 trap 恢复到另一个上下文。riscv64 / aarch64 上恢复地址在寄存器里(mepc / ELR_EL1),dispatcher 可以在切换前后存取;Cortex-M 用 PendSV 专门做这件事。
ARMv7-A 上它由 srsdb 写在 SVC 栈上,所以 trap 中途换栈会改变 rfeia 弹出的帧——只有当被恢复的上下文也经同一路径挂起时才良定义。这是一个真实设计,但不是本后端测量过的设计,所以在 provides 里扣住该能力,让需要它的消费者在解析期按名字被拒,而不是链接到一个能编过却会错的实现。
这与 Cortex-M 扣住 openarch:address-space 是同一机制。
闭合它需要:把「进入 trap 的上下文必须经同一路径挂起」写成 backend 的契约,在两个上下文之间真的抢占一次并断言寄存器与返回地址,与 examples/preempt 同形。
不在此列(已闭合,写出来避免误读)
| 项 |
状态 |
mcpp run 折叠退出码 |
已修,三段契约写在 docs/11 §6(中英) |
| 孤儿 ninja |
已修,判据落在进程状态 |
| 板级包的模拟器分档 |
已修,并连同索引描述符的安装期边一起(只改包不改索引的话,分档什么都买不到) |
xim:ninja@1.12.1 无校验和 |
已补四平台 sha256 |
生态验证现状:全新沙箱 20/20 ECOSYSTEM OK,走已发布索引 + CN 镜像。
2026-09-04 那一轮生态推进(mcpp
2026.9.4.3、openarch0.9.0、三个板级包、两个 C 库源码包)之后剩下的四条。都不是回归,都是没做完或没定因的;开一个汇总备忘,避免它们只活在设计文档里。四条分属不同仓库,汇总在这里只是为了不丢;真正动手时应各自开 PR。
设计与实测记录:
.agents/docs/2026-09-04-four-gaps-after-the-ecosystem-batch.md(方案 + §7 实测回填).agents/docs/2026-09-04-named-runners-and-the-universal-command-surface.md§171.
armv7a没有 C 库路线,也没有板级包归属:
mcpplibs/picolibc+ 一个新的板级包仓已有的部分:
armv7a-none-eabi/armv7a-none-eabihf两行,libdir是三元组0.9.0有 armv7a 后端(cpu / pte / context / trap 四组)336_armv7a_builds_and_boots.sh与 openarch 的 armv7a job 每次 CI 都启动一个真实镜像并读退出码缺的部分:
picolibc.picolibc的[target.'cfg(arch = ...)']块只有五个 M-profile 拼写(thumbv6m/thumbv7m/thumbv7em/thumbv8m.base/thumbv8m.main),没有armv7axim:picolibc-arm预编译载荷只有 7 个 thumb 目录,同样不覆盖⇒ 今天 armv7a 只能零 libc:能编、能启动、能退出,但
printf用不了。闭合它需要:picolibc 加一个
cfg(arch = "armv7a")块(machine 目录与 thumb 共用libc/machine/arm,主要是选文件与排除项),外加一个armv7a-virt-rt之类的板级包,目标机器是 qemu-M virt -cpu cortex-a15——那台机器 CI 已经在用了。判据:一个
printf程序在 armv7a 上打印并退 0,与338_cortex_m_picolibc_sysroot.sh同形。2.
riscv32没有 openarch 后端;x86_64-none-elf没有板级包归属:
mcpplibs/openarch;板级包另议backend-auto的 target 行覆盖 riscv64、aarch64、x86_64、五个 thumb 拼写、armv7a。riscv32不在其中——riscv-virt-rt自己是服务 rv32 的(CI 有 rv32 自测),但那上面用不了 openarch。x86_64-none-elf反过来:openarch 后端四组齐全,却没有板级包,所以没有「一行就能启动」的入口。两条都不紧急,记下来是因为它们让「支持矩阵」有洞而矩阵本身看不出来。
3. ninja 空转,未定因
归属:
mcpp(或上游 ninja)签名已刻画且稳定:99.9% 占一个核、0 个子进程、系统时间为 0(纯用户态循环)、8 条边的图产物齐全无缺失输入。三次全新沙箱都复现,都发生在
mcpp build之后那次mcpp run。不是图的问题:宿主上同一工程、同样两条
stage_file边,2 秒完成;手工对同一目录跑同一个 ninja 二进制立刻退 0;事后在同一沙箱按同样顺序重跑两次都成功 ⇒ 非确定性。取不到栈,三条路都堵死:
gdb -p被ptrace_scope=1拒(空转的 ninja 不是任何能 attach 的 shell 的后代)perf record -p被perf_event_paranoid=4拒嫌疑最窄的描述(推断,非测量):输出不变的
restat边 → phony → 被当 order-only 依赖消费。这张图里唯一「不跑命令就完成」的边就是那条 phony,而stage_file是唯一一条命令经常被跳过却成功的规则。三段全是 mcpp 自己构造的,可改。下次复现时的取证手段已经就位:
MCPP_NINJA_DEBUG=explain会给两处 ninja 启动追加-d explain,空转时会反复打同一条边的判定理由。或者在开发机上放开ptrace_scope=0,一次就能定。已修的那一半(确定的):杀掉 mcpp 不再留下孤儿 ninja(进程组 / job object + SIGKILL,mcpp
2026.9.4.3),回归测试340_no_orphan_survives_a_killed_mcpp.sh对2026.9.4.2会红。4.
openarch:preemption在 armv7a 上被扣住归属:
mcpplibs/openarcharch_trap_switch要求 trap 恢复到另一个上下文。riscv64 / aarch64 上恢复地址在寄存器里(mepc/ELR_EL1),dispatcher 可以在切换前后存取;Cortex-M 用 PendSV 专门做这件事。ARMv7-A 上它由
srsdb写在 SVC 栈上,所以 trap 中途换栈会改变rfeia弹出的帧——只有当被恢复的上下文也经同一路径挂起时才良定义。这是一个真实设计,但不是本后端测量过的设计,所以在provides里扣住该能力,让需要它的消费者在解析期按名字被拒,而不是链接到一个能编过却会错的实现。这与 Cortex-M 扣住
openarch:address-space是同一机制。闭合它需要:把「进入 trap 的上下文必须经同一路径挂起」写成 backend 的契约,在两个上下文之间真的抢占一次并断言寄存器与返回地址,与
examples/preempt同形。不在此列(已闭合,写出来避免误读)
mcpp run折叠退出码docs/11§6(中英)xim:ninja@1.12.1无校验和生态验证现状:全新沙箱 20/20
ECOSYSTEM OK,走已发布索引 + CN 镜像。