From 27e84ca3da9b1db35cb25fa7a2428c28d3391a7b Mon Sep 17 00:00:00 2001 From: QIU SHENGMING Date: Fri, 2 Oct 2026 13:42:02 +0800 Subject: [PATCH] Round 62: the code loads byte-perfect and six bytes change 60 ms later; the memset is not the culprit Sampling the overlay every 50 ms from MENU: before it, crc 169b5c51 matching 262/2568; at +0.00 s, crc 60234c72 matching 2568/2568, which is the app's own CRC; at +0.06 s, crc 4f23f6f3 matching 2562/2568; and every later sample identical. So the load is byte-perfect and the loader's own check would pass at that instant, and exactly six bytes -- the pointer 0x0801D640 plus 28 0a -- change sixty milliseconds later and never change again. That retires round 59's reading: a memset zeroing the whole 4 KiB would change thousands of bytes, not six, so the hundred-a-second memset is not what stops the app. The damage is a six-byte structure write. Together with the offset tracking code_size (code_size - 124 for both the 2744 and 2568 builds), something on the load path that knows code_size writes a six-byte structure at overlay + code_size - 124 just after the copy and before the app is entered. Next: watch those bytes again but ignore the memset, reporting only the first hit whose fill byte is not zero. --- AGENTS.md | 26 ++++++++++++++++++++++++++ AGENTS.zh-CN.md | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 925cd74..ed19821 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2429,3 +2429,29 @@ overlay exactly once. Whatever the routine is called, nothing the loader writes Next: identify 0x080175fe and 0x080047ca against the image itself rather than against a fetched file -- for instance by looking at what each calls and returns -- or fetch the sources for the exact build being run. + +**Round 62: the code loads byte-perfect and exactly six bytes change sixty milliseconds later. The memset is not the culprit.** + +Sampling the overlay every fifty milliseconds from the instant MENU is pressed: + + before MENU crc 169b5c51 matching 262/2568 + +0.00 s crc 60234c72 matching 2568/2568 EXACT + +0.06 s crc 4f23f6f3 matching 2562/2568 + ... every later sample identical + +60234c72 is the app's own CRC, so at that instant the overlay holds the code byte for byte and the loader's own +verification would pass. Sixty milliseconds later exactly six bytes have changed -- the four-byte pointer +0x0801D640, which appears once in the image, plus 28 0a -- and nothing changes after that. + +That retires round 59's reading. A memset that zeroes the whole 4 KiB overlay would change thousands of bytes, +not six, so the hundred-a-second memset is not what stops the app. It is a separate routine doing something +else, and the damage is a six-byte structure write. + +The two measurements together are what make this sharp: the offset that ends up wrong tracks the app's size +(code_size - 124 for both the 2744-byte and the 2568-byte build), and the write is a pointer plus a small +value. Something on the load path, which knows code_size, writes a six-byte structure at overlay + code_size +- 124 just after the copy and before the app is entered. + +Next: watch those four bytes again, but ignore the memset and report only the first hit whose fill byte is not +zero -- and if that is still the memset, watch a byte of the six that the memset does not clear, so the budget +is not spent on it. diff --git a/AGENTS.zh-CN.md b/AGENTS.zh-CN.md index 2e58c0a..5d31621 100644 --- a/AGENTS.zh-CN.md +++ b/AGENTS.zh-CN.md @@ -2123,3 +2123,27 @@ PC=0x0801ae7a(`memset` 内部,入口 0x0801ae70) SP=0x20003c50 **下一步**:**对着镜像本身、而不是对着取来的文件,去认 `0x080175fe` 与 `0x080047ca`** ✓ —— 比如看它们各自调用与返回了什么 ✓ —— **或者去取「正在跑的那个构建」的源码** ✓。 + +**第 62 轮:代码是逐字节完美载入的,而恰好 6 个字节在六十毫秒后改变。那个 `memset` 不是元凶。** + +从按下 MENU 的那一刻起,每五十毫秒给覆盖区采一次样: + +``` +before MENU crc 169b5c51 匹配 262/2568 ++0.00 s crc 60234c72 匹配 2568/2568 EXACT ++0.06 s crc 4f23f6f3 匹配 2562/2568 +... 此后每一次采样都相同 +``` + +`60234c72` **就是应用自己的 CRC** ✓ —— 所以**在那一刻覆盖区里逐字节就是那段代码 ✓,加载器自己的校验会通过** ✓。 +**六十毫秒之后恰好 6 个字节变了** ✗ —— 那个在镜像里只出现一次的 4 字节指针 `0x0801D640` ✓,加上 `28 0a` ✓ —— **而此后什么都不再变** ✓。 + +**这一条推翻了第 59 轮的读法** ✗:**把整片 4 KiB 覆盖区清零的 `memset` 会改掉成千上万个字节 ✓,而不是 6 个 ✓** —— +**所以那个每秒一百次的 `memset` 并不是让应用停下来原因** ✓。**它是另一个例程在做别的事,而真正的损伤是一次 6 字节的结构写入** ✓。 + +**把这两条测量放在一起,图景才锋利起来** ✓:**出错位置随应用大小移动(2744 与 2568 两个构建都是 `code_size − 124`)** ✓, +**而那次写入是一个指针加一个小值** ✓。**是加载路径上某个知道 `code_size` 的东西 ✓,在拷贝之后、应用被进入之前 ✓, +在 `覆盖区 + code_size − 124` 处写下一个 6 字节的结构** ✓。 + +**下一步**:**再看那四个字节一次,但把 `memset` 忽略掉 ✓,只报第一个「填充字节不为零」的命中** ✓ —— +**若那仍然是 `memset`,就改看那 6 个字节里 `memset` 不会清掉的一个字节 ✓,这样观察预算就不会被它吃光** ✓。