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:
+21
-5
@@ -69,14 +69,22 @@ static const uint32_t s_caps[SYSLOG_COUNT] = { SYSLOG_RUN_MAX, SYSLOG_SELFTEST_M
|
||||
#define SYSLOG_BATCH_MAX 8u
|
||||
static uint8_t s_batch[SYSLOG_BATCH_MAX * SYSLOG_REC_SIZE];
|
||||
|
||||
/* 待发条目:desc 为调用方静态串(Flash 字面量),只存指针不拷贝 */
|
||||
/* 待发条目:**投递时就把描述拷进条目**(B 方案整改,2026-10-24 台架实证)。
|
||||
* 旧实现只存 `const char *`,隐含契约「desc 必须是生命周期覆盖到落盘的静态期串」,
|
||||
* 而该契约从未写进 sys_log.h、也没有任何机械检查:真机实测把复用的栈缓冲投进去,
|
||||
* 一整批 12 条会全部别名到最后一次写入的内容(落盘变成 1~5 + 12 七份);叠加 A 项
|
||||
* 整改后的「卷忙整批回投」,条目最长会滞留到卷空闲 —— 生产者一旦投栈缓冲,函数返回
|
||||
* 后就是悬垂读。故改为条目内嵌定长缓冲,长度按 SYSLOG_DESC_MAX 截断。
|
||||
* RAM 代价(实测见 docs/子系统设计/运行与自检记录设计说明.md §4.4):s_preboot 与
|
||||
* s_batch_src 各 8 条由 8 B 变 32 B(.bss 合计 +384 B),消息队列存储由堆多要 192 B
|
||||
* (FreeRTOS 堆已迁 CCM)。 */
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
const char *desc;
|
||||
uint8_t id;
|
||||
char desc[SYSLOG_DESC_MAX + 1u];
|
||||
} syslog_entry_t;
|
||||
|
||||
static syslog_entry_t s_preboot[SYSLOG_QUEUE_MAX]; /* 启动前待发数组(RTOS 前单生产者/后单消费者,免锁) */
|
||||
static syslog_entry_t s_batch_src[SYSLOG_BATCH_MAX]; /* 本批条目的原始来源(N-24:未落盘的尾部要回投队列,故必须留住 id + desc 指针);
|
||||
static syslog_entry_t s_batch_src[SYSLOG_BATCH_MAX]; /* 本批条目的原始来源(N-24:未落盘的尾部要回投队列,故必须留住 id + desc 副本);
|
||||
* 唯一写者 defaultTask,走静态不吃栈 */
|
||||
static volatile uint8_t s_preboot_cnt; /* 数组条目数 */
|
||||
static osMessageQueueId_t s_queue; /* 运行期消息队列(syslog_drain 首次调用时惰性创建) */
|
||||
@@ -682,12 +690,20 @@ void syslog_drain(void) {
|
||||
*/
|
||||
void syslog_post(syslog_id_t id, const char *desc) {
|
||||
syslog_entry_t e;
|
||||
size_t dl;
|
||||
|
||||
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT || desc == NULL) {
|
||||
return;
|
||||
}
|
||||
e.id = (uint8_t)id;
|
||||
e.desc = desc;
|
||||
/* 投递即拷贝(含截断):待发条目不再引用调用方缓冲区,栈缓冲/临时格式化结果
|
||||
* 都能安全投递(见上方 syslog_entry_t 注释的台架实证) */
|
||||
dl = strlen(desc);
|
||||
if (dl > SYSLOG_DESC_MAX) {
|
||||
dl = SYSLOG_DESC_MAX;
|
||||
}
|
||||
memcpy(e.desc, desc, dl);
|
||||
e.desc[dl] = '\0';
|
||||
if (s_queue != NULL && osKernelGetState() == osKernelRunning) {
|
||||
if (osMessageQueuePut(s_queue, &e, 0u, 0u) != osOK) {
|
||||
/* F 项:只报"从正常转入故障"那一次;期间条目照常丢弃(丢弃语义不变,
|
||||
|
||||
Reference in New Issue
Block a user