fix(log,net): 处置主机单测照出的 10 条现状,撤销 1 条误报

用户裁定「全部修」。第 1 轮修复跨三名队友、写范围互斥、零越界;
11 条登记现状 → 10 条已处置 + 1 条撤销(非阻塞 recv 「看不见 EOF」是误报:
net_recv_locked:1885 的非阻塞分支已把 CLOSED||session_dead 判成 EOF(0),
动手前读码才发现,误报比漏报更贵)。

存储与日志(App/log/sys_log_mcu.c、soe_store.c、cfg_file_store_mcu.c、sys_log.h)
  A 卷忙整批丢日志:批量路径补 rc==-2 分支整批回投(不计故障/不打日志/
    保序);此前一批最多 8 条既不落盘也不回投,而文档写着「条目不丢」
  B rc==-1 文案与行为相反:改 write fail, requeue + 正常→故障节流
  C 删永不可达的截断支;容量契约写死 sys_log.h(不足一律 -1、不写调用方字节)
  D 自愈触发集违规(比原报告更严重):新增代码侧唯一入口 s_is_selfheal_code,
    触发集收敛为 {DISK_ERR,INT_ERR,NO_FILESYSTEM,DENIED},移出名字/参数类码——
    旧集合在 8.3 同名同因下每轮删一次自己的历史日志且永不自愈,正是存储架构
    §10.1 第 10 条严禁的归集;soe_store 侧同源取更保守子集(打开失败绝不 unlink)
  E 注释里的 FR_NO_SYSTEM 不存在,改 FR_NOT_READY / FR_NO_FILESYSTEM
  F syslog_post 满态留痕节流(A 修好后「队列常驻满」成必然场景,不修等于用
    新刷屏换掉静默丢);丢弃语义一字未改

NET(net_socket.c/.h、net_types.h、net_socket_sim.c、lftpd.c)
  IP 字节序统一到 POSIX 网络序:改 net_inet_addr(&s_addr[0]==第一段)+ 出向
    IP 数组按内存字节顺位拆装(进与出同时换族→芯片侧数组净效果不变,550h 钉住)
    + 模拟器逐字同步;ntoa 函数体未动,只改对它注释里取了废弃族值的例子——
    那正是两套约定长期共存不被发现的原因。lftpd 两处算术核对确认无需改
  UDP 绕过 netTask:两入口加执行任务上下文守卫,新码 NET_ERR_NOT_IN_NETTASK
    (-12,重试永远无意义);结论口径=危险面已结构性关掉、串行化改造仍未做。
    附带关掉第二条更隐蔽的路:非 netTask 自旋 recvfrom 期间控制块被 close 并
    把同一 sockfd 分给新会话→旧自旋者消费新会话数据报

控制台(console_log.c/.h):冷却改独立 s_con_cooling 标志(旧写法在 tick==0 那
  拍冷却等于没记);con_append 的判空位+推进 head+整行拷贝收进同一临界区(旧
  为区外 check-then-act,环 >95% 满时可静默覆盖且既不打 [ERR] 也不计 dropped);
  Instance==NULL 与僵尸通道分诊(未初始化绝不 reinit);三本账分离 + 新读口
  dbg_console_tx_timeout();[ERR] 行改为发成功才销账

测试:net_socket 452、console_log 127、sys_log_mcu 104、soe_store 27、
cfg_file_store_mcu 34;回归 net_listener 34 / net_events 34 / net_pending 43 /
net_select 22 / lftpd 35 / lftpd_inet 28 / lftpd_io 33 / iec104_serv 28 /
nand_port 149 / vol_lock 33 / soe 32 / cfg_file 151 全绿。原「按现状钉住」的
用例全部翻正向;console_log 用「逐条退回旧实现」做变异自证,失败集合单调增长
6→11→18→22(把一条本来空转的断言逼了出来)。
游标抬升:TC-NETSOCK-545→560、TC-SYSLOG-100→107、TC-CONLOG-135→167。

文档:控制台设计说明 §4.1 口径与 §13-O2 销项 + v1.5;运行与自检记录 §4.4/§4.5
四条;存储架构 §10.1 第 10 条推论(指向 s_is_selfheal_code);TRAP-NET-21 后续
条目;BSD 指南新增 §9.0 s_addr 唯一约定;覆盖率口径 §9「第 1 轮修复处置」表 +
后续项 5 条 + 台架复验 7 条 + 新坑 8 条 + 变更记录。

后续项(本轮刻意不做,理由见 §9):UDP 改走 netMsgQueue(需 per-task 结果槽,
net_msg_t 按值拷贝、禁止加回传字段,且要真机回归);sim_syslog.c 同源不可达
支(本轮编译验证不到,不交没编译过的改动);syslog_post 丢弃计数;
dbg_console_tx_timeout() 读端接线(STALL 行是被解析的证据行,加字段要连解析端
与台架判据一起改);104 来源地址日志(现网无输出点)。

