Merge branch 'feat/sdk-time64-syscall-gaps'
This commit is contained in:
commit
c2635daf3f
4 files changed
+555
-22
No files matched your search
+144
-13
@@ -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,147 @@ 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` 的 5 处条件等待
|
||||
(:312/:351/:417/:431/:499)。
|
||||
`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`(5 处,:312/:351/:417/:431/:499)、
|
||||
`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 分支里定义 `__<api>_time64`(或该 API 的重定向名);
|
||||
2. 入参展成 `meow_time64_spec` / `meow_time64_itimerspec`,调
|
||||
`meow_time32_rel_from_parts()`(超时类,饱和)或 `meow_time32_abs_from_parts()`
|
||||
(绝对时刻类,越界回 `ENOTSUP`);
|
||||
3. 发 `SYS_<api>_time32`(或 `SYS_<api>` 的 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 再超时,而不是立即返回。
|
||||
@@ -304,4 +304,38 @@ int meow_ime_mode_pinyin(void);
|
||||
/* 秒/纳秒 → 单调毫秒的唯一换算点(meow_ui_time_ms 与宿主单测共用)。 */
|
||||
uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec);
|
||||
|
||||
/* ---- mipsel o32 的 time32 结构(内核布局,与 libc 头的 time_t 宽度无关)----
|
||||
* 背景:zig clang 给 32 位目标预定义 __ILP32__,打穿 musl 的 time64 回退守卫,
|
||||
* 一批封装把 16 字节 time64 结构直接交给 time32 系统调用(CLOCK_ABI.md §6)。
|
||||
* 接管这些入口时统一走下面这几个纯转换函数——它们不依赖平台,宿主单测直接覆盖。 */
|
||||
struct meow_time32 {
|
||||
int32_t tv_sec;
|
||||
int32_t tv_nsec;
|
||||
};
|
||||
struct meow_itimerspec32 {
|
||||
struct meow_time32 it_interval;
|
||||
struct meow_time32 it_value;
|
||||
};
|
||||
/* 与 libc 结构解耦的 64 位镜像(避免宿主/交叉两端 time_t 宽度差异进测试)。 */
|
||||
struct meow_time64_spec {
|
||||
int64_t tv_sec;
|
||||
int64_t tv_nsec;
|
||||
};
|
||||
struct meow_time64_itimerspec {
|
||||
struct meow_time64_spec it_interval;
|
||||
struct meow_time64_spec it_value;
|
||||
};
|
||||
|
||||
/* 相对超时(futex / poll / select 的「等多久」):sec 超出 int32 时饱和
|
||||
* (正→INT32_MAX、负→INT32_MIN,与 musl CLAMP 一致),nsec 原样。 */
|
||||
void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out);
|
||||
/* 绝对时刻(clock_settime / timerfd / utimensat):sec 落不进 int32 返回 -1
|
||||
* (调用方回 ENOTSUP,与 musl time64 路径的判定一致),nsec 原样。 */
|
||||
int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out);
|
||||
/* itimerspec(timerfd / POSIX timer)转换;任一侧越界返回 -1 且 *out 不变。 */
|
||||
int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in,
|
||||
struct meow_itimerspec32 *out);
|
||||
void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in,
|
||||
struct meow_time64_itimerspec *out);
|
||||
|
||||
#endif /* MEOW_UI_INTERNAL_H */
|
||||
+207
-9
@@ -25,23 +25,75 @@
|
||||
* `usleep(16000)` 立即返回(SDK 里帧节拍/轮询会变忙等空转)。这里一并按 time32
|
||||
* 转换后重发,恢复亚秒睡眠。
|
||||
*
|
||||
* 残余(未修):futex 相对超时(pthread 定时等待的剩余时间按 8 字节读)、timerfd_settime、
|
||||
* clock_settime/adjtime、pselect/ppoll 超时——同为该守卫的受害者,见 CLOCK_ABI.md §6。
|
||||
* M16(同源第二弹;逐项证据见 CLOCK_ABI.md §6):同一句 `!__ILP32__` 还打穿了别的入口,
|
||||
* 本文件按风险分档接管:
|
||||
* - futex 相对超时(`__timedwait_cp`/`__timedwait`):**已接管**。它是进程同步核心,
|
||||
* 但 musl 原文逻辑可完整复刻(含 __syscall_cp 取消点语义),且是 SDK/app 唯一在用的
|
||||
* 受害者(meow_ui_audio 的 pthread_cond_timedwait 在亚秒等待下会立即超时)。
|
||||
* - timerfd_settime/gettime、clock_settime:**已接管**(纯参数转换,无内部状态)。
|
||||
* - pselect/ppoll/select、getrusage、wait4、clock_adjtime:**未受影响**(musl 回退分支
|
||||
* 自己就转成 `long[2]`/有宽度判断),本单只补证据,不改代码。
|
||||
* - sigtimedwait、semtimedop、timer_settime/gettime、utimensat、recvmmsg、mq_timedsend、
|
||||
* pthread_mutex_timedlock 的 PI 快路径:仍同源受损但 SDK/App 均未使用,风险收益比不划算,
|
||||
* 修复配方已写进 CLOCK_ABI.md §7 待后续单。
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
|
||||
#if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4)
|
||||
#define MEOW_TIME32_KERNEL_ABI 1
|
||||
#include <pthread.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#define MEOW_TIME32_KERNEL_ABI 0
|
||||
#endif
|
||||
|
||||
/* ---- 平台无关的 time64 → 内核 o32 time32 换算(宿主单测直接覆盖)----
|
||||
* 这几个函数不碰系统调用,只为“结构宽度”这件事提供唯一实现;mips 分支的接管入口
|
||||
* 全部经它们转换,宿主 test_meow_time64.c 用同一份代码验边界。 */
|
||||
void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out)
|
||||
{
|
||||
if (sec > INT32_MAX) sec = INT32_MAX;
|
||||
else if (sec < INT32_MIN) sec = INT32_MIN;
|
||||
out->tv_sec = (int32_t)sec;
|
||||
out->tv_nsec = (int32_t)nsec;
|
||||
}
|
||||
|
||||
int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out)
|
||||
{
|
||||
if (sec > INT32_MAX || sec < INT32_MIN) return -1;
|
||||
out->tv_sec = (int32_t)sec;
|
||||
out->tv_nsec = (int32_t)nsec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in,
|
||||
struct meow_itimerspec32 *out)
|
||||
{
|
||||
struct meow_itimerspec32 t;
|
||||
if (meow_time32_abs_from_parts(in->it_interval.tv_sec, in->it_interval.tv_nsec,
|
||||
&t.it_interval)) return -1;
|
||||
if (meow_time32_abs_from_parts(in->it_value.tv_sec, in->it_value.tv_nsec,
|
||||
&t.it_value)) return -1;
|
||||
*out = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in,
|
||||
struct meow_time64_itimerspec *out)
|
||||
{
|
||||
out->it_interval.tv_sec = in->it_interval.tv_sec;
|
||||
out->it_interval.tv_nsec = in->it_interval.tv_nsec;
|
||||
out->it_value.tv_sec = in->it_value.tv_sec;
|
||||
out->it_value.tv_nsec = in->it_value.tv_nsec;
|
||||
}
|
||||
|
||||
uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec)
|
||||
{
|
||||
return (uint64_t)sec * 1000u + (uint64_t)nsec / 1000000u;
|
||||
@@ -50,7 +102,7 @@ uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec)
|
||||
uint64_t meow_ui_time_ms(void)
|
||||
{
|
||||
#if MEOW_TIME32_KERNEL_ABI
|
||||
struct meow_time32 { int32_t tv_sec; int32_t tv_nsec; } t;
|
||||
struct meow_time32 t;
|
||||
t.tv_sec = 0;
|
||||
t.tv_nsec = 0;
|
||||
if (syscall(SYS_clock_gettime32, CLOCK_MONOTONIC, &t) != 0) return 0;
|
||||
@@ -63,12 +115,6 @@ uint64_t meow_ui_time_ms(void)
|
||||
}
|
||||
|
||||
#if MEOW_TIME32_KERNEL_ABI
|
||||
/* 内核 o32 time32 布局(与 libc 头的 time_t 宽度无关)。 */
|
||||
struct meow_time32 {
|
||||
int32_t tv_sec;
|
||||
int32_t tv_nsec;
|
||||
};
|
||||
|
||||
static int32_t time32_clamp(int64_t v)
|
||||
{
|
||||
if (v > INT32_MAX) return INT32_MAX;
|
||||
@@ -144,4 +190,156 @@ int __nanosleep_time64(const struct timespec *req, struct timespec *rem)
|
||||
if (r) { errno = r; return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- futex 相对超时(__timedwait_cp / __timedwait 强定义)----
|
||||
* 同源缺陷:musl thread/__timedwait.c 的 __futex4_cp 也在 `#if defined(SYS_futex_time64)
|
||||
* && !defined(__ILP32__)` 里做 time64,被 __ILP32__ 打穿后走 `#else`:
|
||||
* __syscall_cp(SYS_futex(4238), addr, op, val, to) ← to 是 16 字节 time64 结构
|
||||
* 内核按 o32 time32 只读前 8 字节 {int32 sec, int32 nsec}:
|
||||
* 偏移 0..3 = tv_sec 低字(够用),偏移 4..7 = tv_sec 高字(正常计时恒 0)。
|
||||
* 于是「还剩 0.4s」被当成 {0,0} → 立刻超时;「剩 2.3s」被当成 2s 整。
|
||||
* 受害者:pthread_cond_timedwait / sem_timedwait / pthread_join 超时 / aio_suspend
|
||||
* (SDK 的 meow_ui_audio.c 与 meow_audiod、ncm 播放线程都在用)。
|
||||
*
|
||||
* 风险控制:不重写任何同步算法,只把 musl 的 __timedwait_cp 原文搬过来,唯一改动是
|
||||
* 发系统调用前把相对超时转成 time32;取消点仍走 libc 的 __syscall_cp(ECANCELED/
|
||||
* EINTR 语义不变)。链接期接管必须同时定义 __timedwait —— 否则 libc 的 __timedwait.o
|
||||
* 会因 `__timedwait` 未解析被拉入,与本文的 __timedwait_cp 撞成 duplicate symbol
|
||||
* (ld.lld 实测会报错)。__eintr_valid_flag 给弱定义,真正的强定义在 libc sigaction.o。
|
||||
*
|
||||
* 未覆盖:pthread_mutex_timedlock 的 PTHREAD_PRIO_INHERIT 快路径(同文件里的静态
|
||||
* __futex4,直接调 FUTEX_LOCK_PI);SDK/App 未使用 PI 互斥量,见 CLOCK_ABI.md §7。 */
|
||||
#define MEOW_FUTEX_WAIT 0
|
||||
#define MEOW_FUTEX_PRIVATE 128
|
||||
|
||||
int __pthread_setcancelstate(int new, int *old);
|
||||
long __syscall_cp(long nr, long a, long b, long c, long d, long e, long f);
|
||||
__attribute__((weak)) volatile int __eintr_valid_flag = 0;
|
||||
|
||||
static int meow_futex4_cp(volatile void *addr, int op, int val, const struct timespec *to)
|
||||
{
|
||||
struct meow_time32 t32;
|
||||
void *top = 0;
|
||||
long r;
|
||||
|
||||
if (to) {
|
||||
meow_time32_rel_from_parts((int64_t)to->tv_sec, (int64_t)to->tv_nsec, &t32);
|
||||
top = &t32;
|
||||
}
|
||||
r = __syscall_cp(SYS_futex, (long)addr, op, val, (long)top, 0, 0);
|
||||
if (r != -ENOSYS) return (int)r;
|
||||
return (int)__syscall_cp(SYS_futex, (long)addr,
|
||||
op & ~MEOW_FUTEX_PRIVATE, val, (long)top, 0, 0);
|
||||
}
|
||||
|
||||
int __timedwait_cp(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv);
|
||||
int __timedwait(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv);
|
||||
|
||||
int __timedwait_cp(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv)
|
||||
{
|
||||
int r;
|
||||
struct timespec to, *top = 0;
|
||||
|
||||
if (priv) priv = MEOW_FUTEX_PRIVATE;
|
||||
|
||||
if (at) {
|
||||
/* musl 原文是 `>= 1000000000UL`(负值按无符号也判 EINVAL);显式判负以保 -Wextra 0 warning */
|
||||
if (at->tv_nsec < 0 || at->tv_nsec >= 1000000000L) return EINVAL;
|
||||
if (__clock_gettime(clk, &to)) return EINVAL;
|
||||
to.tv_sec = at->tv_sec - to.tv_sec;
|
||||
if ((to.tv_nsec = at->tv_nsec - to.tv_nsec) < 0) {
|
||||
to.tv_sec--;
|
||||
to.tv_nsec += 1000000000;
|
||||
}
|
||||
if (to.tv_sec < 0) return ETIMEDOUT;
|
||||
top = &to;
|
||||
}
|
||||
|
||||
r = -meow_futex4_cp(addr, MEOW_FUTEX_WAIT | priv, val, top);
|
||||
if (r != EINTR && r != ETIMEDOUT && r != ECANCELED) r = 0;
|
||||
if (r == EINTR && !__eintr_valid_flag) r = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
int __timedwait(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv)
|
||||
{
|
||||
int cs, r;
|
||||
__pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
||||
r = __timedwait_cp(addr, val, clk, at, priv);
|
||||
__pthread_setcancelstate(cs, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ---- timerfd(musl src/linux/timerfd.c 同款缺陷)----
|
||||
* 回退分支把 32 字节 time64 `struct itimerspec` 直接交给内核;o32 内核只认
|
||||
* 16 字节 {interval.sec, interval.nsec, value.sec, value.nsec}。实际读到的
|
||||
* value.sec = 调用者 interval.tv_nsec(错位),value.nsec = 结构填充(垃圾);
|
||||
* gettime 反向把内核的前 16 字节写进错位的字段。SDK/App 当前未用 timerfd,
|
||||
* 但成本极低,顺手按内核 ABI 转换。三个符号必须同一 TU 全定义:libc 的
|
||||
* timerfd.o 同时提供 create/settime/gettime,缺一个就会把它拉进来导致重复定义。 */
|
||||
int timerfd_create(int clockid, int flags)
|
||||
{
|
||||
return (int)syscall(SYS_timerfd_create, clockid, flags);
|
||||
}
|
||||
|
||||
int __timerfd_settime64(int fd, int flags, const struct itimerspec *new,
|
||||
struct itimerspec *old);
|
||||
int __timerfd_gettime64(int fd, struct itimerspec *cur);
|
||||
|
||||
int __timerfd_settime64(int fd, int flags, const struct itimerspec *new,
|
||||
struct itimerspec *old)
|
||||
{
|
||||
struct meow_time64_itimerspec in;
|
||||
struct meow_itimerspec32 n32, o32;
|
||||
|
||||
if (!new) { errno = EFAULT; return -1; }
|
||||
in.it_interval.tv_sec = new->it_interval.tv_sec;
|
||||
in.it_interval.tv_nsec = new->it_interval.tv_nsec;
|
||||
in.it_value.tv_sec = new->it_value.tv_sec;
|
||||
in.it_value.tv_nsec = new->it_value.tv_nsec;
|
||||
if (meow_time32_itimerspec_from_64(&in, &n32)) { errno = ENOTSUP; return -1; }
|
||||
|
||||
if (syscall(SYS_timerfd_settime32, fd, flags, &n32, old ? &o32 : 0) != 0)
|
||||
return -1;
|
||||
if (old) {
|
||||
old->it_interval.tv_sec = o32.it_interval.tv_sec;
|
||||
old->it_interval.tv_nsec = o32.it_interval.tv_nsec;
|
||||
old->it_value.tv_sec = o32.it_value.tv_sec;
|
||||
old->it_value.tv_nsec = o32.it_value.tv_nsec;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __timerfd_gettime64(int fd, struct itimerspec *cur)
|
||||
{
|
||||
struct meow_itimerspec32 c32;
|
||||
|
||||
if (!cur) { errno = EFAULT; return -1; }
|
||||
if (syscall(SYS_timerfd_gettime32, fd, &c32) != 0) return -1;
|
||||
cur->it_interval.tv_sec = c32.it_interval.tv_sec;
|
||||
cur->it_interval.tv_nsec = c32.it_interval.tv_nsec;
|
||||
cur->it_value.tv_sec = c32.it_value.tv_sec;
|
||||
cur->it_value.tv_nsec = c32.it_value.tv_nsec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- clock_settime(musl src/time/clock_settime.c 同款缺陷)----
|
||||
* 回退分支把 16 字节 time64 结构直接交给 SYS_clock_settime32;内核只写/读 8 字节,
|
||||
* nsec 位永远为 0。绝对时刻超出 int32 时回 ENOTSUP(与 musl time64 路径一致)。 */
|
||||
int __clock_settime64(clockid_t clk, const struct timespec *ts)
|
||||
{
|
||||
struct meow_time32 t;
|
||||
|
||||
if (!ts) { errno = EFAULT; return -1; }
|
||||
if (meow_time32_abs_from_parts((int64_t)ts->tv_sec, (int64_t)ts->tv_nsec, &t)) {
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
if (syscall(SYS_clock_settime32, clk, &t) != 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,170 @@
|
||||
/* test_meow_time64.c — 宿主单测:time64 → 内核 o32 time32 的换算与结构宽度。
|
||||
*
|
||||
* 背景(M16):zig clang 给 32 位 mips 预定义 __ILP32__,打穿 musl 的 time64 回退守卫,
|
||||
* futex 相对超时 / timerfd / clock_settime 等入口把 16 字节(time64)结构直接交给 time32
|
||||
* 系统调用。meow_ui_time.c 的接管入口统一经 meow_ui_internal.h 里这几个纯换算函数转换,
|
||||
* 本测试在宿主上把「换算结果 + 内核实际读到的宽度」固定住,不需要 mips 设备。
|
||||
*
|
||||
* 编译(patch_v2/sdk 目录下):
|
||||
* gcc -Os -Wall -Wextra -I. -Iinclude -c meow_ui_time.c -o /tmp/meow_ui_time.o
|
||||
* gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time64.c /tmp/meow_ui_time.o \
|
||||
* -o test_meow_time64
|
||||
* 运行:./test_meow_time64 (exit 0=PASS / 1=FAIL)
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
|
||||
static int g_fail = 0;
|
||||
|
||||
static void check(const char *name, int ok)
|
||||
{
|
||||
if (ok) {
|
||||
printf("ok %s\n", name);
|
||||
} else {
|
||||
printf("FAIL %s\n", name);
|
||||
g_fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_time32(const char *name, const struct meow_time32 *got,
|
||||
int32_t sec, int32_t nsec)
|
||||
{
|
||||
if (got->tv_sec == sec && got->tv_nsec == nsec) {
|
||||
printf("ok %s = {%d, %d}\n", name, (int)got->tv_sec, (int)got->tv_nsec);
|
||||
} else {
|
||||
printf("FAIL %s = {%d, %d},期望 {%d, %d}\n", name,
|
||||
(int)got->tv_sec, (int)got->tv_nsec, (int)sec, (int)nsec);
|
||||
g_fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 模拟 o32 内核对「调用者给的指针」的实际读法:只读前 8 字节,按两个 int32 解释。 */
|
||||
static void kernel_time32_view(const void *ptr, struct meow_time32 *out)
|
||||
{
|
||||
memcpy(out, ptr, sizeof *out);
|
||||
}
|
||||
|
||||
static void test_rel(void)
|
||||
{
|
||||
struct meow_time32 t;
|
||||
|
||||
meow_time32_rel_from_parts(5, 123456789, &t);
|
||||
check_time32("rel 普通值", &t, 5, 123456789);
|
||||
|
||||
meow_time32_rel_from_parts(0, 500000000, &t);
|
||||
check_time32("rel 亚秒(原缺陷会变 {0,0})", &t, 0, 500000000);
|
||||
|
||||
meow_time32_rel_from_parts(INT32_MAX, 999999999, &t);
|
||||
check_time32("rel int32 上界", &t, INT32_MAX, 999999999);
|
||||
|
||||
meow_time32_rel_from_parts((int64_t)INT32_MAX + 1, 1, &t);
|
||||
check_time32("rel 上溢饱和到 INT32_MAX", &t, INT32_MAX, 1);
|
||||
|
||||
meow_time32_rel_from_parts(INT64_MAX, 0, &t);
|
||||
check_time32("rel INT64_MAX 饱和", &t, INT32_MAX, 0);
|
||||
|
||||
meow_time32_rel_from_parts(-1, 500000000, &t);
|
||||
check_time32("rel 负值透传(调用方另判 ETIMEDOUT)", &t, -1, 500000000);
|
||||
|
||||
meow_time32_rel_from_parts((int64_t)INT32_MIN - 1, 0, &t);
|
||||
check_time32("rel 下溢饱和到 INT32_MIN", &t, INT32_MIN, 0);
|
||||
}
|
||||
|
||||
static void test_abs(void)
|
||||
{
|
||||
struct meow_time32 t;
|
||||
|
||||
t.tv_sec = 7;
|
||||
t.tv_nsec = 7;
|
||||
check("abs 普通值", meow_time32_abs_from_parts(1700000000, 250000000, &t) == 0);
|
||||
check_time32("abs 普通值内容", &t, 1700000000, 250000000);
|
||||
|
||||
check("abs int32 上界接受", meow_time32_abs_from_parts(INT32_MAX, 0, &t) == 0);
|
||||
check("abs int32+1 拒绝", meow_time32_abs_from_parts((int64_t)INT32_MAX + 1, 0, &t) == -1);
|
||||
check("abs int32 下界接受", meow_time32_abs_from_parts(INT32_MIN, 0, &t) == 0);
|
||||
check("abs int32-1 拒绝", meow_time32_abs_from_parts((int64_t)INT32_MIN - 1, 0, &t) == -1);
|
||||
check("abs 2038 后一秒拒绝", meow_time32_abs_from_parts(2147483648LL, 0, &t) == -1);
|
||||
}
|
||||
|
||||
static void test_itimerspec(void)
|
||||
{
|
||||
struct meow_time64_itimerspec in;
|
||||
struct meow_itimerspec32 out, back32;
|
||||
struct meow_time64_itimerspec back;
|
||||
|
||||
in.it_interval.tv_sec = 1;
|
||||
in.it_interval.tv_nsec = 250000000;
|
||||
in.it_value.tv_sec = 3600;
|
||||
in.it_value.tv_nsec = 999999999;
|
||||
memset(&out, 0xAA, sizeof out);
|
||||
check("itimerspec 四字段转换成功",
|
||||
meow_time32_itimerspec_from_64(&in, &out) == 0);
|
||||
check_time32("itimerspec interval", &out.it_interval, 1, 250000000);
|
||||
check_time32("itimerspec value", &out.it_value, 3600, 999999999);
|
||||
|
||||
/* 越界必须整体失败且不改 *out(timerfd_settime 回 ENOTSUP 的前提) */
|
||||
memcpy(&back32, &out, sizeof back32);
|
||||
in.it_value.tv_sec = (int64_t)INT32_MAX + 1;
|
||||
check("itimerspec 越界拒绝", meow_time32_itimerspec_from_64(&in, &out) == -1);
|
||||
check("itimerspec 越界不写坏输出", memcmp(&back32, &out, sizeof out) == 0);
|
||||
|
||||
/* 反向(timerfd_gettime)字段不串位 */
|
||||
memset(&back, 0, sizeof back);
|
||||
meow_time64_itimerspec_from_32(&back32, &back);
|
||||
check("itimerspec 反向 sec/nsec 不串位",
|
||||
back.it_interval.tv_sec == 1 && back.it_interval.tv_nsec == 250000000 &&
|
||||
back.it_value.tv_sec == 3600 && back.it_value.tv_nsec == 999999999);
|
||||
}
|
||||
|
||||
/* 结构宽度证明:同一块内存按「libc 头 ABI」与「内核 o32 ABI」读出的值不同。 */
|
||||
static void test_struct_width(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if (sizeof(time_t) != 8 || offsetof(struct timespec, tv_nsec) != 8) {
|
||||
printf("skip 结构宽度模拟(宿主 time_t/timespec 布局与 ILP32+time64 不一致)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 剩 0.4s:16 字节 time64 结构的前 8 字节被内核读成 {sec=0, nsec=0} → 立即超时 */
|
||||
memset(&ts, 0, sizeof ts);
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 400000000;
|
||||
struct meow_time32 old_view;
|
||||
kernel_time32_view(&ts, &old_view);
|
||||
check("旧 ABI:0.4s 被内核读成 {0,0}(立即超时)",
|
||||
old_view.tv_sec == 0 && old_view.tv_nsec == 0);
|
||||
|
||||
/* 剩 2.3s:旧 ABI 丢掉 0.3s,只剩 2s 整 */
|
||||
memset(&ts, 0, sizeof ts);
|
||||
ts.tv_sec = 2;
|
||||
ts.tv_nsec = 300000000;
|
||||
kernel_time32_view(&ts, &old_view);
|
||||
check("旧 ABI:2.3s 被内核读成 2s 整",
|
||||
old_view.tv_sec == 2 && old_view.tv_nsec == 0);
|
||||
|
||||
/* 新路径:先转换再交给内核,宽度与值都对 */
|
||||
struct meow_time32 fixed;
|
||||
meow_time32_rel_from_parts((int64_t)ts.tv_sec, (int64_t)ts.tv_nsec, &fixed);
|
||||
struct meow_time32 new_view;
|
||||
kernel_time32_view(&fixed, &new_view);
|
||||
check("新 ABI:内核读到 {2, 300000000}", new_view.tv_sec == 2 &&
|
||||
new_view.tv_nsec == 300000000);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_rel();
|
||||
test_abs();
|
||||
test_itimerspec();
|
||||
test_struct_width();
|
||||
|
||||
printf(g_fail ? "== FAIL ==\n" : "== PASS ==\n");
|
||||
return g_fail;
|
||||
}
|
||||
Reference in new issue
Block a user