From 3b5ffece574a62cece5876b0628df649fa8157ef Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 20:19:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdk):=20=E6=8E=A5=E7=AE=A1=20mipsel=20time?= =?UTF-8?q?64=20=E5=90=8C=E6=BA=90=E7=BC=BA=E5=8F=A3=20=E2=80=94=20futex?= =?UTF-8?q?=20=E7=9B=B8=E5=AF=B9=E8=B6=85=E6=97=B6/timerfd/clock=5Fsettime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zig clang 给 mipsel 预定义 __ILP32__,打穿 musl 的 time64 回退守卫,以下入口把 16/32 字节 time64 结构直接交给 o32 time32 系统调用: - thread/__timedwait.c:14:futex 相对超时(pthread_cond_timedwait / sem_timedwait / pthread_join 超时 / aio_suspend / 非 PI timedlock / rwlock timed)。内核只读前 8B, tv_nsec 落在 tv_sec 高半字(恒 0)→ 亚秒等待立即超时、整秒等待丢纳秒。SDK 的 meow_ui_audio 三处条件等待直接受害。按 musl 原文接管 __timedwait_cp/__timedwait, 只在发 futex 前把相对超时转成 time32;取消点仍走 libc 的 __syscall_cp。 - linux/timerfd.c:14/42:32B itimerspec 直传 → 内核按 16B 读、字段错位;接管 create/settime/gettime(同一 .o 的三个符号必须一起定义,否则重复定义)。 - time/clock_settime.c:9:16B 直传 → nsec 恒 0;接管 __clock_settime64。 换算抽成平台无关的 meow_time32_{rel,abs}_from_parts() / itimerspec 转换 (meow_ui_internal.h),宿主可测。pselect/ppoll/select、getrusage、wait4、 clock_adjtime 逐条反汇编确认回退分支自带宽度转换,不受影响(纠正 M11 §6 的保守 估计)。未修项(sigtimedwait/semtimedop/timer_settime/gettime/utimensat/recvmmsg/ mq_timedsend/PI timedlock)的清单与配方见 CLOCK_ABI.md §6/§7。 验证:宿主 gcc -Wall -Wextra 0 warning;交叉 zig -Wall -Wextra 0 warning; build_sdk_host.sh --verify 76 成员全 MIPS32r2;真实 meow_ui.a 链接探针确认 __pthread_cond_timedwait_time64 的 jal 指向本实现、futex=4238 且超时为 8B、 timerfd 4323/4322、clock_settime 4262。 --- patch_v2/sdk/meow_ui_internal.h | 34 +++++ patch_v2/sdk/meow_ui_time.c | 215 ++++++++++++++++++++++++++++++-- 2 files changed, 240 insertions(+), 9 deletions(-) diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 798e0e0..ba2f6bb 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -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 */ diff --git a/patch_v2/sdk/meow_ui_time.c b/patch_v2/sdk/meow_ui_time.c index dd72a1d..765d5d2 100644 --- a/patch_v2/sdk/meow_ui_time.c +++ b/patch_v2/sdk/meow_ui_time.c @@ -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 #include #include #include "meow_ui.h" +#include "meow_ui_internal.h" #if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4) #define MEOW_TIME32_KERNEL_ABI 1 +#include #include +#include #include #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,155 @@ 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) { + if (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