gate: @build 0 errors 0 warnings(Code=213720 RO=18860 RW=1204 ZI=131500)。
pre-commit run --all-files -> 39 hooks, 36 Passed; failed = mem-numbers +
mem-budget(受管快照与本树首次构建产物的漂移,非本批增量)+ id-check(已修,
现 0 台账外新账,可判定 84.63% > 基线 82.6%)。实测水位 RW_IRAM1 57.4%、
RW_CCM 87.9%,均低于 92% 红线;RAM/Flash 本批增量为数个 static 变量。
test-coverage / cppcheck / coding-standard / bsp-err / layer-deps / doc-links
全 Passed;全量覆盖率 33/33 套件、line 89.3%、func 92.3%、逐文件下降 0。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
This commit is contained in:
2026-10-24 19:43:56 +08:00
parent 794906cdc8
commit 5a806d8a26
29 changed files with 1690 additions and 278 deletions
@@ -123,7 +123,7 @@ flowchart LR
| 参数 | 值 | 理由 |
|------|-----|------|
| 容量 `DBG_CON_RING_SIZE` | 4096B | 2 的幂 → `&(SIZE-1)` 回绕免除法;≈21 整行(192B/行)余量 |
| head/tail | `uint16` + `volatile`,单生产者/单写者 | head = 生产者写位置,tail = 写者读位置 |
| head/tail | `uint16` + `volatile`;**多生产者安全 / 单写者**(2026-10-24 更正:本表旧写「单生产者」,与本文 §3.1「生产者 = 全部任务 + 内核前主线程 + ISR」互相矛盾,现由代码侧统一——**复核空位 + 推进 head + 整行拷贝收进同一个临界区**,最长 192 B 拷贝在 168 MHz 下约亚微秒;`vTaskDelay` 与 DWT/tick 上界判据一律留在区外,区内绝不睡眠) | head = 生产者写位置(临界区内推进),tail = 写者读位置 |
| 已用字节 | `(head - tail) & (SIZE-1)` | 无符号差自然回绕,免锁可读 |
| 单行可接收上限 | `SIZE/2 = 2048B` | 保证"一行恒可入环";正常路径受 `DBG_CON_LINE_MAX=192B` 约束,不触发 |
| 位置 | BSS(SRAM1,非 CCM) | CCM = FreeRTOS 堆,放 BSS 不占堆;轮询发送不依赖 DMA 可达性(CCM 的 DMA 不可达约束不适用) |
@@ -331,7 +331,7 @@ sequenceDiagram
| # | 项 | 说明 |
|---|----|------|
| O1 | 未注册期裸发无互斥 | 启动窗口(内核启动 → attach)`dbg_console_write_raw` 无互斥 → 多任务并发会**逐字节交错**(非旧 `HAL_BUSY` 丢整行)。2026-10-10 已固化"周期任务用 `dbg_console_writer_ready()` 门控"纪律;若未来开机日志密度上升,可升级为"内核前静态待发行 + attach 后统一补发"或给裸发加轻量互斥 |
| O2 | `HAL_UART_Transmit` 超时未计数 | 超时块(UART 物理失效)丢弃但不入 `s_con_drop`;v2 可加独立超时计数 |
| O2 | `HAL_UART_Transmit` 超时未计数 —— **已处置(2026-10-24,v1.5)** | 三本账已分离:`dbg_console_dropped()` = **真没落地**的数(缓冲满丢的一行,或没能确认送达而出环的一个块);`dbg_console_tx_fault()` = 外设**软复位自愈次数**(语义不变);新增 `dbg_console_tx_timeout()` = 写者排空时看到的**块超时次数**(TXE 与 TC 两条硬上界都算)。**口径边界**:裸发路径(内核前 / ISR / 两条告警行自身)的超时**不进**超时账——裸发没有「块」的概念,混进来读数失去意义。**尚未接线**:本读口暂无生产调用者;候选位置 `App/util/task_watchdog.c` 的 `STALL` 行是**被解析的证据行**,加字段要连解析端与台架判据一起改 → 列为后续项 |
| O3 | `printf` 逐字节开销 | 遗留 `printf` 路径 = 逐字节临界区 + 逐字节事件置位;遗留调用点全部切 `DBG_*` 宏后开销自然消失(不改变机制) |
| O4 | **ORE 僵尸未根治** | §12:现仅有缓解(250ms 预算 / 300ms 冷却 / DWT 超时),机制根源是 USART1 使能了 RX。根治需 `.ioc` 改 TX-only + CubeMX 重生成 → 已登记 `docs/任务规划/登记簿.md` **S-3** |
@@ -351,4 +351,5 @@ sequenceDiagram
| v1.1 | 2026-09-25 | 文件拆分同步:写者任务体迁至 `App/task/default_task.c`(`Src/freertos.c` 只留 CubeMX 接线与崩溃钩子);§3.2 文件清单 / §4.3 / §6 / §11 位置引用更新 |
| v1.2 | 2026-10-02 | 新增 §12 ORE 僵尸通道(台架 gen-288 实证:节奏性喂狗饥饿,非死等;缓解已做、TX-only 根治待办)+ §11 时间线两条 10-02 记录;§13 新增 O4;原 §12/§13 顺延为 §13/§14 |
| v1.3 | 2026-10-10 | 栈链整改(**未抬栈**)三处:① 裸发改为纯寄存器轮询——DWT 不可用时走 `con_tx_counted`(计数上界),**不再回退 `HAL_UART_Transmit`**(tick 依赖不可信 + 其调用链污染所有任务最坏栈链);② 切断 `drain → syslog_post → write_line` **调用图成环**,改为置标志 + 写者任务在 drain 之外补记(新 API `dbg_console_tx_fault_log_take`);③ 行缓冲按模块可收紧(`dbg_log.h` 新增 `DBG_LINE_BUF_MAX`,深链模块 128)+ 时间戳缓冲并入行缓冲(32 → 24)。§4.4 / §5 / §6 / §7 / §8 同步;实测 defaultTask 静态最坏链 5312 → 2776 B |
| v1.5 | 2026-10-24 | **主机单测批次照出的四条现状全部处置**(生产码在 `App/log/console_log.c`,用例在 `test/host/console_log/`):① 冷却判据改用独立 `s_con_cooling` 标志,不再拿 `HAL_GetTick()` 的 0 值当哨兵——旧写法在 tick==0 那一拍(开机头 1 ms,或 49.7 天回绕到 0)冷却等于没记;② `con_append` 的空位判据 / 推进 head / 整行拷贝收进同一临界区——旧实现是临界区外 check-then-act,多生产者可在环 >95% 满时双双过判据把 head 推过容量 → 静默覆盖且**既不打 [ERR] 也不计 dropped**;③ `Instance == NULL`(未初始化)与僵尸通道**分诊**,未初始化绝不 reinit(旧路径会在 HAL 未就绪时经 MspInit 重配 RCC/GPIO);④ 三本账,见本文 §13-O2。**dropped 的单位口径变了**:一次 = 一行(缓冲满)或一个块(没能确认送达 / 通道不可用),故 `[ERR] CONSOLE dropped N lines` 的 N **不恒等于行数**(为兼容 `@test` 标记匹配,文案未改词)。本文 §3.1 与 §4.1 的「单生产者 vs 全上下文生产者」矛盾一并消除 |
| v1.4 | 2026-10-10 | 新增 `dbg_console_writer_ready()`(写者就绪查询)并固化"未注册期只有 defaultTask 记日志、周期任务用就绪门控"纪律;更正 §3.4 / §7 / §9-D5 / §13-O1 的"只丢不交错"旧结论(裸发已改寄存器级轮询、无 `gState` 互斥) |
@@ -131,9 +131,12 @@ defaultTask(唯一写者,挂载成功后):
- **N-24(2026-10-15,报告 N-16 写侧):一次排空合并成"每文件一次 open/write/close"**。旧实现每条记录一轮,而 `f_close` 要回写目录项、`f_write` 要对目标扇区做读-改-写 → **每条日志 2~4 次 NAND 页读写**。现在同一文件的一批(≤8 条 = 512 B 静态缓冲 `s_batch`)合并成一次追加写:
- **掉电语义不变**:批次仍在**同一次 `syslog_drain()` 调用内**写完并 `f_close`;`f_close` 内部 `sync_fs` → `disk_ioctl(CTRL_SYNC)` 会 `ftl_flush_cache()` 把 FTL 的单页写回缓存刷进 NAND(`Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 的 `CTRL_SYNC` 分支)。落盘时机与旧实现每条 close 等价,**批次不跨调用驻留 RAM**:"写完即关"的路径关文件即落盘,残余只到"单次写事务进行中掉电/复位"为止(`../任务规划/登记簿.md` N-22:无空闲/掉电前周期 flush,2026-10-22 用户裁定暂不做)。
- **条目不丢**:启动前数组按下标"看而不删",整批成功才删;队列条目在落盘失败(-2 卷忙 / -1 写失败 / -3 容量不足)时**回投队列**——旧实现 -2 会直接丢掉那一条。
- **条目不丢**:启动前数组按下标"看而不删",整批成功才删;队列条目在落盘失败(-2 卷忙 / -1 写失败 / -3 容量不足)时**回投队列**。
⚠ **2026-10-24 更正本条**:旧实现里 -2 只在**按条慢路径**被正确回投,**批量路径根本没有 -2 分支**(`done` 保持 `n` → 回投循环空转)→ 卷忙时一批(≤8 条)既不落盘也不回投、静默丢失,而本节长期写着"条目不丢"替它背书。现已补 `rc == -2` 分支整批回投(不计故障、不打日志、保序),由 `test/host/sys_log_mcu/` 的 TC-SYSLOG-035 组逐条钉住。**真机必验**:FTP 传输期间持续投日志,传输结束后条数 == 投递条数、顺序不乱。
- **容量轮转仍只在按条慢路径做**:批量路径发现剩余容量装不下一批时返回 -3,退回逐条 `s_append()`("满了 unlink 重建"只保留一处实现,避免一批跨多轮重建)。
- 自愈重试(L1 丢弃重建 + 重试一次)与错误去重(L2 只报第一次)判据与单条路径一致。
- **自愈触发集分类(2026-10-24,代码侧唯一入口 `s_is_selfheal_code()`)**:只有"删了这个文件真能修好"的介质/结构类码才触发 unlink 重建——`FR_DISK_ERR` / `FR_INT_ERR` / `FR_NO_FILESYSTEM` / `FR_DENIED`;**名字/参数类严禁归入**(`FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT`,另显式排除 `FR_NOT_READY` / `FR_NOT_ENABLED` / `FR_TIMEOUT`)。同名同因下 unlink 必然同样失败,旧集合的真实后果是**每轮排空删一次自己的历史日志且永远修不好**,违反 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 10 条。`f_write` 之后的"失败/短写 → unlink 重试一次"**不接**本判定(能走到那里说明已打开成功;而簇分配失败是 `FR_OK` + 短写、根本没有码可判,必须无条件重试)。钉住:TC-SYSLOG-101 / 101b / 102 / 103;`App/log/soe_store.c` 侧同源但取**更保守子集**(TC-SOESTORE-022 / 022b)。
- **留痕节流分两层、各自独立抑制位**:drain 侧"整批回投"只在正常→故障那一次报(`write fail, requeue %lu entries`,落盘成功一轮复位;旧文案写作 "drop N entries" 与实际行为相反——整批其实一条没丢);post 侧 `queue full, drop` / `preboot full, drop` 各一位(**丢弃语义不变,满则丢照旧**,只压留痕次数)。节流之后**没有丢弃计数**——要计数属新增对外可见信息,另列后续项。
- 读路径与写路径并发安全:UI 页在 uiTask 经 `syslog_get_record()` 读记录(内部走 `s_read_raw()`,见 §4.5);读写两侧都取卷锁 try 语义(P2-10),FatFS 系统锁保护单次调用。
- **存储未就绪/挂载失败**:待发条目滞留 RAM(下次上电丢失),串口打印告警——存储故障时本就无法持久化,属预期行为。
@@ -142,12 +145,13 @@ defaultTask(唯一写者,挂载成功后):
```c
uint32_t syslog_count(syslog_id_t id); /* = filesize/64 */
int syslog_get_record(syslog_id_t id, uint32_t idx, char *ts, size_t ts_len,
char *desc, size_t desc_len); /* 越界返回 -1 */
char *desc, size_t desc_len); /* 越界 / 读失败 / 缓冲容量不足 均返回 -1 */
```
- MCU:**8 条滑动读窗口**(N-24,报告 N-16 读侧)——`s_read_raw()` 先查窗口(同 id + 代数未变 + 落在区间内 → **零 I/O** 直接从 512 B 静态窗口返回);未命中才 `f_open(FA_READ) → f_lseek(窗口首条×64) → f_read(≤8×64B) → f_close` **一次取回连续 8 条**,再从中切出请求的那条。旧实现每读一条都要 open/lseek/read/close 一轮,UI 翻 20 条记录 = 20 次目录项/FAT 往返。
- 失效机制:写者每次落盘后把 `s_rd_gen[id]` 自增,读者比对填充时记录的代数,不匹配就重填。对齐 32 位在 ARM 上原子,跨任务(uiTask 填、defaultTask 增)无需临界区;最坏情况多读一轮,不会读错。
- **API 与调用方一律不变**(`syslog_get_record()` 签名照旧,`ui_page_runlog` / `ui_page_selftest` 零改动)。
- **容量契约(2026-10-24 写死在 `App/log/sys_log.h`)**:`ts_len > SYSLOG_TS_LEN`、`desc_len > SYSLOG_DESC_MAX`,**不足一律 -1,既不静默截断也不写调用方一个字节**(描述在写入侧已截到 `SYSLOG_DESC_MAX`,按最小容量给就一定装得下,读侧不需要第二道截断)。旧实现里那句 `if (dl >= desc_len) dl = desc_len - 1` 由上面的守卫保证**永不可达**,已删除;它的存在还会误导调用方"给 28 字节缓冲能读到 27 字符"——实际是整条读不到。UI 侧现用 32 B 列缓冲,走允许路径、行为不变。钉住:TC-SYSLOG-057、057b(0x7A 哨兵逐字节回比)、098。
- 模拟器:宿主文件同语义(UI 页双端零差异)。
- UI 页每帧仅读当前窗口 ≤8 条记录(每次重绘 8×64B SPI 读,用户翻页触发,频率极低)。
@@ -579,7 +579,8 @@ if (nand_ftl_format() == 0) {
7. **单写者规则** — 存储由 **defaultTask 独占**:挂载 / `f_mkfs` / 卷健康探针 / `net_cfg_load` / syslog 落盘 / 配置引擎全收敛于它。netTask / uiTask **不得**直接触碰配置引擎或 FatFS。启用存储测试套件(`TEST_SUITE_STORAGE`)时存储所有权整体移交 testTask,defaultTask 必须**完全跳过**存储 bring-up(`App/task/default_task.c` 中 `#if !defined(TEST_SUITE_STORAGE)` 包裹,2026-09-25 自 `Src/freertos.c` 迁出)——否则后台文件写与测试的 `nand_ftl_deinit` 掉电模拟并发,互相踩坏 FTL/FatFs 状态(实测:mkfs 半途冻结 + BBT 被误写)。
8. **内存布局与 DMA/CCM 约束** — FreeRTOS 堆(含全部任务栈)已迁至 **CCM `0x10000000`**(64KB,直连 M4 零等待),但 **DMA1/DMA2 物理不可达 CCM**。GD5F 驱动 `GD5F_DMA_THRESHOLD = 1024`:栈上的小缓冲(dhara meta 读 132B、`dhara_nand_is_free` 64B)一律走 **CPU 轮询**、绝不经 DMA;页级大块(2048)才走 DMA 且源/目的**全部是文件作用域静态缓冲**(落 SRAM)。新增任何 DMA 收发缓冲必须①文件作用域静态②大小≥阈值③确认落 SRAM。违反任一条 = 静默数据损坏(CCM 首轮扩容实验实证)。换料注意:克隆片可能缺 SRAM3(CCM),必须重测。
9. **LVGL 绘制缓冲越界护栏** — LVGL 9.3.0 对 I1(1bpp) 整帧渲染会在其算出的 `stride*h`(160×160/8=3200) 之后**多写 1 个字节**,紧邻 `g_ui_draw_buf` 之后的 `.bss`(如 `ui_menu_view` 的 `s_frame_box`)指针最低字节被打成 `0xFF` → 野指针 → HardFault(`PC=lv_obj_get_scroll_y`)。`ui_port.c` 的 `g_ui_draw_buf` 已预留 `UI_DRAW_BUF_GUARD=32` 字节护栏吸收该越界写(逻辑尺寸仍按 `UI_FB_SIZE` 上报,渲染数学不变)。**勿删护栏、勿把缓冲缩到与邻接 `.bss` 紧邻**。Lib/LVGL 为第三方不改。
10. **FatFs 严格 8.3 短名** — 本移植 `Lib/FatFs/ffconf.h` 为 `FF_USE_LFN=0`,**只支持严格 8.3 短名**:主名 ≤8 字符、扩展 ≤3 字符、**不得以点开头**(2026-09-20 台架实证,`.fschk` 无限重格式化事故根因)。`.fschk` 会被 `ff.c` 的 `create_short` 把「点之后」当扩展域解析而超限 → `f_open` 恒返 `FR_INVALID_NAME`(6);这是**纯 RAM 里的名字判定,不碰盘**。`ff.c` `create_short` 全部拒绝点的地方已加 DEBUG 打印:`[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符`——非法名字调用看开机日志即可定位。自愈类逻辑**严禁**把名字/参数类错误码(`FR_INVALID_NAME` 等)归入"卷损坏"触发集(整卷重格式化不可逆)。补充:含冒号 `:` 的名字被 FatFs 先解析为**盘符前缀** → `f_open` 返回 `FR_INVALID_DRIVE`(11) 而非 `FR_INVALID_NAME`(6);两者都属合法拒绝(测试用例须同时接受)。
10. **FatFs 严格 8.3 短名** — 本移植 `Lib/FatFs/ffconf.h` 为 `FF_USE_LFN=0`,**只支持严格 8.3 短名**:主名 ≤8 字符、扩展 ≤3 字符、**不得以点开头**(2026-09-20 台架实证,`.fschk` 无限重格式化事故根因)。
- **推论(2026-10-24 补齐,代码侧唯一入口 `s_is_selfheal_code()`)**:既然名字/参数类错误码在 8.3 配置下**必然重现**(同名同因),它们就**严禁**被归进任何"卷/文件坏了"的自愈触发集——否则自愈会退化成反复删自己的数据。本仓落点:`App/log/sys_log_mcu.c` 的触发集只收 `FR_DISK_ERR` / `FR_INT_ERR` / `FR_NO_FILESYSTEM` / `FR_DENIED`,`FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT` 一律立即失败(逐码依据 + `Lib/FatFs/ff.c` 行号写在该函数上方资料块;`App/log/soe_store.c` 侧同源取更保守子集并互指)。**本条拦下的真事**:旧集合含名字类码,后果是路径/卷异常期间**每轮排空删一次自己的历史日志且永远修不好**,由主机单测照出(TC-SYSLOG-101 / 102、TC-SOESTORE-022b),详见 `docs/测试说明/主机单测覆盖率口径.md` §9「第 1 轮修复处置」。。`.fschk` 会被 `ff.c` 的 `create_short` 把「点之后」当扩展域解析而超限 → `f_open` 恒返 `FR_INVALID_NAME`(6);这是**纯 RAM 里的名字判定,不碰盘**。`ff.c` `create_short` 全部拒绝点的地方已加 DEBUG 打印:`[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符`——非法名字调用看开机日志即可定位。自愈类逻辑**严禁**把名字/参数类错误码(`FR_INVALID_NAME` 等)归入"卷损坏"触发集(整卷重格式化不可逆)。补充:含冒号 `:` 的名字被 FatFs 先解析为**盘符前缀** → `f_open` 返回 `FR_INVALID_DRIVE`(11) 而非 `FR_INVALID_NAME`(6);两者都属合法拒绝(测试用例须同时接受)。
11. **FA_OPEN_APPEND 陷阱 / 短写** — `FA_OPEN_APPEND`(0x30) = `FA_OPEN_ALWAYS|FA_SEEKEND`,**不含 `FA_WRITE`(0x02)**——不带写位打开的文件 `f_write` 恒返回 `FR_DENIED`(7)(这是当年 `/log` 写失败的真根因);追加写必须写 `FA_WRITE | FA_OPEN_APPEND`(2026-09-20 台架实测,`Lib/FatFs` 移植版)。该移植的 `f_write` 在簇分配失败时返回 `FR_OK` + 短写(**没有错误码**)→ 判失败必须校验写回的字节数。标志位为**旧式编号**:`FA_READ`=0x01 / `FA_WRITE`=0x02 / `FA_CREATE_NEW`=0x04 / `FA_CREATE_ALWAYS`=0x08 / `FA_OPEN_ALWAYS`=0x10 / `FA_OPEN_APPEND`=0x30 / `FA_SEEKEND`=0x20(`ff.c` 内部定义),与上游 FatFs R0.14 不同,**勿照抄上游代码示例**。
12. **崩溃可观测性(勿屏蔽)** — 已开启 `configCHECK_FOR_STACK_OVERFLOW=2` + `configUSE_MALLOC_FAILED_HOOK` + HardFault 现场报告(`Src/stm32f4xx_it.c`:裸 UART 打印 PC/LR/xPSR + CFSR/HFSR/BFAR/MMFAR 后复位,对照 `.map` 定位符号)。defaultTask **1024** words、testTask **2048** words 是**实测下限,勿降**。开机日志出现 `[ERR] STACK OVERFLOW task=` / `[ERR] HARDFAULT ...` / `MALLOC FAILED` 是精确定位线索,禁止屏蔽或降级。
13. **BBT 恢复模式** — 运行期坏块会随 TC-STORAGE-03 注入累积;当介质被**误标脏**(如 DMA 误打 CCM 栈导致随机块被写坏并持久化)时,打开 `test/board/storage_test.h` 的 `ENABLE_STORAGE_RECOVERY_TESTS` 编译**恢复构建**,testTask 会执行 `gd5f2gq5ue_bbt_rebuild()`(出厂扫描清除运行期误标)+ `nand_ftl_format()` 清账后复位。恢复后容量回到基线(即扣除 BBT 保留池后的正常容量,非出厂裸容量)。
@@ -483,7 +483,7 @@ uint16_t ch395f_link_change_count(void);
`FF_USE_LFN=0` 下 `.fschk` 走的正是"扩展域 5 字符超限"这条真实路径;自愈只动自己的文件
(反复失败 20 轮后 `/log` 目录与邻居文件未被触碰)。
**本批照出的现状(只登记、未改生产码;逐条已上报用户待裁定)**:
**本批照出的 11 条现状(缺陷原文保留在此,处置结论见紧随其后的「第 1 轮修复处置」)**:
| # | 位置 | 现象与影响 | 钉住它的用例 |
|---|---|---|---|
@@ -498,8 +498,61 @@ uint16_t ch395f_link_change_count(void);
| 9 | `App/log/console_log.c:394~412` | `con_append` 的**空位判据在 `taskENTER_CRITICAL` 之外**(check-then-act)。生产者按 `docs/子系统设计/控制台单写者日志设计说明.md` §3.1 是"全部任务 + 内核前主线程 + ISR",而同文档 §4.1 的 head/tail 行写作"单生产者/单写者"——**两处口径互斥,后者需要更正**。多生产者可在环 >95% 满时同时过判据 → `head` 各自推进后合计越过容量 → `con_used()` 回绕变小 → 写者少发一段,被覆盖的行**既不打 `[ERR]` 也不进 `dropped`**(静默损坏;索引恒取模,不会越界)。**这条没有用例、只报推断**:单线程宿主构造不出真抢占点(把注入点挂在 `taskENTER_CRITICAL` 上已在区内,不算抢占点) | —(无用例,仅登记) |
| 10 | `App/log/console_log.c:181~183` + `:306~331` | `huart1.Instance == NULL`(HAL 早期 / 断言-故障上报路径的专用短路,返回 `HAL_ERROR`)在 drain 里被当成**僵尸通道**走 `con_uart_reinit()` → `MX_USART1_UART_Init()`(含 MspInit 的 RCC/GPIO 重配)。当前接线顺序(`MX_USART1_UART_Init` 早于 `dbg_console_writer_attach`)挡住了,但"未初始化"与"通道卡死"共用一条自愈支路,attach 时机一旦被提前就会在 HAL 未就绪时重配外设 | TC-CONLOG-103(钉现状) |
### 第 1 轮修复处置(2026-10-24,同日;生产码改动 + 用例翻正向)
用户裁定「全部修」。三名队友按域并行改生产码(写范围互斥、零越界),Lead 统一 `@build` 与跑闸。
**`@build` 0 错误 0 警告**(本工作树首次全量构建):`Code=213720 RO-data=18860 RW-data=1204 ZI-data=131500`。
全量闸:**33/33 套件全绿**、`line=5605/6275(89.3%)`、`func=396/429(92.3%)`、未触及仍 29 个 `.c`;
与修复前逐文件比对**下降文件数 = 0**(改过生产码的三个文件覆盖随代码同步增长:
`console_log.c` 188→199、`sys_log_mcu.c` 310→324、`net_socket.c` 881→887)。
| 原表条目 | 处置 | 钉住它的正向断言 |
|---|---|---|
| 1 卷忙整批丢日志 | **已修**:`syslog_drain` 批量路径补 `rc == -2` 分支把 `done` 置 0 → 整批回投队列;三条纪律逐条有断言(不计故障 / 不打日志 / 保序),慢路径(参照物)一字未动 | TC-SYSLOG-035、035a、035c、035d、104 |
| 2 文案与行为相反 | **已修**:文案改 `write fail, requeue %lu entries`,并加「正常→故障」节流(`s_requeue_reported[]`,落盘成功一轮复位);真丢仍由 `hold-over drop` 兜底 | TC-SYSLOG-039、039b |
| 3 截断死分支 | **已修**:删掉永不可达的截断支,容量契约写死在 `sys_log.h`(`ts_len > SYSLOG_TS_LEN`、`desc_len > SYSLOG_DESC_MAX`,不足**一律 -1 且不写调用方一个字节**);API 签名与返回码集合不变 | TC-SYSLOG-057、057b(0x7A 哨兵逐字节回比)、098 |
| 6 soe_store 注释夸大实现 | **已修(改注释,实现不动)**:打开失败**任何错误码都不 unlink** 是有意的——soe.log 是事件序列,「打开失败就删文件」等于销毁事件证据;两侧资料块各写一份理由并**互指对方文件与测试号**,要求同步修改 | TC-SOESTORE-022 |
| 7 FR_NO_SYSTEM 不存在 | **已修**:改成实际会返回的 `FR_NOT_READY(3)` / `FR_NO_FILESYSTEM(13)`,并写明「本层对任何非 FR_OK 一律转 -1,别去枚举错误码集合」,唯一例外指向自愈触发集 | 注释面(无行为改动) |
| 4 IP 两套字节序 | **已修(统一到 POSIX 网络序)**:改 `net_inet_addr`(`&s_addr[0] == 第一段`,小端上 192.168.1.100 = `0x6401A8C0`)+ `net_connect_locked`/`net_sendto` 的 IP 数组改按内存字节顺位拆装(**进与出同时换族 → 芯片侧数组净效果不变**,由 550h 直接钉住)+ 模拟器 `net_socket_sim.c` 逐字同语义;`net_inet_ntoa` **函数体一字未动**,只把它注释里那个取了废弃族值的例子改对(就是那条隐蔽原因)。lftpd 两处经算术核对**确认无需改**(PASV 227 的双重反转仍成立),只加「勿顺手改对」的注释 | TC-NETSOCK-100 组(新增往返不变量 100g/100h、边界 100i/100j、截断 100k)、550h |
| 5 UDP 绕过 netTask | **危险面已结构性关掉,串行化改造仍未做**(按 Lead 定案,未自升级为改队列):两个 UDP 入口加**执行任务上下文守卫**,非 netTask 调用回新码 `NET_ERR_NOT_IN_NETTASK = -12`(住 `net_types.h`,与 BUSY/TIMEDOUT 的本质差别 = **重试永远无意义**,不参与任何可重试判定);守卫位于参数校验之后、任何芯片事务之前;模拟器**刻意不加**并标注为有意的不对称(单线程 + Winsock 自身线程安全、没有那根 SPI)。**附带关掉第二条更隐蔽的危险**:非 netTask 在 recvfrom 里自旋期间,netTask 可经 close 清零控制块并把同一 sockfd 分给新会话 → 旧自旋者消费新会话数据报,这条路现在断了 | TC-NETSOCK-560 组(含「零芯片事务」与「守卫不吞正常错误路径」) |
| 8 冷却哨兵与 tick 撞值 | **已修**:新增独立 `s_con_cooling` 判定「是否在冷却」,`s_con_cool_until` 只存截止时刻(无符号差跨回绕的写法保留) | TC-CONLOG-080~084、107、108 |
| 9 环空位判据在临界区外 | **已修**:「复核空位 + 整行拷贝 + 推进 head」收进同一个临界区(最长 192 B、168 MHz 下亚微秒;`vTaskDelay` 与 DWT/tick 判据一律留区外,区内绝不睡眠)→ 多生产者结构性安全,写者仍唯一。**原「这条主机测不出来」的结论本轮作废**:桩里记临界区嵌套深度并把第二生产者精确塞进第一个生产者的临界区,再把环形缓冲的宏名成函数调用使每一次碰环都可断言 | TC-CONLOG-160~167(**167 是正控**,证明 160~166 不是空转) |
| 10 未初始化被误当僵尸通道 | **已修**:drain 的失败处理先分诊——`Instance == NULL` 视为通道不可用(不 reinit、不计 tx_fault、不进冷却、只记 dropped);只有确认超时/僵尸通道才走软复位自愈 + 冷却 | TC-CONLOG-103~106 |
| (撤销)非阻塞 recv 看不见 EOF | **误报,未改任何码**:`net_recv_locked:1885` 的非阻塞分支里 `CLOSED || session_dead` 已判 EOF(0),只有会话存活才 WOULDBLOCK。顺带核实 `net_recvfrom` **不存在** EOF 误判面(UDP 无连接,无数据只有 WOULDBLOCK 或继续自旋两条出口) | —(记一次「描述得很合理」的误报:修之前先读码,误报比漏报更贵) |
**修复过程中顺带照出的两条(原表没有,一并修掉)**:
- `syslog_post` 的 `queue full, drop` / `preboot full, drop` **无节流**——1 号修好之后「整批回投 → 队列常驻满」成为必然场景,不修等于**用一个新刷屏换掉一个静默丢**。已按同一「正常→故障」纪律加两个独立抑制位(**丢弃语义一字未改**,满则丢照旧)。TC-SYSLOG-041b/041d/041e/041f、105、106、107、107b、107c。
- 自愈触发集里混着 `FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT`——**比原报告的 6 号严重**:这类码下 unlink 自己的文件必然同样失败(同名同因),后果是**每轮排空删一次自己的历史日志且永远修不好**,正是 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 10 条严禁的归集。已新增代码侧唯一入口 `s_is_selfheal_code()`:触发集收敛为 `{FR_DISK_ERR, FR_INT_ERR, FR_NO_FILESYSTEM, FR_DENIED}`,逐码依据(含 `Lib/FatFs/ff.c` 行号)与显式排除清单写在资料块里;`soe_store.c` 侧同源但**取更保守子集**。TC-SYSLOG-101/101b/102/103/103b、TC-SOESTORE-022b。
**本轮刻意没做(后续项,含理由)**:① UDP 改走 `netMsgQueue`(需先设计 per-task 结果槽:`net_msg_t` 按值拷入队列、错误码只搭任务通知值回传,AGENTS 明文禁止给 `net_msg_t` 加回传字段,且必须配真机回归);
② `simulator/sim/sim_syslog.c:171` 那条与 3 号**同源的不可达截断支**(本轮编译验证不到它——模拟器走 CMake+VS,不在 `@build` 覆盖内,不交没编译过的改动);③ `syslog_post` 的**丢弃计数**(属新增对外可见信息,本轮没有实测频率支撑阈值);
④ `dbg_console_tx_timeout()` 的**读端接线**(现无生产调用者;候选位置 `task_watchdog.c` 的 `STALL snap=… req=… h=… mg=… cf=… seg=…` 是**被解析的证据行**,加字段要连解析端与台架判据一起改,本批不碰台架);⑤ 给 104 加来源地址日志(现网 `iec104_serv.c` 根本不打印来源 IP、无 `getpeername/ntoa`,**没有输出点可看**)。
**必须上台架复验(本轮按指示未碰台架;主机测覆盖不到的部分逐条列出)**:
1. TCP 出向连接(唯一真正穿过被改的 `des_ip` 拆装路径):板级 `net_test_task` Phase 7(TC-NET-701/703/704,PC 侧起 server),判据 connect 能建立、PC 侧看到的源端口正确;Phase 2 的 `test_parse_ip` 期望 `[192,168,1,2]` 不变。
2. FTP 被动模式取一个文件并抓 227 响应:括号内前 4 元组 == 设备实际 IP、后 2 元组解出的端口 == 实际拨的端口(验 lftpd 那处双重反转没被改坏)。
3. FTP 对端日志 `connection received from <ip>:<port>`:ip 必须是 PC 地址且正序(验 `getpeername` → `ntoa` 同族)。
4. UI 网络配置页改 IP 保存 → 回读一致、按新 IP 通、104/FTP 仍可连(代次重建链路;UI 显示取自配置字符串、不经 `s_addr`,列进来是兜住「配置字符串→芯片下发→回读」这条主机测不到的路)。
5. FTP 传输期间持续投日志:传输结束后 `run.log`/`selftest.log` 条数 == 投递条数、顺序不乱、全程无 `write fail, requeue`;同窗口 `queue full, drop` 只应出现**一行**。
6. 制造持续写故障(坏块/写保护):串口只在**转入故障那一刻**出一条,恢复后再故障要再出一条。
7. 控制台灌高频日志把环逼到将满:不再出现半行/串行,且 `[ERR] CONSOLE dropped N lines` 与实际缺失数一致(**单位口径已变**:N 是「行或块」的计数,不恒等于行数);拔/短接 TX 造一次僵尸通道,核对 `dropped` / `tx_fault` / `tx_timeout` 各 +1。
**第 1 轮新踩到的坑(都有复现路径)**:
1. **`edit` 替换块会吞掉相邻代码**:在 `net_sendto` 插守卫时 `new_string` 漏写了紧随其后的 `buf == NULL → INVAL` 检查,那块被整段删掉,现场只表现为「sendto 空指针不报 INVAL」——**是上一节刚落的存量断言 440d 当场抓到的**。教训:`old_string` 覆盖多个语义块时 `new_string` 必须逐块回读,别信「我只加了一段」。
2. **`read` 全文回写会截断大文件**:`console_log_test.c` 到约 54 KB 后用「read 全文 → 替换 → write」连续两次把尾部(含 `main()`)吃掉,报 `C1075` 才发现。**超过约 40 KB 的文件只能定点改**(`edit` 或直接 fs 读写),不要整文件回写。
3. **块注释里替换把结尾的注释闭合符提前引出**:给 `console_log.h` 的 API 清单加说明时,替换文本自带了注释闭合符 → 注释中途关闭、后面中文裸露成代码,报 `C3872` / `C4138` 且**错误落在远处中文字符上**,第一眼完全看不出根因。改注释类替换宁可让 old/new 都不含行尾的注释闭合符。
4. **假时钟不要只挂在单一计数点**:假内核的时基挂在 `net_poll` 必读的 GPIO 上,而 `net_init` 未跑时 `net_poll` 直接短路 → tick 永不前进 → 停滞超时永不命中,**连 40 万次跑飞保险也挂在同一计数点、同样失效**(进程真挂死)。凡「可能短路返回」的路径都要能推进时间或独立触发保险;那条用例最后改用 `recvfrom(DONTWAIT)` 测同一分支。
5. **被 kill 的测试进程会占住 `tempfile\u003csuite\u003e.exe`**:下一个套件表现为 `LNK1169` / `LNK1104 无法打开文件`,看着像构建配置坏了,其实先 `taskkill` 那个 exe——这是「逐套 runner 固定共享路径」的又一处连带代价(与上面第 12 条同源)。
6. **cppcheck 会把「仅置位时才清」的抑制位写法判成 `duplicateConditionalAssign`(style,拦提交闸)**:为省一次 volatile 写而写成「判非零再清零」会被判等价赋值。处置:**回退成无条件写 + 注释写明别再「优化」回去**,不要为一条 style 告警加 inline 抑制。
7. **新增断言要用「逐条退回生产码」验一次牙**(变异自证):把四条修复逐条退回旧实现,失败集合单调增长 6 → 11 → 18 → 22 条、每步都是上一步的超集,改回即 127/127 全绿——**没有这一步,一条空转断言就要被交上去**。
8. **任务书里给的临时缺陷标签会被编号门禁当自造编号拦**——本批 Lead 在任务书里用的两个「单字母+连字符+序号」临时标签被原样抄进生产注释,一次冒出 8 类台账外错误(连本文档举这个反例时把字面量写出来,都会被同一根闸再拦一次,故此处只能描述形态、不能给样例)。纪律:**Lead 给的内部标签不得写进代码**,注释只写行为(如「两套 IP 字节序统一为 POSIX 网络序」),要指用例就指已登记的 `TC-*` 号。
**给下一批的可复用手法与坑**(完整清单写在各套件 `.c` 文件头,这里只留跨模块通用的):
1. **测试 TU 里 `#define fputc <改名符号>` 再纳入生产源**:`App/log/console_log.c`:492 的
`int fputc(int, FILE*)` 与宿主 CRT 撞符号(`libucrt.lib(fputc.obj) error LNK2005` + `LNK1169`)。
改名纳入后函数体与行覆盖一字不动;顺带实测确认**宿主 printf 不走用户 `fputc`**(测试输出不会被吞进环形缓冲)。
@@ -606,3 +659,4 @@ P2 5 个 2,226 行、P3 29 个 4,814 行明确不做**(四条对得上:13+5+
(重点:`sys_log_mcu.c:509~537` 卷忙整批丢日志、`net_socket.c` 同文件两套 IP 字节序、UDP 两个公开 API
绕过 netTask、`console_log.c` 冷却哨兵与空位判据),**生产码一行未改**,逐条登记在 §9「P2 落地」表内待裁定;
§8 第 1 条同步改为「清单已吃穿」 |
| 2026-10-24 | **第 1 轮修复(同日续)**:用户裁定「全部修」,11 条现状里 **10 条已处置 + 1 条撤销**(`net_recv_locked` 那条是误报:非阻塞分支已能报 EOF,`:1885` 核过)。生产码改动跨 `sys_log_mcu.c`(整批回投 / 文案与节流 / 删不可达截断支 / 新增 `s_is_selfheal_code` 收敛自愈触发集 / post 两层留痕节流)、`soe_store.c`(注释更正 + 触发集同源更保守子集)、`cfg_file_store_mcu.c`(`FR_NO_SYSTEM` 不存在)、`sys_log.h`(读侧容量契约)、`net_socket.c/.h/net_types.h`(`s_addr` 统一到 POSIX 网络序 + UDP 两入口执行任务守卫,新码 `NET_ERR_NOT_IN_NETTASK = -12`)、`net_socket_sim.c`(同步 + 有意不对称)、`console_log.c/.h`(冷却独立标志 / 空位判据收进临界区 / 未初始化分诊 / 三本账 + 新读口)。**`@build` 0 错 0 警**(`Code=213720 RO=18860 RW=1204 ZI=131500`);全量闸 **33/33 全绿**、`5605/6275(89.3%)`、`396/429(92.3%)`、逐文件比对**下降 0**。设计文档同步:`控制台单写者日志设计说明.md`(§4.1 口径 + §13-O2 销项 + v1.5)、`运行与自检记录设计说明.md`(§4.4/§4.5 四条)、`STM32F4-Base存储架构说明.md`(§10.1 第 10 条推论)、`CH395F_Trap_Records.md`(TRAP-NET-21 后续)、`BSD_Socket_API_使用指南.md`(新增 §9.0 唯一约定)。本轮新增处置表、后续项 5 条、台架复验 7 条、新踩坑 8 条见 §9 |
@@ -723,6 +723,26 @@ void event_callback_example(void)
网络协议使用**大端序**(网络字节序),而 STM32 使用**小端序**(主机字节序)。发送和接收数据时需要进行字节序转换。
### 9.0 `s_addr` 的唯一约定(2026-10-24 统一;全仓只此一族,勿再引入第二套)
**定义**:`sin_addr.s_addr` 一律存**网络字节序整数**,判据是 `&s_addr` 的**第 0 个内存字节 == 点分第一段**。
小端主机(STM32F407 与 PC 模拟器都是)上 `"192.168.1.100"` 的数值 = **`0x6401A8C0`**(内存里是 `C0 A8 01 64`)。
| 方向 | 用哪个 API | 要点 |
|---|---|---|
| 字符串 → `s_addr` | `net_inet_addr()` | 失败返回 **0**(与 BSD 一致),故 `"0.0.0.0"` 与非法串同值;**别拿 `NET_INADDR_NONE` 当失败哨兵** |
| `s_addr` → 字符串 | `net_inet_ntoa()` | 内部先 `NET_NTOHL` 再**高段在前**打印(TRAP-NET-21 定案) |
**不变量(有单测钉住)**:`net_inet_ntoa(net_inet_addr(s), buf)` 还原 `s`;`net_inet_addr(net_inet_ntoa(v, buf))` 还原 `v`。
`net_getsockname` / `net_getpeername` / `net_recvfrom` 填出来的 `s_addr`,与喂进 `net_bind` / `net_connect` / `net_sendto`
的 `s_addr` **同族、可直接相等比较**。历史背景(同一文件曾并存两套约定、`ntoa` 注释里的例子取了废弃族的值)
见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-21 的 2026-10-24 后续条目。
**UDP 直调限制**:`net_sendto` / `net_recvfrom` **不走消息队列**——它们在调用方上下文里直接 `net_poll()` + 读写芯片缓冲,
因此**仅限本层执行任务(NET 层 = netTask)调用**;别的任务调用回 `NET_ERR_NOT_IN_NETTASK`(-12)。该码**不是运行时故障,
重试永远无意义**:要么把调用挪进执行任务,要么改用走队列的 `net_send` / `net_recv`,没有第三条路。
「把这两个接口改成经 `netMsgQueue` 串行化」是已知后续项,未做的理由见 `Drivers/BSP/NET/net_socket.h` 同名函数头。
### 9.1 端口号转换
```c
+1
View File
@@ -233,6 +233,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
- **根因(定案)**:① `net_inet_ntoa` 段序反转——`NET_NTOHL` 后主机数字的 **MSB 才是点分首段**,原实现低段在前;② lftpd 打印 `net_getsockname` = 本机监听地址(端口恒 21),对端地址应读 `net_getpeername`。PASV 227 消费点做了 `net_htonl` 再 MSB 拆,**双重反转恰好正确,勿误判**。
- **修复**:`net_inet_ntoa` 段序改 MSB 首(MCU+模拟器同步);新增 `net_getpeername`(读 CONNECT 事件缓存,零 SPI);lftpd 改打印**对端** IP:port(台架判据:日志 `connection received from 192.168.1.x:<高位端口>`)。
- **教训**:显示层字节序 bug 不影响协议本身(FTP 功能全程正常),但会系统性误导排障——**日志里的地址也要当被测对象核对**。
- **后续(2026-10-24):同一文件里的`s_addr` 两套约定已统一**。修完段序之后,本条只解决了显示侧——当时 `net_inet_addr` 仍属**另一族**(小端累加后又 `NET_HTONL`,返回 `0xC0A80164`),而 `net_inet_ntoa` / `net_getsockname` 属 POSIX 网络序那族(`0x6401A8C0`),两者**不互逆**;按前者写的消费者(`net_connect_locked` 用 `>>24` 取首段)恰好自洽,所以一直没人撞上。现已统一到 **POSIX 网络序一族**(判据 `&s_addr[0] == 第一段`;小端上 192.168.1.100 = `0x6401A8C0`):改 `net_inet_addr` + 出向 IP 数组改按内存字节顺位拆装(**进与出同时换族 → 芯片侧数组净效果不变**)+ 模拟器 `simulator/sim/net_socket_sim.c` 逐字同步;`net_inet_ntoa` 函数体一字未动。**为什么它能长期不被发现**:本条"修复"行下方 `ntoa` 自己注释里的例子(`192.168.1.100 → 0xC0A80164`)**取的是废弃族的值**——注释替错误背书,比没有注释更糟。规范定义见 `docs/网络/BSD_Socket_API_使用指南.md` §9.0;往返不变量由 TC-NETSOCK-100g / 100h 钉住。
---