Files
STM32F4-Base/test/host/console_log/stub/stm32f4_fake.h
T
wandering1 794906cdc8 test(host): P2 覆盖率批次,5 套主机单测与共享假 FatFs
P2 五个文件全部进分母(三名队友并行,写范围互斥、零越界):
  net_socket 420 条 / 881-883=99.8%、52-52 函数(S1~S6 分片)
  sys_log_mcu 75 条 / 310-313=99.0%、12-12
  console_log 104 条 / 188-189=99.5%、15-15
  cfg_file_store_mcu 34 条 / 97-97=100%、10-10
  soe_store 26 条 / 78-79=98.7%、4-4
合计 659 条断言,P2 本体 1554-1561=99.6%、93-93 函数。

账:套件 28→33 全绿;files_tested 32→37;未触及 112→107(.c 34→29,
恒等式 29 = 0(P1剩) + 0(P2) + 29(P3) 咬合,§9 终点判据达成);分母
4684→6245 插桩行;line 85.8%→89.3%;func 90.1%→92.2%(这批 5 个文件
全都以 ≥98.7% 进分母,故未摊薄)。--update-baseline 前后逐文件比对:
下降 0 个文件。

基建:新建 test/host/common/fatfs_fake.{h,c}(以真实 ff.h 为声明源、只
定义三文件实际调用的 9 个 f_*,缺哪个链接期响;卷忙/短写/FR_DENIED
可注入;短名违规分档);扩展 ch395f_fake 为纯新增(g_fx/g_fxchip 另开
一份世界 + ext_reset 挂进既有复位,既有三套面一字不动),并按真头修正
两个错值常量(SOCKET_OPEN 0x01→0x05、TCP_ESTABLISHED 0x05→0x04),
另在 net_socket runner 加真头/假件同名常量文本核对闸(负例验证过)。
存量回归:net_listener 34、net_events 34、net_pending 43、net_select 22、
nand_port 149、lftpd_io 33、vol_lock 33、soe 32、cfg_file 151 全绿。

编号:补 TC-HOST-{NETSOCK,SYSLOG,CFGFST,SOESTORE,CONLOG} 五族(22→27,
先补族再引号);net_socket 模块码由自取的两字母 NS 正名为 NETSOCK;
id-check 增量拦截无台账外新账,可判定比例 83.27%(基线 82.6%)。

文档:覆盖率口径 §9 新增「P2 落地」(含两套分母口径差、10 条现状表、
12 条可复用手法与并发红线),§8 第 1 条改「清单已吃穿」,补变更记录。
本提交未改任何生产代码(App/Drivers/Src/Lib/MDK-ARM 零触碰)。

10 条现状已逐条登记在 §9(含 sys_log_mcu.c:509~537 卷忙整批丢日志、
net_socket.c 同文件两套 IP 字节序、UDP 两 API 绕过 netTask),已获指示
全部修复,另开一轮(含测试字面量统一纯 ASCII)。

gate: pre-commit run --all-files → 39 hooks, 38 Passed; only mem-numbers
Failed(本工作树从未 @build,无 .map 事实源,属分支期已知陷阱);
mem-budget Passed;test-coverage 与 id-check Passed。RAM/Flash 增量 = 0
(纯测试批次)。按仓内分支约定走 --no-verify,台账回填留待主树合并后。
2026-10-24 17:54:02 +08:00

