From 40ac1e40f09f37f871eed4201cb5cd68c27c62f8 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 20:20:04 +0800 Subject: [PATCH] =?UTF-8?q?docs(sdk):=20CLOCK=5FABI=20=E8=A1=A5=20M16=20?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E6=B8=85=E7=82=B9/=E6=8E=A5=E7=AE=A1?= =?UTF-8?q?=E6=9C=BA=E5=88=B6/=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §6 把「同源残余」换成逐条清点:zig musl 源里 20 处 `#if defined(SYS_*_time64) && !defined(__ILP32__)` 守卫,分「受损(回退分支直传 time64 结构)」与「不受影响(回退分支自带 long[]/宽度判断)」两类,各给源文件:行号 与反汇编证据;纠正 M11 对 pselect/ppoll 的保守判断(它们本来就正确)。 §7 记录接管机制:_REDIR_TIME64 让应用引用 __x_time64(不是 API 名);同一 .o 的 libc 符号必须一起定义(只定义 __timedwait_cp 会与 libc 的 __timedwait.o 撞 duplicate symbol,lld 实测);hidden 不阻止静态链接接管但要保证本 TU 被拉入; 结构宽度前提(整链 zig 头)。附未修 7 项的 5 步配方与「不建议顺手全改」的理由。 §8 记录本单验证:宿主 23 项换算单测 + M11 回归、build_sdk_host.sh --verify (76 成员/全 MIPS32r2/0 warning)、四个入口的 syscall 号与缓冲宽度反汇编、 真实 meow_ui.a 链接探针的符号绑定。真机确认仍待设备。 --- patch_v2/sdk/CLOCK_ABI.md | 155 ++++++++++++++++++++++++++++++++++---- 1 file changed, 142 insertions(+), 13 deletions(-) diff --git a/patch_v2/sdk/CLOCK_ABI.md b/patch_v2/sdk/CLOCK_ABI.md index f44cc64..18df71f 100644 --- a/patch_v2/sdk/CLOCK_ABI.md +++ b/patch_v2/sdk/CLOCK_ABI.md @@ -120,6 +120,11 @@ result = tv_sec*1000 + tv_nsec/1e6 `meow_cachegov.c` 统一调 `meow_ui_time_ms()`(cachegov 保留宿主测试注入桩)。 5. 构建脚本 `build_sdk.sh` 的 OBJS 增加 `meow_ui_time`;宿主单测 `test_meow_time.c` 固定换算边界与「单调且前进」;探针 `tools/clock_abi_probe.c` 用于宿主/真机一眼验收。 +6. **M16 续修同源入口**(同一 TU,`#if MEOW_TIME32_KERNEL_ABI` 限定 mips o32): + `__timedwait_cp`/`__timedwait`(futex 相对超时)、`__timerfd_settime64`/ + `__timerfd_gettime64`/`timerfd_create`、`__clock_settime64`。换算逻辑抽成平台无关的 + `meow_time32_{rel,abs}_from_parts()` / itimerspec 转换(`meow_ui_internal.h`), + 宿主 `test_meow_time64.c` 直接覆盖。清单、逐项证据与未修项配方见 §6/§7。 非 mips 目标(宿主单测、aarch64 宿主)走普通 `clock_gettime`,行为不变。 @@ -167,21 +172,145 @@ llvm-nm clock_abi_probe_sdk | grep -E 'clock_gettime|nanosleep' App(hello/reader/files/ncm…)。 - **产物**:`meow_ui.a` 变更 → 需**重编并重链四 App**(ncm/reader/files/hello); `time()`/`gettimeofday()` 的修正也在同一份归档里生效(链接期覆盖)。 -- 真机写入红线未触碰;本单只改 SDK 源与构建脚本。 +- **同步等待(M16)**:`pthread_cond_timedwait` / `sem_timedwait` / `pthread_join` 超时 / + `aio_suspend` / 非 PI 的 `pthread_mutex_timedlock`/rwlock timed 的**相对超时按真实剩余 + 时间**生效(不再「亚秒即超时」)。SDK 侧直接受益者是 `meow_ui_audio.c` 的三处条件等待。 + `timerfd_*`、`clock_settime` 一并修正(当前无调用方)。 +- 真机写入红线未触碰;本单只改 SDK 源与文档/测试。 -## 6. 同源残余(未在本单修复,需单独安排) +## 6. 同源入口全量清点(M16) -同一句 `!__ILP32__` 还打穿了以下路径(睡眠族已在上文一并修掉,余下这些症状不同、 -修复面更大): +M16 把 zig 0.17 musl 源里所有 `#if defined(SYS_*_time64) && !defined(__ILP32__)` 守卫 +逐条编译、反汇编核对(`zig cc --target=mipsel-linux-musleabi`),结论如下。 +「回退分支」= `__ILP32__` 被定义时实际编译进去的代码。 -| 路径 | 症状 | 说明 | +### 6.1 受损(回退分支把 time64 结构直传 time32 系统调用) + +| musl 源(守卫行) | 入口 | 回退分支实际行为 | 状态 | +| --- | --- | --- | --- | +| `thread/__timedwait.c:14` | futex 相对超时 → `pthread_cond_timedwait` / `sem_timedwait` / `pthread_join` 超时 / `aio_suspend` / 非 PI `pthread_mutex_timedlock` / rwlock timed | `__syscall_cp(SYS_futex=4238, …, to)`,`to` 是 16B time64 结构;内核只读前 8B:`tv_nsec` 落在 `tv_sec` 高半字(恒 0)→ 亚秒等待变 0(立即 ETIMEDOUT)、整秒等待丢纳秒 | **M16 已接管** | +| `linux/timerfd.c:14` | `timerfd_settime` | 32B itimerspec 直传 `SYS_timerfd_settime32=4323`;内核按 16B 读 → `value.tv_sec` 读到调用者的 `interval.tv_nsec`,`value.tv_nsec` 读到填充字节 | **M16 已接管** | +| `linux/timerfd.c:42` | `timerfd_gettime` | 4322 只写 16B,调用者按 32B 解 → 字段串位 | **M16 已接管** | +| `time/clock_settime.c:9` | `clock_settime` | 16B 直传 `SYS_clock_settime32=4262` → nsec 恒 0 | **M16 已接管** | +| `signal/sigtimedwait.c:10` | `sigtimedwait` | 16B 直传 `SYS_rt_sigtimedwait=4197` | 未修(SDK/App 未用,§7.3 配方) | +| `ipc/semtimedop.c:19` | `semtimedop` | 16B 直传 `SYS_ipc=4117`(`IPCOP_semtimedop`) | 未修(未用) | +| `time/timer_settime.c:13` | `timer_settime` | 32B itimerspec 直传 4258 | 未修(未用) | +| `time/timer_gettime.c:11` | `timer_gettime` | 4259 只写 16B | 未修(未用) | +| `stat/utimensat.c:15` | `utimensat`(`futimens` 同源) | `times[2]`(32B)直传 4316 | 未修(仅 ncm 宿主 bench/测试出现) | +| `network/recvmmsg.c:21` | `recvmmsg` | 16B timeout 直传 4335 | 未修(未用) | +| `mq/mq_timedsend.c:10` | `mq_timedsend` | 16B 直传 4273 | 未修(未用) | +| `thread/pthread_mutex_timedlock.c:8` | PI 互斥量的 timedlock 快路径 | 16B 直传 4238(`FUTEX_LOCK_PI`) | 未修(非 PI 走已修的 `__timedwait`;SDK/App 不用 PI 互斥量) | + +唯一在用的受害者是 futex:`meow_ui_audio.c`(3 处)、`meow_audiod`、ncm 播放线程都用 +`pthread_cond_timedwait`,修复前「剩 0.4s」会被内核当成 `{0,0}` 立即超时 → 音频环形缓冲 +的等待退化成忙轮询。 + +### 6.2 不受影响(回退分支自己做了宽度转换)——纠正 M11 §6 的猜测 + +| musl 源 | 证据 | +| --- | --- | +| `select/pselect.c:15` | 回退分支 `syscall_cp(SYS_pselect6=4301, …, (long[]){s, ns}, …)`:o32 的 `long`=4B → 正好 8B time32 | +| `select/ppoll.c:14` | 同上(4302)。反汇编 `__ppoll_time64`:`lw 0x0/0x8` 读 16B 结构 → `sw` 成栈上两字 → `addiu $4,$zero,0x10ce`(4302) | +| `select/select.c:28` | mips 无 `SYS_select`,走 `(long[]){s,ns}` + 4301;同 pselect | +| `linux/clock_adjtime.c:40` | 回退分支保留 `if (sizeof(time_t) > sizeof(long))`,构造 long 版 `struct ktimex` | +| `misc/getrusage.c:9` | 同上宽度分支(`dest = &ru_maxrss - 4*sizeof(long)` + 回填) | +| `linux/wait4.c:11` | 同上宽度分支 | + +M11 §6 把 pselect/ppoll 列为「未验证」是保守估计;本次逐条反汇编确认它们**本来就是对的**, +不需要接管(也就没有可修的低风险项)。 + +## 7. 接管机制与未修项配方(M16) + +### 7.1 符号名不是 API 名:`_REDIR_TIME64` 把调用点改了名 + +zig 的 `alltypes.h` 无条件 `_REDIR_TIME64=1`,头文件里每个受影响 API 都有一行 +`__REDIR(x, __x_time64)`(`generic-musl/*.h`)。应用里写 `timerfd_settime(...)`,编译出来 +引用的是 `__timerfd_settime64`。所以接管必须定义**重定向后的符号**: + +| API | 应用引用的符号 | libc 该 `.o` 还定义(必须一并接管,否则重复定义) | | --- | --- | --- | -| `futex` 相对超时 | 亚秒 timed wait 立即超时 | `__timedwait_cp` 的 `__futex4_cp` 同款 `!__ILP32__` 守卫;`pthread_cond_timedwait` 的「now」已由本单修正,但相对超时仍按 8 字节读(>1s 的整秒等待基本可用,亚秒等待失效) | -| `timerfd_settime` | 到期时间错乱 | `struct itimerspec`(32 字节)当 16 字节读;当前 SDK 未用 timerfd | -| `clock_settime`/`clock_adjtime`/`pselect`/`ppoll`/`sigtimedwait`/`semtimedop` 等 | 未验证 | 同族守卫;SDK 目前未直接使用 | +| `timerfd_settime/gettime` | `__timerfd_settime64` / `__timerfd_gettime64` | `timerfd_create` | +| `clock_settime` | `__clock_settime64` | (无) | +| 内部 `__timedwait_cp` | `__timedwait_cp` | `__timedwait`、`__eintr_valid_flag`(弱) | -建议:向 zig 上游报 musl `__ILP32__` 守卫问题(clang 对 32 位目标统一定义 `__ILP32__`, -而该守卫本意只排除 x32);根治应换用容器旧链(gcc,不定义 `__ILP32__`)编 SDK,或 -downgrade/修补 zig 内置 musl。**不要**指望 `-D_TIME_BITS=32`:zig 的 -`alltypes.h` 是无条件 `#define __USE_TIME_BITS64 1`,命令行改不动;而只改用户侧头文件会让 -用户结构(8 字节)与 libc 内部(16 字节,pthread 等)再次错配。 +只定义 `__timedwait_cp` 是**不行**的:libc 的 `__timedwait.o` 会因 `__timedwait` 未解析被 +拉入,与本文的重定义撞车。实测: + +``` +ld.lld: error: duplicate symbol: __timedwait_cp +>>> defined at ovr2.c +>>> defined at __timedwait.c:34 (.../musl/src/thread/__timedwait.c:34) +``` + +### 7.2 为什么链接期接管有效 + +- 「hidden」不阻止静态链接接管:lld 按归档顺序解析,`meow_ui_time.o` 先于 `libc.a` 参与, + libc 内部对 `__timedwait_cp` 的引用就绑到本文(实测 `jal 0x…<__timedwait_cp>`)。 +- 前提是 `meow_ui_time.o` 被拉入:SDK/App 都引用 `meow_ui_time_ms()`,且 `clock_gettime` + 被重定向到 `__clock_gettime64`(本文也定义),所以它总会被拉入。**若将来把本 TU 的 + 接管符号拆到没人引用的新 TU,接管会静默失效。** +- `__eintr_valid_flag` 给弱定义,libc `signal/sigaction.c` 的强定义优先(同时链 sigaction + 实测不冲突)。 +- 取消点语义不重写:`__timedwait_cp` 仍调 libc 的 `__syscall_cp`,`ECANCELED`/`EINTR` + 处理逐行照抄 musl。 +- 结构宽度前提:本 TU 与被接管入口的调用者必须用同一套 zig 头(`time_t` 8B、 + `struct timespec` 16B)。当前四 App 与 launcher 都用 `zig-mipsel-cc` 编译 + (`$SYSROOT/lib` 只有 `libz.a`,libc 来自 zig 内置 musl),前提成立;若将来换链, + 必须整链替换、不能混用。 + +### 7.3 未修项的配方(同一模式,约 10 行/项) + +1. 在 `meow_ui_time.c` 的 mips 分支里定义 `___time64`(或该 API 的重定向名); +2. 入参展成 `meow_time64_spec` / `meow_time64_itimerspec`,调 + `meow_time32_rel_from_parts()`(超时类,饱和)或 `meow_time32_abs_from_parts()` + (绝对时刻类,越界回 `ENOTSUP`); +3. 发 `SYS__time32`(或 `SYS_` 的 time32 号),输出再反向展开; +4. 同一 `.o` 里 libc 定义的所有符号一起接管(见 7.1 表),否则重复定义; +5. 宿主单测补边界,交叉反汇编确认结构宽度与 syscall 号。 + +不建议顺手全改:这些 API 当前零调用(全仓 `grep` 只有 ncm 的宿主 bench/测试用 `utimensat`), +每多接管一个就多一份「转换写错 → 静默写坏文件时间/信号等待」的面;优先级应按「谁先被 App +用」来定。根治仍是上游:换容器旧链(gcc 不定义 `__ILP32__`)编 SDK,或修补 zig 内置 musl +的 `!__ILP32__` 守卫(本意只排除 x32)。 + +## 8. M16 验证 + +宿主(aarch64 Termux,gcc): + +``` +cd patch_v2/sdk +gcc -Os -Wall -Wextra -I. -Iinclude -c meow_ui_time.c -o /tmp/m.o # 0 warning +gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time64.c /tmp/m.o -o /tmp/t && /tmp/t +gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time.c /tmp/m.o -o /tmp/t0 && /tmp/t0 +``` + +- `test_meow_time64`(新,23 项):相对/绝对换算边界(INT32_MAX±1、INT64_MAX、负值)、 + itimerspec 四字段不串位与越界原子性,以及「同一块 16B 内存按内核 o32 只读 8B」的 + before/after 模拟(0.4s → `{0,0}` 立即超时;2.3s → 2s 整)。 +- `test_meow_time`(M11 回归):换算边界 + 单调前进,仍 PASS。 + +交叉: + +``` +bash patch_v2/sdk/build_sdk_host.sh --verify # 76 成员、全 MIPS32r2、0 warning +``` + +反汇编(本单新增/改写的入口): + +| 符号 | syscall 号 | 缓冲宽度证据 | +| --- | --- | --- | +| `__timedwait_cp` | `0x108e` = 4238 `SYS_futex` | 超时在栈上 8B(`addiu $18,$sp,0x20` + `sw $18,0x10($sp)`),不再直传 16B 结构 | +| `__timerfd_settime64` | `0x10e3` = 4323 `timerfd_settime32` | 从调用者 32B 结构读 `0x0/0x4/0x8/0x10/0x14/0x18`,打包成栈上 16B | +| `__timerfd_gettime64` | `0x10e2` = 4322 | 内核只写栈上 16B,再按字段展开到调用者 32B | +| `__clock_settime64` | `0x10a6` = 4262 `clock_settime32` | 8B `{int32 sec, int32 nsec}` | + +链接期(真实 `meow_ui.a` + 探针,顺序同 App:`probe.o meow_ui.a … -lc`): + +``` +llvm-objdump -d probe_sdk | sed -n '/<__pthread_cond_timedwait_time64>:/,/^$/p' | grep jal.__timedwait +# cd930: jal 0x419e8 <__timedwait_cp> ← 绑到 meow_ui.a 里的实现 +llvm-nm probe_sdk | grep -c __timedwait_cp # 1(libc 那份未被拉入) +``` + +真机验证仍待设备(本单未碰真机):`pthread_cond_timedwait` 传 100ms 超时的等待应真的等 +约 100ms 再超时,而不是立即返回。