Files
MeowOS-SDK/sdk/meow_ui_time.c
T
AskaEth a07f9752d3 SDK: 内置 mipsel o32 时钟/睡眠 ABI 修复(meow_ui_time.c)+ 宿主单测
zig 0.17 内置 musl 在 mipsel 上头声明 64 位 time_t、实现走 time32 系统调用
(__ILP32__ 守卫),导致 meow_ui_time_ms() 毫秒位恒 0、高 32 位随机,
usleep(16000) 立即返回(帧节拍/轮询变忙等空转)。主仓已验证的修复逐字节
搬入公开仓:取时按内核 o32 ABI(8 字节 {int32 sec; int32 nsec})再升 64 位,
同 TU 强定义 __clock_gettime/64 与睡眠入口,链接期接管 libc。

- meow_ui_time.c / CLOCK_ABI.md:cp 搬运,md5 与主仓一致(含原注释与依据链)
- meow_ui_gfx.c:删掉 now_ms/meow_ui_time_ms(避免与 meow_ui_time.o 重复符号),
  6 处内部调用改外部 meow_ui_time_ms;<time.h> 不再使用
- meow_ui_internal.h:补 meow_time_ms_from_parts 声明
- build_sdk.sh:OBJS 首位加 meow_ui_time,头注释写明归档须排在 -lc 之前
- test_meow_time.c:适配公开仓构建方式,加负 sec 回绕两例,输出断言数(15)
- sdk/README.md:公开说明本次修复、链接顺序要求与已知限制(2038 前有效)
- tuku/test/run_tests.sh、run_webtoon_tests.sh:$SDK 源文件清单补 meow_ui_time.c
  (宿主集成测试直接编 SDK 源,否则链接期 undefined symbol)

验证:sdk/build_sdk.sh 0 warning;宿主 test_meow_time == PASS == checks=15;
tuku 两套测试全 PASS(run_tests 462+106+325 断言、webtoon 240 断言,0 failed);
探针/ picbrowser 反汇编确认 __clock_gettime 取自本 TU(8 字节 time32 + 升位),
无 libc VDSO 前导;picbrowser 仍为 MIPS32r2 静态 ELF。
2026-11-05 20:27:26 +08:00