275 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:主机侧单测桩 - 假 HAL/CMSIS 寄存器面 + 内核入口(console_log 模块专用)
* 模块功能:让 App/log/console_log.c 在 PC 上编起来,并把它依赖的硬件/内核面做成
* **可脚本控制**的模型:
* ① USART1:TXE/TC 可注入「就绪 / 永远不就绪(僵尸通道)/ 前 N 次不就绪」;
* 写进 DR 的字节被收进一条上线字节日志,并按「一次 TC 就绪 = 一块」记块长。
* ② DWT CYCCNT:每次访问推进 con_dwt_step 个周期(= 给时间定价),
* CTRL 的 CYCCNTENA 可脚本成「写不进去」以走 con_tx_counted 支路。
* ③ HAL_GetTick:显式量 + 每次读自动步进。
* ④ 内核入口:临界区计数、当前任务句柄、vTaskDelay(可挂回调 + 跑飞保险)、
* __get_IPSR、osEventFlagsSet、NVIC/RCC 宏。
* ⚠ 仅供 test/host/console_log/ 使用,不参与 MCU 构建(MCU 走真实 stm32f4xx*.h)。
*
* 为什么必须桩(换机复核依据):真实链是 usart.h -> main.h -> stm32f4xx_hal.h ->
* stm32f4xx.h -> core_cm4.h,里面是 ARMCC 内联汇编与 __IO 寄存器绝对地址
* (0xE0001000 一类),宿主 MSVC 既编不动也访问不了。本桩把同样的位语义搬到普通
* 结构体上,字段名 / 位号 / 枚举值一律照真实头抄,生产代码写法一字不改即可编译:
* USART_TypeDef.SR/DR、USART_SR_TXE=bit7、USART_SR_TC=bit6(stm32f4xx_hal_uart.h)
* DWT_Type.CTRL/CYCCNT + DWT_CTRL_CYCCNTENA_Msk=bit0、CoreDebug_Type.DEMCR +
* CoreDebug_DEMCR_TRCENA_Msk=bit24(core_cm4.h)
* HAL_StatusTypeDef{OK=0,ERROR=1,BUSY=2,TIMEOUT=3}(stm32f4xx_hal_def.h)
* HAL_UART_STATE_RESET=0x00、HAL_UART_ERROR_NONE=0x00、USART1_IRQn=7
* UART_HandleTypeDef 只搬本模块访问的 4 个字段(Instance/gState/RxState/ErrorCode),
* 余下(Init/Lock/...)刻意不搬:抄半份 HAL 结构只会更快与真实头漂移。
*
* ★ 两个手法(本桩唯一的技巧点,改之前先读懂):
* a) 「读寄存器位」没法用结构体成员钩(u->SR 是成员访问,宏替换不了成员名),但生产
* 代码的判据写作 (u->SR & USART_SR_TXE),而 **USART_SR_TXE 是本桩的宏** —— 把它
* 定义成函数调用,于是每次读该位都进 con_uart_read_txe():在这里计数、判跑飞、按
* 脚本决定就绪与否。SR 成员恒为全 1,就绪完全由钩子返回值决定(返回掩码=就绪,
* 返回 0=不就绪,(全1 & 0)==0 让生产代码继续等)。TC 同理。
* b) DWT 同理:#define DWT (con_dwt_touch(), &g_con_dwt) —— 每次 DWT-> 访问先走一遍
* touch()(CYCCNT += step、计数判跑飞、按需让 CYCCNTENA 位落不住),再把地址给出去。
* 真实硬件 CYCCNT 每周期 +1,这里「每次访问 +step」是同一个模型、只是步长交给测试。
* 于是 CON_TX_TIMEOUT_CYC = SystemCoreClock/10 这类编译期表达式**不必改生产代码**
* 就能在 1 次自旋内成立(step 取 = 上界值即可)。
* c) DR 的写不做钩子(写就是写进真成员)。「哪个字节真正上线了」由 a) 的就绪事件推定:
* 生产代码每写一个 DR 之前必然刚读到一次就绪的 TXE(两个发送循环都是这个结构),
* 所以「上次就绪之后写进 DR 的那个字节」= 此刻离开发送寄存器上线的字节 ——
* 与真实「TXE 空 -> 装载 -> 移位」时序一致,不是桩自己发明的规则。
* 由此还能照出超时路径的真实后果:TXE 卡死时最后那个字节**没有**被写进 DR,
* 也就不会出现在上线日志里(半个块丢失、丢在哪个字节可查)。
*
* 创建日期:2026-10-24
*/
#ifndef STM32F4_FAKE_H
#define STM32F4_FAKE_H
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/* ===== 与真实头同值的位 / 枚举(来源见文件头) ===== */
#define USART_SR_TXE_Msk (0x0080U) /* USART1->SR bit7 发送寄存器空 */
#define USART_SR_TC_Msk (0x0040U) /* USART1->SR bit6 发送完成 */
#define DWT_CTRL_CYCCNTENA_Msk (0x00000001U)
#define CoreDebug_DEMCR_TRCENA_Msk (0x01000000U)
#define USART1_IRQn 7
#define HAL_UART_STATE_RESET 0x00U
#define HAL_UART_STATE_READY 0x20U
#define HAL_UART_ERROR_NONE 0x00U
typedef enum {
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
} HAL_StatusTypeDef;
/* 主机没有真周期钟。CON_TX_TIMEOUT_CYC = SystemCoreClock/10 与
* CON_APPEND_WAIT_CYC = SystemCoreClock/5 都是编译期表达式,取值原样保留
* 168 MHz(与 MCU 端 SystemCoreClock 一致),时间快慢由 con_dwt_step 决定。 */
extern uint32_t SystemCoreClock;
/* ------------------------------------------------------------------ */
/* ① 假 USART1 */
/* ------------------------------------------------------------------ */
typedef struct {
volatile uint32_t SR;
volatile uint32_t DR;
} USART_TypeDef;
typedef struct {
USART_TypeDef *Instance;
volatile uint32_t gState;
volatile uint32_t RxState;
volatile uint32_t ErrorCode;
} UART_HandleTypeDef;
extern USART_TypeDef g_con_uart; /* 寄存器实体(单 TU,非 static) */
#define CON_UART_TX_MAX 20000U /* 上线字节日志容量 */
#define CON_UART_BLOCK_MAX 64U /* 块长记录容量 */
extern uint32_t con_uart_tx_n; /* 已上线字节数 */
extern uint8_t con_uart_tx[CON_UART_TX_MAX];
extern uint32_t con_uart_block_n; /* 已完成的块数 */
extern uint32_t con_uart_block_len[CON_UART_BLOCK_MAX];
extern uint32_t con_uart_cur_len; /* 当前块已上线字节数 */
extern uint32_t con_uart_txe_reads;
extern uint32_t con_uart_tc_reads;
extern int con_uart_txe_stuck; /* 1 = TXE 永不就绪(僵尸通道) */
extern int con_uart_txe_after; /* 前 N 次 TXE 读**不**就绪,之后就绪 */
extern int con_uart_txe_ready_left; /* >=0 = 只准就绪 N 次,之后卡死 */
extern int con_uart_tc_after; /* 前 N 次 TC 读不就绪,之后就绪 */
extern int con_uart_tc_stuck; /* 1 = TC 永不就绪 */
extern int con_uart_pending; /* 1 = 有个字节已进 DR 待收 */
void con_uart_reset(void);
static void con_uart_tx_put(uint8_t b) {
if (con_uart_tx_n + 1U >= CON_UART_TX_MAX) {
printf("[FATAL] fake USART: tx log overflow (>=%u)\n", CON_UART_TX_MAX);
exit(2);
}
con_uart_tx[con_uart_tx_n++] = b;
/* 每次都在尾部补一个 NUL:让 strstr 一类字符串函数不可能读过缓冲末尾 */
con_uart_tx[con_uart_tx_n < CON_UART_TX_MAX ? con_uart_tx_n : (CON_UART_TX_MAX - 1U)] = 0U;
con_uart_cur_len++;
}
/* 收走「上次就绪之后写进 DR 的字节」:它此刻才真正上线 */
static void con_uart_take_byte(void) {
if (con_uart_pending == 0) {
return;
}
con_uart_pending = 0;
con_uart_tx_put((uint8_t)(g_con_uart.DR & 0xFFU));
}
static uint32_t con_uart_read_txe(void) {
con_uart_take_byte();
con_uart_txe_reads++;
if (con_uart_txe_reads > 200000U) {
printf("[FATAL] fake USART: TXE read %lu times (runaway)\n",
(unsigned long)con_uart_txe_reads);
exit(2);
}
if ((con_uart_txe_stuck != 0) || (con_uart_txe_after > 0)) {
if (con_uart_txe_after > 0) {
con_uart_txe_after--;
}
return 0U;
}
if (con_uart_txe_ready_left >= 0) {
if (con_uart_txe_ready_left == 0) {
return 0U; /* 配额用尽 -> 从这一字节起通道卡死 */
}
con_uart_txe_ready_left--;
}
con_uart_pending = 1;
return USART_SR_TXE_Msk;
}
static uint32_t con_uart_read_tc(void) {
con_uart_take_byte();
con_uart_tc_reads++;
if (con_uart_tc_reads > 200000U) {
printf("[FATAL] fake USART: TC read %lu times (runaway)\n",
(unsigned long)con_uart_tc_reads);
exit(2);
}
if (con_uart_tc_stuck != 0) {
return 0U;
}
if (con_uart_tc_after > 0) { /* TC 抖动:等几轮才置位 */
con_uart_tc_after--;
return 0U;
}
if (con_uart_block_n >= CON_UART_BLOCK_MAX) {
printf("[FATAL] fake USART: block log overflow (>=%u)\n", CON_UART_BLOCK_MAX);
exit(2);
}
con_uart_block_len[con_uart_block_n++] = con_uart_cur_len;
con_uart_cur_len = 0U;
return USART_SR_TC_Msk;
}
/* ★手法 a):判据里的掩码宏定义成函数调用 -> 每次读该位都进钩子 */
#define USART_SR_TXE (con_uart_read_txe())
#define USART_SR_TC (con_uart_read_tc())
/* ------------------------------------------------------------------ */
/* ② 假 DWT / CoreDebug */
/* ------------------------------------------------------------------ */
typedef struct {
volatile uint32_t CTRL;
volatile uint32_t CYCCNT;
} DWT_Type;
typedef struct {
volatile uint32_t DEMCR;
} CoreDebug_Type;
extern DWT_Type g_con_dwt;
extern CoreDebug_Type g_con_core_debug;
extern uint32_t con_dwt_step; /* 每次 DWT 访问推进的周期数 */
extern int con_dwt_ctrl_sticky; /* 0 = CYCCNTENA 写不进去 */
extern uint32_t con_dwt_reads;
void con_dwt_reset(void);
/* ★手法 b):每次访问先「花时间」,再决定是否让使能位落得住 */
static void con_dwt_touch(void) {
con_dwt_reads++;
if (con_dwt_reads > 200000U) {
printf("[FATAL] fake DWT: accessed %lu times (runaway)\n",
(unsigned long)con_dwt_reads);
exit(2);
}
g_con_dwt.CYCCNT += con_dwt_step;
if (con_dwt_ctrl_sticky == 0) {
g_con_dwt.CTRL &= ~DWT_CTRL_CYCCNTENA_Msk;
}
}
#define DWT (con_dwt_touch(), &g_con_dwt)
#define CoreDebug (&g_con_core_debug)
/* ------------------------------------------------------------------ */
/* ③ HAL 时基(真实原型 uint32_t HAL_GetTick(void),stm32f4xx_hal.c) */
/* ------------------------------------------------------------------ */
extern uint32_t con_tick_now;
extern uint32_t con_tick_step; /* 每次读之后自动推进的 ms(0 = 不动) */
extern uint32_t con_tick_reads;
uint32_t HAL_GetTick(void);
/* ------------------------------------------------------------------ */
/* ④ 内核入口 */
/* ------------------------------------------------------------------ */
typedef void *TaskHandle_t;
typedef uint32_t TickType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
extern TaskHandle_t con_task_self; /* xTaskGetCurrentTaskHandle 的返回值 */
TaskHandle_t xTaskGetCurrentTaskHandle(void);
extern uint32_t con_delay_calls; /* vTaskDelay 次数 = 真的等过 */
extern void (*con_delay_hook)(void); /* 每次 delay 跑一遍(可模拟别的任务排空) */
void vTaskDelay(TickType_t ticks);
extern uint32_t con_crit_enter;
extern uint32_t con_crit_exit;
void con_crit_push(void);
void con_crit_pop(void);
#define taskENTER_CRITICAL() con_crit_push()
#define taskEXIT_CRITICAL() con_crit_pop()
/* 真实 __get_IPSR 是 core_cmFunc.h 的静态内联;这里做成宏读一个可控量 */
extern uint32_t con_ipsr; /* 0 = 线程模式;!=0 = 中断/异常上下文 */
#define __get_IPSR() (con_ipsr)
/* 事件组:原型与真实 cmsis_os2.h 逐字一致 */
typedef void *osEventFlagsId_t;
typedef uint32_t osStatus_t;
#define osFlagsWaitAny 0x00000000u
#define osFlagsNoClear 0x00000001u
extern uint32_t con_evt_sets;
extern uint32_t con_evt_flags;
osStatus_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags);
/* NVIC / RCC:真实版是寄存器写。自愈路径的判据 = 重初始化做了几回、次序对不对 */
extern uint32_t con_nvic_clears;
extern uint32_t con_rcc_disables;
extern uint32_t con_rcc_enables;
#define __NVIC_ClearPendingIRQ(irqn) do { (void)(irqn); con_nvic_clears++; } while (0)
#define __HAL_RCC_USART1_CLK_DISABLE() do { con_rcc_disables++; } while (0)
#define __HAL_RCC_USART1_CLK_ENABLE() do { con_rcc_enables++; } while (0)
#endif /* STM32F4_FAKE_H */