From b52e6a08bc881d1ff1531f603317b420d30f2ef3 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 19:04:56 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(devkit):=20M24=20=E9=9D=99=E6=80=81=20?= =?UTF-8?q?MIPS32r2=20=E8=B0=83=E8=AF=95=E5=B7=A5=E5=85=B7=E9=9B=86=20?= =?UTF-8?q?=E2=80=94=20vmstat/slabtop/free/ps/pmap/lsof?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TF 卡 MeowOS/opt/devkit 用的交叉编译流水线:zig 0.17 musl 静态链接, 产物 fw_apps/devkit/{bin,MANIFEST.txt,licenses,README.txt}(gitignore 内)。 - build_devkit.sh:源码固定 版本+sha256+URL(ncurses 6.5 / procps-ng 4.0.4 / lsof 4.99.4),缓存命中零联网;冷启动 3 次下载约 80s,重跑 0 次联网约 65s, stamp 命中 1s 跳过;--force/--clean/--offline/--with-sources/--verify-only - 自编静态 ncursesw 只为 slabtop(procps 的 slabtop 硬依赖 ncurses); terminfo 目录钉到设备 /usr/share/terminfo,交互式 slabtop 可用 - 逐项自检 file/readelf(static、ELF32、mips32r2、0 NEEDED),任一不过即失败 - 不交付 top(真机输出不可信)、htop(卡上已有 3.5.3 且我们的更大) - DEVKIT.md:许可证留痕(procps=GPL-2+ 需提供源码、ncurses=MIT、lsof=Purdue)、 可行性实测、上卡 PATH 与 busybox 同名关系、真机验证输出 --- scripts/build/DEVKIT.md | 224 ++++++++++++++++ scripts/build/README.md | 4 +- scripts/build/build_devkit.sh | 487 ++++++++++++++++++++++++++++++++++ 3 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 scripts/build/DEVKIT.md create mode 100755 scripts/build/build_devkit.sh diff --git a/scripts/build/DEVKIT.md b/scripts/build/DEVKIT.md new file mode 100644 index 0000000..f5d317b --- /dev/null +++ b/scripts/build/DEVKIT.md @@ -0,0 +1,224 @@ +# MeowOS devkit — 静态 MIPS32r2 调试工具集 + +TF 卡 `MeowOS/opt/devkit/bin/` 里那批静态二进制的构建说明、可行性实测、许可证留痕。 +构建入口:`bash scripts/build/build_devkit.sh`(产物在 `fw_apps/devkit/`,已被 `.gitignore` +的 `fw_apps/` 规则覆盖,不入库)。 + +## 1. 为什么要有这个 + +设备(HiBy R3 Pro II / Ingenic X1600 / MIPS32r2 / Linux 4.4.94+ / 单核 / 56 MB)自带 +busybox 1.31.1,`free / ps / top / lsof` 都是指向 `/bin/busybox` 的软链,字段残缺; +`vmstat`、`slabtop`、`pmap` 干脆没有。做内存分析时最缺的正是: + +- `slabtop` —— Slab 总量 ~11 MB 到底被谁占了(buffer_head / dentry / squashfs_inode…) +- `vmstat` —— 系统级趋势(r/b、si/so、in/cs、us/sy/id/wa),busybox 无替代 +- 真版 `lsof` —— busybox lsof 只有 pid/fd/设备号,没有 SIZE/OFF、TCP 状态、NAME + +所以本工具集只围绕「这台机器的真实调试痛点」,不做大而全。 + +## 2. 产物清单 + +全部:`ELF 32-bit LSB executable, MIPS, MIPS32 rel2, statically linked, stripped`, +`readelf -d` 零 `NEEDED`(musl libc 静态链入),可直接从 exFAT 卡上跑。 + +| 工具 | 来源版本 | 许可证 | 体积(B) | 用途 / 相对 busybox 的增量 | +|---|---|---|---|---| +| `slabtop` | procps-ng 4.0.4 | GPL-2.0-or-later | 394 408 | Slab 明细;`-o` 一次性输出不需要 TTY/terminfo | +| `vmstat` | procps-ng 4.0.4 | GPL-2.0-or-later | 216 924 | 系统趋势;busybox 无 | +| `ps` | procps-ng 4.0.4 | GPL-2.0-or-later | 324 980 | `-eo pid,rss,vsz,comm --sort=-rss` 等完整字段 | +| `pmap` | procps-ng 4.0.4 | GPL-2.0-or-later | 173 460 | 地址空间映射(读 `/proc/pid/maps`);busybox 无 | +| `free` | procps-ng 4.0.4 | GPL-2.0-or-later | 94 312 | 拆 buff/cache、available | +| `lsof` | lsof 4.99.4 | Purdue/BSD-style | 789 496 | SIZE/OFF、TCP 状态、NAME、`-i` 网络视图 | +| | | **合计** | **1 993 580 ≈ 1.9 MB** | | + +同目录还有构建时生成的 `MANIFEST.txt`(每个二进制的 sha256)、`licenses/`(上游 COPYING)、 +`README.txt`(上卡说明)。`--with-sources` 会额外生成 `src/`(源码包,GPL 合规用)。 + +## 3. 上卡后怎么用 + +```sh +# 临时(SSH 会话) +export PATH=/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH + +# 持久化:rootfs 的 /etc/profile.d/meow_card_path.sh 只前插了 MeowOS/opt/bin, +# devkit 是独立目录,需要再补一行(由塔/维护者决定是否进固件): +if [ -d /usr/data/mnt/sd_0/MeowOS/opt/devkit/bin ]; then + export PATH="/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH" +fi +``` + +**与 busybox 同名命令的先后关系**(前插 PATH 时): + +| 名字 | 现状 | 前插后 | +|---|---|---| +| `free` `ps` `lsof` | `/usr/bin/*` 均是指向 `/bin/busybox` 的软链 | 解析到 devkit 版(功能更多) | +| `vmstat` `slabtop` `pmap` | 不存在 | 新增 | +| `top` | busybox 软链;**本工具集不提供 top**(见 §7) | 仍为 busybox | + +要临时用回 busybox 版:`/bin/busybox free`、`busybox ps`、`busybox lsof`。 +不要覆盖 `/usr/bin` 里的软链:rootfs 是 ubifs,随固件回刷即被还原,而且这些软链是基线的一部分。 + +## 4. exFAT 语义(权限 / 软链) + +设备挂载:`/dev/mmcblk0 on /usr/data/mnt/sd_0 type exfat (rw, fmask=0022, dmask=0022, symlink=0, noexec 未设置)`。 + +- **无符号链接**(`symlink=0`):所以本工具集全部是实体文件,不靠 `argv[0]` 分派, + 也不提供任何 symlink(不像 busybox 那样一个多合一二进制 + 一堆软链)。 +- **无 Unix 权限位**:`fmask=0022` 让卡上新文件恒为 `0755`,因此**不需要 chmod**; + 反过来说,卡上也表达不了 `4755` 之类,任何需要 setuid 的工具都不适合放这。 +- **可执行性**:挂载参数里没有 `noexec`,且卡上现有 `MeowOS/opt/MeowOS…/meowpanel` + 就是 0755 直接执行的。真机验证时我们把同一批二进制放在设备 `/tmp` 跑通(见 §9)。 + 未在卡上实测执行,是因为本次任务约定「不往卡上写任何东西」(塔来推)。 + +## 5. 重跑代价与可复现性 + +- **联网次数**:冷启动 **3 次**(ncurses / procps-ng / lsof 各一个 tarball,共 5.5 MB); + 源码包按 sha256 校验后落在 `$MEOW_DEVKIT_WORK/src/`,此后**重跑 0 次联网**。 +- **耗时**(8 核宿主实测):冷启动(含 3 次下载)**80 秒**(两次实测 80s / 86s); + 源码已缓存时全量重编约 **85 秒**;`stamp` 命中(脚本与版本都没变)时 **1 秒**直接跳过; + `--verify-only` 秒级,且不需要 zig。 +- **可复现(逐字节)**:在两个不同工作区各建一次,六个二进制 **sha256 完全相同** + (`ps` 用 `-ffile-prefix-map` 抹掉绝对源码路径;`lsof` 用裸名 `CC=zigcc` + 相对源码路径, + 不再把工具链/构建目录写进 `LSOF_CC/LSOF_CCFLAGS`)。同一份 `MANIFEST.txt` 里的 sha256 + 与设备上 `sha256sum` 的结果一致,可用来核对「跑的到底是哪个二进制」。 +- **可复现性**:三份源码都固定 `版本 + sha256 + URL`,校验不符直接失败;`zig 0.17.x` 版本 + 也做硬检查。不依赖任何「这次网络恰好成功」的偶然性 —— 离线环境可先拷 `src/*.tar.*` + 进工作区再用 `--offline`。 +- 工作区默认 `~/.cache/meow-devkit`(`MEOW_DEVKIT_WORK` 可改),不污染仓库。 + +## 6. 许可证与分发合规(重点) + +| 上游 | 许可证 | 分发二进制时的义务 | +|---|---|---| +| procps-ng 4.0.4 | **GPL-2.0-or-later**(`COPYING`=GPLv2;`library/` 为 LGPL-2.1+) | **必须提供完整对应源码**(GPLv2 §3),或书面的三年有效要约 | +| ncurses 6.5 | MIT 风格(X11 许可) | 保留版权与许可声明;不单独分发(只静态链入 slabtop) | +| lsof 4.99.4 | Purdue Research Foundation 许可(BSD 风格 4 条) | 文档与源码中保留作者/Purdue 署名;改过的版本要标注 | + +**结论(不要含糊)**:`build_devkit.sh` 里的固定版本 + 下载 URL **不构成 GPLv2 §3 的合规**。 +URL 作者随时可能撤、镜像可能消失,而且 GPL 要求的是*分发者*提供的源码或要约。所以: + +1. 若这批二进制随卡交到第三方手上 → 构建时加 **`--with-sources`**,把 + `ncurses-6.5.tar.gz` / `procps-ng-4.0.4.tar.xz` / `lsof-4.99.4.tar.xz` 一起放到卡上 + `MeowOS/opt/devkit/src/`(合计约 5.5 MB,TF 卡的容量代价可忽略); +2. 或者随卡附**书面要约**(有效期内提供源码,GPLv2 §3(b)); +3. `licenses/` 里的上游 COPYING 只是「许可声明留痕」,**不能替代源码义务**。 + +内部自用(只有自己插卡)不触发 §3 的分发义务,但既然这台机器的产物可能被拷来拷去, +建议一律 `--with-sources`。 + +## 7. 未交付清单(以及为什么) + +| 候选 | 结论 | 原因 | +|---|---|---| +| `top`(procps 4.0.4) | **不交付** | 真机实测输出不可信:表头时长字段溢出成 `-343597384:00`,`Mem` 显示 `59.4/0.1 GiB`(MemTotal 只有 55 MB)。同时它在无 TTY 的批处理下会静默 `exit 1`(strace 显示 `ioctl(1,TIOCGWINSZ)=ENOTTY` 后直接 `exit_group(1)`),即使批处理可用也拿不到正确数据。卡上已有 htop 3.5.3,`top` 属重复且不可信 → 不凑数 | +| `htop` | 不构建 | 卡上 `MeowOS/opt/bin/htop` 已经是 **3.5.3**(673 744 B)。我们实测用 zig + 自编 ncursesw 也能编出来,但 strip 后 **756 908 B**,比现有版本更大(静态链入 musl + ncurses),没有增量价值 | +| `watch` | 不构建 | busybox 1.31 已有 `watch`;procps 版 403 KB,收益不抵体积 | +| `pgrep`/`pkill` | 不构建 | busybox 已有;procps 版各 260 KB(链接整份 libproc) | +| `uptime`/`tload`/`pidof`/`pidwait`/`w` | 不构建 | busybox 已有 `uptime`/`pidof`;`tload`/`w` 在本机无调试价值 | +| `sysctl` | 不构建 | busybox 已有 `sysctl`(读写 `/proc/sys` 足够) | +| `perf` / `valgrind` / `gdb` / `ltrace` / `sysstat` / `smem` | 不可行 | 见 §8 | + +`pmap` 的低价值要说明白:本机 `CONFIG_PROC_PAGE_MONITOR=n`,`/proc//smaps` 不存在, +所以 `pmap -x` 拿不到 PSS/私有脏页。它仍有用的一点是 `/proc//maps` **存在**, +`pmap ` 能列出地址段 + 映射文件名(busybox 没有等价物),排查「谁映射了这块内存」够用。 + +## 8. 可行性实测记录(编不出来的卡在哪) + +宿主:Termux/Android aarch64,zig 0.17.0(自带 musl libc 与头文件),`make`、`curl`、`pkg-config`。 +**没有** autoconf/automake/libtool,**没有**目标内核头,sysroot 里只有 zlib(本工具集不需要它)。 + +- **procps-ng 4.0.4** ✅ 可编。两个必须处理的坑: + 1. `slabtop`(`src/slabtop.c` 直接 `#include `)与 `top` 硬依赖 ncurses; + `configure --without-ncurses` 会把它们整个跳过 → 要 `slabtop` 就必须先交叉编 ncurses。 + 2. 交叉编译下 autoconf 的 `malloc(0)/realloc(0)` 运行期探测无法执行,不缓存 + `ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes` 会引入 + `rpl_malloc/rpl_realloc` 未定义引用,链接失败。 + 3. 另外必须把 `PKG_CONFIG_LIBDIR` 收紧到自编 prefix,否则会捡到宿主 aarch64 的 + `ncursesw.pc`,结果是「musl 头 + bionic 头」混编(`watch` 直接编不过)。 +- **ncurses 6.5** ✅ 可编(`--without-shared --with-normal --enable-widec --without-progs` 等)。 + **必须** `--with-default-terminfo-dir=/usr/share/terminfo --with-terminfo-dirs=...`: + 否则交互式 `slabtop` 会 `Error opening terminal: xterm.`(默认值是编译期 prefix,设备上不存在)。 + 体积代价:`libncursesw.a` 3.3 MB(未 strip,不单独分发),链进 `slabtop` 后只增加约 100 KB。 +- **lsof 4.99.4** ✅ 可编。坑: + 1. 4.99 源码包**不带** `configure`(autotools),但带老式 `Configure` 脚本,`./Configure -n linux` 可用。 + 2. 版本探测读宿主 `/usr/include/linux/version.h` + `uname`,交叉时拿不到 4.4.94, + 会退化成 `LINUXV=00000 / LSOF_VSTR="0.0.0"`(Configure 打 "unsupported dialect or version")。 + 这两个宏在 linux dialect 里只影响 `lsof -v` 显示,仍用 `-DLINUXV/LSOF_VSTR` 覆盖成 4.4.94。 + 3. zig 的 musl 头没有 glibc 侧经由 `linux/tcp.h` 间接带来的 `TCP_ESTABLISHED` 等 11 个 + 状态宏,`dsock.c` 会编不过 → 脚本里 `-D` 补齐(值取自 `linux/tcp.h`)。 + 4. zig cc **不带 `-O` 时按 Debug 编**,会插入 UBSan 桩(`__ubsan_handle_negate_overflow`) + 导致链接失败 → 所有编译都显式 `-O2`。 +- **htop 3.5.3** ✅ 可编(用自编 ncursesw,`--disable-hwloc/sensors/delayacct`),strip 后 756 908 B —— 比卡上现有版本大,故不交付。 +- **perf** ❌ 不可行:依赖目标内核源码树(`tools/perf` 要按内核版本配 `perf_event_attr`), + 且设备内核未必开了 `CONFIG_PERF_EVENTS` 相关选项;没有内核头也是硬阻塞。 +- **valgrind** ❌ 不可行:没有 MIPS32 后端(valgrind 官方支持 x86/amd64/arm/aarch64/ppc/s390, + 无 mips),不是交叉编译问题,是根本没有后端。 +- **gdb** ❌ 未尝试/不建议:设备已带 `gdbserver`,宿主侧可以用交叉 gdb 连;在 56 MB 单核设备上 + 跑 gdb 本体收益极低、体积巨大。 +- **ltrace** ❌ 不可行:需要解析目标 ELF 动态符号(`PT_DYNAMIC` + PLT),本机程序多为 + 静态/精简,且 ltrace 的构建依赖 libelf + 复杂 autoconf 探测,投入产出不划算。 +- **sysstat(sar/iostat/mpstat)** ❌ 未做:autoconf 探测多、依赖 `gettext`/`libsensors` 等, + 且本机是单核、无块设备统计(`/proc/diskstats` 对 TF 卡意义有限),收益低。 +- **smem** ⚠️ 价值为零:依赖 `/proc//smaps` 的 PSS,本机 `CONFIG_PROC_PAGE_MONITOR=n`, + 没有 smaps → 拿不到任何它要的数据。 + +## 9. 真机验证证据(2026-11-06,root@10.76.208.50) + +产物拷到设备 `/tmp/devkit`(tmpfs)执行,验证完已删除;**没有**往 TF 卡写过任何东西。 + +``` +# free -m + total used free shared buff/cache available +Mem: 55 30 2 1 27 24 +Swap: 0 0 0 + +# vmstat 1 3 +procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu------- + r b swpd free buff cache si so bi bo in cs us sy id wa st gu + 1 0 0 2584 8764 19388 0 0 0 0 449 6 3 3 94 0 0 0 + 0 0 0 2644 8764 19388 0 0 0 0 340 566 1 5 94 0 0 0 + 0 0 0 2644 8764 19388 0 0 0 0 332 526 1 2 97 0 0 0 + +# ps -eo pid,rss,vsz,comm --sort=-rss + PID RSS VSZ COMMAND + 1368 4832 16516 meow_launcherd + 4241 1608 3012 sh + ... + +# slabtop -o | head -6 + Active / Total Objects (% used) : 55852 / 61357 (91.0%) + Active / Total Slabs (% used) : 2794 / 2794 (100.0%) + Active / Total Caches (% used) : 57 / 81 (70.4%) + Active / Total Size (% used) : 11024.86K / 11593.48K (95.1%) + Minimum / Average / Maximum Object : 0.02K / 0.19K / 32768.00K + OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME + 12474 10746 86% 0.06K 198 63 792K buffer_head + +# pmap 1 +1: init +00400000 864K r-x-- busybox +004e8000 4K rw--- busybox +00969000 132K rwx-- [ anon ] + +# lsof -nP -i +COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME +dropbear 1354 root 3u inet 2196 0t0 TCP *:22 (LISTEN) +meow_netm 1372 root 5u inet 2611 0t0 UDP 127.0.0.1:53 +meow_mdns 1375 root 3u inet 2906 0t0 UDP *:5353 +meowpanel 1395 root 3u inet 2587 0t0 TCP *:8080 (LISTEN) +``` + +- 六个二进制 `--help`/`-V` 全部 rc=0(`lsof --help` 不是合法选项,`lsof -v` 正常打印 4.99.4)。 +- 交互式 `slabtop`(无 `-o`)在 `TERM=xterm` 下正常初始化 curses(stdout 写出 1599 字节转义序列); + 对照 `TERM=bogus-term` 报 `Error opening terminal: bogus-term.` —— 说明 terminfo 路径确实命中 + `/usr/share/terminfo`,不是内嵌 fallback。 +- 宿主侧静态/架构自检(每个产物):`file` = `statically linked, stripped`; + `readelf -h` = `ELF32` / `Machine: MIPS R3000` / `Flags: ... mips32r2`;`readelf -d` 无 `NEEDED`。 + 可用 `bash scripts/build/build_devkit.sh --verify-only` 复跑。 + +## 10. 已知限制 + +- `pmap -x` 无 PSS(无 smaps),只有 maps 级别的段/文件信息。 +- 不提供 `top`(原因见 §7);需要交互式总览请用卡上已有的 `htop`。 +- 二进制按设备内核 4.4.94 编 `LINUXV`;换内核/换卡不影响运行(这些宏只影响显示)。 +- 产物是 32 位 MIPS32r2(o32 ABI,软浮点/无 FPU 依赖),只适用于 X1600 这类 MIPS32r2 目标。 diff --git a/scripts/build/README.md b/scripts/build/README.md index 91a73d1..635f6a0 100644 --- a/scripts/build/README.md +++ b/scripts/build/README.md @@ -12,6 +12,8 @@ |---|---| | `build_fw_apps.sh` | **通用打包脚本**(配置驱动)。读清单 → 组树 → repack → 校验 → 出固件。**不要改它的逻辑**,除非打包流程本身要变。 | | `fw_content.conf` | **固件内容清单**(默认配置)。每行一条 `|||`,声明"哪个本地文件装到固件哪里"。**改固件内容只改这里。** | +| `build_devkit.sh` | **TF 卡调试工具集**交叉编译脚本(zig → 静态 MIPS32r2):`vmstat/slabtop/free/ps/pmap/lsof` → `fw_apps/devkit/`。与固件打包无关,独立跑。详见 `DEVKIT.md`。 | +| `DEVKIT.md` | devkit 的产物清单、许可证留痕、可行性实测、上卡 PATH/busybox 关系、已知限制。 | | `README.md` | 本说明。 | > 曾有一批独立脚本 `build_fw_apps_m2xx.sh`(每版复制一个、内部硬编码 md5),**已清理删除**,不再使用。它们的问题:需每版改脚本、且共享 `fw_apps/` 目录互相清空。现统一走"一个通用脚本 + 一个配置文件"。 @@ -125,7 +127,7 @@ md5sum patch_v2/meow_launcherd/meow_launcherd - **改源码忘更新 md5**:清单里 md5 是强校验,源变了但 md5 没改 → 打包直接失败。**凡是动了清单里列的源文件,先 `md5sum` 更新对应行。** - **SDK 库(`meow_ui.a`)不入固件**:它由 APP(如 hello)链接,走 TF 卡 `MeowAPPs/` 热更新,**不要**加进清单。 - **`fw_apps/` 被 gitignore**:固件产物与组装树都是构建产物,勿提交。 -- **`scripts/build/` 根目录 gitignore**:构建产物与组装树勿提交。但本目录的 **`README.md`/`build_fw_apps.sh`/`fw_content.conf` 已 `git add -f` 入库**(这三个是仓库侧应保留的脚本/配置/说明);其余(`fw_apps/` 产物等)不入库。 +- **`scripts/build/` 根目录 gitignore**:构建产物与组装树勿提交。但本目录的 **`README.md`/`build_fw_apps.sh`/`fw_content.conf`/`build_devkit.sh`/`DEVKIT.md` 已 `git add -f` 入库**(这几个是仓库侧应保留的脚本/配置/说明);其余(`fw_apps/` 产物、devkit 编译树等)不入库。 --- diff --git a/scripts/build/build_devkit.sh b/scripts/build/build_devkit.sh new file mode 100755 index 0000000..d085f1c --- /dev/null +++ b/scripts/build/build_devkit.sh @@ -0,0 +1,487 @@ +#!/bin/bash +# ============================================================================ +# build_devkit.sh — 交叉编译静态 MIPS32r2 调试工具集(TF 卡 MeowOS/opt/devkit) +# +# 为什么:设备(HiBy R3 Pro II / Ingenic X1600 / Linux 4.4.94 / MIPS32r2)上 +# busybox 提供的 free/ps/lsof 字段不全(都是 busybox 软链),且没有 vmstat/slabtop。 +# 内存分析时最缺 slabtop(拆 Slab 占用)与 vmstat(系统级趋势)。本脚本用 zig 自带 +# musl 交叉编出完全静态的二进制,放进 TF 卡供开发调试。 +# +# 产物(全部静态链接、strip 过、无 NEEDED): +# fw_apps/devkit/bin/{vmstat,slabtop,free,ps,pmap,lsof} +# fw_apps/devkit/MANIFEST.txt 版本/许可证/体积/校验摘要 +# fw_apps/devkit/licenses/ 各上游 COPYING(分发留痕) +# fw_apps/devkit/README.txt 上卡后怎么加 PATH、和 busybox 的同名关系 +# (--with-sources 时另有 fw_apps/devkit/src/ 源码包,GPL 合规用) +# +# 源码固定为「版本 + sha256 + 固定 URL」,全部校验后才编译;缓存命中则零联网。 +# +# 工具链:zig cc --target=mipsel-linux-musleabi(需 zig 0.17.x) +# 不依赖 ~/.tools 下的私有脚本,自己生成 wrapper($WORK/bin/zigcc)。 +# 不需要内核头(musl 头足够;只有 lsof 缺 11 个 TCP 状态宏,本脚本 -D 补上)。 +# +# 许可证(二进制将来随卡分发时请保留此节): +# procps-ng 4.0.4 GPL-2.0-or-later(COPYING=GPLv2;library/ 为 LGPL-2.1+) +# *** 分发二进制必须提供对应源码(GPLv2 §3)*** +# → 用 --with-sources 把源码包一起放上卡,或随卡附书面要约。 +# ncurses 6.5 MIT 风格(X11 许可),保留版权声明即可 +# lsof 4.99.4 Purdue Research Foundation 许可(BSD 风格 4 条), +# 要求文档与源码中保留作者与 Purdue 的署名(COPYING 第 2 条) +# 本脚本链接的 ncurses 只作为 slabtop 的静态依赖进入产物,不单独分发。 +# +# 用法: +# bash scripts/build/build_devkit.sh # 增量:缓存命中则不联网 +# bash scripts/build/build_devkit.sh --force # 强制重编 +# bash scripts/build/build_devkit.sh --clean # 清空工作区后重编 +# bash scripts/build/build_devkit.sh --offline # 只用已缓存的源码包(CI/离线) +# bash scripts/build/build_devkit.sh --with-sources +# bash scripts/build/build_devkit.sh --verify-only # 只对现有产物做静态/架构自检 +# +# 环境变量: +# MEOW_DEVKIT_WORK 工作区(源码包/编译树/prefix),默认 ~/.cache/meow-devkit +# MEOW_DEVKIT_JOBS 并行度,默认 nproc +# ============================================================================ +set -eu + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +OUT_DIR="$ROOT/fw_apps/devkit" +BIN_DIR="$OUT_DIR/bin" +WORK="${MEOW_DEVKIT_WORK:-$HOME/.cache/meow-devkit}" +SRC_DIR="$WORK/src" +BUILD_DIR="$WORK/build" +PREFIX="$WORK/prefix" +WRAP_DIR="$WORK/bin" +JOBS="${MEOW_DEVKIT_JOBS:-$(nproc 2>/dev/null || echo 4)}" + +ZIG_TARGET="mipsel-linux-musleabi" +ZIG_WANT="0.17" # 已验证;0.18+ 参数可能变,见 check_toolchain + +NCURSES_VER="6.5" +PROCPS_VER="4.0.4" +LSOF_VER="4.99.4" + +# 设备内核版本:写进 lsof 的 LINUXV,让它按目标内核(4.4)选代码路径 +TARGET_KERNEL_VSTR="4.4.94" + +# name|url|sha256 (sha256 是“哪份源码”的唯一权威,URL 只是取法之一) +SRC_NCURSES="ncurses-${NCURSES_VER}.tar.gz|https://mirrors.tuna.tsinghua.edu.cn/gnu/ncurses/ncurses-${NCURSES_VER}.tar.gz|136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6" +SRC_PROCPS="procps-ng-${PROCPS_VER}.tar.xz|https://downloads.sourceforge.net/project/procps-ng/Production/procps-ng-${PROCPS_VER}.tar.xz|22870d6feb2478adb617ce4f09a787addaf2d260c5a8aa7b17d889a962c5e42e" +SRC_LSOF="lsof-${LSOF_VER}.tar.xz|https://deb.debian.org/debian/pool/main/l/lsof/lsof_${LSOF_VER}+dfsg.orig.tar.xz|dc3e7f51cf03bc7fbcdd9e62d4a52825682092031bd1ae08b15fe7f4f582aebf" + +FORCE=0 +CLEAN=0 +OFFLINE=0 +WITH_SOURCES=0 +VERIFY_ONLY=0 +DOWNLOADS=0 + +log() { printf '\n== %s\n' "$*"; } +info() { printf ' %s\n' "$*"; } +die() { printf '\n!!! %s\n' "$*" >&2; exit 1; } + +usage() { + sed -n '3,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' +} + +while [ $# -gt 0 ]; do + case "$1" in + -f|--force) FORCE=1 ;; + --clean) CLEAN=1 ;; + --offline) OFFLINE=1 ;; + --with-sources) WITH_SOURCES=1 ;; + --verify-only) VERIFY_ONLY=1 ;; + -h|--help) usage; exit 0 ;; + *) die "未知参数: $1(-h 看用法)" ;; + esac + shift +done + +# --------------------------------------------------------------------------- +# 0) 工具链与宿主依赖 +# --------------------------------------------------------------------------- +check_toolchain() { + local zig_bin zig_ver major_minor + zig_bin="$(command -v zig || true)" + [ -n "$zig_bin" ] || die "找不到 zig。装法:pkg install zig(需 0.17.x)" + zig_ver="$("$zig_bin" version)" + major_minor="${zig_ver%.*}" + [ "$major_minor" = "$ZIG_WANT" ] || \ + die "zig 版本是 $zig_ver,本脚本只在 $ZIG_WANT.x 上验证过(target/静态链接行为可能变)" + + mkdir -p "$WRAP_DIR" + cat > "$WRAP_DIR/zigcc" </dev/null 2>&1 || die "缺宿主工具: $t" + done + # strip 必须能处理 ELF32/MIPS:优先 llvm-strip(zig 自带同源 llvm), + # GNU strip 来自 binutils 一般也支持,但 Android 自带的那份不保证。 + if command -v llvm-strip >/dev/null 2>&1; then + STRIP=llvm-strip + elif command -v "$zig_bin" >/dev/null 2>&1 && [ -x "$(dirname "$zig_bin")/llvm-strip" ]; then + STRIP="$(dirname "$zig_bin")/llvm-strip" + else + STRIP=strip + fi + info "strip → $STRIP" +} + +# --------------------------------------------------------------------------- +# 1) 源码获取(固定版本 + sha256;缓存命中不联网) +# --------------------------------------------------------------------------- +fetch_src() { + local spec="$1" name url sha got + name="${spec%%|*}"; spec="${spec#*|}" + url="${spec%%|*}"; sha="${spec#*|}" + if [ -f "$SRC_DIR/$name" ]; then + got="$(sha256sum "$SRC_DIR/$name" | cut -d' ' -f1)" + if [ "$got" = "$sha" ]; then + info "源码已缓存: $name" + return 0 + fi + info "缓存校验不符,删除重下: $name" + rm -f "$SRC_DIR/$name" + fi + [ "$OFFLINE" = 1 ] && die "离线模式但缺少源码: $SRC_DIR/$name" + info "下载 $name" + curl -fL --retry 3 --connect-timeout 20 -o "$SRC_DIR/$name.part" "$url" \ + || { rm -f "$SRC_DIR/$name.part"; die "下载失败: $url"; } + got="$(sha256sum "$SRC_DIR/$name.part" | cut -d' ' -f1)" + [ "$got" = "$sha" ] || { rm -f "$SRC_DIR/$name.part"; die "sha256 不符: $name(期望 $sha 实得 $got)"; } + mv "$SRC_DIR/$name.part" "$SRC_DIR/$name" + DOWNLOADS=$((DOWNLOADS + 1)) +} + +unpack() { # unpack + rm -rf "$2"; mkdir -p "$2" + tar xf "$SRC_DIR/$1" -C "$2" --strip-components=1 +} + +# --------------------------------------------------------------------------- +# 2) ncurses:只为 slabtop 提供静态 ncursesw(不单独分发) +# procps 的 slabtop/top 硬依赖 ncurses(configure 里 --without-ncurses 会把 +# 它们整个跳过),所以这是编 slabtop 的必要成本,不是为 htop 顺手加的。 +# terminfo 目录必须指向设备真实路径(/usr/share/terminfo),否则交互式 +# slabtop 会 "Error opening terminal":默认值是编译时 prefix,设备上没有。 +# slabtop -o(一次性输出)不初始化 curses,本来不受影响。 +# --------------------------------------------------------------------------- +build_ncurses() { + log "编译 ncurses $NCURSES_VER(静态 widec,无 progs/man/tests)" + unpack "ncurses-${NCURSES_VER}.tar.gz" "$BUILD_DIR/ncurses-src" + rm -rf "$BUILD_DIR/ncurses"; mkdir -p "$BUILD_DIR/ncurses" + ( + cd "$BUILD_DIR/ncurses" + CC="$ZIGCC" BUILD_CC=cc CFLAGS="-O2 -ffile-prefix-map=$BUILD_DIR/ncurses-src=/ncurses-$NCURSES_VER" \ + "$BUILD_DIR/ncurses-src/configure" \ + --host="$ZIG_TARGET" --prefix="$PREFIX/ncurses" \ + --without-shared --with-normal --without-debug --without-profile \ + --without-manpages --without-tests --without-ada \ + --without-cxx --without-cxx-binding --without-progs --without-tack \ + --enable-widec --disable-db-install \ + --enable-pc-files --with-pkg-config-libdir="$PREFIX/ncurses/lib/pkgconfig" \ + --with-default-terminfo-dir=/usr/share/terminfo \ + --with-terminfo-dirs="/usr/share/terminfo:/etc/terminfo:/lib/terminfo" \ + > "$BUILD_DIR/ncurses-configure.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/ncurses-configure.log"; die "ncurses configure 失败"; } + make -j"$JOBS" > "$BUILD_DIR/ncurses-make.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/ncurses-make.log"; die "ncurses make 失败"; } + make install > "$BUILD_DIR/ncurses-install.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/ncurses-install.log"; die "ncurses install 失败"; } + ) + info "libncursesw.a 就绪: $PREFIX/ncurses/lib" +} + +# --------------------------------------------------------------------------- +# 3) procps-ng:vmstat / slabtop / free / ps / top / pmap +# --------------------------------------------------------------------------- +build_procps() { + log "编译 procps-ng $PROCPS_VER" + unpack "procps-ng-${PROCPS_VER}.tar.xz" "$BUILD_DIR/procps-src" + rm -rf "$BUILD_DIR/procps"; mkdir -p "$BUILD_DIR/procps" + ( + cd "$BUILD_DIR/procps" + # PKG_CONFIG_LIBDIR 必须收紧:否则会捡到宿主(Termux/aarch64)的 ncursesw.pc, + # 结果是一边用 musl 头一边混进 bionic 头,watch/top 直接编不过。 + # 注意:下面这段环境变量赋值必须写成“无注释的连续续行”, + # 注释行会截断续行链,导致变量只作用于一条空命令。 + # + # ac_cv_func_malloc_0_nonnull / realloc:交叉编译时 autoconf 的运行期探测 + # 无法执行,不缓存这两个值会生成 rpl_malloc/rpl_realloc 引用而链接失败。 + PKG_CONFIG_PATH="$PREFIX/ncurses/lib/pkgconfig" \ + PKG_CONFIG_LIBDIR="$PREFIX/ncurses/lib/pkgconfig" \ + ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes \ + CC="$ZIGCC" CFLAGS="-O2 -ffunction-sections -fdata-sections -ffile-prefix-map=$BUILD_DIR/procps-src=/procps-ng-$PROCPS_VER" \ + LDFLAGS="-static -Wl,--gc-sections" \ + "$BUILD_DIR/procps-src/configure" \ + --host="$ZIG_TARGET" --prefix="$PREFIX/procps" \ + --disable-shared --enable-static --disable-nls --disable-kill \ + > "$BUILD_DIR/procps-configure.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/procps-configure.log"; die "procps configure 失败"; } + # 防呆:确认 ncurses 来自本 devkit 的 prefix,而不是宿主 Termux 的那份 + grep -q "$PREFIX/ncurses" "$BUILD_DIR/procps/Makefile" \ + || die "procps 的 NCURSES_CFLAGS/LIBS 没指向 devkit prefix(pkg-config 串到宿主了)" + make -j"$JOBS" > "$BUILD_DIR/procps-make.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/procps-make.log"; die "procps make 失败"; } + ) +} + +# --------------------------------------------------------------------------- +# 4) lsof:真版 lsof(busybox lsof 只有 pid/fd/设备号,没有 size/offset/协议) +# lsof 4.99.4 自带老式 Configure(不需要 autoconf),但版本探测读的是宿主 +# /usr/include/linux/version.h + uname,在 Termux/aarch64 上拿不到目标内核版本, +# 于是 Makefile 里退化成 -DLINUXV=00000 -DLSOF_VSTR="0.0.0"(Configure 会打 +# "unsupported dialect or version")。这两个宏在 linux dialect 里只影响 +# `lsof -v` 的显示,不影响代码分支 —— 但仍显式覆盖成目标内核 4.4.94,便于事后对账。 +# zig 的 musl 头没有 glibc 侧 linux/tcp.h 间接带来的 TCP 状态宏(TCP_ESTABLISHED +# 等 11 个),这里按 linux/tcp.h 的定义补齐;不补则 dsock.c 编译失败。 +# --------------------------------------------------------------------------- +build_lsof() { + log "编译 lsof $LSOF_VER" + rm -rf "$BUILD_DIR/lsof" + mkdir -p "$BUILD_DIR/lsof" + tar xf "$SRC_DIR/lsof-${LSOF_VER}.tar.xz" -C "$BUILD_DIR/lsof" --strip-components=1 + ( + cd "$BUILD_DIR/lsof" + CC="$ZIGCC" ./Configure -n linux > "$BUILD_DIR/lsof-configure.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/lsof-configure.log"; die "lsof Configure 失败"; } + grep -q 'unsupported dialect' "$BUILD_DIR/lsof-configure.log" \ + && info "注意:Configure 报 unsupported dialect(宿主 uname 6.x),版本宏下面用 -D 覆盖" + # lsof 的 Makefile 里 CFLAGS = CDEFS INCL DEP DEBUG;额外宏走 DEBUG 注入。 + # -O2 必须显式给:zig cc 无 -O 时按 Debug 编,会插入 UBSan 桩导致链接失败。 + make -j"$JOBS" CC="$ZIGCC" \ + DEBUG="-O2 -DLINUXV=44094 -DLSOF_VSTR=\"$TARGET_KERNEL_VSTR\" -DTCP_ESTABLISHED=1 -DTCP_SYN_SENT=2 -DTCP_SYN_RECV=3 -DTCP_FIN_WAIT1=4 -DTCP_FIN_WAIT2=5 -DTCP_TIME_WAIT=6 -DTCP_CLOSE=7 -DTCP_CLOSE_WAIT=8 -DTCP_LAST_ACK=9 -DTCP_LISTEN=10 -DTCP_CLOSING=11" \ + > "$BUILD_DIR/lsof-make.log" 2>&1 \ + || { tail -20 "$BUILD_DIR/lsof-make.log"; die "lsof make 失败"; } + ) +} + +# --------------------------------------------------------------------------- +# 5) 收集 + strip + 逐项验证 +# --------------------------------------------------------------------------- +# 产出清单:<源路径>|<目标名>|<出处>|<许可证> +# 只放「真机实测能跑且真有价值」的:vmstat/slabtop(busybox 没有)、 +# free/ps/pmap(busybox 版字段残缺)、lsof(busybox 版只有 pid/fd)。 +# 不交付 top:本机(32 位 MIPS + 4.4.94)上 procps 4.0.4 的 top 表头/内存单位 +# 输出明显错误(时长字段溢出、Mem 显示 59.4 GiB 而 MemTotal 只有 55 MB), +# 且卡上已有 htop 3.5.3(673KB),top 属重复且不可信 → 见 DEVKIT.md「未交付清单」。 +TOOLS=" +$BUILD_DIR/procps/src/vmstat|vmstat|procps-ng $PROCPS_VER|GPL-2.0-or-later +$BUILD_DIR/procps/src/slabtop|slabtop|procps-ng $PROCPS_VER|GPL-2.0-or-later +$BUILD_DIR/procps/src/free|free|procps-ng $PROCPS_VER|GPL-2.0-or-later +$BUILD_DIR/procps/src/ps/pscommand|ps|procps-ng $PROCPS_VER|GPL-2.0-or-later +$BUILD_DIR/procps/src/pmap|pmap|procps-ng $PROCPS_VER|GPL-2.0-or-later +$BUILD_DIR/lsof/lsof|lsof|lsof $LSOF_VER|Purdue/BSD-style +" + +FAIL=0 +collect_and_verify() { + log "收集产物并验证" + rm -rf "$BIN_DIR"; mkdir -p "$BIN_DIR" + printf '%-8s %10s %s\n' "工具" "体积(B)" "静态/架构" + printf '%-8s %10s %s\n' "--------" "----------" "--------------------" + while IFS='|' read -r src name origin lic; do + [ -n "$src" ] || continue + [ -f "$src" ] || die "缺产物: $src(编译没成功?)" + cp "$src" "$BIN_DIR/$name" + chmod 755 "$BIN_DIR/$name" + "$STRIP" --strip-all "$BIN_DIR/$name" + if verify_binary "$BIN_DIR/$name"; then + printf '%-8s %10s %s\n' "$name" "$(wc -c < "$BIN_DIR/$name")" "OK: static/ELF32/MIPS32r2/0 NEEDED" + else + FAIL=$((FAIL + 1)) + fi + done </dev/null | grep -c NEEDED || true)" + case "$desc" in + *"statically linked"*) ;; + *) info "FAIL $(basename "$f"): 非静态 → $desc"; return 1 ;; + esac + [ "$cls" = "ELF32" ] || { info "FAIL $(basename "$f"): Class=$cls"; return 1; } + case "$mach" in *MIPS*) ;; *) info "FAIL $(basename "$f"): Machine=$mach"; return 1 ;; esac + case "$flags" in *mips32r2*) ;; *) info "FAIL $(basename "$f"): Flags=$flags"; return 1 ;; esac + [ "$needed" = "0" ] || { info "FAIL $(basename "$f"): 有 $needed 条 NEEDED"; return 1; } + case "$desc" in *"not stripped"*) info "警告 $(basename "$f"): 未 strip";; esac + return 0 +} + +# --------------------------------------------------------------------------- +# 6) MANIFEST / README / 许可证 / (可选)源码 +# --------------------------------------------------------------------------- +write_meta() { + log "写 MANIFEST / README / 许可证" + mkdir -p "$OUT_DIR/licenses" + + cp "$BUILD_DIR/procps-src/COPYING" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING" 2>/dev/null || true + cp "$BUILD_DIR/procps-src/COPYING.LIB" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING.LIB" 2>/dev/null || true + cp "$BUILD_DIR/ncurses-src/COPYING" "$OUT_DIR/licenses/ncurses-${NCURSES_VER}-COPYING" 2>/dev/null || true + cp "$BUILD_DIR/lsof/COPYING" "$OUT_DIR/licenses/lsof-${LSOF_VER}-COPYING" 2>/dev/null || true + + { + echo "MeowOS devkit — 静态 MIPS32r2 调试工具集" + echo "生成时间: $(date -u '+%Y-%m-%dT%H:%M:%SZ') zig: $("${ZIGCC}" --version 2>/dev/null | head -1) target: $ZIG_TARGET" + echo + printf '%-8s %-18s %-20s %10s %s\n' "tool" "source" "license" "size(B)" "sha256" + while IFS='|' read -r src name origin lic; do + [ -n "$src" ] || continue + printf '%-8s %-18s %-20s %10s %s\n' "$name" "$origin" "$lic" \ + "$(wc -c < "$BIN_DIR/$name")" "$(sha256sum "$BIN_DIR/$name" | cut -d' ' -f1)" + done < "$OUT_DIR/MANIFEST.txt" + + cat > "$OUT_DIR/README.txt" <<'EOS' +MeowOS devkit — 设备端调试工具(静态 MIPS32r2) +================================================ + +加到 PATH(SSH 会话,临时): + export PATH=/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH + +持久化(rootfs 的 /etc/profile.d/meow_card_path.sh 已把 MeowOS/opt/bin 前插, +但 devkit 是独立目录,需要再加一行): + if [ -d /usr/data/mnt/sd_0/MeowOS/opt/devkit/bin ]; then + export PATH="/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH" + fi + +exFAT 没有 Unix 权限位(挂载 fmask=0022 → 文件一律 0755),也没有符号链接。 +本目录里都是实体可执行文件,拷到卡上即可执行,不需要 chmod,也不能用软链。 +若手工从其他文件系统拷入后发现不可执行,执行一次: + chmod 755 /usr/data/mnt/sd_0/MeowOS/opt/devkit/bin/* + +与 busybox 同名命令的关系(devkit/bin 前插 PATH 时生效): + free ps lsof → 会被本目录版本覆盖(procps/lsof 字段更全) + vmstat slabtop pmap → busybox 没有,直接新增 +要临时用回 busybox 版:/bin/busybox free / busybox ps / busybox lsof +注意:/usr/bin/{free,ps,lsof,top} 目前都是指向 /bin/busybox 的软链, +所以“前插 devkit/bin”是唯一让新版本生效的方式(不要覆盖 /usr/bin 里的软链: +rootfs 是 ubifs,随固件回刷会被还原,而且 busybox 软链是基线的一部分)。 + +已知限制: + - pmap 只能读 /proc//maps;本机 CONFIG_PROC_PAGE_MONITOR=n, + /proc//smaps 不存在,所以 pmap -x 拿不到 PSS/私有脏页。 + - slabtop -o(一次性输出)不需要 terminfo,也不要求 TTY; + 交互模式走 /usr/share/terminfo(设备已有 xterm 等条目;已编进 ncurses)。 + - 不提供 top:procps 4.0.4 的 top 在本机输出不可信(见仓库 scripts/build/DEVKIT.md)。 +EOS +} + +# --------------------------------------------------------------------------- +# 主流程 +# --------------------------------------------------------------------------- +stamp_value() { + { + sha256sum "${BASH_SOURCE[0]}" + echo "$NCURSES_VER $PROCPS_VER $LSOF_VER $ZIG_TARGET $TARGET_KERNEL_VSTR" + } | sha256sum | cut -d' ' -f1 +} + +main() { + local t0 t1 + t0=$(date +%s) + mkdir -p "$SRC_DIR" "$BUILD_DIR" "$PREFIX" + + if [ "$VERIFY_ONLY" = 1 ]; then + # 只校验现有产物:不要求 zig/make,评审者用系统自带 file/readelf 即可 + collect_verify_existing + [ "$FAIL" = 0 ] || die "$FAIL 个产物未通过自检" + return 0 + fi + + check_toolchain + + local cur_stamp old_stamp="" + cur_stamp="$(stamp_value)" + [ -f "$WORK/stamp" ] && old_stamp="$(cat "$WORK/stamp")" + + if [ "$CLEAN" = 1 ]; then + log "清理工作区 $WORK" + rm -rf "$BUILD_DIR" "$PREFIX" + mkdir -p "$BUILD_DIR" "$PREFIX" + old_stamp="" + fi + + if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ -d "$BIN_DIR" ]; then + local ok=1 + for n in vmstat slabtop free ps pmap lsof; do + [ -f "$BIN_DIR/$n" ] && verify_binary "$BIN_DIR/$n" >/dev/null 2>&1 || ok=0 + done + if [ "$ok" = 1 ]; then + t1=$(date +%s) + log "产物已是最新(脚本与版本未变),跳过编译" + info "输出目录: $BIN_DIR" + info "用时 $((t1 - t0))s,联网 $DOWNLOADS 次" + return 0 + fi + fi + + log "取源码(固定版本 + sha256 校验)" + fetch_src "$SRC_NCURSES" + fetch_src "$SRC_PROCPS" + fetch_src "$SRC_LSOF" + + build_ncurses + build_procps + build_lsof + + collect_and_verify + [ "$FAIL" = 0 ] || die "$FAIL 个产物未通过静态/架构自检(见上)" + + write_meta + + if [ "$WITH_SOURCES" = 1 ]; then + log "复制源码包到 $OUT_DIR/src(GPL 合规用)" + mkdir -p "$OUT_DIR/src" + cp "$SRC_DIR/ncurses-${NCURSES_VER}.tar.gz" "$SRC_DIR/procps-ng-${PROCPS_VER}.tar.xz" \ + "$SRC_DIR/lsof-${LSOF_VER}.tar.xz" "$OUT_DIR/src/" + info "共 $(du -sh "$OUT_DIR/src" | cut -f1)" + fi + + printf '%s\n' "$cur_stamp" > "$WORK/stamp" + t1=$(date +%s) + log "完成" + info "输出: $OUT_DIR" + info "体积合计: $(du -sh "$BIN_DIR" | cut -f1)" + info "用时 $((t1 - t0))s,本次联网下载 $DOWNLOADS 次" +} + +collect_verify_existing() { + local n bad=0 + for n in vmstat slabtop free ps pmap lsof; do + if [ ! -f "$BIN_DIR/$n" ]; then + info "缺 $BIN_DIR/$n"; bad=$((bad + 1)); continue + fi + verify_binary "$BIN_DIR/$n" && info "OK $n $(wc -c < "$BIN_DIR/$n") B" || bad=$((bad + 1)) + done + FAIL=$bad +} + +main "$@" From 0531603779eef26d4fdd7452dcc47c4f81af6bec Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 19:29:27 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(devkit):=20M24=20r1=20=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=20=E2=80=94=20P1=20--with-sources=20=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E6=BC=8F=E6=BA=90=E7=A0=81=20+=20time64=20=E5=BD=92?= =?UTF-8?q?=E5=9B=A0=E6=9B=B4=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P1(阻断):stamp 命中时 main 提前 return,--with-sources 在 return 之后, 于是「先构建再 --with-sources」会打印成功、退出码 0、却不产出 src/。 - 把源码整理抽成 stage_sources(),stamp 命中路径先兑现 WITH_SOURCES 再返回; 整理后按钉住的 sha256 复核(sha 漂移/缺文件都重铺),任何失败即非零退出 - 新增 test_build_devkit.sh(12 项,秒级):stamp 命中下的 --with-sources、 src 缺文件/被改坏的自愈、--verify-only 的 MANIFEST 篡改检测 - skip 条件额外要求 MANIFEST 与 bin 逐条 sha 一致;--clean 同时清 out/src; 全新构建未带 --with-sources 时删除上次残留 src/(避免源码与二进制错配) P2-a:top 排除理由改写。根因是 zig 0.17 musl 的 time64 ABI 错配(mips o32 是 ILP32 + 内核 4.4 无 time64 syscall,16B timespec 直传 32 位 syscall),不是 procps 的 bug;top 的 Mem 一栏是「百分比/总容量」不是 used GiB。新增 DEVKIT.md §9 记录证据(vmstat -t 空列、usleep(0.5s)×10 实测 0s、sleep(1) 正常、ps lstart 正常),并说明 M16 的 patch_v2/sdk/meow_ui_time.c 可复用(本轮实测该 .o 独立 编译、仅 5 个 libc 未定义符号)但本轮不接入的取舍——需要单独 mission 做链接期 接管 + 全量回归,不是 -D 能修的。 P2-b:--with-sources 现在同时放 build_devkit.sh + DEVKIT.md(GPLv2 §3 的 "scripts used to control compilation");补 LGPL-2.1 §6 静态链接 libproc2 的 义务说明;MANIFEST/README.txt 写明 src/ 与 licenses/。 P2-c:lsof 内嵌 Termux musl 的 resolv.conf 路径记为工具链副作用(实测 lsof -i 约 3s 超时、-nP 瞬时;端口名/本机名不受影响),不换工具链。 P3:软链路径写实(/bin/ps、/usr/bin/{free,lsof,top});MANIFEST 用 zig version 而非 clang 版本;chmod 措辞改成检查挂载参数;--verify-only 增加 MANIFEST 逐条 sha256/体积比对且不再依赖 date;stamp 纳入 zig 完整版本;许可证复制失败即报错。 验证:test_build_devkit.sh 12/12 通过;--verify-only 通过(含 MANIFEST 对账); 独立工作区离线重建 85s/0 下载,六个二进制 sha256 与上一版逐字节一致;设备侧 二进制未变(sha 与 r1 真机验证过的一致)。 --- scripts/build/DEVKIT.md | 235 +++++++++++++++++----------- scripts/build/README.md | 5 +- scripts/build/build_devkit.sh | 241 ++++++++++++++++++++++------- scripts/build/test_build_devkit.sh | 93 +++++++++++ 4 files changed, 431 insertions(+), 143 deletions(-) create mode 100755 scripts/build/test_build_devkit.sh diff --git a/scripts/build/DEVKIT.md b/scripts/build/DEVKIT.md index f5d317b..e046af8 100644 --- a/scripts/build/DEVKIT.md +++ b/scripts/build/DEVKIT.md @@ -1,13 +1,14 @@ # MeowOS devkit — 静态 MIPS32r2 调试工具集 TF 卡 `MeowOS/opt/devkit/bin/` 里那批静态二进制的构建说明、可行性实测、许可证留痕。 -构建入口:`bash scripts/build/build_devkit.sh`(产物在 `fw_apps/devkit/`,已被 `.gitignore` -的 `fw_apps/` 规则覆盖,不入库)。 +构建入口:`bash scripts/build/build_devkit.sh`;回归测试:`bash scripts/build/test_build_devkit.sh` +(产物在 `fw_apps/devkit/`,已被 `.gitignore` 的 `fw_apps/` 规则覆盖,不入库)。 ## 1. 为什么要有这个 设备(HiBy R3 Pro II / Ingenic X1600 / MIPS32r2 / Linux 4.4.94+ / 单核 / 56 MB)自带 -busybox 1.31.1,`free / ps / top / lsof` 都是指向 `/bin/busybox` 的软链,字段残缺; +busybox 1.31.1,`free / ps / top / lsof` 都是 busybox 的软链 +(实测:`/bin/ps -> busybox`,`/usr/bin/{free,lsof,top} -> ../../bin/busybox`),字段残缺; `vmstat`、`slabtop`、`pmap` 干脆没有。做内存分析时最缺的正是: - `slabtop` —— Slab 总量 ~11 MB 到底被谁占了(buffer_head / dentry / squashfs_inode…) @@ -23,16 +24,17 @@ busybox 1.31.1,`free / ps / top / lsof` 都是指向 `/bin/busybox` 的软链 | 工具 | 来源版本 | 许可证 | 体积(B) | 用途 / 相对 busybox 的增量 | |---|---|---|---|---| -| `slabtop` | procps-ng 4.0.4 | GPL-2.0-or-later | 394 408 | Slab 明细;`-o` 一次性输出不需要 TTY/terminfo | -| `vmstat` | procps-ng 4.0.4 | GPL-2.0-or-later | 216 924 | 系统趋势;busybox 无 | -| `ps` | procps-ng 4.0.4 | GPL-2.0-or-later | 324 980 | `-eo pid,rss,vsz,comm --sort=-rss` 等完整字段 | -| `pmap` | procps-ng 4.0.4 | GPL-2.0-or-later | 173 460 | 地址空间映射(读 `/proc/pid/maps`);busybox 无 | -| `free` | procps-ng 4.0.4 | GPL-2.0-or-later | 94 312 | 拆 buff/cache、available | +| `slabtop` | procps-ng 4.0.4 | GPL-2.0-or-later(依赖 libproc2 LGPL-2.1+) | 394 408 | Slab 明细;`-o` 一次性输出不需要 TTY/terminfo | +| `vmstat` | procps-ng 4.0.4 | 同上 | 216 924 | 系统趋势;busybox 无;`-t` 时间戳列本工具链下为空,见 §9 | +| `ps` | procps-ng 4.0.4 | 同上 | 324 980 | `-eo pid,rss,vsz,comm --sort=-rss` 等完整字段 | +| `pmap` | procps-ng 4.0.4 | 同上 | 173 460 | 地址空间映射(读 `/proc/pid/maps`);busybox 无 | +| `free` | procps-ng 4.0.4 | 同上 | 94 312 | 拆 buff/cache、available | | `lsof` | lsof 4.99.4 | Purdue/BSD-style | 789 496 | SIZE/OFF、TCP 状态、NAME、`-i` 网络视图 | | | | **合计** | **1 993 580 ≈ 1.9 MB** | | -同目录还有构建时生成的 `MANIFEST.txt`(每个二进制的 sha256)、`licenses/`(上游 COPYING)、 -`README.txt`(上卡说明)。`--with-sources` 会额外生成 `src/`(源码包,GPL 合规用)。 +同目录还有 `MANIFEST.txt`(每个二进制的 sha256 + 版本 + 许可证 + 体积)、 +`licenses/`(上游 COPYING / COPYING.LIB)、`README.txt`(上卡说明)。 +`--with-sources` 会额外生成 `src/`(三份源码包 + `build_devkit.sh` + 本文件)。 ## 3. 上卡后怎么用 @@ -49,69 +51,74 @@ fi **与 busybox 同名命令的先后关系**(前插 PATH 时): -| 名字 | 现状 | 前插后 | +| 名字 | 现状(实测软链) | 前插后 | |---|---|---| -| `free` `ps` `lsof` | `/usr/bin/*` 均是指向 `/bin/busybox` 的软链 | 解析到 devkit 版(功能更多) | +| `free` `lsof` `top` | `/usr/bin/x -> ../../bin/busybox` | `free`/`lsof` 解析到 devkit 版;`top` 仍是 busybox(本工具集不提供) | +| `ps` | `/bin/ps -> busybox` | 解析到 devkit 版 | | `vmstat` `slabtop` `pmap` | 不存在 | 新增 | -| `top` | busybox 软链;**本工具集不提供 top**(见 §7) | 仍为 busybox | -要临时用回 busybox 版:`/bin/busybox free`、`busybox ps`、`busybox lsof`。 -不要覆盖 `/usr/bin` 里的软链:rootfs 是 ubifs,随固件回刷即被还原,而且这些软链是基线的一部分。 +要临时用回 busybox 版:`busybox free`、`busybox ps`、`busybox lsof`。 +不要覆盖 `/usr/bin`、`/bin` 里的软链:rootfs 是 ubifs,随固件回刷即被还原,而且这些软链是基线的一部分。 ## 4. exFAT 语义(权限 / 软链) 设备挂载:`/dev/mmcblk0 on /usr/data/mnt/sd_0 type exfat (rw, fmask=0022, dmask=0022, symlink=0, noexec 未设置)`。 -- **无符号链接**(`symlink=0`):所以本工具集全部是实体文件,不靠 `argv[0]` 分派, - 也不提供任何 symlink(不像 busybox 那样一个多合一二进制 + 一堆软链)。 -- **无 Unix 权限位**:`fmask=0022` 让卡上新文件恒为 `0755`,因此**不需要 chmod**; - 反过来说,卡上也表达不了 `4755` 之类,任何需要 setuid 的工具都不适合放这。 -- **可执行性**:挂载参数里没有 `noexec`,且卡上现有 `MeowOS/opt/MeowOS…/meowpanel` - 就是 0755 直接执行的。真机验证时我们把同一批二进制放在设备 `/tmp` 跑通(见 §9)。 - 未在卡上实测执行,是因为本次任务约定「不往卡上写任何东西」(塔来推)。 +- **无符号链接**(`symlink=0`):本工具集全部是实体文件,不做 `argv[0]` 分派,也不提供 symlink + (不像 busybox 那样一个多合一二进制 + 一堆软链)。 +- **无 Unix 权限位**:`fmask=0022` 让卡上新文件恒为 `0755`,因此**不需要 chmod**—— + 反过来说 `chmod` 在这里本身是 no-op;卡上也表达不了 setuid,任何需要 setuid 的工具都不适合放这。 +- **可执行性**:挂载参数里没有 `noexec`,且卡上现有 `MeowOS/opt/MeowPanel/meowpanel` 就是 0755 直接执行的。 + 本轮真机验证是在设备 `/tmp`(同为 MIPS 目标、root 身份)做的,**没有往卡上写过任何东西**(约定:卡由塔推)。 ## 5. 重跑代价与可复现性 - **联网次数**:冷启动 **3 次**(ncurses / procps-ng / lsof 各一个 tarball,共 5.5 MB); 源码包按 sha256 校验后落在 `$MEOW_DEVKIT_WORK/src/`,此后**重跑 0 次联网**。 - **耗时**(8 核宿主实测):冷启动(含 3 次下载)**80 秒**(两次实测 80s / 86s); - 源码已缓存时全量重编约 **85 秒**;`stamp` 命中(脚本与版本都没变)时 **1 秒**直接跳过; - `--verify-only` 秒级,且不需要 zig。 + 源码已缓存时全量重编约 **65–85 秒**;`stamp` 命中(脚本、版本、zig 版本都没变)**1–2 秒**; + `--verify-only` 秒级,且只依赖 `file` / `readelf` / `sha256sum` / `awk`(不需要 zig、make、date、网络)。 +- **stamp 内容**:脚本 sha256 + zig 完整版本(0.17.0 / 0.17.1 会区分)+ target + 内核版本 + 三个上游版本。 - **可复现(逐字节)**:在两个不同工作区各建一次,六个二进制 **sha256 完全相同** (`ps` 用 `-ffile-prefix-map` 抹掉绝对源码路径;`lsof` 用裸名 `CC=zigcc` + 相对源码路径, - 不再把工具链/构建目录写进 `LSOF_CC/LSOF_CCFLAGS`)。同一份 `MANIFEST.txt` 里的 sha256 - 与设备上 `sha256sum` 的结果一致,可用来核对「跑的到底是哪个二进制」。 -- **可复现性**:三份源码都固定 `版本 + sha256 + URL`,校验不符直接失败;`zig 0.17.x` 版本 - 也做硬检查。不依赖任何「这次网络恰好成功」的偶然性 —— 离线环境可先拷 `src/*.tar.*` - 进工作区再用 `--offline`。 -- 工作区默认 `~/.cache/meow-devkit`(`MEOW_DEVKIT_WORK` 可改),不污染仓库。 + 不再把工具链/构建目录写进 `LSOF_CC/LSOF_CCFLAGS`)。`MANIFEST.txt` 里的 sha256 与设备上 + `sha256sum` 的结果一致,可用来核对「跑的到底是哪个二进制」。 +- `--verify-only` 除了静态/架构/NEEDED,还会把 `bin/` 六个文件与 `MANIFEST.txt` 的 sha256/体积 + **逐条比对**(只查 static/MIPS 是看不出内容漂移的)。 +- **回归测试**:`bash scripts/build/test_build_devkit.sh`(12 项,秒级,不重编)覆盖: + stamp 命中时 `--with-sources` 必须产出完整 `src/`(r1 P1 的回归点)、src/ 缺文件/被改坏能自愈、 + `--verify-only` 能拒绝被篡改的 MANIFEST。 ## 6. 许可证与分发合规(重点) | 上游 | 许可证 | 分发二进制时的义务 | |---|---|---| -| procps-ng 4.0.4 | **GPL-2.0-or-later**(`COPYING`=GPLv2;`library/` 为 LGPL-2.1+) | **必须提供完整对应源码**(GPLv2 §3),或书面的三年有效要约 | -| ncurses 6.5 | MIT 风格(X11 许可) | 保留版权与许可声明;不单独分发(只静态链入 slabtop) | +| procps-ng 4.0.4(apps) | **GPL-2.0-or-later** | **提供完整对应源码**(GPLv2 §3),含 "scripts used to control compilation and installation" | +| procps `library/`(libproc2) | **LGPL-2.1+** | `ps/vmstat/slabtop/free/pmap` **静态链接**了它 → LGPL-2.1 §6 另有要求,见下 | +| ncurses 6.5 | MIT 风格(X11) | 保留版权与许可声明;只静态链入 slabtop,不单独分发 | | lsof 4.99.4 | Purdue Research Foundation 许可(BSD 风格 4 条) | 文档与源码中保留作者/Purdue 署名;改过的版本要标注 | -**结论(不要含糊)**:`build_devkit.sh` 里的固定版本 + 下载 URL **不构成 GPLv2 §3 的合规**。 -URL 作者随时可能撤、镜像可能消失,而且 GPL 要求的是*分发者*提供的源码或要约。所以: +**GPLv2 §3**:`build_devkit.sh` 里的固定版本 + 下载 URL **不构成合规**。URL 作者可能撤、 +镜像可能消失,而且 GPL 要求的是*分发者*提供的源码或书面要约。因此 `--with-sources` 会把 +**三份源码包 + `build_devkit.sh` + 本文件**一起放进 `src/`(合计约 5.6 MB)。少一样都不算完整: +没有构建脚本,收卡人拿不到本轮用的 `configure`/`-D` 参数(这正是 "corresponding source" 明确包含的)。 -1. 若这批二进制随卡交到第三方手上 → 构建时加 **`--with-sources`**,把 - `ncurses-6.5.tar.gz` / `procps-ng-4.0.4.tar.xz` / `lsof-4.99.4.tar.xz` 一起放到卡上 - `MeowOS/opt/devkit/src/`(合计约 5.5 MB,TF 卡的容量代价可忽略); -2. 或者随卡附**书面要约**(有效期内提供源码,GPLv2 §3(b)); -3. `licenses/` 里的上游 COPYING 只是「许可声明留痕」,**不能替代源码义务**。 +**LGPL-2.1 §6(静态链接)**:五个 procps 工具静态链入 libproc2,除 GPL 的义务外还必须 +「显著提示使用了该库 + 附许可证副本 + 三选一」:(a) 随作品提供该库的完整源码(含修改); +(b) 改用共享库机制(静态交付做不到);(c) 书面要约(≥3 年)提供 (a) 的材料。 +本工具集走 **(a)**:`src/procps-ng-4.0.4.tar.xz` 里含 `library/` 全量源码, +`src/build_devkit.sh` 是本轮的唯一构建描述,`licenses/procps-ng-4.0.4-COPYING.LIB` 是许可证副本。 +若想更保守(收卡人不想自己重编),可以再额外提供可重链接的 `.o`(当前**未做**,属可选加固)。 -内部自用(只有自己插卡)不触发 §3 的分发义务,但既然这台机器的产物可能被拷来拷去, -建议一律 `--with-sources`。 +**结论**:内部自用不触发分发义务;但既然这批二进制可能被拷来拷去,建议一律 +`--with-sources` 上卡。`licenses/` 只是声明留痕,**不能替代源码义务**。 ## 7. 未交付清单(以及为什么) | 候选 | 结论 | 原因 | |---|---|---| -| `top`(procps 4.0.4) | **不交付** | 真机实测输出不可信:表头时长字段溢出成 `-343597384:00`,`Mem` 显示 `59.4/0.1 GiB`(MemTotal 只有 55 MB)。同时它在无 TTY 的批处理下会静默 `exit 1`(strace 显示 `ioctl(1,TIOCGWINSZ)=ENOTTY` 后直接 `exit_group(1)`),即使批处理可用也拿不到正确数据。卡上已有 htop 3.5.3,`top` 属重复且不可信 → 不凑数 | -| `htop` | 不构建 | 卡上 `MeowOS/opt/bin/htop` 已经是 **3.5.3**(673 744 B)。我们实测用 zig + 自编 ncursesw 也能编出来,但 strip 后 **756 908 B**,比现有版本更大(静态链入 musl + ncurses),没有增量价值 | +| `top`(procps 4.0.4) | **不交付** | 表头时钟被工具链 time64 问题打坏(显示 `-1202590843:00` 一类),无 TTY 时还会静默 `exit 1`。**不是 procps 的 bug**(r1 评审已更正我此前的错误归因),根因见 §9。卡上已有 htop,`top` 属重复 | +| `htop` | 不构建 | 卡上 `MeowOS/opt/bin/htop` 已是 **3.5.3**(673 744 B)。实测用 zig + 自编 ncursesw 也能编出 3.5.3,但 strip 后 **756 908 B**,比现有版本更大(静态链 musl+ncurses)。**且我此前只量了体积、没做运行时验证** —— 它会继承 §9 的 time64 问题(`htop -d 5`(0.5s)这类亚秒刷新会退化成忙等空转),所以「加个 build_htop() 即可」的说法不成立 | | `watch` | 不构建 | busybox 1.31 已有 `watch`;procps 版 403 KB,收益不抵体积 | | `pgrep`/`pkill` | 不构建 | busybox 已有;procps 版各 260 KB(链接整份 libproc) | | `uptime`/`tload`/`pidof`/`pidwait`/`w` | 不构建 | busybox 已有 `uptime`/`pidof`;`tload`/`w` 在本机无调试价值 | @@ -127,14 +134,14 @@ URL 作者随时可能撤、镜像可能消失,而且 GPL 要求的是*分发 宿主:Termux/Android aarch64,zig 0.17.0(自带 musl libc 与头文件),`make`、`curl`、`pkg-config`。 **没有** autoconf/automake/libtool,**没有**目标内核头,sysroot 里只有 zlib(本工具集不需要它)。 -- **procps-ng 4.0.4** ✅ 可编。两个必须处理的坑: - 1. `slabtop`(`src/slabtop.c` 直接 `#include `)与 `top` 硬依赖 ncurses; - `configure --without-ncurses` 会把它们整个跳过 → 要 `slabtop` 就必须先交叉编 ncurses。 +- **procps-ng 4.0.4** ✅ 可编。三个必须处理的坑: + 1. `slabtop`(`src/slabtop.c` 直接 `#include `)硬依赖 ncurses; + `configure --without-ncurses` 会把它整个跳过 → 要 `slabtop` 就必须先交叉编 ncurses。 2. 交叉编译下 autoconf 的 `malloc(0)/realloc(0)` 运行期探测无法执行,不缓存 `ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes` 会引入 `rpl_malloc/rpl_realloc` 未定义引用,链接失败。 - 3. 另外必须把 `PKG_CONFIG_LIBDIR` 收紧到自编 prefix,否则会捡到宿主 aarch64 的 - `ncursesw.pc`,结果是「musl 头 + bionic 头」混编(`watch` 直接编不过)。 + 3. 必须把 `PKG_CONFIG_LIBDIR` 收紧到自编 prefix,否则会捡到宿主 aarch64 的 + `ncursesw.pc`,结果是「musl 头 + bionic 头」混编(`watch` 直接编不过)。脚本里有事后断言。 - **ncurses 6.5** ✅ 可编(`--without-shared --with-normal --enable-widec --without-progs` 等)。 **必须** `--with-default-terminfo-dir=/usr/share/terminfo --with-terminfo-dirs=...`: 否则交互式 `slabtop` 会 `Error opening terminal: xterm.`(默认值是编译期 prefix,设备上不存在)。 @@ -148,43 +155,78 @@ URL 作者随时可能撤、镜像可能消失,而且 GPL 要求的是*分发 状态宏,`dsock.c` 会编不过 → 脚本里 `-D` 补齐(值取自 `linux/tcp.h`)。 4. zig cc **不带 `-O` 时按 Debug 编**,会插入 UBSan 桩(`__ubsan_handle_negate_overflow`) 导致链接失败 → 所有编译都显式 `-O2`。 -- **htop 3.5.3** ✅ 可编(用自编 ncursesw,`--disable-hwloc/sensors/delayacct`),strip 后 756 908 B —— 比卡上现有版本大,故不交付。 +- **htop 3.5.3** ✅ 可编(用自编 ncursesw,`--disable-hwloc/sensors/delayacct`),strip 后 756 908 B —— + 比卡上现有版本大,且未做运行时验证(见 §7、§9),故不交付。 - **perf** ❌ 不可行:依赖目标内核源码树(`tools/perf` 要按内核版本配 `perf_event_attr`), - 且设备内核未必开了 `CONFIG_PERF_EVENTS` 相关选项;没有内核头也是硬阻塞。 -- **valgrind** ❌ 不可行:没有 MIPS32 后端(valgrind 官方支持 x86/amd64/arm/aarch64/ppc/s390, - 无 mips),不是交叉编译问题,是根本没有后端。 -- **gdb** ❌ 未尝试/不建议:设备已带 `gdbserver`,宿主侧可以用交叉 gdb 连;在 56 MB 单核设备上 - 跑 gdb 本体收益极低、体积巨大。 -- **ltrace** ❌ 不可行:需要解析目标 ELF 动态符号(`PT_DYNAMIC` + PLT),本机程序多为 - 静态/精简,且 ltrace 的构建依赖 libelf + 复杂 autoconf 探测,投入产出不划算。 + 且设备内核未必开了相关选项;没有内核头也是硬阻塞。 +- **valgrind** ❌ 不可行:**没有 MIPS32 后端**(官方只支持 x86/amd64/arm/aarch64/ppc/s390), + 不是交叉编译问题。 +- **gdb** ❌ 不建议:设备已带 `gdbserver`,宿主侧可用交叉 gdb 连;在 56 MB 单核设备上跑 gdb 本体收益极低、体积巨大。 +- **ltrace** ❌ 不可行:需要 libelf + 复杂 autoconf 探测,且本机程序多为静态/精简,投入产出不划算。 - **sysstat(sar/iostat/mpstat)** ❌ 未做:autoconf 探测多、依赖 `gettext`/`libsensors` 等, - 且本机是单核、无块设备统计(`/proc/diskstats` 对 TF 卡意义有限),收益低。 -- **smem** ⚠️ 价值为零:依赖 `/proc//smaps` 的 PSS,本机 `CONFIG_PROC_PAGE_MONITOR=n`, - 没有 smaps → 拿不到任何它要的数据。 + 且本机单核、`/proc/diskstats` 对 TF 卡意义有限。 +- **smem** ⚠️ 价值为零:依赖 `/proc//smaps` 的 PSS,本机没有 smaps。 -## 9. 真机验证证据(2026-11-06,root@10.76.208.50) +## 9. 已知限制:工具链的 time64 在 4.4 内核上不可用 -产物拷到设备 `/tmp/devkit`(tmpfs)执行,验证完已删除;**没有**往 TF 卡写过任何东西。 +**这不是 procps/htop 的 bug,是 zig 0.17 自带 musl 在 mipsel 上的 ABI 错配**(r1 评审独立定根因, +我此前把 `top` 的表头问题误判成 procps bug,此处更正)。 + +机制(三段,均可复验): + +1. zig 0.17 的 `mipsel-linux-musleabi` 是 **time64**:`sizeof(time_t)=8`、`sizeof(long)=4`, + `struct timespec` 16 字节;同时 clang 前端给 mips o32 预定义了 `__ILP32__`。 +2. zig 自带 musl(`libc/musl/src/time/clock_gettime.c`、`time/clock_nanosleep.c` 等)的守卫写成 + `#if defined(SYS_*_time64) && !defined(__ILP32__)`;上游 musl 1.2.5 没有这个 `!defined(__ILP32__)`。 +3. MIPS o32 是 ILP32、内核 4.4 又没有 time64 syscall → 走 `#else` 分支,把 **16 字节的 time64 结构** + 直接交给 **32 位 time32 系统调用**:内核只写/只读前 8 字节,高 32 位保持未初始化。 + +真机实测(本轮): + +| 现象 | 结果 | 影响 | +|---|---|---| +| `clock_gettime` / `time()` / `gettimeofday()` | rc=0 但值是垃圾(评审另测:`sec=2823213556008072608`,`localtime_r` 返回 NULL) | 任何按墙钟格式化时间的功能 | +| `vmstat -t` | 表头有 `-----timestamp-----`,**数据行该列整列为空**(`localtime()` 返回 NULL,`strftime` 被跳过) | `vmstat` 核心的 3 组计数**正常**;只有时间戳列不可用 | +| `top` 表头时钟 | 显示 `-1202590843:00` / `-343597384:00` 一类 | 这就是 §7 不交付 top 的原因 | +| `usleep(0.5s)` ×10 | **实测 0 秒**(应为 5 秒):`sleep()` 走 32 位 syscall,内核把 `tv_sec` 高半字当 `tv_nsec`(恒 0),亚秒被抹平 | 亚秒轮询会退化成忙等/空转 | +| `sleep(1)`(整秒) | 正常:`vmstat 1 3` 墙钟实测 2 秒 | 整秒采样周期不受影响 | +| `ps -eo lstart` | 正常 | 时间来自 `/proc/stat` 的 `btime` + jiffies,不走 libc 取时 | +| `slabtop`/`free`/`pmap`/`lsof` | 不受影响 | 不显示墙钟 | + +**能否复用仓里已有的修复?** 有。M16 的 `patch_v2/sdk/meow_ui_time.c`(文档 `patch_v2/sdk/CLOCK_ABI.md`) +用「链接期强定义」接管了 `__clock_gettime` / `__clock_gettime64` / `__nanosleep_time64` / +`__clock_nanosleep(_time64)` / `__timedwait*` / `__timerfd_*` / `__clock_settime64`, +内部按内核 o32 time32 ABI 取数再升成 64 位。 + +- **可复用的证据(本轮实测)**:`zigcc -O2 -I. -Iinclude -c patch_v2/sdk/meow_ui_time.c` 单文件编译成功, + 未定义符号只有 5 个 libc 符号(`__errno_location`、`__pthread_setcancelstate`、`__syscall_cp`、 + `__udivdi3`、`syscall`),不依赖 SDK 其它目标文件。 +- **代价**:必须把该 `.o` 放在 libc 之前参与链接(才会盖掉 libc 里那份),要给两套构建系统分别注入: + procps 走 libtool 的 `LDADD`、lsof 是手写 Makefile 的固定链接行;然后六个二进制全部重新真机验证。 + 估计 1–2 小时(含设备回归),产物每个 +几 KB。 +- **本轮为什么不做**:收益只有 `vmstat -t` 一列(`top` 不交付、`ps` 本来正常、其余不打时间), + 而这个改动会动到 libc 的取时/睡眠入口,风险面比收益大。**结论:可复用,但需要单独一条 mission + 做「接入 + 全量回归」**,不适合塞进这次修 P1 的补丁里。 +- **不加 shim 的替代方案**:给 zig 的 musl 打补丁(去掉 `!defined(__ILP32__)`)或换上游 musl 工具链。 + **这不是加一个 `-D` 能修的**:守卫在 libc 源码里,编译产物(libc.a)已经固定。 + +## 10. 路径清洁:lsof 里有一处宿主路径(工具链副作用) + +`strings lsof` 有一处 `/data/data/com.termux/files/usr/etc/resolv.conf`(其余五个产物没有宿主路径)。 +来源是 **Termux 打包的 zig 自带 musl**(`libc/musl/src/network/resolvconf.c` 被改成 Termux prefix), +不是 lsof 源码,也不是本脚本的路径泄漏(源码路径已用 `-ffile-prefix-map` 抹掉)。 + +- 设备上该路径不存在 → **真正的 DNS 反查会失败**;`lsof -i` 不带 `-n` 时会因此卡在超时(实测约 3 秒), + 带 `-nP` 瞬时返回。 +- 端口名/本机名不受影响(来自 `/etc/services`、`/etc/hosts`):实测 `lsof -i` 仍显示 `TCP *:ssh`、`UDP localhost:domain`。 +- **结论**:不改。换成非 Termux 的 zig/上游 musl 需要重装工具链并重做全部验证,代价大而收益仅「反向 DNS」; + 文档与 `README.txt` 已写明**用 `lsof -nP`**。 + +## 11. 真机验证证据(2026-11-06,root@10.76.208.50) + +产物拷到设备 `/tmp`(tmpfs)执行,验证完已删除;**没有**往 TF 卡写过任何东西。 ``` -# free -m - total used free shared buff/cache available -Mem: 55 30 2 1 27 24 -Swap: 0 0 0 - -# vmstat 1 3 -procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu------- - r b swpd free buff cache si so bi bo in cs us sy id wa st gu - 1 0 0 2584 8764 19388 0 0 0 0 449 6 3 3 94 0 0 0 - 0 0 0 2644 8764 19388 0 0 0 0 340 566 1 5 94 0 0 0 - 0 0 0 2644 8764 19388 0 0 0 0 332 526 1 2 97 0 0 0 - -# ps -eo pid,rss,vsz,comm --sort=-rss - PID RSS VSZ COMMAND - 1368 4832 16516 meow_launcherd - 4241 1608 3012 sh - ... - # slabtop -o | head -6 Active / Total Objects (% used) : 55852 / 61357 (91.0%) Active / Total Slabs (% used) : 2794 / 2794 (100.0%) @@ -194,13 +236,31 @@ procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu---- OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 12474 10746 86% 0.06K 198 63 792K buffer_head +# vmstat 1 3 (墙钟实测 2 秒,采样间隔正常) +procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu------- + r b swpd free buff cache si so bi bo in cs us sy id wa st gu + 1 0 0 2584 8764 19388 0 0 0 0 449 6 3 3 94 0 0 0 + 0 0 0 2644 8764 19388 0 0 0 0 340 566 1 5 94 0 0 0 + 0 0 0 2644 8764 19388 0 0 0 0 332 526 1 2 97 0 0 0 + +# free -m + total used free shared buff/cache available +Mem: 55 30 2 1 27 24 +Swap: 0 0 0 + +# ps -eo pid,rss,vsz,comm --sort=-rss + PID RSS VSZ COMMAND + 1368 4832 16516 meow_launcherd + 4241 1608 3012 sh + ... + # pmap 1 1: init 00400000 864K r-x-- busybox 004e8000 4K rw--- busybox 00969000 132K rwx-- [ anon ] -# lsof -nP -i +# lsof -nP -i (瞬时;不带 -n 约 3 秒,见 §10) COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME dropbear 1354 root 3u inet 2196 0t0 TCP *:22 (LISTEN) meow_netm 1372 root 5u inet 2611 0t0 UDP 127.0.0.1:53 @@ -208,17 +268,16 @@ meow_mdns 1375 root 3u inet 2906 0t0 UDP *:5353 meowpanel 1395 root 3u inet 2587 0t0 TCP *:8080 (LISTEN) ``` -- 六个二进制 `--help`/`-V` 全部 rc=0(`lsof --help` 不是合法选项,`lsof -v` 正常打印 4.99.4)。 +- 六个二进制 `--help`/`-V` 全部正常(`lsof --help` 不是合法选项,`lsof -v` 打印 4.99.4)。 - 交互式 `slabtop`(无 `-o`)在 `TERM=xterm` 下正常初始化 curses(stdout 写出 1599 字节转义序列); 对照 `TERM=bogus-term` 报 `Error opening terminal: bogus-term.` —— 说明 terminfo 路径确实命中 `/usr/share/terminfo`,不是内嵌 fallback。 -- 宿主侧静态/架构自检(每个产物):`file` = `statically linked, stripped`; - `readelf -h` = `ELF32` / `Machine: MIPS R3000` / `Flags: ... mips32r2`;`readelf -d` 无 `NEEDED`。 - 可用 `bash scripts/build/build_devkit.sh --verify-only` 复跑。 +- 设备上 `sha256sum` 与 `MANIFEST.txt` 逐条一致(测的就是交付的那份字节)。 +- 宿主侧静态/架构自检与 MANIFEST 对账:`bash scripts/build/build_devkit.sh --verify-only`。 -## 10. 已知限制 +## 12. 其它已知限制 - `pmap -x` 无 PSS(无 smaps),只有 maps 级别的段/文件信息。 -- 不提供 `top`(原因见 §7);需要交互式总览请用卡上已有的 `htop`。 -- 二进制按设备内核 4.4.94 编 `LINUXV`;换内核/换卡不影响运行(这些宏只影响显示)。 -- 产物是 32 位 MIPS32r2(o32 ABI,软浮点/无 FPU 依赖),只适用于 X1600 这类 MIPS32r2 目标。 +- 不提供 `top`(§7/§9);需要交互式总览请用卡上已有的 `htop`(注意其时钟/亚秒刷新同样受 §9 影响)。 +- 二进制按设备内核 4.4.94 编 `LINUXV`;该宏只影响 `lsof -v` 显示,换内核不影响运行。 +- 产物是 32 位 MIPS32r2(o32 ABI),只适用于 X1600 这类 MIPS32r2 目标。 diff --git a/scripts/build/README.md b/scripts/build/README.md index 635f6a0..de338b4 100644 --- a/scripts/build/README.md +++ b/scripts/build/README.md @@ -13,7 +13,8 @@ | `build_fw_apps.sh` | **通用打包脚本**(配置驱动)。读清单 → 组树 → repack → 校验 → 出固件。**不要改它的逻辑**,除非打包流程本身要变。 | | `fw_content.conf` | **固件内容清单**(默认配置)。每行一条 `|||`,声明"哪个本地文件装到固件哪里"。**改固件内容只改这里。** | | `build_devkit.sh` | **TF 卡调试工具集**交叉编译脚本(zig → 静态 MIPS32r2):`vmstat/slabtop/free/ps/pmap/lsof` → `fw_apps/devkit/`。与固件打包无关,独立跑。详见 `DEVKIT.md`。 | -| `DEVKIT.md` | devkit 的产物清单、许可证留痕、可行性实测、上卡 PATH/busybox 关系、已知限制。 | +| `test_build_devkit.sh` | `build_devkit.sh` 的回归测试(不重编,秒级):`--with-sources` 在 stamp 命中时必须产出完整 `src/`、`--verify-only` 必须能查出 MANIFEST 漂移。 | +| `DEVKIT.md` | devkit 的产物清单、许可证留痕(GPLv2 §3 / LGPL-2.1 §6)、可行性实测、time64 限制、上卡 PATH/busybox 关系。 | | `README.md` | 本说明。 | > 曾有一批独立脚本 `build_fw_apps_m2xx.sh`(每版复制一个、内部硬编码 md5),**已清理删除**,不再使用。它们的问题:需每版改脚本、且共享 `fw_apps/` 目录互相清空。现统一走"一个通用脚本 + 一个配置文件"。 @@ -127,7 +128,7 @@ md5sum patch_v2/meow_launcherd/meow_launcherd - **改源码忘更新 md5**:清单里 md5 是强校验,源变了但 md5 没改 → 打包直接失败。**凡是动了清单里列的源文件,先 `md5sum` 更新对应行。** - **SDK 库(`meow_ui.a`)不入固件**:它由 APP(如 hello)链接,走 TF 卡 `MeowAPPs/` 热更新,**不要**加进清单。 - **`fw_apps/` 被 gitignore**:固件产物与组装树都是构建产物,勿提交。 -- **`scripts/build/` 根目录 gitignore**:构建产物与组装树勿提交。但本目录的 **`README.md`/`build_fw_apps.sh`/`fw_content.conf`/`build_devkit.sh`/`DEVKIT.md` 已 `git add -f` 入库**(这几个是仓库侧应保留的脚本/配置/说明);其余(`fw_apps/` 产物、devkit 编译树等)不入库。 +- **`scripts/build/` 根目录 gitignore**:构建产物与组装树勿提交。但本目录的 **`README.md`/`build_fw_apps.sh`/`fw_content.conf`/`build_devkit.sh`/`test_build_devkit.sh`/`DEVKIT.md` 已 `git add -f` 入库**(这几个是仓库侧应保留的脚本/配置/说明);其余(`fw_apps/` 产物、devkit 编译树等)不入库。 --- diff --git a/scripts/build/build_devkit.sh b/scripts/build/build_devkit.sh index d085f1c..ebb8508 100755 --- a/scripts/build/build_devkit.sh +++ b/scripts/build/build_devkit.sh @@ -12,18 +12,22 @@ # fw_apps/devkit/MANIFEST.txt 版本/许可证/体积/校验摘要 # fw_apps/devkit/licenses/ 各上游 COPYING(分发留痕) # fw_apps/devkit/README.txt 上卡后怎么加 PATH、和 busybox 的同名关系 -# (--with-sources 时另有 fw_apps/devkit/src/ 源码包,GPL 合规用) +# (--with-sources 时另有 fw_apps/devkit/src/:三份源码包 + build_devkit.sh + +# DEVKIT.md,GPLv2 §3 的“对应源码”含构建控制脚本) # # 源码固定为「版本 + sha256 + 固定 URL」,全部校验后才编译;缓存命中则零联网。 # # 工具链:zig cc --target=mipsel-linux-musleabi(需 zig 0.17.x) # 不依赖 ~/.tools 下的私有脚本,自己生成 wrapper($WORK/bin/zigcc)。 # 不需要内核头(musl 头足够;只有 lsof 缺 11 个 TCP 状态宏,本脚本 -D 补上)。 +# ⚠ 该工具链在 4.4 内核上是 time64 ABI 错配:`vmstat -t` 时间戳列会是空, +# 原因/证据/后续修法见 DEVKIT.md「已知限制:工具链 time64」。不是 -D 能修的。 # # 许可证(二进制将来随卡分发时请保留此节): -# procps-ng 4.0.4 GPL-2.0-or-later(COPYING=GPLv2;library/ 为 LGPL-2.1+) -# *** 分发二进制必须提供对应源码(GPLv2 §3)*** -# → 用 --with-sources 把源码包一起放上卡,或随卡附书面要约。 +# procps-ng 4.0.4 GPL-2.0-or-later(COPYING=GPLv2) +# *** 分发二进制必须提供对应源码(GPLv2 §3,含构建脚本)*** +# procps library/ LGPL-2.1+:ps/vmstat/slabtop/free/pmap 静态链接 libproc2, +# LGPL-2.1 §6 要求提供可重链接 object code 或书面要约(见 DEVKIT.md §6) # ncurses 6.5 MIT 风格(X11 许可),保留版权声明即可 # lsof 4.99.4 Purdue Research Foundation 许可(BSD 风格 4 条), # 要求文档与源码中保留作者与 Purdue 的署名(COPYING 第 2 条) @@ -32,10 +36,14 @@ # 用法: # bash scripts/build/build_devkit.sh # 增量:缓存命中则不联网 # bash scripts/build/build_devkit.sh --force # 强制重编 -# bash scripts/build/build_devkit.sh --clean # 清空工作区后重编 +# bash scripts/build/build_devkit.sh --clean # 清空工作区与 out/src 后重编 # bash scripts/build/build_devkit.sh --offline # 只用已缓存的源码包(CI/离线) # bash scripts/build/build_devkit.sh --with-sources -# bash scripts/build/build_devkit.sh --verify-only # 只对现有产物做静态/架构自检 +# # 整理 src/(三份源码包 + 本脚本 + DEVKIT.md)。**产物已是最新时也会执行**, +# # 且整理后按 sha256 复核;任何一步失败即非零退出(P1 回归点)。 +# bash scripts/build/build_devkit.sh --verify-only +# # 只对现有产物自检:静态/架构/NEEDED + MANIFEST 逐条 sha256/体积比对。 +# # 只需 file / readelf / sha256sum / awk;不需要 zig、make、date、网络。 # # 环境变量: # MEOW_DEVKIT_WORK 工作区(源码包/编译树/prefix),默认 ~/.cache/meow-devkit @@ -44,6 +52,7 @@ set -eu ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" OUT_DIR="$ROOT/fw_apps/devkit" BIN_DIR="$OUT_DIR/bin" WORK="${MEOW_DEVKIT_WORK:-$HOME/.cache/meow-devkit}" @@ -68,6 +77,14 @@ SRC_NCURSES="ncurses-${NCURSES_VER}.tar.gz|https://mirrors.tuna.tsinghua.edu.cn/ SRC_PROCPS="procps-ng-${PROCPS_VER}.tar.xz|https://downloads.sourceforge.net/project/procps-ng/Production/procps-ng-${PROCPS_VER}.tar.xz|22870d6feb2478adb617ce4f09a787addaf2d260c5a8aa7b17d889a962c5e42e" SRC_LSOF="lsof-${LSOF_VER}.tar.xz|https://deb.debian.org/debian/pool/main/l/lsof/lsof_${LSOF_VER}+dfsg.orig.tar.xz|dc3e7f51cf03bc7fbcdd9e62d4a52825682092031bd1ae08b15fe7f4f582aebf" +# 卡上 src/ 必须包含的文件:三份源码包 + 构建脚本 + 本文档。 +# GPLv2 §3 的“对应源码”明确包含 "scripts used to control compilation and +# installation",缺了脚本,收卡人拿不到本轮用的 -D/configure 参数。 +SRC_STAGE_LIST="ncurses-${NCURSES_VER}.tar.gz procps-ng-${PROCPS_VER}.tar.xz lsof-${LSOF_VER}.tar.xz build_devkit.sh DEVKIT.md" + +# 交付的工具——验证、MANIFEST 比对、stamp 自检都从这里取,避免多处手写漂移 +TOOL_NAMES="vmstat slabtop free ps pmap lsof" + FORCE=0 CLEAN=0 OFFLINE=0 @@ -80,7 +97,7 @@ info() { printf ' %s\n' "$*"; } die() { printf '\n!!! %s\n' "$*" >&2; exit 1; } usage() { - sed -n '3,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + sed -n '3,/^# ============/p' "$SCRIPT_PATH" | sed 's/^# \{0,1\}//' } while [ $# -gt 0 ]; do @@ -100,13 +117,13 @@ done # 0) 工具链与宿主依赖 # --------------------------------------------------------------------------- check_toolchain() { - local zig_bin zig_ver major_minor + local zig_bin major_minor zig_bin="$(command -v zig || true)" [ -n "$zig_bin" ] || die "找不到 zig。装法:pkg install zig(需 0.17.x)" - zig_ver="$("$zig_bin" version)" - major_minor="${zig_ver%.*}" + ZIG_VERSION="$("$zig_bin" version)" + major_minor="${ZIG_VERSION%.*}" [ "$major_minor" = "$ZIG_WANT" ] || \ - die "zig 版本是 $zig_ver,本脚本只在 $ZIG_WANT.x 上验证过(target/静态链接行为可能变)" + die "zig 版本是 $ZIG_VERSION,本脚本只在 $ZIG_WANT.x 上验证过(target/静态链接行为可能变)" mkdir -p "$WRAP_DIR" cat > "$WRAP_DIR/zigcc" </dev/null 2>&1 || die "缺宿主工具: $t" @@ -166,6 +183,54 @@ unpack() { # unpack tar xf "$SRC_DIR/$1" -C "$2" --strip-components=1 } +# --------------------------------------------------------------------------- +# 1b) 交付源码包(--with-sources):卡上 src/ 的完整性与整理 +# P1 回归点:stamp 命中时也必须走这里,绝不能因为“跳过编译”就把源码静默漏掉。 +# --------------------------------------------------------------------------- +expected_sha_for() { # expected_sha_for → 钉住的 sha256 + case "$1" in + "ncurses-${NCURSES_VER}.tar.gz") printf '%s' "$SRC_NCURSES" | awk -F'|' '{print $3}' ;; + "procps-ng-${PROCPS_VER}.tar.xz") printf '%s' "$SRC_PROCPS" | awk -F'|' '{print $3}' ;; + "lsof-${LSOF_VER}.tar.xz") printf '%s' "$SRC_LSOF" | awk -F'|' '{print $3}' ;; + *) return 1 ;; + esac +} + +sources_complete() { + local f got + [ -d "$OUT_DIR/src" ] || return 1 + for f in $SRC_STAGE_LIST; do + [ -s "$OUT_DIR/src/$f" ] || return 1 + done + # 三份源码包按钉住的 sha 逐一核对(版本漂移比缺文件更危险:看起来有源码,其实对不上) + for f in "ncurses-${NCURSES_VER}.tar.gz" "procps-ng-${PROCPS_VER}.tar.xz" "lsof-${LSOF_VER}.tar.xz"; do + got="$(sha256sum "$OUT_DIR/src/$f" | cut -d' ' -f1)" + [ "$got" = "$(expected_sha_for "$f")" ] || return 1 + done + cmp -s "$SCRIPT_PATH" "$OUT_DIR/src/build_devkit.sh" || return 1 + cmp -s "$ROOT/scripts/build/DEVKIT.md" "$OUT_DIR/src/DEVKIT.md" || return 1 + return 0 +} + +stage_sources() { + local f + log "整理 $OUT_DIR/src(三份源码包 + 构建脚本 + 文档)" + fetch_src "$SRC_NCURSES" + fetch_src "$SRC_PROCPS" + fetch_src "$SRC_LSOF" + rm -rf "$OUT_DIR/src" + mkdir -p "$OUT_DIR/src" + for f in "ncurses-${NCURSES_VER}.tar.gz" "procps-ng-${PROCPS_VER}.tar.xz" "lsof-${LSOF_VER}.tar.xz"; do + cp "$SRC_DIR/$f" "$OUT_DIR/src/" || die "复制源码包失败: $f" + done + cp "$SCRIPT_PATH" "$OUT_DIR/src/build_devkit.sh" \ + || die "复制构建脚本失败: $SCRIPT_PATH(GPLv2 §3 要求带构建脚本)" + cp "$ROOT/scripts/build/DEVKIT.md" "$OUT_DIR/src/DEVKIT.md" \ + || die "复制 DEVKIT.md 失败: $ROOT/scripts/build/DEVKIT.md" + sources_complete || die "src/ 整理后仍不完整(sha256 或文件缺失)" + info "共 $(du -sh "$OUT_DIR/src" | cut -f1):$(ls "$OUT_DIR/src" | tr '\n' ' ')" +} + # --------------------------------------------------------------------------- # 2) ncurses:只为 slabtop 提供静态 ncursesw(不单独分发) # procps 的 slabtop/top 硬依赖 ncurses(configure 里 --without-ncurses 会把 @@ -330,14 +395,20 @@ write_meta() { log "写 MANIFEST / README / 许可证" mkdir -p "$OUT_DIR/licenses" - cp "$BUILD_DIR/procps-src/COPYING" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING" 2>/dev/null || true - cp "$BUILD_DIR/procps-src/COPYING.LIB" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING.LIB" 2>/dev/null || true - cp "$BUILD_DIR/ncurses-src/COPYING" "$OUT_DIR/licenses/ncurses-${NCURSES_VER}-COPYING" 2>/dev/null || true - cp "$BUILD_DIR/lsof/COPYING" "$OUT_DIR/licenses/lsof-${LSOF_VER}-COPYING" 2>/dev/null || true + # 许可证文件是分发合规的一部分:复制失败必须报错,不能 `|| true` 静默吞掉 + cp "$BUILD_DIR/procps-src/COPYING" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING" \ + || die "复制许可证失败: procps COPYING(分发合规必需)" + cp "$BUILD_DIR/procps-src/COPYING.LIB" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING.LIB" \ + || die "复制许可证失败: procps COPYING.LIB(LGPL-2.1 静态链接义务用)" + cp "$BUILD_DIR/ncurses-src/COPYING" "$OUT_DIR/licenses/ncurses-${NCURSES_VER}-COPYING" \ + || die "复制许可证失败: ncurses COPYING" + cp "$BUILD_DIR/lsof/COPYING" "$OUT_DIR/licenses/lsof-${LSOF_VER}-COPYING" \ + || die "复制许可证失败: lsof COPYING" { echo "MeowOS devkit — 静态 MIPS32r2 调试工具集" - echo "生成时间: $(date -u '+%Y-%m-%dT%H:%M:%SZ') zig: $("${ZIGCC}" --version 2>/dev/null | head -1) target: $ZIG_TARGET" + # zig 版本用 `zig version`(不是 zigcc --version 的 clang 版本号) + echo "生成时间: $(date -u '+%Y-%m-%dT%H:%M:%SZ') zig: $ZIG_VERSION target: $ZIG_TARGET" echo printf '%-8s %-18s %-20s %10s %s\n' "tool" "source" "license" "size(B)" "sha256" while IFS='|' read -r src name origin lic; do @@ -348,15 +419,18 @@ write_meta() { $(printf '%s\n' "$TOOLS" | sed '/^$/d') EOF echo - echo "源码(可复现性 + GPL 合规):" + echo "源码(可复现性 + GPL/LGPL 合规):" echo " ncurses $NCURSES_VER MIT-style sha256 $(printf '%s' "$SRC_NCURSES" | awk -F'|' '{print $3}')" - echo " procps-ng $PROCPS_VER GPL-2.0+ sha256 $(printf '%s' "$SRC_PROCPS" | awk -F'|' '{print $3}')" + echo " procps-ng $PROCPS_VER GPL-2.0+ / library=LGPL-2.1+ sha256 $(printf '%s' "$SRC_PROCPS" | awk -F'|' '{print $3}')" echo " lsof $LSOF_VER Purdue/BSD sha256 $(printf '%s' "$SRC_LSOF" | awk -F'|' '{print $3}')" - echo " 下载地址与重编方法: scripts/build/build_devkit.sh(--with-sources 可把源码包放到 src/)" + echo " (--with-sources 时)src/ 内还有 build_devkit.sh + DEVKIT.md(构建控制脚本,GPLv2 §3 要求)" + echo " 下载地址与重编方法: scripts/build/build_devkit.sh(--with-sources 把上述文件放进 src/)" echo echo "注意: procps-ng 是 GPL-2.0-or-later。把本目录随卡交付给第三方时," - echo " GPLv2 §3 要求提供对应源码 —— 请用 --with-sources 一起带上 src/," + echo " GPLv2 §3 要求提供对应源码(含构建脚本)—— 请用 --with-sources 带上 src/," echo " 或随卡附书面要约(有效期 ≥3 年)。仅给出下载 URL 不构成合规。" + echo " 另外 ps/vmstat/slabtop/free/pmap 静态链接了 LGPL-2.1+ 的 libproc2:" + echo " LGPL-2.1 §6 要求提供可重链接的 object code 或书面要约,详见 DEVKIT.md §6。" } > "$OUT_DIR/MANIFEST.txt" cat > "$OUT_DIR/README.txt" <<'EOS' @@ -372,17 +446,21 @@ MeowOS devkit — 设备端调试工具(静态 MIPS32r2) export PATH="/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH" fi -exFAT 没有 Unix 权限位(挂载 fmask=0022 → 文件一律 0755),也没有符号链接。 -本目录里都是实体可执行文件,拷到卡上即可执行,不需要 chmod,也不能用软链。 -若手工从其他文件系统拷入后发现不可执行,执行一次: - chmod 755 /usr/data/mnt/sd_0/MeowOS/opt/devkit/bin/* +源码与许可证: + 若随卡分发了源码:源码(含构建脚本 build_devkit.sh、DEVKIT.md)在 ../src/; + 各上游许可证原文在 ../licenses/;版本/体积/sha256 见 ../MANIFEST.txt。 + +exFAT 没有 Unix 权限位(挂载 fmask=0022 → 新文件一律 0755),也没有符号链接。 +本目录里都是实体可执行文件,拷到卡上即可执行,无需 chmod,也不能用软链。 +若拷入后无法执行,请检查挂载参数(不要有 noexec;`mount | grep sd_0`), +而不是反复 chmod —— fmask=0022 下 chmod 本身是 no-op。 与 busybox 同名命令的关系(devkit/bin 前插 PATH 时生效): free ps lsof → 会被本目录版本覆盖(procps/lsof 字段更全) vmstat slabtop pmap → busybox 没有,直接新增 -要临时用回 busybox 版:/bin/busybox free / busybox ps / busybox lsof -注意:/usr/bin/{free,ps,lsof,top} 目前都是指向 /bin/busybox 的软链, -所以“前插 devkit/bin”是唯一让新版本生效的方式(不要覆盖 /usr/bin 里的软链: +要临时用回 busybox 版:busybox free / busybox ps / busybox lsof +注意:/bin/ps -> busybox,/usr/bin/{free,lsof,top} -> ../../bin/busybox, +所以“前插 devkit/bin”是唯一让新版本生效的方式(不要覆盖这些软链: rootfs 是 ubifs,随固件回刷会被还原,而且 busybox 软链是基线的一部分)。 已知限制: @@ -390,7 +468,13 @@ rootfs 是 ubifs,随固件回刷会被还原,而且 busybox 软链是基线 /proc//smaps 不存在,所以 pmap -x 拿不到 PSS/私有脏页。 - slabtop -o(一次性输出)不需要 terminfo,也不要求 TTY; 交互模式走 /usr/share/terminfo(设备已有 xterm 等条目;已编进 ncurses)。 - - 不提供 top:procps 4.0.4 的 top 在本机输出不可信(见仓库 scripts/build/DEVKIT.md)。 + - 本工具链(zig 0.17 musl)在 4.4 内核上是 time64 ABI 错配: + `vmstat -t` 的时间戳列会是**空**(localtime 返回 NULL)。 + vmstat 采样间隔、ps 的 lstart、slabtop/free/pmap/lsof 均正常。 + 原因与后续修法见 ../src/DEVKIT.md「已知限制:工具链 time64」。 + - lsof 不带 -n 时会尝试反向解析(编译进 musl 的 resolv.conf 路径是构建机的, + 设备上不存在)→ 实测 `lsof -i` 约 3s 超时;请用 `lsof -nP`。 + - 不提供 top:见 ../src/DEVKIT.md(同样是 time64 问题,表头时钟溢出)。 EOS } @@ -399,23 +483,25 @@ EOS # --------------------------------------------------------------------------- stamp_value() { { - sha256sum "${BASH_SOURCE[0]}" - echo "$NCURSES_VER $PROCPS_VER $LSOF_VER $ZIG_TARGET $TARGET_KERNEL_VSTR" + sha256sum "$SCRIPT_PATH" + # 带 zig 的完整版本(0.17.0 → 0.17.1 也要触发重建:工具链 patch 会改产物) + echo "zig=$ZIG_VERSION target=$ZIG_TARGET kernel=$TARGET_KERNEL_VSTR" + echo "ncurses=$NCURSES_VER procps=$PROCPS_VER lsof=$LSOF_VER" } | sha256sum | cut -d' ' -f1 } main() { local t0 t1 - t0=$(date +%s) - mkdir -p "$SRC_DIR" "$BUILD_DIR" "$PREFIX" if [ "$VERIFY_ONLY" = 1 ]; then - # 只校验现有产物:不要求 zig/make,评审者用系统自带 file/readelf 即可 + # 只校验现有产物:不要求 zig/make/date,评审者用 file/readelf/sha256sum 即可 collect_verify_existing - [ "$FAIL" = 0 ] || die "$FAIL 个产物未通过自检" + [ "$FAIL" = 0 ] || die "$FAIL 项未通过自检" return 0 fi + t0=$(date +%s) + mkdir -p "$SRC_DIR" "$BUILD_DIR" "$PREFIX" check_toolchain local cur_stamp old_stamp="" @@ -423,24 +509,41 @@ main() { [ -f "$WORK/stamp" ] && old_stamp="$(cat "$WORK/stamp")" if [ "$CLEAN" = 1 ]; then - log "清理工作区 $WORK" + log "清理工作区 $WORK 与输出目录的 src/" rm -rf "$BUILD_DIR" "$PREFIX" mkdir -p "$BUILD_DIR" "$PREFIX" + if [ -d "$OUT_DIR/src" ]; then + rm -rf "$OUT_DIR/src" + fi old_stamp="" fi - if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ -d "$BIN_DIR" ]; then - local ok=1 - for n in vmstat slabtop free ps pmap lsof; do - [ -f "$BIN_DIR/$n" ] && verify_binary "$BIN_DIR/$n" >/dev/null 2>&1 || ok=0 - done - if [ "$ok" = 1 ]; then - t1=$(date +%s) - log "产物已是最新(脚本与版本未变),跳过编译" - info "输出目录: $BIN_DIR" - info "用时 $((t1 - t0))s,联网 $DOWNLOADS 次" - return 0 + local bin_ok=1 mf_ok=0 n + for n in $TOOL_NAMES; do + [ -f "$BIN_DIR/$n" ] && verify_binary "$BIN_DIR/$n" >/dev/null 2>&1 || bin_ok=0 + done + # 交付完整性也要一并作为 skip 条件:MANIFEST 缺失或与 bin 不符时宁可重建 + if [ -f "$OUT_DIR/MANIFEST.txt" ] && verify_manifest >/dev/null 2>&1; then + mf_ok=1 + fi + + if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ "$bin_ok" = 1 ] && [ "$mf_ok" = 1 ]; then + # P1 回归点:--with-sources 必须在这里就兑现,不能因为“跳过编译”而漏掉源码。 + # (曾经把这段放在 return 之后,结果 stamp 命中时静默不产出 src/,退出码仍是 0。) + if [ "$WITH_SOURCES" = 1 ]; then + if sources_complete; then + info "源码包已就绪: $OUT_DIR/src" + else + stage_sources + fi + elif [ -d "$OUT_DIR/src" ] && ! sources_complete; then + info "注意: $OUT_DIR/src 与当前版本/脚本不一致;若要分发请用 --with-sources 重建" fi + t1=$(date +%s) + log "产物已是最新(脚本与版本未变),跳过编译" + info "输出目录: $BIN_DIR" + info "用时 $((t1 - t0))s,联网 $DOWNLOADS 次" + return 0 fi log "取源码(固定版本 + sha256 校验)" @@ -458,11 +561,11 @@ main() { write_meta if [ "$WITH_SOURCES" = 1 ]; then - log "复制源码包到 $OUT_DIR/src(GPL 合规用)" - mkdir -p "$OUT_DIR/src" - cp "$SRC_DIR/ncurses-${NCURSES_VER}.tar.gz" "$SRC_DIR/procps-ng-${PROCPS_VER}.tar.xz" \ - "$SRC_DIR/lsof-${LSOF_VER}.tar.xz" "$OUT_DIR/src/" - info "共 $(du -sh "$OUT_DIR/src" | cut -f1)" + stage_sources + elif [ -d "$OUT_DIR/src" ]; then + # 上次 --with-sources 的残留可能与本次二进制不对应;留着会被误当“合规源码” + info "删除上次残留的 $OUT_DIR/src(本次未请求源码,避免与二进制错配)" + rm -rf "$OUT_DIR/src" fi printf '%s\n' "$cur_stamp" > "$WORK/stamp" @@ -471,17 +574,49 @@ main() { info "输出: $OUT_DIR" info "体积合计: $(du -sh "$BIN_DIR" | cut -f1)" info "用时 $((t1 - t0))s,本次联网下载 $DOWNLOADS 次" + if [ "$WITH_SOURCES" = 1 ]; then + info "源码包: $OUT_DIR/src($(du -sh "$OUT_DIR/src" | cut -f1))" + fi } collect_verify_existing() { local n bad=0 - for n in vmstat slabtop free ps pmap lsof; do + for n in $TOOL_NAMES; do if [ ! -f "$BIN_DIR/$n" ]; then info "缺 $BIN_DIR/$n"; bad=$((bad + 1)); continue fi verify_binary "$BIN_DIR/$n" && info "OK $n $(wc -c < "$BIN_DIR/$n") B" || bad=$((bad + 1)) done + verify_manifest || bad=$((bad + 1)) FAIL=$bad } +# MANIFEST.txt 的逐条 sha256/体积比对:只查“是不是静态 MIPS”看不出内容漂移 +# (旧版脚本/旧工具链编出来的同样是 static/MIPS)。这里把 MANIFEST 当权威对账。 +verify_manifest() { + local n line got_sha got_size bad=0 + local mf="$OUT_DIR/MANIFEST.txt" + if [ ! -f "$mf" ]; then + info "FAIL 缺 $mf(无法核对产物内容)" + return 1 + fi + for n in $TOOL_NAMES; do + # 只认表格行:$1=工具名,$(NF-1)=纯数字体积,$NF=64 位 sha。 + # (MANIFEST 末尾「源码」小节里也有以 lsof 开头的行,不加这两个约束会把两行都收进来。) + line="$(awk -v k="$n" '$1 == k && $(NF-1) ~ /^[0-9]+$/ && length($NF) == 64' "$mf")" + if [ -z "$line" ]; then + info "FAIL MANIFEST 缺 $n 的表格行"; bad=$((bad + 1)); continue + fi + got_sha="$(printf '%s\n' "$line" | awk '{print $NF}')" + got_size="$(printf '%s\n' "$line" | awk '{print $(NF-1)}')" + if [ "$got_sha" != "$(sha256sum "$BIN_DIR/$n" | cut -d' ' -f1)" ]; then + info "FAIL $n sha256 与 MANIFEST 不一致(产物被替换/旧版残留?)"; bad=$((bad + 1)) + elif [ "$got_size" != "$(wc -c < "$BIN_DIR/$n")" ]; then + info "FAIL $n 体积与 MANIFEST 不一致($got_size vs $(wc -c < "$BIN_DIR/$n"))"; bad=$((bad + 1)) + fi + done + [ "$bad" = 0 ] && info "OK MANIFEST.txt 与 bin/ 六项 sha256/体积逐条一致" + return $((bad > 0)) +} + main "$@" diff --git a/scripts/build/test_build_devkit.sh b/scripts/build/test_build_devkit.sh new file mode 100755 index 0000000..fe1f154 --- /dev/null +++ b/scripts/build/test_build_devkit.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# ============================================================================ +# test_build_devkit.sh — build_devkit.sh 的回归测试(不重编,秒级) +# +# 针对 r1 评审 P1 的回归点:**stamp 命中(跳过编译)时,--with-sources 必须 +# 真正产出 src/,不能静默成功**。另覆盖 --verify-only 的 MANIFEST 漂移检测。 +# +# 前置:MEOW_DEVKIT_WORK 里已有一次构建(脚本会先跑一次增量构建,命中则秒回; +# 缓存缺失时会联网下载/编译,第一次运行约 1 分钟)。 +# +# 用法:bash scripts/build/test_build_devkit.sh [-v] +# 退出码 0 = 全过;非 0 = 用例失败数。 +# ============================================================================ +set -u + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SCRIPT="$ROOT/scripts/build/build_devkit.sh" +export MEOW_DEVKIT_WORK="${MEOW_DEVKIT_WORK:-$HOME/.cache/meow-devkit}" +OUT="$ROOT/fw_apps/devkit" +TMP="$MEOW_DEVKIT_WORK/test-tmp" + +VERBOSE=0 +[ "${1:-}" = "-v" ] && VERBOSE=1 + +PASS=0 +FAILN=0 +pass() { PASS=$((PASS + 1)); printf 'PASS %s\n' "$*"; } +fail() { FAILN=$((FAILN + 1)); printf 'FAIL %s\n' "$*"; } +run() { # run <说明> <命令...> + local desc="$1"; shift + local log="$TMP/last.log" + if "$@" >"$log" 2>&1; then + [ "$VERBOSE" = 1 ] && sed 's/^/ | /' "$log" + return 0 + fi + printf ' 命令失败: %s\n' "$*" + sed 's/^/ | /' "$log" | tail -20 + return 1 +} + +mkdir -p "$TMP" +# 无论成败都恢复现场,别把测试的破坏留在产物目录里 +restore() { + [ -f "$TMP/MANIFEST.txt.bak" ] && cp "$TMP/MANIFEST.txt.bak" "$OUT/MANIFEST.txt" + rm -rf "$TMP" +} +trap restore EXIT + +echo "== 0) 确保基线构建(幂等;已构建则秒回)" +run "基线构建" bash "$SCRIPT" || { echo "!!! 基线构建失败,后续用例无意义"; exit 1; } +pass "基线构建" + +echo "== 1) P1 回归:stamp 命中 + --with-sources 必须产出 src/" +rm -rf "$OUT/src" +run "普通重跑(应命中 stamp 且不建 src/)" bash "$SCRIPT" || fail "T1a 普通重跑" +[ ! -d "$OUT/src" ] && pass "T1a 普通重跑不创建 src/(符合预期)" || fail "T1a 普通重跑意外创建了 src/" +run "--with-sources(stamp 命中路径)" bash "$SCRIPT" --with-sources || fail "T1b --with-sources 退出码非 0" +for f in ncurses-6.5.tar.gz procps-ng-4.0.4.tar.xz lsof-4.99.4.tar.xz build_devkit.sh DEVKIT.md; do + [ -s "$OUT/src/$f" ] && pass "T1b src/$f 存在且非空" || fail "T1b 缺 src/$f(P1 未修好)" +done + +echo "== 2) 缺文件自愈:删掉构建脚本后再 --with-sources" +rm -f "$OUT/src/build_devkit.sh" +run "--with-sources 补齐" bash "$SCRIPT" --with-sources || fail "T2 --with-sources 退出码非 0" +[ -s "$OUT/src/build_devkit.sh" ] && pass "T2 src/build_devkit.sh 被补回" || fail "T2 未补回 src/build_devkit.sh" + +echo "== 3) 内容漂移自愈:把 src 里的源码包改坏" +printf 'tamper\n' >> "$OUT/src/ncurses-6.5.tar.gz" +run "--with-sources 重铺" bash "$SCRIPT" --with-sources || fail "T3 --with-sources 退出码非 0" +if cmp -s "$OUT/src/ncurses-6.5.tar.gz" "$MEOW_DEVKIT_WORK/src/ncurses-6.5.tar.gz"; then + pass "T3 被改坏的源码包已恢复成钉住版本" +else + fail "T3 源码包未恢复" +fi + +echo "== 4) --verify-only:正常通过" +run "--verify-only" bash "$SCRIPT" --verify-only && pass "T4a --verify-only 通过" || fail "T4a --verify-only 失败" + +echo "== 5) --verify-only:能查出 MANIFEST 内容漂移" +cp "$OUT/MANIFEST.txt" "$TMP/MANIFEST.txt.bak" +awk 'BEGIN{done=0} { if (!done && $1=="vmstat") { $NF="0000000000000000000000000000000000000000000000000000000000000000"; done=1 } print }' \ + "$TMP/MANIFEST.txt.bak" > "$OUT/MANIFEST.txt" +if run "--verify-only(MANIFEST 被篡改)" bash "$SCRIPT" --verify-only; then + fail "T5 --verify-only 未发现 MANIFEST sha 被篡改" +else + pass "T5 --verify-only 正确拒绝被篡改的 MANIFEST" +fi +cp "$TMP/MANIFEST.txt.bak" "$OUT/MANIFEST.txt" +run "--verify-only(恢复后)" bash "$SCRIPT" --verify-only && pass "T5b 恢复后重新通过" || fail "T5b 恢复后仍失败" + +echo +printf '== 结果: %d 通过, %d 失败\n' "$PASS" "$FAILN" +[ "$FAILN" = 0 ] From adaed09f948260432a2dd66fa064a112eb2b356d Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 19:53:45 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(devkit):=20M24=20r2=20=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=20=E2=80=94=20=E4=BA=A4=E4=BB=98=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=80=A7=E9=97=B8=E9=97=A8=20+=20=E8=A1=A5=20sysctl=20+=20time?= =?UTF-8?q?64=20=E6=96=87=E6=A1=A3=E6=9B=B4=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P2-1(与 P1 同类):licenses/ 与 README 缺失仍会假成功。 - 新增 meta_complete()/verify_meta():MANIFEST + README + 五份许可证副本 (procps COPYING/COPYING.LIB、ncurses、lsof、musl-COPYRIGHT)缺一即不完整 - 纳入 skip 条件与 --verify-only;产物未变但 meta 缺失时就地 write_meta 补全(不重编), 构建树不在则退回全量重建 - 回归测试新增 T6(删 licenses 后 --with-sources 必须恢复)、T7(--verify-only 必须拒绝)、 T8(删 README 后恢复);共 20 项全过 P2-2:README.txt/MANIFEST.txt 的卡上路径全错(写成 ../src、scripts/build/…)。 - 全部改为卡内相对路径:src/、licenses/、MANIFEST.txt、src/DEVKIT.md、src/build_devkit.sh - README 增加「目录内容」段并注明 src/ 是生成目录、勿手放文件 P2-3:valgrind 的排除理由(“无 MIPS32 后端”)是错的 —— 官方支持 MIPS32/Linux。 - 改为「未尝试」:交叉构建需 autotools + 目标 vDSO 细节、产物 10MB+,56MB 单核不实用 P2-4:busybox 1.31 实测有 pgrep/kill/killall,没有 pkill、没有 sysctl。 - 表格改准确;pgrep 保留(增量有限不交付);pkill 写明「busybox 无此 applet, killall 只能部分替代,本轮未做」 - 新增交付 sysctl(procps 4.0.4,148184 B,静态):MANIFEST/verify-only/真机 (sysctl -a 428 键、kernel.hostname/panic 读取、-n)均已验证 P2-5:free -s <小数> 也受 time64 影响(usleep 亚秒被抹平),文档说反了。 - DEVKIT §9 增加一行(free -s 0.5 实测 0s、free -s 1 实测 1s);README 同步; “收益只有 vmstat -t 一列”改为包含 free -s P3: 1. §9 机制改准:内核只写 8 字节 → tv_sec 高 32 位是内核写入的 nsec、offset 8 的 tv_nsec 才是未初始化;nanosleep 是把 tv_sec 高半字当 nsec 读(0) 2. 删掉脚本里 top 的旧错误归因(时长溢出/59.4 GiB),与 DEVKIT 对齐 3. 测试耗时改为「热缓存约 15s,冷启动 1–2 分钟」(实测 28s 含多次构建/修复路径) 4. --verify-only 依赖清单写全(file/readelf/sha256sum/awk/sed/grep/wc/cut/basename) 5. licenses/ 增加 musl-COPYRIGHT(zig env → libc/musl/COPYRIGHT,取不到即失败),§6 补一行 6. htop 说明改准:亚秒刷新走 ncurses halfdelay()(select + 32 位 timeval),大概率不受 time64 影响;受影响的是时钟显示与部分采样(仍注明未做运行时验证) 7. README/脚本注明 src/ 是生成目录 8. LGPL 摘要改为「源码(§6a) 或 共享库(§6b) 或 书面要约(§6c)」 9. ltrace/sysstat 从「不可行」改为「未做(成本高)」,与 perf 的硬阻塞区分 验证:test_build_devkit.sh 20/20;--verify-only 通过(7 个二进制 + MANIFEST + 5 份许可证); 独立工作区离线重建 64s/0 下载,七个 sha256 与前一次逐字节一致;设备实测 sysctl 与其余六项正常。 --- scripts/build/DEVKIT.md | 133 +++++++++++++++++------------ scripts/build/README.md | 4 +- scripts/build/build_devkit.sh | 108 +++++++++++++++++------ scripts/build/test_build_devkit.sh | 34 +++++++- 4 files changed, 195 insertions(+), 84 deletions(-) diff --git a/scripts/build/DEVKIT.md b/scripts/build/DEVKIT.md index e046af8..5275d05 100644 --- a/scripts/build/DEVKIT.md +++ b/scripts/build/DEVKIT.md @@ -2,14 +2,16 @@ TF 卡 `MeowOS/opt/devkit/bin/` 里那批静态二进制的构建说明、可行性实测、许可证留痕。 构建入口:`bash scripts/build/build_devkit.sh`;回归测试:`bash scripts/build/test_build_devkit.sh` -(产物在 `fw_apps/devkit/`,已被 `.gitignore` 的 `fw_apps/` 规则覆盖,不入库)。 +(热缓存约 15 秒;冷启动需先重建基线,1–2 分钟)。 +产物在 `fw_apps/devkit/`,已被 `.gitignore` 的 `fw_apps/` 规则覆盖,不入库。 ## 1. 为什么要有这个 设备(HiBy R3 Pro II / Ingenic X1600 / MIPS32r2 / Linux 4.4.94+ / 单核 / 56 MB)自带 -busybox 1.31.1,`free / ps / top / lsof` 都是 busybox 的软链 -(实测:`/bin/ps -> busybox`,`/usr/bin/{free,lsof,top} -> ../../bin/busybox`),字段残缺; -`vmstat`、`slabtop`、`pmap` 干脆没有。做内存分析时最缺的正是: +busybox 1.31.1:`free / ps / top / lsof` 是 busybox 的软链(实测 `/bin/ps -> busybox`、 +`/usr/bin/{free,lsof,top} -> ../../bin/busybox`),字段残缺;`vmstat`、`slabtop`、`pmap`、 +`sysctl` **没有对应 applet**(`busybox --list` 实测:有 `pgrep`/`kill`/`killall`,无 `pkill`/`sysctl`)。 +做内存分析时最缺的正是: - `slabtop` —— Slab 总量 ~11 MB 到底被谁占了(buffer_head / dentry / squashfs_inode…) - `vmstat` —— 系统级趋势(r/b、si/so、in/cs、us/sy/id/wa),busybox 无替代 @@ -28,13 +30,14 @@ busybox 1.31.1,`free / ps / top / lsof` 都是 busybox 的软链 | `vmstat` | procps-ng 4.0.4 | 同上 | 216 924 | 系统趋势;busybox 无;`-t` 时间戳列本工具链下为空,见 §9 | | `ps` | procps-ng 4.0.4 | 同上 | 324 980 | `-eo pid,rss,vsz,comm --sort=-rss` 等完整字段 | | `pmap` | procps-ng 4.0.4 | 同上 | 173 460 | 地址空间映射(读 `/proc/pid/maps`);busybox 无 | +| `sysctl` | procps-ng 4.0.4 | 同上 | 148 184 | 读写 `/proc/sys`(`-a` 列出 428 个键);busybox 1.31 无此 applet | | `free` | procps-ng 4.0.4 | 同上 | 94 312 | 拆 buff/cache、available | | `lsof` | lsof 4.99.4 | Purdue/BSD-style | 789 496 | SIZE/OFF、TCP 状态、NAME、`-i` 网络视图 | -| | | **合计** | **1 993 580 ≈ 1.9 MB** | | +| | | **合计** | **2 141 764 ≈ 2.1 MB** | | 同目录还有 `MANIFEST.txt`(每个二进制的 sha256 + 版本 + 许可证 + 体积)、 -`licenses/`(上游 COPYING / COPYING.LIB)、`README.txt`(上卡说明)。 -`--with-sources` 会额外生成 `src/`(三份源码包 + `build_devkit.sh` + 本文件)。 +`licenses/`(procps COPYING + COPYING.LIB、ncurses COPYING、lsof COPYING、**musl COPYRIGHT**)、 +`README.txt`(上卡说明)。`--with-sources` 会额外生成 `src/`(三份源码包 + `build_devkit.sh` + 本文件)。 ## 3. 上卡后怎么用 @@ -51,11 +54,11 @@ fi **与 busybox 同名命令的先后关系**(前插 PATH 时): -| 名字 | 现状(实测软链) | 前插后 | +| 名字 | 现状(实测) | 前插后 | |---|---|---| | `free` `lsof` `top` | `/usr/bin/x -> ../../bin/busybox` | `free`/`lsof` 解析到 devkit 版;`top` 仍是 busybox(本工具集不提供) | | `ps` | `/bin/ps -> busybox` | 解析到 devkit 版 | -| `vmstat` `slabtop` `pmap` | 不存在 | 新增 | +| `vmstat` `slabtop` `pmap` `sysctl` | 不存在(busybox 无对应 applet) | 新增 | 要临时用回 busybox 版:`busybox free`、`busybox ps`、`busybox lsof`。 不要覆盖 `/usr/bin`、`/bin` 里的软链:rootfs 是 ubifs,随固件回刷即被还原,而且这些软链是基线的一部分。 @@ -75,40 +78,45 @@ fi - **联网次数**:冷启动 **3 次**(ncurses / procps-ng / lsof 各一个 tarball,共 5.5 MB); 源码包按 sha256 校验后落在 `$MEOW_DEVKIT_WORK/src/`,此后**重跑 0 次联网**。 -- **耗时**(8 核宿主实测):冷启动(含 3 次下载)**80 秒**(两次实测 80s / 86s); - 源码已缓存时全量重编约 **65–85 秒**;`stamp` 命中(脚本、版本、zig 版本都没变)**1–2 秒**; - `--verify-only` 秒级,且只依赖 `file` / `readelf` / `sha256sum` / `awk`(不需要 zig、make、date、网络)。 +- **耗时**(8 核宿主实测):冷启动(含 3 次下载)**80–86 秒**;源码已缓存时全量重编 **60–85 秒**; + `stamp` 命中(脚本、版本、zig 版本都没变)**2–3 秒**;回归测试热缓存 **约 15 秒**(冷启动基线另算)。 +- **`--verify-only`**:只做静态/架构/NEEDED 检查 + MANIFEST 逐条 sha256/体积比对 + + MANIFEST/README/licenses 完整性检查。**不需要 zig、make、date、网络**; + 实际依赖 `file`、`readelf`、`sha256sum`、`awk`、`sed`、`grep`、`wc`、`cut`、`basename`。 - **stamp 内容**:脚本 sha256 + zig 完整版本(0.17.0 / 0.17.1 会区分)+ target + 内核版本 + 三个上游版本。 -- **可复现(逐字节)**:在两个不同工作区各建一次,六个二进制 **sha256 完全相同** +- **可复现(逐字节)**:在两个不同工作区各建一次,七个二进制 **sha256 完全相同** (`ps` 用 `-ffile-prefix-map` 抹掉绝对源码路径;`lsof` 用裸名 `CC=zigcc` + 相对源码路径, 不再把工具链/构建目录写进 `LSOF_CC/LSOF_CCFLAGS`)。`MANIFEST.txt` 里的 sha256 与设备上 `sha256sum` 的结果一致,可用来核对「跑的到底是哪个二进制」。 -- `--verify-only` 除了静态/架构/NEEDED,还会把 `bin/` 六个文件与 `MANIFEST.txt` 的 sha256/体积 - **逐条比对**(只查 static/MIPS 是看不出内容漂移的)。 -- **回归测试**:`bash scripts/build/test_build_devkit.sh`(12 项,秒级,不重编)覆盖: - stamp 命中时 `--with-sources` 必须产出完整 `src/`(r1 P1 的回归点)、src/ 缺文件/被改坏能自愈、 - `--verify-only` 能拒绝被篡改的 MANIFEST。 +- **skip 的完整条件**:stamp 未变 **且** 七个 bin 通过静态/架构自检 **且** MANIFEST 与 bin 逐条一致 + **且** MANIFEST/README/licenses 齐全。任一不满足就补(meta 缺失时直接重写 meta,不必重编)或重建。 ## 6. 许可证与分发合规(重点) | 上游 | 许可证 | 分发二进制时的义务 | |---|---|---| | procps-ng 4.0.4(apps) | **GPL-2.0-or-later** | **提供完整对应源码**(GPLv2 §3),含 "scripts used to control compilation and installation" | -| procps `library/`(libproc2) | **LGPL-2.1+** | `ps/vmstat/slabtop/free/pmap` **静态链接**了它 → LGPL-2.1 §6 另有要求,见下 | +| procps `library/`(libproc2) | **LGPL-2.1+** | `ps/vmstat/slabtop/free/pmap/sysctl` **静态链接**了它 → LGPL-2.1 §6 另有要求,见下 | | ncurses 6.5 | MIT 风格(X11) | 保留版权与许可声明;只静态链入 slabtop,不单独分发 | | lsof 4.99.4 | Purdue Research Foundation 许可(BSD 风格 4 条) | 文档与源码中保留作者/Purdue 署名;改过的版本要标注 | +| **musl**(zig 0.17 自带) | MIT | 七个产物都静态链入了它 → 随副本保留版权与许可声明(`licenses/musl-COPYRIGHT`) | **GPLv2 §3**:`build_devkit.sh` 里的固定版本 + 下载 URL **不构成合规**。URL 作者可能撤、 镜像可能消失,而且 GPL 要求的是*分发者*提供的源码或书面要约。因此 `--with-sources` 会把 **三份源码包 + `build_devkit.sh` + 本文件**一起放进 `src/`(合计约 5.6 MB)。少一样都不算完整: 没有构建脚本,收卡人拿不到本轮用的 `configure`/`-D` 参数(这正是 "corresponding source" 明确包含的)。 -**LGPL-2.1 §6(静态链接)**:五个 procps 工具静态链入 libproc2,除 GPL 的义务外还必须 -「显著提示使用了该库 + 附许可证副本 + 三选一」:(a) 随作品提供该库的完整源码(含修改); -(b) 改用共享库机制(静态交付做不到);(c) 书面要约(≥3 年)提供 (a) 的材料。 -本工具集走 **(a)**:`src/procps-ng-4.0.4.tar.xz` 里含 `library/` 全量源码, +**LGPL-2.1 §6(静态链接)**:六个 procps 工具静态链入 libproc2,除 GPL 义务外还必须 +「显著提示使用了该库 + 附许可证副本 + 三选一」: +**(§6a)** 随作品提供该库的完整源码(含修改); +**(§6b)** 改用共享库机制(静态交付做不到); +**(§6c)** 书面要约(≥3 年)提供 §6a 的材料。 +本工具集走 **§6a**:`src/procps-ng-4.0.4.tar.xz` 含 `library/` 全量源码, `src/build_devkit.sh` 是本轮的唯一构建描述,`licenses/procps-ng-4.0.4-COPYING.LIB` 是许可证副本。 -若想更保守(收卡人不想自己重编),可以再额外提供可重链接的 `.o`(当前**未做**,属可选加固)。 +若想更保守(收卡人不想自己重编),可再额外提供可重链接的 `.o`(当前**未做**,属可选加固)。 + +**musl(MIT)**:MIT 要求在副本中保留版权与许可声明,因此 `licenses/musl-COPYRIGHT` 是必需项 +(构建脚本从 `zig env` 的 `lib_dir/libc/musl/COPYRIGHT` 取,取不到直接失败)。 **结论**:内部自用不触发分发义务;但既然这批二进制可能被拷来拷去,建议一律 `--with-sources` 上卡。`licenses/` 只是声明留痕,**不能替代源码义务**。 @@ -118,12 +126,17 @@ fi | 候选 | 结论 | 原因 | |---|---|---| | `top`(procps 4.0.4) | **不交付** | 表头时钟被工具链 time64 问题打坏(显示 `-1202590843:00` 一类),无 TTY 时还会静默 `exit 1`。**不是 procps 的 bug**(r1 评审已更正我此前的错误归因),根因见 §9。卡上已有 htop,`top` 属重复 | -| `htop` | 不构建 | 卡上 `MeowOS/opt/bin/htop` 已是 **3.5.3**(673 744 B)。实测用 zig + 自编 ncursesw 也能编出 3.5.3,但 strip 后 **756 908 B**,比现有版本更大(静态链 musl+ncurses)。**且我此前只量了体积、没做运行时验证** —— 它会继承 §9 的 time64 问题(`htop -d 5`(0.5s)这类亚秒刷新会退化成忙等空转),所以「加个 build_htop() 即可」的说法不成立 | +| `htop` | 不构建 | 卡上 `MeowOS/opt/bin/htop` 已是 **3.5.3**(673 744 B)。实测用 zig + 自编 ncursesw 也能编出 3.5.3,但 strip 后 **756 908 B**,比现有版本更大(静态链 musl+ncurses)。**未做运行时验证**;按代码路径,它的亚秒刷新走 ncurses `halfdelay()`(内部 `select()` + 32 位 `struct timeval`),**大概率不受 §9 的 time64 影响**,真正会受影响的是时钟显示(`localtime_r`/`clock_gettime`)与部分采样 | | `watch` | 不构建 | busybox 1.31 已有 `watch`;procps 版 403 KB,收益不抵体积 | -| `pgrep`/`pkill` | 不构建 | busybox 已有;procps 版各 260 KB(链接整份 libproc) | +| `pgrep` | 不构建 | busybox 已有 `pgrep`;procps 版 260 KB(链接整份 libproc),增量有限 | +| `pkill` | **不构建** | busybox **没有** `pkill` applet(只有 `kill`/`killall`,`killall` 只能部分替代);procps 版 260 KB,本轮未做 | | `uptime`/`tload`/`pidof`/`pidwait`/`w` | 不构建 | busybox 已有 `uptime`/`pidof`;`tload`/`w` 在本机无调试价值 | -| `sysctl` | 不构建 | busybox 已有 `sysctl`(读写 `/proc/sys` 足够) | -| `perf` / `valgrind` / `gdb` / `ltrace` / `sysstat` / `smem` | 不可行 | 见 §8 | +| `perf` | 不可行 | 依赖目标内核源码树(`tools/perf` 要按内核版本配 `perf_event_attr`),没有内核头,属硬阻塞 | +| `valgrind` | **未尝试** | valgrind **支持 MIPS32/Linux**(官方平台页有 MIPS32/MIPS64),所以不是「无后端」;但交叉构建需 autotools 工具链 + 目标内核/vDSO 细节,产物通常 10 MB+,在 56 MB 单核设备上不实用。本轮未做可行性验证 | +| `ltrace` | **未做(成本高)** | 需要 libelf + 复杂 autoconf 探测,且本机程序多为静态/精简;属投入产出判断,不是「不可能」 | +| `sysstat`(sar/iostat/mpstat) | **未做(成本高)** | autoconf 探测多、依赖 `gettext`/`libsensors` 等;本机单核、`/proc/diskstats` 对 TF 卡意义有限 | +| `gdb` | 不建议 | 设备已带 `gdbserver`,宿主侧可用交叉 gdb 连;在 56 MB 单核设备上跑 gdb 本体收益极低、体积巨大 | +| `smem` | 价值为零 | 依赖 `/proc//smaps` 的 PSS,本机没有 smaps | `pmap` 的低价值要说明白:本机 `CONFIG_PROC_PAGE_MONITOR=n`,`/proc//smaps` 不存在, 所以 `pmap -x` 拿不到 PSS/私有脏页。它仍有用的一点是 `/proc//maps` **存在**, @@ -134,7 +147,7 @@ fi 宿主:Termux/Android aarch64,zig 0.17.0(自带 musl libc 与头文件),`make`、`curl`、`pkg-config`。 **没有** autoconf/automake/libtool,**没有**目标内核头,sysroot 里只有 zlib(本工具集不需要它)。 -- **procps-ng 4.0.4** ✅ 可编。三个必须处理的坑: +- **procps-ng 4.0.4** ✅ 可编(vmstat/slabtop/free/ps/pmap/sysctl 全部产出)。三个必须处理的坑: 1. `slabtop`(`src/slabtop.c` 直接 `#include `)硬依赖 ncurses; `configure --without-ncurses` 会把它整个跳过 → 要 `slabtop` 就必须先交叉编 ncurses。 2. 交叉编译下 autoconf 的 `malloc(0)/realloc(0)` 运行期探测无法执行,不缓存 @@ -155,21 +168,16 @@ fi 状态宏,`dsock.c` 会编不过 → 脚本里 `-D` 补齐(值取自 `linux/tcp.h`)。 4. zig cc **不带 `-O` 时按 Debug 编**,会插入 UBSan 桩(`__ubsan_handle_negate_overflow`) 导致链接失败 → 所有编译都显式 `-O2`。 -- **htop 3.5.3** ✅ 可编(用自编 ncursesw,`--disable-hwloc/sensors/delayacct`),strip 后 756 908 B —— - 比卡上现有版本大,且未做运行时验证(见 §7、§9),故不交付。 -- **perf** ❌ 不可行:依赖目标内核源码树(`tools/perf` 要按内核版本配 `perf_event_attr`), - 且设备内核未必开了相关选项;没有内核头也是硬阻塞。 -- **valgrind** ❌ 不可行:**没有 MIPS32 后端**(官方只支持 x86/amd64/arm/aarch64/ppc/s390), - 不是交叉编译问题。 -- **gdb** ❌ 不建议:设备已带 `gdbserver`,宿主侧可用交叉 gdb 连;在 56 MB 单核设备上跑 gdb 本体收益极低、体积巨大。 -- **ltrace** ❌ 不可行:需要 libelf + 复杂 autoconf 探测,且本机程序多为静态/精简,投入产出不划算。 -- **sysstat(sar/iostat/mpstat)** ❌ 未做:autoconf 探测多、依赖 `gettext`/`libsensors` 等, - 且本机单核、`/proc/diskstats` 对 TF 卡意义有限。 -- **smem** ⚠️ 价值为零:依赖 `/proc//smaps` 的 PSS,本机没有 smaps。 +- **htop 3.5.3** ✅ 可编(用自编 ncursesw),strip 后 756 908 B —— 比卡上现有版本大, + 且未做运行时验证,故不交付(见 §7)。 +- **perf** ❌ 不可行:依赖目标内核源码树/头文件。 +- **valgrind** ⚠️ 未尝试(支持 MIPS32/Linux,但交叉构建与体积/内存代价不划算;见 §7)。 +- **ltrace** / **sysstat** ⚠️ 未做(成本高,见 §7)。 +- **smem** ⚠️ 价值为零:依赖 smaps,本机没有。 ## 9. 已知限制:工具链的 time64 在 4.4 内核上不可用 -**这不是 procps/htop 的 bug,是 zig 0.17 自带 musl 在 mipsel 上的 ABI 错配**(r1 评审独立定根因, +**这不是 procps/htop 的 bug,是 zig 0.17 自带 musl 在 mipsel 上的 ABI 错配**(r1 评审定根因, 我此前把 `top` 的表头问题误判成 procps bug,此处更正)。 机制(三段,均可复验): @@ -179,19 +187,27 @@ fi 2. zig 自带 musl(`libc/musl/src/time/clock_gettime.c`、`time/clock_nanosleep.c` 等)的守卫写成 `#if defined(SYS_*_time64) && !defined(__ILP32__)`;上游 musl 1.2.5 没有这个 `!defined(__ILP32__)`。 3. MIPS o32 是 ILP32、内核 4.4 又没有 time64 syscall → 走 `#else` 分支,把 **16 字节的 time64 结构** - 直接交给 **32 位 time32 系统调用**:内核只写/只读前 8 字节,高 32 位保持未初始化。 + 直接交给 **32 位 time32 系统调用**。内核按 `{int32 tv_sec; int32 tv_nsec}` 解释这 16 字节: + + - `clock_gettime`:内核只写前 8 字节 → 调用者的 `tv_sec`(offset 0..7)= `sec | (nsec << 32)`, + **高 32 位是内核写入的 nsec**;而 offset 8 的 `tv_nsec` **完全没被写,保持未初始化** + (评审 memset 0xAA 实测读到 `nsec=0xAAAAAAAA`)。 + - `gettimeofday` 同理:`tv_sec` 高半字是内核写入的 usec,offset 8 的 `tv_usec` 未初始化。 + - `nanosleep`:内核把 `tv_sec` 的**高半字当 nsec 读**(小秒数时为 0),调用者真正的 `tv_nsec` + (offset 8)被忽略 → 亚秒被抹平、整秒正常。 真机实测(本轮): | 现象 | 结果 | 影响 | |---|---|---| | `clock_gettime` / `time()` / `gettimeofday()` | rc=0 但值是垃圾(评审另测:`sec=2823213556008072608`,`localtime_r` 返回 NULL) | 任何按墙钟格式化时间的功能 | -| `vmstat -t` | 表头有 `-----timestamp-----`,**数据行该列整列为空**(`localtime()` 返回 NULL,`strftime` 被跳过) | `vmstat` 核心的 3 组计数**正常**;只有时间戳列不可用 | -| `top` 表头时钟 | 显示 `-1202590843:00` / `-343597384:00` 一类 | 这就是 §7 不交付 top 的原因 | -| `usleep(0.5s)` ×10 | **实测 0 秒**(应为 5 秒):`sleep()` 走 32 位 syscall,内核把 `tv_sec` 高半字当 `tv_nsec`(恒 0),亚秒被抹平 | 亚秒轮询会退化成忙等/空转 | -| `sleep(1)`(整秒) | 正常:`vmstat 1 3` 墙钟实测 2 秒 | 整秒采样周期不受影响 | +| `vmstat -t` | 表头有 `-----timestamp-----`,**数据行该列整列为空**(`localtime()` 返回 NULL) | `vmstat` 核心的 3 组计数**正常**;只有时间戳列不可用 | +| `free -s 0.5 -c 2` | **实测 0 秒**(应约 0.5 秒):`free.c` 用 `usleep(interval)`,亚秒被抹平 | **小数间隔失效**;`free -s 1 -c 2` 实测 1 秒,正常 | +| `top` 表头时钟 | 显示 `-1202590843:00` 一类 | §7 不交付 top 的原因 | +| `usleep(0.5s)` ×10(独立探针) | **实测 0 秒**(应 5 秒) | 亚秒轮询会退化成空转 | +| `sleep(1)`(整秒) | 正常:`vmstat 1 3` 墙钟实测 2 秒、`vmstat 1 2` 实测 1 秒 | 整秒采样周期不受影响 | | `ps -eo lstart` | 正常 | 时间来自 `/proc/stat` 的 `btime` + jiffies,不走 libc 取时 | -| `slabtop`/`free`/`pmap`/`lsof` | 不受影响 | 不显示墙钟 | +| `slabtop`/`pmap`/`sysctl`/`lsof` | 不受影响 | 不显示墙钟、也不做亚秒等待 | **能否复用仓里已有的修复?** 有。M16 的 `patch_v2/sdk/meow_ui_time.c`(文档 `patch_v2/sdk/CLOCK_ABI.md`) 用「链接期强定义」接管了 `__clock_gettime` / `__clock_gettime64` / `__nanosleep_time64` / @@ -202,17 +218,17 @@ fi 未定义符号只有 5 个 libc 符号(`__errno_location`、`__pthread_setcancelstate`、`__syscall_cp`、 `__udivdi3`、`syscall`),不依赖 SDK 其它目标文件。 - **代价**:必须把该 `.o` 放在 libc 之前参与链接(才会盖掉 libc 里那份),要给两套构建系统分别注入: - procps 走 libtool 的 `LDADD`、lsof 是手写 Makefile 的固定链接行;然后六个二进制全部重新真机验证。 + procps 走 libtool 的 `LDADD`、lsof 是手写 Makefile 的固定链接行;然后七个二进制全部重新真机验证。 估计 1–2 小时(含设备回归),产物每个 +几 KB。 -- **本轮为什么不做**:收益只有 `vmstat -t` 一列(`top` 不交付、`ps` 本来正常、其余不打时间), - 而这个改动会动到 libc 的取时/睡眠入口,风险面比收益大。**结论:可复用,但需要单独一条 mission - 做「接入 + 全量回归」**,不适合塞进这次修 P1 的补丁里。 +- **本轮为什么不做**:收益是 `vmstat -t` 时间戳列 + `free -s <小数>` 两处(`top` 不交付、`ps` 本来正常、 + 其余不打时间),而这个改动会动到 libc 的取时/睡眠入口,风险面比收益大。 + **结论:可复用,但需要单独一条 mission 做「接入 + 全量回归」**,不适合塞进修 P2 的补丁里。 - **不加 shim 的替代方案**:给 zig 的 musl 打补丁(去掉 `!defined(__ILP32__)`)或换上游 musl 工具链。 **这不是加一个 `-D` 能修的**:守卫在 libc 源码里,编译产物(libc.a)已经固定。 ## 10. 路径清洁:lsof 里有一处宿主路径(工具链副作用) -`strings lsof` 有一处 `/data/data/com.termux/files/usr/etc/resolv.conf`(其余五个产物没有宿主路径)。 +`strings lsof` 有一处 `/data/data/com.termux/files/usr/etc/resolv.conf`(其余六个产物没有宿主路径)。 来源是 **Termux 打包的 zig 自带 musl**(`libc/musl/src/network/resolvconf.c` 被改成 Termux prefix), 不是 lsof 源码,也不是本脚本的路径泄漏(源码路径已用 `-ffile-prefix-map` 抹掉)。 @@ -260,6 +276,12 @@ Swap: 0 0 0 004e8000 4K rw--- busybox 00969000 132K rwx-- [ anon ] +# sysctl -a | wc -l → 428(键数) +# sysctl kernel.hostname kernel.panic +kernel.hostname = meowos +kernel.panic = 10 +# sysctl -n kernel.ostype → Linux + # lsof -nP -i (瞬时;不带 -n 约 3 秒,见 §10) COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME dropbear 1354 root 3u inet 2196 0t0 TCP *:22 (LISTEN) @@ -268,16 +290,17 @@ meow_mdns 1375 root 3u inet 2906 0t0 UDP *:5353 meowpanel 1395 root 3u inet 2587 0t0 TCP *:8080 (LISTEN) ``` -- 六个二进制 `--help`/`-V` 全部正常(`lsof --help` 不是合法选项,`lsof -v` 打印 4.99.4)。 +- 七个二进制 `--help`/`-V` 全部正常(`lsof --help` 不是合法选项,`lsof -v` 打印 4.99.4)。 - 交互式 `slabtop`(无 `-o`)在 `TERM=xterm` 下正常初始化 curses(stdout 写出 1599 字节转义序列); 对照 `TERM=bogus-term` 报 `Error opening terminal: bogus-term.` —— 说明 terminfo 路径确实命中 `/usr/share/terminfo`,不是内嵌 fallback。 - 设备上 `sha256sum` 与 `MANIFEST.txt` 逐条一致(测的就是交付的那份字节)。 -- 宿主侧静态/架构自检与 MANIFEST 对账:`bash scripts/build/build_devkit.sh --verify-only`。 +- 宿主侧静态/架构自检、MANIFEST 对账、licenses 完整性:`bash scripts/build/build_devkit.sh --verify-only`。 ## 12. 其它已知限制 - `pmap -x` 无 PSS(无 smaps),只有 maps 级别的段/文件信息。 -- 不提供 `top`(§7/§9);需要交互式总览请用卡上已有的 `htop`(注意其时钟/亚秒刷新同样受 §9 影响)。 +- 不提供 `top`(§7/§9);需要交互式总览请用卡上已有的 `htop`(其时钟显示同样受 §9 影响,亚秒刷新走 `select()` 大概率正常)。 - 二进制按设备内核 4.4.94 编 `LINUXV`;该宏只影响 `lsof -v` 显示,换内核不影响运行。 - 产物是 32 位 MIPS32r2(o32 ABI),只适用于 X1600 这类 MIPS32r2 目标。 +- 卡上 `src/` 是 `--with-sources` 的**生成目录**(每次整理会整体重建),不要往里手放文件。 diff --git a/scripts/build/README.md b/scripts/build/README.md index de338b4..8f1dd7d 100644 --- a/scripts/build/README.md +++ b/scripts/build/README.md @@ -12,8 +12,8 @@ |---|---| | `build_fw_apps.sh` | **通用打包脚本**(配置驱动)。读清单 → 组树 → repack → 校验 → 出固件。**不要改它的逻辑**,除非打包流程本身要变。 | | `fw_content.conf` | **固件内容清单**(默认配置)。每行一条 `|||`,声明"哪个本地文件装到固件哪里"。**改固件内容只改这里。** | -| `build_devkit.sh` | **TF 卡调试工具集**交叉编译脚本(zig → 静态 MIPS32r2):`vmstat/slabtop/free/ps/pmap/lsof` → `fw_apps/devkit/`。与固件打包无关,独立跑。详见 `DEVKIT.md`。 | -| `test_build_devkit.sh` | `build_devkit.sh` 的回归测试(不重编,秒级):`--with-sources` 在 stamp 命中时必须产出完整 `src/`、`--verify-only` 必须能查出 MANIFEST 漂移。 | +| `build_devkit.sh` | **TF 卡调试工具集**交叉编译脚本(zig → 静态 MIPS32r2):`vmstat/slabtop/free/ps/pmap/sysctl/lsof` → `fw_apps/devkit/`。与固件打包无关,独立跑。详见 `DEVKIT.md`。 | +| `test_build_devkit.sh` | `build_devkit.sh` 的回归测试(不重编;热缓存约 15s,冷启动需先重建基线 1–2 分钟):stamp 命中时 `--with-sources` 必须产出完整 `src/`;`licenses`/`README` 被删要能恢复、`--verify-only` 必须拒绝;MANIFEST 漂移检测。 | | `DEVKIT.md` | devkit 的产物清单、许可证留痕(GPLv2 §3 / LGPL-2.1 §6)、可行性实测、time64 限制、上卡 PATH/busybox 关系。 | | `README.md` | 本说明。 | diff --git a/scripts/build/build_devkit.sh b/scripts/build/build_devkit.sh index ebb8508..59c5b84 100755 --- a/scripts/build/build_devkit.sh +++ b/scripts/build/build_devkit.sh @@ -8,7 +8,7 @@ # musl 交叉编出完全静态的二进制,放进 TF 卡供开发调试。 # # 产物(全部静态链接、strip 过、无 NEEDED): -# fw_apps/devkit/bin/{vmstat,slabtop,free,ps,pmap,lsof} +# fw_apps/devkit/bin/{vmstat,slabtop,free,ps,pmap,sysctl,lsof} # fw_apps/devkit/MANIFEST.txt 版本/许可证/体积/校验摘要 # fw_apps/devkit/licenses/ 各上游 COPYING(分发留痕) # fw_apps/devkit/README.txt 上卡后怎么加 PATH、和 busybox 的同名关系 @@ -26,7 +26,7 @@ # 许可证(二进制将来随卡分发时请保留此节): # procps-ng 4.0.4 GPL-2.0-or-later(COPYING=GPLv2) # *** 分发二进制必须提供对应源码(GPLv2 §3,含构建脚本)*** -# procps library/ LGPL-2.1+:ps/vmstat/slabtop/free/pmap 静态链接 libproc2, +# procps library/ LGPL-2.1+:ps/vmstat/slabtop/free/pmap/sysctl 静态链接 libproc2, # LGPL-2.1 §6 要求提供可重链接 object code 或书面要约(见 DEVKIT.md §6) # ncurses 6.5 MIT 风格(X11 许可),保留版权声明即可 # lsof 4.99.4 Purdue Research Foundation 许可(BSD 风格 4 条), @@ -83,7 +83,12 @@ SRC_LSOF="lsof-${LSOF_VER}.tar.xz|https://deb.debian.org/debian/pool/main/l/lsof SRC_STAGE_LIST="ncurses-${NCURSES_VER}.tar.gz procps-ng-${PROCPS_VER}.tar.xz lsof-${LSOF_VER}.tar.xz build_devkit.sh DEVKIT.md" # 交付的工具——验证、MANIFEST 比对、stamp 自检都从这里取,避免多处手写漂移 -TOOL_NAMES="vmstat slabtop free ps pmap lsof" +TOOL_NAMES="vmstat slabtop free ps pmap sysctl lsof" + +# licenses/ 下必须存在的许可证副本(缺一个都算交付不完整): +# procps 是 GPL-2.0+(COPYING)且 library/ 是 LGPL-2.1+(COPYING.LIB), +# ncurses/lsof 是 MIT 风格 / Purdue,musl 是 MIT 且七个产物都静态链入了它。 +LICENSE_FILES="procps-ng-${PROCPS_VER}-COPYING procps-ng-${PROCPS_VER}-COPYING.LIB ncurses-${NCURSES_VER}-COPYING lsof-${LSOF_VER}-COPYING musl-COPYRIGHT" FORCE=0 CLEAN=0 @@ -137,6 +142,13 @@ EOF ZIGCC="zigcc" info "zig $ZIG_VERSION → $WRAP_DIR/zigcc" + # 七个产物都静态链入了 zig 自带 musl(MIT),其 COPYRIGHT 要求随副本保留, + # 所以构建时必须能拿到它,拿不到就明确失败(不能交付“缺许可证”的包)。 + ZIG_LIB_DIR="$("$zig_bin" env 2>/dev/null | sed -n 's/.*\.lib_dir *= *"\(.*\)".*/\1/p')" + MUSL_COPYRIGHT="${ZIG_LIB_DIR}/libc/musl/COPYRIGHT" + [ -s "$MUSL_COPYRIGHT" ] || die "找不到 musl 的 COPYRIGHT(zig lib_dir=$ZIG_LIB_DIR),无法留痕许可证" + info "musl COPYRIGHT → $MUSL_COPYRIGHT" + for t in make curl tar sha256sum file readelf; do command -v "$t" >/dev/null 2>&1 || die "缺宿主工具: $t" done @@ -334,16 +346,17 @@ build_lsof() { # --------------------------------------------------------------------------- # 产出清单:<源路径>|<目标名>|<出处>|<许可证> # 只放「真机实测能跑且真有价值」的:vmstat/slabtop(busybox 没有)、 -# free/ps/pmap(busybox 版字段残缺)、lsof(busybox 版只有 pid/fd)。 -# 不交付 top:本机(32 位 MIPS + 4.4.94)上 procps 4.0.4 的 top 表头/内存单位 -# 输出明显错误(时长字段溢出、Mem 显示 59.4 GiB 而 MemTotal 只有 55 MB), -# 且卡上已有 htop 3.5.3(673KB),top 属重复且不可信 → 见 DEVKIT.md「未交付清单」。 +# free/ps/pmap(busybox 版字段残缺)、sysctl(busybox 1.31 没有此 applet)、 +# lsof(busybox 版只有 pid/fd)。 +# 不交付 top:表头时钟被工具链 time64 打坏(不是 procps 的 bug,见 DEVKIT.md §9), +# 且卡上已有 htop 3.5.3(673KB)→ 属重复且不可信。 TOOLS=" $BUILD_DIR/procps/src/vmstat|vmstat|procps-ng $PROCPS_VER|GPL-2.0-or-later $BUILD_DIR/procps/src/slabtop|slabtop|procps-ng $PROCPS_VER|GPL-2.0-or-later $BUILD_DIR/procps/src/free|free|procps-ng $PROCPS_VER|GPL-2.0-or-later $BUILD_DIR/procps/src/ps/pscommand|ps|procps-ng $PROCPS_VER|GPL-2.0-or-later $BUILD_DIR/procps/src/pmap|pmap|procps-ng $PROCPS_VER|GPL-2.0-or-later +$BUILD_DIR/procps/src/sysctl|sysctl|procps-ng $PROCPS_VER|GPL-2.0-or-later $BUILD_DIR/lsof/lsof|lsof|lsof $LSOF_VER|Purdue/BSD-style " @@ -404,6 +417,9 @@ write_meta() { || die "复制许可证失败: ncurses COPYING" cp "$BUILD_DIR/lsof/COPYING" "$OUT_DIR/licenses/lsof-${LSOF_VER}-COPYING" \ || die "复制许可证失败: lsof COPYING" + # musl:七个 procps/lsof 产物都静态链入了 zig 自带 musl(MIT),必须随副本留声明 + cp "$MUSL_COPYRIGHT" "$OUT_DIR/licenses/musl-COPYRIGHT" \ + || die "复制许可证失败: musl COPYRIGHT($MUSL_COPYRIGHT)" { echo "MeowOS devkit — 静态 MIPS32r2 调试工具集" @@ -423,14 +439,16 @@ EOF echo " ncurses $NCURSES_VER MIT-style sha256 $(printf '%s' "$SRC_NCURSES" | awk -F'|' '{print $3}')" echo " procps-ng $PROCPS_VER GPL-2.0+ / library=LGPL-2.1+ sha256 $(printf '%s' "$SRC_PROCPS" | awk -F'|' '{print $3}')" echo " lsof $LSOF_VER Purdue/BSD sha256 $(printf '%s' "$SRC_LSOF" | awk -F'|' '{print $3}')" + echo " musl zig $ZIG_VERSION 自带(MIT) 许可证原文见 licenses/musl-COPYRIGHT" echo " (--with-sources 时)src/ 内还有 build_devkit.sh + DEVKIT.md(构建控制脚本,GPLv2 §3 要求)" - echo " 下载地址与重编方法: scripts/build/build_devkit.sh(--with-sources 把上述文件放进 src/)" + echo " 重编方法: src/build_devkit.sh(卡上路径;仓库里是 scripts/build/build_devkit.sh)" + echo " 许可证原文: licenses/*-COPYING / licenses/*-COPYRIGHT;说明见 src/DEVKIT.md" echo echo "注意: procps-ng 是 GPL-2.0-or-later。把本目录随卡交付给第三方时," echo " GPLv2 §3 要求提供对应源码(含构建脚本)—— 请用 --with-sources 带上 src/," echo " 或随卡附书面要约(有效期 ≥3 年)。仅给出下载 URL 不构成合规。" - echo " 另外 ps/vmstat/slabtop/free/pmap 静态链接了 LGPL-2.1+ 的 libproc2:" - echo " LGPL-2.1 §6 要求提供可重链接的 object code 或书面要约,详见 DEVKIT.md §6。" + echo " 另外 ps/vmstat/slabtop/free/pmap/sysctl 静态链接了 LGPL-2.1+ 的 libproc2:" + echo " LGPL-2.1 §6 要求「源码(§6a) 或 共享库(§6b) 或 书面要约(§6c)」三选一,详见 DEVKIT.md §6。" } > "$OUT_DIR/MANIFEST.txt" cat > "$OUT_DIR/README.txt" <<'EOS' @@ -446,9 +464,13 @@ MeowOS devkit — 设备端调试工具(静态 MIPS32r2) export PATH="/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH" fi -源码与许可证: - 若随卡分发了源码:源码(含构建脚本 build_devkit.sh、DEVKIT.md)在 ../src/; - 各上游许可证原文在 ../licenses/;版本/体积/sha256 见 ../MANIFEST.txt。 +目录内容(本文件所在目录就是 devkit 根): + bin/ 可执行文件(实体文件,非软链) + MANIFEST.txt 版本 / 许可证 / 体积 / sha256 + licenses/ 上游许可证原文(procps GPLv2 + LGPL-2.1、ncurses、lsof、musl) + src/ 仅当随卡附带了源码(--with-sources)时存在:三份源码包 + + build_devkit.sh + DEVKIT.md。**src/ 是生成目录,请勿手放文件。** + 说明文档:src/DEVKIT.md(若本源码未随附,见仓库 scripts/build/DEVKIT.md) exFAT 没有 Unix 权限位(挂载 fmask=0022 → 新文件一律 0755),也没有符号链接。 本目录里都是实体可执行文件,拷到卡上即可执行,无需 chmod,也不能用软链。 @@ -456,8 +478,8 @@ exFAT 没有 Unix 权限位(挂载 fmask=0022 → 新文件一律 0755), 而不是反复 chmod —— fmask=0022 下 chmod 本身是 no-op。 与 busybox 同名命令的关系(devkit/bin 前插 PATH 时生效): - free ps lsof → 会被本目录版本覆盖(procps/lsof 字段更全) - vmstat slabtop pmap → busybox 没有,直接新增 + free ps lsof → 会被本目录版本覆盖(procps/lsof 字段更全) + vmstat slabtop pmap sysctl → busybox 1.31 没有这些 applet,直接新增 要临时用回 busybox 版:busybox free / busybox ps / busybox lsof 注意:/bin/ps -> busybox,/usr/bin/{free,lsof,top} -> ../../bin/busybox, 所以“前插 devkit/bin”是唯一让新版本生效的方式(不要覆盖这些软链: @@ -469,15 +491,16 @@ rootfs 是 ubifs,随固件回刷会被还原,而且 busybox 软链是基线 - slabtop -o(一次性输出)不需要 terminfo,也不要求 TTY; 交互模式走 /usr/share/terminfo(设备已有 xterm 等条目;已编进 ncurses)。 - 本工具链(zig 0.17 musl)在 4.4 内核上是 time64 ABI 错配: - `vmstat -t` 的时间戳列会是**空**(localtime 返回 NULL)。 - vmstat 采样间隔、ps 的 lstart、slabtop/free/pmap/lsof 均正常。 - 原因与后续修法见 ../src/DEVKIT.md「已知限制:工具链 time64」。 + * `vmstat -t` 的时间戳列会是**空**(localtime 返回 NULL) + * `free -s 0.5`(小数间隔)会**立即返回**(亚秒等待被抹平); + `free -s 1` 与 `vmstat 1 N` 的整秒间隔正常 + * ps 的 lstart、slabtop/pmap/sysctl 不受影响 + 原因与后续修法见 src/DEVKIT.md「已知限制:工具链 time64」。 - lsof 不带 -n 时会尝试反向解析(编译进 musl 的 resolv.conf 路径是构建机的, 设备上不存在)→ 实测 `lsof -i` 约 3s 超时;请用 `lsof -nP`。 - - 不提供 top:见 ../src/DEVKIT.md(同样是 time64 问题,表头时钟溢出)。 + - 不提供 top:见 src/DEVKIT.md(同样是 time64 问题:表头时钟不可信)。 EOS } - # --------------------------------------------------------------------------- # 主流程 # --------------------------------------------------------------------------- @@ -518,16 +541,29 @@ main() { old_stamp="" fi - local bin_ok=1 mf_ok=0 n + local bin_ok=1 mf_ok=0 meta_ok=0 n for n in $TOOL_NAMES; do [ -f "$BIN_DIR/$n" ] && verify_binary "$BIN_DIR/$n" >/dev/null 2>&1 || bin_ok=0 done - # 交付完整性也要一并作为 skip 条件:MANIFEST 缺失或与 bin 不符时宁可重建 + # 交付完整性也要一并作为 skip 条件:MANIFEST/许可证/README 缺失或与 bin 不符时 + # 宁可重建(与 P1 同类——不能因为“跳过编译”就让合规材料静默消失)。 if [ -f "$OUT_DIR/MANIFEST.txt" ] && verify_manifest >/dev/null 2>&1; then mf_ok=1 fi + meta_complete && meta_ok=1 - if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ "$bin_ok" = 1 ] && [ "$mf_ok" = 1 ]; then + # 产物本身没问题、只是 MANIFEST/README/licenses 被删:就地补,不必重编 + if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ "$bin_ok" = 1 ] \ + && { [ "$mf_ok" = 0 ] || [ "$meta_ok" = 0 ]; } \ + && [ -d "$BUILD_DIR/procps-src" ] && [ -d "$BUILD_DIR/ncurses-src" ] && [ -d "$BUILD_DIR/lsof" ]; then + log "产物未变,但 MANIFEST/README/licenses 不全 → 就地补全" + write_meta + if verify_manifest >/dev/null 2>&1; then mf_ok=1; else mf_ok=0; fi + if meta_complete; then meta_ok=1; else meta_ok=0; fi + fi + + if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ "$bin_ok" = 1 ] \ + && [ "$mf_ok" = 1 ] && [ "$meta_ok" = 1 ]; then # P1 回归点:--with-sources 必须在这里就兑现,不能因为“跳过编译”而漏掉源码。 # (曾经把这段放在 return 之后,结果 stamp 命中时静默不产出 src/,退出码仍是 0。) if [ "$WITH_SOURCES" = 1 ]; then @@ -588,6 +624,7 @@ collect_verify_existing() { verify_binary "$BIN_DIR/$n" && info "OK $n $(wc -c < "$BIN_DIR/$n") B" || bad=$((bad + 1)) done verify_manifest || bad=$((bad + 1)) + verify_meta || bad=$((bad + 1)) FAIL=$bad } @@ -615,7 +652,30 @@ verify_manifest() { info "FAIL $n 体积与 MANIFEST 不一致($got_size vs $(wc -c < "$BIN_DIR/$n"))"; bad=$((bad + 1)) fi done - [ "$bad" = 0 ] && info "OK MANIFEST.txt 与 bin/ 六项 sha256/体积逐条一致" + [ "$bad" = 0 ] && info "OK MANIFEST.txt 与 bin/ 逐条 sha256/体积一致" + return $((bad > 0)) +} + +# 交付完整性:MANIFEST + README + 五份许可证副本,缺一个都算不完整 +# (和 P1 同类:这些是合规/可用性的一部分,不能因为“跳过编译”就静默缺失) +meta_complete() { + local f + [ -s "$OUT_DIR/MANIFEST.txt" ] || return 1 + [ -s "$OUT_DIR/README.txt" ] || return 1 + for f in $LICENSE_FILES; do + [ -s "$OUT_DIR/licenses/$f" ] || return 1 + done + return 0 +} + +verify_meta() { + local f bad=0 + [ -s "$OUT_DIR/MANIFEST.txt" ] || { info "FAIL 缺 MANIFEST.txt(版本/许可证/sha 无从核对)"; bad=$((bad + 1)); } + [ -s "$OUT_DIR/README.txt" ] || { info "FAIL 缺 README.txt(上卡说明)"; bad=$((bad + 1)); } + for f in $LICENSE_FILES; do + [ -s "$OUT_DIR/licenses/$f" ] || { info "FAIL 缺 licenses/$f(许可证留痕)"; bad=$((bad + 1)); } + done + [ "$bad" = 0 ] && info "OK MANIFEST/README/licenses($(printf '%s' "$LICENSE_FILES" | wc -w) 份)齐全" return $((bad > 0)) } diff --git a/scripts/build/test_build_devkit.sh b/scripts/build/test_build_devkit.sh index fe1f154..7fa92d0 100755 --- a/scripts/build/test_build_devkit.sh +++ b/scripts/build/test_build_devkit.sh @@ -1,9 +1,14 @@ #!/bin/bash # ============================================================================ -# test_build_devkit.sh — build_devkit.sh 的回归测试(不重编,秒级) +# test_build_devkit.sh — build_devkit.sh 的回归测试(不重编) # -# 针对 r1 评审 P1 的回归点:**stamp 命中(跳过编译)时,--with-sources 必须 -# 真正产出 src/,不能静默成功**。另覆盖 --verify-only 的 MANIFEST 漂移检测。 +# 耗时:热缓存约 15 秒;若需冷启动基线重建(首次运行/源码未缓存)约 1–2 分钟。 +# +# 覆盖: +# r1 P1 回归点 —— stamp 命中(跳过编译)时,--with-sources 必须真正产出 src/ +# r2 P2-1 —— licenses/README 被删后 --with-sources 必须恢复, +# --verify-only 必须拒绝缺许可证的交付 +# MANIFEST 内容漂移检测、src/ 缺文件与内容漂移自愈 # # 前置:MEOW_DEVKIT_WORK 里已有一次构建(脚本会先跑一次增量构建,命中则秒回; # 缓存缺失时会联网下载/编译,第一次运行约 1 分钟)。 @@ -88,6 +93,29 @@ fi cp "$TMP/MANIFEST.txt.bak" "$OUT/MANIFEST.txt" run "--verify-only(恢复后)" bash "$SCRIPT" --verify-only && pass "T5b 恢复后重新通过" || fail "T5b 恢复后仍失败" +echo "== 6) 交付完整性:删 licenses/ 后 --with-sources 必须恢复" +LICENSES="procps-ng-4.0.4-COPYING procps-ng-4.0.4-COPYING.LIB ncurses-6.5-COPYING lsof-4.99.4-COPYING musl-COPYRIGHT" +rm -rf "$OUT/licenses" +run "--with-sources 补 licenses" bash "$SCRIPT" --with-sources || fail "T6 --with-sources 退出码非 0" +for f in $LICENSES; do + [ -s "$OUT/licenses/$f" ] && pass "T6 licenses/$f 已恢复" || fail "T6 缺 licenses/$f" +done + +echo "== 7) --verify-only 必须拒绝缺 licenses 的交付" +rm -rf "$OUT/licenses" +if run "--verify-only(缺 licenses)" bash "$SCRIPT" --verify-only; then + fail "T7 --verify-only 未发现 licenses/ 缺失" +else + pass "T7 --verify-only 正确拒绝缺 licenses 的交付" +fi +run "--with-sources 恢复" bash "$SCRIPT" --with-sources || fail "T7 --with-sources 恢复失败" +run "--verify-only(恢复后)" bash "$SCRIPT" --verify-only && pass "T7b 恢复后通过" || fail "T7b 恢复后仍失败" + +echo "== 8) 交付完整性:删 README.txt 后 --with-sources 必须恢复" +rm -f "$OUT/README.txt" +run "--with-sources 补 README" bash "$SCRIPT" --with-sources || fail "T8 --with-sources 退出码非 0" +[ -s "$OUT/README.txt" ] && pass "T8 README.txt 已恢复" || fail "T8 README.txt 未恢复" + echo printf '== 结果: %d 通过, %d 失败\n' "$PASS" "$FAILN" [ "$FAILN" = 0 ]