148 lines
5.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* meow_ui_time.c — SDK 单调时钟(meow_ui_time_ms)+ mipsel o32 时间 ABI 兜底。
*
* 根因(M11;真机日志 + 反汇编证据链见 sdk/CLOCK_ABI.md):
* zig 0.17 的 clang 前端给 32 位 mips 目标预定义了 __ILP32__;zig 自带 musl 的
* time64 回退分支统一写作 `#if defined(SYS_x_time64) && !defined(__ILP32__)`
* (该 `!__ILP32__` 本意只排除 x32),于是 mipsel 的 libc 把「time64 入口」编译成了
* 老 32 位实现:__clock_gettime64 直接做 SYS_clock_gettime(4263),只写 8 字节
* `{ int32 tv_sec; int32 tv_nsec; }`。而 zig 头文件又无条件把 time_t 声明成 64 位
* (struct timespec 16 字节、tv_sec 8 字节),两边 ABI 不一致:
* tv_sec 低字 = 真 sec,高字 = 真 nsec → 64 位结果高 32 位随机、非单调;
* tv_nsec(结构偏移 8)根本没被写 → 毫秒位恒 0(真机实测全是 1000 的倍数)。
* 容器旧链(gcc)不定义 __ILP32__,time64 回退正确——对照 patch_v2/ncm_app/ncm:
* 同一函数里先 SYS_clock_gettime64(4403)、ENOSYS 后走 4263 并做宽度转换。
*
* 修复:不依赖 libc 头声明的 struct timespec 宽度,直接按**内核 o32 ABI**
* (8 字节 time32:int32 sec + int32 nsec)取时,再自行升成 64 位毫秒;
* 同时在本 TU 给出 __clock_gettime / __clock_gettime64 的强定义。应用链接顺序是
* `... meow_ui.a ... -lc`,归档先参与解析,因此链接期整体替换掉 libc 里那份
* ABI 不一致的实现:time()、gettimeofday()、pthread 定时等待的内部取时
* (musl 的 time()/__timedwait_cp 都调 __clock_gettime)随之修正。
* 上层时间位宽仍是 64 位,只有内核取数按 o32 time32;2038 前有效。
*
* 同一句 `!__ILP32__` 还打穿了睡眠:__nanosleep_time64/__clock_nanosleep 走 `#else`
* 把 16 字节 time64 结构直接交给 time32 系统调用,内核读到的 nsec 恒为 0 →
* `usleep(16000)` 立即返回(SDK 里帧节拍/轮询会变忙等空转)。这里一并按 time32
* 转换后重发,恢复亚秒睡眠。
*
* 残余(未修):futex 相对超时(pthread 定时等待的剩余时间按 8 字节读)、timerfd_settime、
* clock_settime/adjtime、pselect/ppoll 超时——同为该守卫的受害者,见 CLOCK_ABI.md §6。
*/
#include <errno.h>
#include <stdint.h>
#include <time.h>
#include "meow_ui.h"
#if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4)
#define MEOW_TIME32_KERNEL_ABI 1
#include <sys/syscall.h>
#include <unistd.h>
#else
#define MEOW_TIME32_KERNEL_ABI 0
#endif
uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec)
{
return (uint64_t)sec * 1000u + (uint64_t)nsec / 1000000u;
}
uint64_t meow_ui_time_ms(void)
{
#if MEOW_TIME32_KERNEL_ABI
struct meow_time32 { int32_t tv_sec; int32_t tv_nsec; } t;
t.tv_sec = 0;
t.tv_nsec = 0;
if (syscall(SYS_clock_gettime32, CLOCK_MONOTONIC, &t) != 0) return 0;
return meow_time_ms_from_parts(t.tv_sec, t.tv_nsec);
#else
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;
return meow_time_ms_from_parts((int64_t)ts.tv_sec, (int64_t)ts.tv_nsec);
#endif
}
#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;
if (v < 0) return 0;
return (int32_t)v;
}
/* ---- libc 取时入口的强定义 ----
* 返回约定与 musl 一致:0 成功 / -1 失败(errno)。
* 说明:<time.h> 在 _REDIR_TIME64 下把标识符 clock_gettime 的引用改名为
* __clock_gettime64,故本 TU 不定义 clock_gettime 这个名字;musl 里它只是
* __clock_gettime 的 weak alias,同一份语义已由下面的强定义接管。 */
int __clock_gettime(clockid_t clk, struct timespec *ts);
int __clock_gettime64(clockid_t clk, struct timespec *ts);
int __clock_gettime(clockid_t clk, struct timespec *ts)
{
struct meow_time32 t;
if (!ts) { errno = EFAULT; return -1; }
t.tv_sec = 0;
t.tv_nsec = 0;
if (syscall(SYS_clock_gettime32, clk, &t) != 0) return -1;
ts->tv_sec = t.tv_sec;
ts->tv_nsec = t.tv_nsec;
return 0;
}
int __clock_gettime64(clockid_t clk, struct timespec *ts)
{
return __clock_gettime(clk, ts);
}
/* ---- 睡眠入口(同源缺陷:亚秒 usleep 变空转)----
* __clock_nanosleep 按 musl 内部约定返回 0 或正 errno;__nanosleep_time64 是
* nanosleep/usleep 的重定向目标,按公共约定返回 0/-1(errno)。 */
int __clock_nanosleep(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem);
int __clock_nanosleep_time64(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem);
int __nanosleep_time64(const struct timespec *req, struct timespec *rem);
int __clock_nanosleep(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem)
{
struct meow_time32 rq, rm;
long r;
if (clk == CLOCK_THREAD_CPUTIME_ID) return EINVAL;
if (!req) return EFAULT;
if (req->tv_nsec < 0 || req->tv_nsec >= 1000000000L) return EINVAL;
rq.tv_sec = time32_clamp(req->tv_sec);
rq.tv_nsec = (int32_t)req->tv_nsec;
rm.tv_sec = 0;
rm.tv_nsec = 0;
if (clk == CLOCK_REALTIME && !flags)
r = syscall(SYS_nanosleep, &rq, rem ? &rm : 0);
else
r = syscall(SYS_clock_nanosleep_time32, clk, flags, &rq, rem ? &rm : 0);
if (r != 0) return errno;
if (rem) {
rem->tv_sec = rm.tv_sec;
rem->tv_nsec = rm.tv_nsec;
}
return 0;
}
int __clock_nanosleep_time64(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem)
{
return __clock_nanosleep(clk, flags, req, rem);
}
int __nanosleep_time64(const struct timespec *req, struct timespec *rem)
{
int r = __clock_nanosleep(CLOCK_REALTIME, 0, req, rem);
if (r) { errno = r; return -1; }
return 0;
}
#endif