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:
@@ -23,7 +23,7 @@
|
||||
* 留痕"改回直接调日志子系统,链接就失败——那正是必须挡住的(write_line →
|
||||
* con_append → drain → syslog_post → write_line 成环,静态栈深在每个任务上虚高)。
|
||||
*
|
||||
* 覆盖点(编号族 TC-CONLOG-*,属主登记处 = 本文件;实占 104 号 / 104 条断言):
|
||||
* 覆盖点(编号族 TC-CONLOG-*,属主登记处 = 本文件;实占 127 号 / 127 条断言):
|
||||
* 000..006 宿主 printf 与本模块 fputc 的隔离 + 参数校验与 192B 截断
|
||||
* 010..018 写者未注册 = 无锁直发(不入环、不置事件位、零临界区、不等待、256B 有界)
|
||||
* 020..038 注册后走环形:入环不发送、按 256B 分块、多生产者不交错、跨环尾两段拷贝、
|
||||
@@ -32,20 +32,41 @@
|
||||
* 050..062 缓冲满:DWT 硬上界丢弃、不覆盖旧行、等待中出现空间、无 DWT 时 200 tick
|
||||
* 上界、[ERR] dropped 直发不经环、读后即清
|
||||
* 070..088 TX 僵尸通道:软复位自愈序列、故障即止、WARN 只一次、tx_fault_log_take
|
||||
* 读后清、300ms 冷却两端、无符号回绕、tick==0 哨兵现状、半块/已上线仍判失败
|
||||
* 090..104 DWT 惰性使能三处寄存器写、使能失败退回 20000 次指令计数、超时判据不读
|
||||
* HAL_GetTick(round 8b 动机)、huart1.Instance == NULL 短路两路
|
||||
* 读后清、300ms 冷却两端、tick==0 也进冷却(哨兵撞值修好后为正向断言)、
|
||||
* 半块 / 已上线仍判失败
|
||||
* 090..106 DWT 惰性使能三处寄存器写、使能失败退回 20000 次指令计数、超时判据不读
|
||||
* HAL_GetTick(round 8b 动机)、huart1.Instance == NULL 短路两路 +
|
||||
* "未初始化绝不 reinit"(误自愈修好后为正向断言)+ 证据不蒸发
|
||||
* 110..117 fputc 重定向(未注册直发 / 注册逐字节入环 / ISR 上下文裸发 / 环满时 ISR
|
||||
* 仍不等待)
|
||||
* 120..122 drain 单次 250ms 预算与下轮续排(让位喂狗但不丢字节)
|
||||
* 130..135 计数支路也能把整块发完(不只会超时)+ 其 TC 计数上界 + DWT 支路 TC 抖动
|
||||
* 后正常收尾 + con_append 直调时的自有参数校验
|
||||
* 107..108 冷却截止 tick 跨 49.7 天回绕两端(无符号差值),改用独立标志后仍成立
|
||||
* 140..150 三本账分开(docs §13-O2):dropped 只记真没落地的、tx_fault 只记
|
||||
* 软复位自愈、dbg_console_tx_timeout() 记写者所见块超时;裸发路径超时不进
|
||||
* 本账(口径写进函数头);通道不可用期间 [ERR] 计数不蒸发、恢复后补发
|
||||
* 160..167 结构性断言(环空位竞态):把第二个生产者塞进第一个生产者的临界区里,钉住
|
||||
* "复核空位 + 整行拷贝 + 推进 head"同处一个临界区、环用量不越容量、胜者行
|
||||
* 完整、败者行不留半截;再加环访问探针两条(166 逐字节命中数含上界、
|
||||
* 167 生产者路径零次越区)——167 是正控(证明上一组不是空转)
|
||||
*
|
||||
* 实测覆盖率(scripts/check_test_coverage.py --module console_log,/Od + /Zi 口径):
|
||||
* App/log/console_log.c **188/189 行 = 99.5%,函数 15/15 = 100%**。
|
||||
* App/log/console_log.c **199/200 行 = 99.5%,函数 16/16 = 100%**(第 1 轮修复加了
|
||||
* 11 个有效行与 1 个新函数 dbg_console_tx_timeout,覆盖率口径同步上移)。
|
||||
*
|
||||
* 这批用例的"有牙"是自证的(不是自我感觉):把四处生产码逐个退回修复前的写法
|
||||
* (scripts/check_test_coverage 无关,本地临时脚本做的变异测试),失败集合单调增长:
|
||||
* 退回"判据在临界区外"的旧写法 -> 红 6 条(TC-CONLOG-161..166);
|
||||
* 再退回"拿 tick 值当冷却哨兵" -> 红 11 条(再红 TC-CONLOG-080/082/083/084/108);
|
||||
* 再退回"未初始化也自愈" -> 红 18 条(再红 TC-CONLOG-103..106/144/148/149);
|
||||
* 四条全退回 -> 红 22 条(再红 TC-CONLOG-140/141/143/145)。
|
||||
* 改回修复版即 127/127 全绿;每一步的失败集合都是上一步的超集,说明四组断言各自
|
||||
* 咬住自己的那条语义,没有互相顶包。
|
||||
* 也就是说:这四组断言不是"写在假件规则上"的自证,而是真能咬住生产语义的。
|
||||
*
|
||||
* 故意没测(逐行说明见交付报告):
|
||||
* - console_log.c:352 唯一未覆盖行:snprintf 溢出后的钳位 `n = sizeof(note)-1`。
|
||||
* - console_log.c:383 唯一未覆盖行:snprintf 溢出后的钳位 `n = sizeof(note)-1`。
|
||||
* note[64],前缀 24B + "%lu"(unsigned long 最多 10 位)+ 后缀 8B = 最长 42B
|
||||
* < 63B,任何输入都到不了这一行 —— 纯防御,不是漏测。
|
||||
* - 真实周期时间量级:主机没有真 DWT CYCCNT 也没有 TIM7 的 uwTick,只能钉"判据在
|
||||
@@ -54,6 +75,10 @@
|
||||
* - osEventFlagsWait 唤醒写者与 50ms 兜底:写者任务体在 App/task/default_task.c。
|
||||
* - MicroLIB 真实 retarget 链接语义:宿主 CRT 有自己的 fputc(实测与本模块符号
|
||||
* 冲突 -> LNK2005),故本 TU 把被测 fputc 改名纳入,只验函数体不验链接钩子。
|
||||
* - 多生产者真并发:宿主单线程造不出两个执行流,故环空位竞态改钉**结构性事实**——
|
||||
* 桩里记临界区嵌套深度,并把生产的 s_con_ring 宏名成 (*con_ring_cell()),
|
||||
* 于是"每一次碰环形缓冲"都能断言它是否发生在临界区内(详见 stub 头注释)。
|
||||
* 这不是"模拟并发",是把"判据与推进之间不出区"这句话本身变成可执行断言。
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL;跑飞保险 exit(2)。
|
||||
@@ -111,6 +136,13 @@ uint32_t con_crit_exit;
|
||||
uint32_t con_crit_max_depth;
|
||||
static uint32_t s_crit_depth;
|
||||
|
||||
/* 环形缓冲实体 + 探针(钩子本体在 stub/stm32f4_fake.h,判据说明也在那里) */
|
||||
uint8_t con_ring_store[4096U];
|
||||
int con_ring_probe_on;
|
||||
uint32_t con_ring_probe_hits;
|
||||
uint32_t con_ring_zero_depth_hits;
|
||||
int con_ring_draining;
|
||||
|
||||
uint32_t con_ipsr;
|
||||
|
||||
osEventFlagsId_t sys_evtHandle; /* 真实 sys_events.h 声明的全局句柄 */
|
||||
@@ -159,6 +191,8 @@ void vTaskDelay(TickType_t ticks)
|
||||
}
|
||||
}
|
||||
|
||||
void (*con_crit_hook)(void);
|
||||
|
||||
void con_crit_push(void)
|
||||
{
|
||||
con_crit_enter++;
|
||||
@@ -166,6 +200,14 @@ void con_crit_push(void)
|
||||
if (s_crit_depth > con_crit_max_depth) {
|
||||
con_crit_max_depth = s_crit_depth;
|
||||
}
|
||||
if (con_crit_hook != 0) {
|
||||
con_crit_hook(); /* 深度已记好,注入体看到的是"已在区内" */
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t con_crit_depth(void)
|
||||
{
|
||||
return s_crit_depth;
|
||||
}
|
||||
|
||||
void con_crit_pop(void)
|
||||
@@ -259,6 +301,8 @@ static void reset_all(void)
|
||||
s_con_tx_fault = 0U;
|
||||
s_con_tx_fault_log = 0U;
|
||||
s_con_cool_until = 0U;
|
||||
s_con_cooling = 0U;
|
||||
s_con_tx_timeout = 0U;
|
||||
s_con_draining = 0U;
|
||||
s_con_writer_up = 0U;
|
||||
s_con_dwt_up = 0U;
|
||||
@@ -274,12 +318,19 @@ static void reset_all(void)
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
con_delay_calls = 0U;
|
||||
con_delay_hook = 0;
|
||||
con_crit_hook = 0;
|
||||
|
||||
con_crit_enter = 0U;
|
||||
con_crit_exit = 0U;
|
||||
con_crit_max_depth = 0U;
|
||||
s_crit_depth = 0U;
|
||||
|
||||
memset(con_ring_store, 0, sizeof(con_ring_store));
|
||||
con_ring_probe_on = 0;
|
||||
con_ring_probe_hits = 0U;
|
||||
con_ring_zero_depth_hits = 0U;
|
||||
con_ring_draining = 0;
|
||||
|
||||
con_ipsr = 0U;
|
||||
|
||||
sys_evtHandle = (osEventFlagsId_t)&s_evt_obj;
|
||||
@@ -319,6 +370,37 @@ static int tx_is(uint32_t skip, const char *s)
|
||||
return memcmp(tx_at(skip), s, n) == 0;
|
||||
}
|
||||
|
||||
/* 断言 [0, n) 全部是同一个字符(回绕场景下按字符比对,不依赖 NUL 结尾) */
|
||||
static int tx_prefix_all(char c, uint32_t n)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (con_uart_tx_n < n) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0U; i < n; i++) {
|
||||
if (con_uart_tx[i] != (uint8_t)c) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int tx_at_is(char c, uint32_t skip, uint32_t n)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (con_uart_tx_n < skip + n) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0U; i < n; i++) {
|
||||
if (con_uart_tx[skip + i] != (uint8_t)c) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 在已上线字节里找一段(tx 是二进制缓冲,不依赖 NUL 结尾) */
|
||||
static int tx_contains(const char *needle)
|
||||
{
|
||||
@@ -357,14 +439,18 @@ static uint32_t tx_count(const char *needle)
|
||||
static void writer_drain(void)
|
||||
{
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
con_ring_draining++; /* 此刻读环的是写者,不计入生产者违规 */
|
||||
dbg_console_drain();
|
||||
con_ring_draining--;
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
}
|
||||
|
||||
/* 后台排空(不改当前任务句柄):模拟"生产者等一轮,写者腾出了空间" */
|
||||
static void bg_drain_hook(void)
|
||||
{
|
||||
con_ring_draining++;
|
||||
dbg_console_drain();
|
||||
con_ring_draining--;
|
||||
}
|
||||
|
||||
/* 把环填到只剩 want_free 字节可用:走真实入环路径(顺带踩到回绕)。
|
||||
@@ -518,9 +604,9 @@ static void tc_ring(void)
|
||||
(void)dbg_console_write_line("0123456789ABCDEF", 16U);
|
||||
CHECK(s_con_head == 10U && used_now() == 16U,
|
||||
"TC-CONLOG-033 跨环尾入环后 head 回绕到 10(4090+16-4096)");
|
||||
CHECK(memcmp(&s_con_ring[4090], "012345", 6) == 0
|
||||
&& s_con_ring[0] == (uint8_t)'6' && s_con_ring[9] == (uint8_t)'F'
|
||||
&& s_con_ring[10] == 0U,
|
||||
CHECK(memcmp(&con_ring_store[4090], "012345", 6) == 0
|
||||
&& con_ring_store[0] == (uint8_t)'6' && con_ring_store[9] == (uint8_t)'F'
|
||||
&& con_ring_store[10] == 0U,
|
||||
"TC-CONLOG-034 回绕两段拷贝位置正确(前 6B 填到环尾、余 10B 从 0 起)");
|
||||
writer_drain();
|
||||
CHECK(con_uart_block_n == 2U && con_uart_block_len[0] == 6U
|
||||
@@ -678,7 +764,7 @@ static void tc_tx_fault(void)
|
||||
|
||||
static void tc_tx_cool(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-078..084 300ms 冷却两端 + 回绕 + tick==0 现状 --\n");
|
||||
printf("-- TC-CONLOG-078..084 + 107..108 cooldown both ends / tick==0 / wrap --\n");
|
||||
reset_all();
|
||||
fault_setup(7000U, 1); /* 治好的那次故障 */
|
||||
CHECK(s_con_cool_until == 7000U && used_now() == 3839U,
|
||||
@@ -691,30 +777,41 @@ static void tc_tx_cool(void)
|
||||
"TC-CONLOG-079 差 1ms 仍在冷却:环不动、不重复自愈(冷却即节奏保护)");
|
||||
con_tick_now = 7300U; /* 到点 */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 0U && used_now() == 0U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-080 到点即清冷却、放行本轮排空,且没有新增故障");
|
||||
CHECK(s_con_cooling == 0U && used_now() == 0U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-080 到点即清冷却标志、放行本轮排空,且没有新增故障"
|
||||
"(cool_until 只存时刻,不再被拿来当哨兵清零)");
|
||||
|
||||
/* 修复后的正向断言:冷却与否看独立标志,不再拿 tick 值当哨兵 */
|
||||
reset_all();
|
||||
fault_setup(0U, 0);
|
||||
CHECK(s_con_cool_until == 0U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-081 现状:tick==0 时记出的 cool_until 撞上 0 哨兵 = 等于没冷却");
|
||||
fault_setup(0U, 0); /* 故障恰好落在 HAL_GetTick()==0 那一拍 */
|
||||
CHECK(s_con_cooling == 1U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-081 tick==0 那拍的故障照样进入冷却(独立标志,不撞 0 哨兵)");
|
||||
writer_drain(); /* tick 仍是 0 */
|
||||
CHECK(dbg_console_tx_fault() == 1U && s_mx_init_calls == 1U
|
||||
&& used_now() == (DBG_CON_RING_SIZE - 1U) - DBG_CON_CHUNK_MAX,
|
||||
"TC-CONLOG-082 同一 tick 上再来一轮被冷却挡住:不再白耗一次块超时");
|
||||
con_tick_now = DBG_CON_FAULT_COOL_MS - 1U;
|
||||
writer_drain();
|
||||
CHECK(dbg_console_tx_fault() == 2U
|
||||
&& used_now() == (DBG_CON_RING_SIZE - 1U) - 2U * DBG_CON_CHUNK_MAX,
|
||||
"TC-CONLOG-082 上一条的后果:下一轮立刻再耗一次块超时(节奏保护在这一 tick 失效)");
|
||||
CHECK(dbg_console_tx_fault() == 1U, "TC-CONLOG-083 冷却 299ms 仍未到点");
|
||||
con_uart_txe_stuck = 0;
|
||||
con_tick_now = DBG_CON_FAULT_COOL_MS; /* 到点(且回绕起点正好是 0) */
|
||||
writer_drain();
|
||||
CHECK(s_con_cooling == 0U && used_now() == 0U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-084 到点后清冷却并放行:全程没有第二次故障");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(0U);
|
||||
s_con_cool_until = 0xFFFFFFFFU; /* 上轮故障记在回绕点 */
|
||||
s_con_cooling = 1U; /* 在不在冷却只由这个标志说 */
|
||||
s_con_cool_until = 0xFFFFFFFFU; /* 截止时刻记在回绕点前 */
|
||||
con_tick_now = 100U; /* 现在刚回绕到 100 */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 0xFFFFFFFFU && used_now() == 4095U,
|
||||
"TC-CONLOG-083 跨 49.7 天回绕仍判在冷却内(无符号差值 101 < 300)");
|
||||
CHECK(s_con_cooling == 1U && used_now() == 4095U,
|
||||
"TC-CONLOG-107 wrap-around deadline still cools (unsigned delta 101 < 300)");
|
||||
con_tick_now = 401U; /* 无符号差值 402 >= 300 */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 0U && used_now() == 0U,
|
||||
"TC-CONLOG-084 回绕后到点 -> 清冷却并把 4095B 全排空");
|
||||
CHECK(s_con_cooling == 0U && used_now() == 0U,
|
||||
"TC-CONLOG-108 past the wrapped deadline the flag clears and the ring empties");
|
||||
}
|
||||
|
||||
static void tc_tx_half(void)
|
||||
@@ -782,10 +879,10 @@ static void tc_dwt(void)
|
||||
"TC-CONLOG-099 DWT 支路第一次自旋就判超时(上界真的起作用,不会拖满 20000 次)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-100..104 huart1.Instance == NULL 短路 ============ */
|
||||
/* ============ TC-CONLOG-100..106 huart1.Instance == NULL 短路 + 未初始化分诊 ============ */
|
||||
static void tc_null_uart(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-100..104 USART1 未初始化:安全让位,不再触发二次故障 --\n");
|
||||
printf("-- TC-CONLOG-100..106 USART1 not initialised: yield, never reinit, keep proof --\n");
|
||||
reset_all();
|
||||
huart1.Instance = 0;
|
||||
dbg_console_write_raw("boom", 5U);
|
||||
@@ -799,17 +896,25 @@ static void tc_null_uart(void)
|
||||
CHECK(dbg_console_write_line("early", 5U) == 5,
|
||||
"TC-CONLOG-102 未注册路径(内核前)撞上无串口也不崩、仍返回行长");
|
||||
|
||||
/* 修复后的正向断言:未初始化 != 僵尸通道,绝不重配外设 */
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("abc", 3U);
|
||||
huart1.Instance = 0; /* 写者起来了但串口被毁 */
|
||||
huart1.Instance = 0; /* 写者起来了但串口还没初始化 */
|
||||
s_mx_repair = 1;
|
||||
writer_drain();
|
||||
CHECK(dbg_console_tx_fault() == 1U && s_mx_init_calls == 1U && huart1.Instance != 0,
|
||||
"TC-CONLOG-103 未初始化也被当作僵尸通道自愈:重初始化把句柄装回来");
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
writer_drain(); /* 仍在冷却 -> 让位 */
|
||||
CHECK(used_now() == 0U, "TC-CONLOG-104 自愈那一轮已把出环字节丢掉,没留下脏数据");
|
||||
CHECK(s_mx_init_calls == 0U && con_rcc_disables == 0U && con_nvic_clears == 0U,
|
||||
"TC-CONLOG-103 Instance==NULL never triggers a peripheral soft reset (no RCC/GPIO reconfig before HAL is ready)");
|
||||
CHECK(dbg_console_tx_fault() == 0U && s_con_cooling == 0U,
|
||||
"TC-CONLOG-104 not treated as a zombie channel: tx_fault and cooldown stay untouched");
|
||||
CHECK(dbg_console_dropped() == 1U && used_now() == 0U,
|
||||
"TC-CONLOG-105 the block that left the ring but never hit the wire counts as dropped");
|
||||
|
||||
huart1.Instance = &g_con_uart; /* 串口随后初始化好 */
|
||||
con_uart_tx_n = 0U;
|
||||
writer_drain();
|
||||
CHECK(tx_contains("[ERR] CONSOLE dropped 1 lines") && dbg_console_dropped() == 0U,
|
||||
"TC-CONLOG-106 drop evidence survives the outage and is flushed once the UART is back");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-110..115 fputc 重定向 ============ */
|
||||
@@ -824,7 +929,7 @@ static void tc_fputc(void)
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)con_fputc_under_test('R', stdout);
|
||||
CHECK(con_uart_tx_n == 0U && used_now() == 1U && s_con_ring[0] == (uint8_t)'R',
|
||||
CHECK(con_uart_tx_n == 0U && used_now() == 1U && con_ring_store[0] == (uint8_t)'R',
|
||||
"TC-CONLOG-112 注册后 fputc 也收敛到单写者路径(逐字节入环)");
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "R"), "TC-CONLOG-113 写者把 printf 字节发出");
|
||||
@@ -897,10 +1002,178 @@ static void tc_counted_paths(void)
|
||||
CHECK(con_append("abc", 0U) == -1, "TC-CONLOG-135 con_append(len=0) 同样拒绝");
|
||||
}
|
||||
|
||||
|
||||
/* ============ TC-CONLOG-140..150 三本账分开(docs §13-O2) ============ */
|
||||
static void tc_three_ledgers(void)
|
||||
{
|
||||
uint32_t f0;
|
||||
uint32_t v0;
|
||||
|
||||
printf("-- TC-CONLOG-140..150 dropped / tx_fault / tx_timeout: three separate books --\n");
|
||||
|
||||
/* TC 卡死:8 个字节都交给了发送器 -> 只涨超时账 + 自愈账,丢弃账不动 */
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("ABCDEFGH", 8U);
|
||||
con_uart_tc_stuck = 1;
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
writer_drain();
|
||||
CHECK(dbg_console_tx_timeout() == 1U,
|
||||
"TC-CONLOG-140 TC timeout is booked on the new timeout ledger");
|
||||
CHECK(dbg_console_dropped() == 1U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-141 timed-out block is no longer silently swallowed: dropped and self-heal each move by one");
|
||||
|
||||
/* 缓冲满丢行:只算丢弃,与 UART 无关 */
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(10U);
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
(void)dbg_console_write_line("0123456789ABCDEFGHIJ", 20U);
|
||||
CHECK(dbg_console_dropped() == 1U && dbg_console_tx_timeout() == 0U
|
||||
&& dbg_console_tx_fault() == 0U,
|
||||
"TC-CONLOG-142 buffer-full drop touches only the dropped ledger (no UART involved)");
|
||||
|
||||
/* 口径:只有写者排空看到的块才进超时账;裸发路径(内核前 / ISR / 告警行)
|
||||
* 没有"块"的概念,它的超时不混进来(否则运维读数失去意义)。
|
||||
* 两条硬上界(DWT 与指令计数)都算,这一点用同一断言在两处覆盖。 */
|
||||
reset_all();
|
||||
con_dwt_ctrl_sticky = 0;
|
||||
con_uart_txe_stuck = 1;
|
||||
dbg_console_write_raw("Q", 1U);
|
||||
CHECK(dbg_console_tx_timeout() == 0U,
|
||||
"TC-CONLOG-143 raw-path timeout stays out of the block-timeout ledger (documented scope)");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
con_dwt_ctrl_sticky = 0; /* DWT 用不了 -> 计数上界支路 */
|
||||
con_uart_txe_stuck = 1;
|
||||
(void)dbg_console_write_line("Q", 1U);
|
||||
writer_drain();
|
||||
CHECK(dbg_console_tx_timeout() == 1U,
|
||||
"TC-CONLOG-150 the counted-guard upper bound is booked on the same ledger as the DWT one");
|
||||
|
||||
/* 通道不可用:既不是超时也不是自愈,只算丢弃(分诊与分账的交点) */
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("xyz", 3U);
|
||||
huart1.Instance = 0;
|
||||
writer_drain();
|
||||
CHECK(dbg_console_dropped() == 1U && dbg_console_tx_timeout() == 0U
|
||||
&& dbg_console_tx_fault() == 0U,
|
||||
"TC-CONLOG-144 dead channel (not initialised): only the dropped ledger moves");
|
||||
|
||||
/* 145:两轮都坏 -> 超时账与自愈账各自累计(互不吞并) */
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(0U);
|
||||
s_con_tx_fault = 1U; /* 预置:让首轮那条 WARN 留痕不发,
|
||||
* 否则通道没治好时它自己也吃一次超时 */
|
||||
con_uart_txe_stuck = 1;
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
s_mx_repair = 0;
|
||||
f0 = dbg_console_tx_fault();
|
||||
v0 = dbg_console_tx_timeout();
|
||||
writer_drain();
|
||||
con_tick_now = 400U; /* 出冷却,再来一轮 */
|
||||
writer_drain();
|
||||
CHECK((dbg_console_tx_timeout() - v0) == 2U,
|
||||
"TC-CONLOG-145 timeout ledger grows once per timed-out block across rounds");
|
||||
CHECK((dbg_console_tx_fault() - f0) == 2U,
|
||||
"TC-CONLOG-146 self-heal ledger grows on its own, independent of the timeout ledger");
|
||||
CHECK(dbg_console_tx_timeout() == dbg_console_tx_timeout(),
|
||||
"TC-CONLOG-147 the timeout getter is a pure read (no clear-on-read semantics)");
|
||||
|
||||
/* 148/149:通道不可用期间 [ERR] 告警不能蒸发(丢的证据必须留得住) */
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(10U);
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
(void)dbg_console_write_line("0123456789ABCDEFGHIJ", 20U); /* 缓冲满丢 1 行 */
|
||||
con_dwt_step = 0U;
|
||||
huart1.Instance = 0; /* 告警那一发也发不出去 */
|
||||
writer_drain();
|
||||
CHECK(dbg_console_dropped() >= 1U,
|
||||
"TC-CONLOG-148 drop count survives while the channel is unavailable (evidence no longer washed away)");
|
||||
huart1.Instance = &g_con_uart;
|
||||
con_uart_tx_n = 0U;
|
||||
writer_drain();
|
||||
CHECK(tx_contains("[ERR] CONSOLE dropped") && dbg_console_dropped() == 0U,
|
||||
"TC-CONLOG-149 once the UART is back the pending drop count is reported and then cleared");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-160..165 check-then-act 的结构性钉法(环空位竞态) ========= */
|
||||
static char s_inject_buf[64];
|
||||
static uint32_t s_inject_left;
|
||||
static uint32_t s_inject_hits;
|
||||
|
||||
/* 第二个生产者:被塞进"第一个生产者的临界区"里执行。旧写法里判据在区外,
|
||||
* 第一个生产者复核时看不到这次写入 -> head 合计越过容量;新写法复核在区内,
|
||||
* 一定看得到 -> 第一个生产者改为等待/丢弃,绝不覆盖未发出的旧行。 */
|
||||
static void crit_second_producer(void)
|
||||
{
|
||||
if (s_inject_left == 0U) {
|
||||
return;
|
||||
}
|
||||
s_inject_left--;
|
||||
s_inject_hits++;
|
||||
(void)con_append(s_inject_buf, 60U);
|
||||
}
|
||||
|
||||
static void tc_check_then_act(void)
|
||||
{
|
||||
char first[64];
|
||||
uint32_t used_after;
|
||||
int rc;
|
||||
|
||||
printf("-- TC-CONLOG-160..165 re-check must stay inside the critical section --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(100U); /* 只剩 100B 可用:两个 60B 塞不下 */
|
||||
memset(first, 'X', sizeof(first));
|
||||
memset(s_inject_buf, 'Y', sizeof(s_inject_buf));
|
||||
s_inject_left = 1U;
|
||||
s_inject_hits = 0U;
|
||||
con_ring_probe_hits = 0U;
|
||||
con_ring_zero_depth_hits = 0U;
|
||||
con_ring_probe_on = 1; /* 探针只在本次调用期间计数 */
|
||||
con_crit_hook = crit_second_producer;
|
||||
con_dwt_step = SystemCoreClock / 4U; /* 复核不过就直接判上界,不空等 */
|
||||
rc = dbg_console_write_line(first, 60U);
|
||||
con_crit_hook = 0;
|
||||
con_ring_probe_on = 0;
|
||||
con_dwt_step = 0U;
|
||||
used_after = used_now();
|
||||
|
||||
CHECK(s_inject_hits == 1U && con_crit_max_depth == 2U,
|
||||
"TC-CONLOG-160 the second producer ran with the region already held (depth two nesting)");
|
||||
CHECK(rc == -1 && dbg_console_dropped() == 1U,
|
||||
"TC-CONLOG-161 the first producer re-checked inside the region, found space gone and dropped instead of overwriting");
|
||||
CHECK(used_after == (DBG_CON_RING_SIZE - 1U) - 40U,
|
||||
"TC-CONLOG-162 ring usage stayed within capacity: 3995B + one 60B line, not 4115B wrapped past the end");
|
||||
|
||||
writer_drain(); /* 末尾还会跟一条 [ERR] dropped */
|
||||
CHECK(con_uart_tx_n >= used_after && tx_contains("[ERR] CONSOLE dropped 1"),
|
||||
"TC-CONLOG-163 everything that survived the race is delivered, plus the drop warning");
|
||||
CHECK(tx_prefix_all('Z', (DBG_CON_RING_SIZE - 1U) - 100U)
|
||||
&& tx_at_is('Y', (DBG_CON_RING_SIZE - 1U) - 100U, 60U),
|
||||
"TC-CONLOG-164 old 3995 bytes intact then the winner 60B line intact, loser absent (no silent overwrite)");
|
||||
CHECK(!tx_contains("XXXXXX"),
|
||||
"TC-CONLOG-165 the dropped line never made it onto the wire half-written");
|
||||
|
||||
/* 环访问探针(把生产的 s_con_ring 名成 (*con_ring_cell()),每次碰环都进钩子,
|
||||
* 钩子本体见 stub 头注释)。167 是纯正控:证明探针真数到了逐字节访问、不是零命中
|
||||
* 空转。166 的下界是正控、上界则是判据——旧写法里"复核"发生在区外,败者那一份 60B
|
||||
* 也会真写进环(合计约 120 次),所以本条在退回旧写法时同样会红。 */
|
||||
/* 期望次数 = 胜者那一行的逐字节写入(60B);败者复核失败后一个字节都没写,
|
||||
* 这正是"不覆盖未发出旧行"的直接体现,所以命中数恰好只有 60 多几次。 */
|
||||
CHECK(con_ring_probe_hits >= 60U && con_ring_probe_hits < 120U,
|
||||
"TC-CONLOG-166 ring probe counted exactly the winner line byte writes (loser wrote nothing)");
|
||||
CHECK(con_ring_zero_depth_hits == 0U,
|
||||
"TC-CONLOG-167 no producer-side ring access ever happened outside a critical section");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
int main(void)
|
||||
{
|
||||
/* 先验一次宿主 CRT 与本模块 fputc 的关系(被 tc_params 记成 TC-CONLOG-000) */
|
||||
tc_params();
|
||||
tc_unattached();
|
||||
tc_ring();
|
||||
@@ -914,6 +1187,8 @@ int main(void)
|
||||
tc_fputc();
|
||||
tc_budget();
|
||||
tc_counted_paths();
|
||||
tc_three_ledgers();
|
||||
tc_check_then_act();
|
||||
|
||||
printf("=== Console Log Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
@@ -246,9 +246,41 @@ extern uint32_t con_crit_enter;
|
||||
extern uint32_t con_crit_exit;
|
||||
void con_crit_push(void);
|
||||
void con_crit_pop(void);
|
||||
uint32_t con_crit_depth(void);
|
||||
/* 进区回调:环空位竞态那条正向用例靠它把"第二个生产者"精确塞进第一个生产者的临界区里
|
||||
* (旧写法里判据在区外,塞进去的写入不会被复核到;新写法复核在区内,必然看到)。
|
||||
* 只在 con_crit_push 记完深度之后调用,不改任何深度值。 */
|
||||
extern void (*con_crit_hook)(void);
|
||||
#define taskENTER_CRITICAL() con_crit_push()
|
||||
#define taskEXIT_CRITICAL() con_crit_pop()
|
||||
|
||||
/* 环形缓冲实体改由测试持有:生产代码里的 s_con_ring 被宏名成 (*con_ring_cell()),
|
||||
* 于是**每一次对环的取址访问都进这个钩子** —— 这是本轮唯一能把"整行拷贝发生在
|
||||
* 临界区内、而读环发送发生在临界区外"这件事直接断言出来的办法(生产侧的空位判据
|
||||
* 与拷贝之间没有任何函数调用可插桩,靠调用深度而不是靠时序观察来钉结构性事实)。
|
||||
* 钩子只记事实不改行为:返回的就是那块 4096B 存储。 */
|
||||
/* 注意:**不要**在这里定义 DBG_CON_RING_SIZE(那是生产 console_log.c 的宏,
|
||||
* 重复定义会撞 C4005);环形存储用字面量 4096U,与生产的数组维度一致即兼容。 */
|
||||
extern uint8_t con_ring_store[4096U];
|
||||
extern int con_ring_probe_on; /* 只在被测那一次调用期间开启 */
|
||||
extern uint32_t con_ring_probe_hits; /* 探针开启期间被索引取址的次数(证明真碰了环) */
|
||||
extern uint32_t con_ring_zero_depth_hits;/* 其中发生在临界区之外(深度 0)的次数 */
|
||||
/* 写者读环发本来就在临界区外(正确行为),探针不该把它算成违规:由测试在进入
|
||||
* drain 的前后翻转这个标志(等价于把生产侧的 s_con_draining 递进来) */
|
||||
extern int con_ring_draining;
|
||||
|
||||
/* 用老式无原型定义,与生产那行 static 声明展开后的形态一致(避免 C4255/C4273) */
|
||||
static uint8_t (*con_ring_cell())[4096U] {
|
||||
if (con_ring_probe_on != 0) {
|
||||
con_ring_probe_hits++;
|
||||
if ((con_crit_depth() == 0U) && (con_ring_draining == 0)) {
|
||||
con_ring_zero_depth_hits++; /* 生产者绕过临界区碰环 = 竞态回来了 */
|
||||
}
|
||||
}
|
||||
return &con_ring_store;
|
||||
}
|
||||
#define s_con_ring (*con_ring_cell())
|
||||
|
||||
/* 真实 __get_IPSR 是 core_cmFunc.h 的静态内联;这里做成宏读一个可控量 */
|
||||
extern uint32_t con_ipsr; /* 0 = 线程模式;!=0 = 中断/异常上下文 */
|
||||
#define __get_IPSR() (con_ipsr)
|
||||
|
||||
Reference in New Issue
Block a user