fix(log): syslog_post 投递即拷贝(台架实证驱动的 B 方案整改)

待发条目 syslog_entry_t 原为 {id, const char *desc},只存调用方指针,
隐含「desc 必须活到落盘」的契约,但契约既未写进 sys_log.h、也无机械
检查。台架一次可控实验照出后果:testTask 临时钩子复用同一块缓冲连投
12 条带序号记录、并与 8 MB FTP STOR(占住卷锁)重叠,落盘的 12 条变
成「1~5 + 第 12 条七份」——滞留条目全部别名到该缓冲最后一次写入。
叠加卷忙整批回投(条目可滞留到卷空闲),投栈缓冲即成悬垂读。

改法(B 方案):条目内嵌 char desc[SYSLOG_DESC_MAX + 1u],syslog_post
投递时按 SYSLOG_DESC_MAX 截断拷贝并补终止符;sys_log.h 参数说明同步
写明「投递即拷贝、不要求生命周期覆盖到落盘」。

R1 台架正/负双向实证(非破坏性,钩子与临时改动已全量反向还原):
- 修复在位:12/12 全落、序号严格递增、无重无缺(768 B = 12x64)
- 临时把 -2 支路改成 rc == -2 && 0u:只落 5/12、6~12 全丢,两轮独立
  复现一致,且该轮无 queue full 留痕 => 丢的路径确为「卷忙整批丢」

RAM/Flash 前后(scripts/ram_report.py 同口径,同一 .map 来源):
- ZI   131,500 -> 131,852 B(+352 B;s_preboot/s_batch_src 各 8 条
  由 8 B 变 32 B);RW_IRAM1 75,280/57.4% -> 75,632/57.7%
- Code 213,720 -> 213,780 B(+60 B);RW 1,204 B 不变
- RW_CCM 静态不变(57,376 B / 87.9%);队列存储多要 192 B 取自
  FreeRTOS 堆池,烧后串口水位 [STACK] heap free=15624 min_ever=8464
  余量充足;总量仍远低于 mem-budget 92% 红线

测试:sys_log_mcu 套件 104 -> 106 条全绿,新增 TC-SYSLOG-108(投出
后改写调用方缓冲,落盘必须仍是投递时原文)与 TC-SYSLOG-108b(超长
在条目内钳位)。假消息队列护栏 MQ_MAXMSG 由 24 抬到 40:条目变大后
它拒收一切 Put、从既有用例起整片失败,属「尺寸变了就响」的正向记录。
主机侧此前照不出该类问题,是因为用例传字面量、静态存储期无限。

门禁 pre-commit run --all-files:39 项中 37 通过,含 test-coverage
(已覆盖行只增不减)、id-check(0 条台账外新发现、游标 TC-SYSLOG-108)、
doc-links --sections-strict、coding-standard、cppcheck/test-cppcheck、
bsp-err、layer-deps、no-mojibake、ui-golden、font-charset。仅
mem-numbers 与 mem-budget 失败:docs/内存管理/ 受管台账仍记改前
131,500/213,720。按分支约定不在分支上 --write 受管台账,留待合回
主干时统一回填,故 --no-verify 提交。

真机:产品构建 0 错 0 警告,重烧 + 回读校验 OK(suite=NONE dstr=off)、
启动标记齐全、无 [ERR]、VERDICT PASS。
This commit is contained in:
2026-10-24 23:15:18 +08:00
parent 924ac3d428
commit 4a09d4c63f
6 changed files with 94 additions and 8 deletions
+40 -1
View File
@@ -365,7 +365,9 @@ void vol_lock_release(void)
/* ===================== 假消息队列(真 FIFO + 满/空语义) ===================== */
#define MQ_SLOTS 16u
#define MQ_MAXMSG 24u /* sizeof(syslog_entry_t) 在 x64 = 16,留余量 */
#define MQ_MAXMSG 40u /* sizeof(syslog_entry_t) 在 x64 = 32(B 方案:条目内嵌 desc[29],只存指针时为 16),留余量。
* 本宏是**有牙齿的护栏**:条目尺寸变大而这里忘抬 -> 假队列静默拒收,
* TC-SYSLOG-035 起整片失败(2026-10-24 实测正是这个现象)。 */
typedef struct {
int used;
@@ -895,6 +897,43 @@ static void tc_drain(void)
CHECK(ok && (g_sys_err_count == 0),
"TC-SYSLOG-035a requeued batch keeps original order and stays silent");
/* 108【B 项整改 2026-10-24:投递即拷贝】投出去之后**改写调用方缓冲**,
* 落盘内容必须仍是投递时的原文。旧实现条目里只存 `const char *`,两次投递
* 共用一块缓冲时会全部别名到最后一次写入 —— 真机上的实测现象是 12 条记录
* 变成「1~5 + 12 七份」。主机侧此前照不出来:用例传的都是字面量,静态存储期
* 无限,正好绕开这个坑(教训:断言里出现「生命周期」维度时才需要可变缓冲)。 */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain();
{
static char mut[24];
(void)strcpy(mut, "keepA");
syslog_post(SYSLOG_RUN, mut);
(void)strcpy(mut, "zzzB"); /* 投递之后才改写:不得影响已投出去的条目 */
syslog_post(SYSLOG_RUN, mut);
syslog_drain();
ok = (syslog_count(SYSLOG_RUN) == 2u)
&& raw_at(P_RUN, 0u, rec) && (memcmp(rec + SYSLOG_DESC_OFFS, "keepA", 5u) == 0)
&& raw_at(P_RUN, 1u, rec) && (memcmp(rec + SYSLOG_DESC_OFFS, "zzzB", 4u) == 0);
CHECK(ok,
"TC-SYSLOG-108 post copies desc: caller buffer reuse cannot corrupt queued entries");
}
/* 108b 拷贝路径上的长度钳位:超过 SYSLOG_DESC_MAX 的部分不得溢出条目缓冲,
* 且落盘只剩前 28 字节(条目内缓冲 = 28 + 1,越界即踩相邻字段) */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain();
syslog_post(SYSLOG_RUN, "0123456789012345678901234567890123456789");
syslog_drain();
ok = (syslog_count(SYSLOG_RUN) == 1u) && raw_at(P_RUN, 0u, rec)
&& (memcmp(rec + SYSLOG_DESC_OFFS, "012345678901234567890123456789", 28u) == 0)
&& (rec[SYSLOG_DESC_OFFS + 28u] == 0);
CHECK(ok, "TC-SYSLOG-108b over-long desc is clamped to SYSLOG_DESC_MAX inside the entry");
/* 035b 对照组:慢路径(-3 分支内)遇到 -2 时**会**把尾部回投 —— 同一卷忙
* 在两条路径上行为不一致,这就是"批量路径漏了一个分支"最直接的证据 */
reset_all();