usmart_str.c
usmart_str2num(usmart_str.c:103)4.3 单参数解析:usmart_get_aparm(usmart_str.c:324)4.4 参数打包:usmart_get_parmpos 与 usmart_get_fparam4.5 与 uCOS-II 的配合4.6 关键全局变量的读写5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
common/usmart/usmart_str.c所属模块:common / usmart(串口调试交互组件) 代码行数:459
1. 文件职责
本文件是 USMART 串口调试组件的文本解析层,全部由手写字符扫描循环组成,不依赖 strtok/sscanf 等标准库(在 Keil MDK + ARMCC 的 microlib/半主机环境下这类函数常被裁剪或体积过大)。它负责把串口收到的一行文本(如 usmart_test() 或 get_e_linker_sn("ABC"))拆解成「函数名 + 参数个数 + 每个参数的类型与原始文本」,并按 USMART 约定的打包格式写入 usmart_dev.parm[] / plentbl[] / parmtype。它处在调试通道的处理链中段:common/usmart/usmart.c 的 usmart_cmd_rec() 与 usmart_exe() 是它的唯一驱动者,usmart_scan() 从 UART0 取到数据后即调用到这里。整个文件(usmart_str.c:24 起至 usmart_str.c:458)被 #if DEBUG_SUMART 包住,而 DEBUG_SUMART 在 midware/debug/shell_usmart.h:13 定义为 0,当前发布固件中本文件编译后为空。本文件不定义任何全局变量、不涉及定时器或 RTOS 对象。
2. 关键数据结构与宏
| 名称 | 类型(结构体/枚举/宏/联合体) | 说明 |
|---|---|---|
DEBUG_SUMART | 宏(定义于 midware/debug/shell_usmart.h:13) | 值为 0,本文件整体编译开关(usmart_str.c:24) |
struct _m_usmart_dev | 结构体(定义于 usmart.h:45) | 本文件主要操作对象;实际读写的字段是 parm[PARM_LEN]、plentbl[MAX_PARM]、parmtype、runtime、runtimeflag |
usmart_dev | 全局变量(extern,定义于 midware/debug/shell_usmart.c:32) | USMART 单例,参数打包的落点 |
MAX_PARM | 宏(usmart.h:18,值 10) | plentbl[] 元素个数与参数个数的上限;usmart_str.c:414 用它清零长度表 |
PARM_LEN | 宏(usmart.h:19,值 200) | 所有参数合并后的字节上限;决定 usmart_str.c:413 的局部缓冲 tstr[PARM_LEN+1] |
USMART_FUNCERR | 宏(usmart.h:30,值 1) | 函数格式错误,usmart_str.c:259、usmart_str.c:269、usmart_str.c:419 使用 |
USMART_PARMERR | 宏(usmart.h:31,值 2) | 参数格式错误,usmart_str.c:433、usmart_str.c:451 使用 |
USMART_PARMOVER | 宏(usmart.h:32,值 3) | 参数个数溢出,usmart_str.c:438、usmart_str.c:448 使用 |
USMART_OK | 宏(usmart.h:29,值 0) | 无错误,usmart_str.c:457 返回 |
本文件内部使用的局部数组与临时标志:fpname[6](usmart_str.c:206,暂存返回类型或首参数类型文本)、tstr[PARM_LEN+1](usmart_str.c:413,暂存单个参数文本)。本文件不定义结构体、枚举或联合体。
3. 函数清单
| 函数名 | 作用域(static/全局) | 说明(一句话) |
|---|---|---|
usmart_strcmp | 全局(usmart_str.h:20 声明) | 自实现的字符串比较:完全相同返回 0,出现差异返回 1(语义与 strcmp 相反) |
usmart_strcopy | 全局(usmart_str.h 未声明,仅 usmart_str.c:52 定义) | 自实现的字符串拷贝,str1 为源、str2 为目的,连 '\0' 一起拷 |
usmart_strlen | 全局(usmart_str.h 未声明,仅 usmart_str.c:70 定义) | 自实现的字符串长度,返回值为 u8,超过 255 会回绕 |
usmart_pow | 全局(usmart_str.h:21 声明) | 整数幂运算 m^n,用循环累乘实现,返回 u32 |
usmart_str2num | 全局(usmart_str.h:22 声明) | 把文本转成数字:自动识别十进制 / 0X 前缀十六进制,返回 0 成功、1~4 为各类错误码 |
usmart_get_cmdname | 全局(usmart_str.h:23 声明) | 从字符串首部提取命令词(遇空格或 '\0' 结束),同时返回长度;超 maxlen 返回 1 |
usmart_search_nextc | 全局(usmart_str.h 未声明,仅本文件使用) | 跳过当前字符及其后的空格,返回下一个非空格字符 |
usmart_get_fname | 全局(usmart_str.h:24 声明) | 从一个函数声明串中解析出裸函数名、形参个数、以及返回类型是否为 void |
usmart_get_aparm | 全局(usmart_str.h:25 声明) | 从实参串中解析出「一个」参数:数字转大写文本、字符串去引号存入 fparm,并通过 ptype 回传类型 |
usmart_get_parmpos | 全局(usmart_str.h:19 声明) | 用前缀和算出第 num 个参数在 usmart_dev.parm[] 中的起始偏移 |
usmart_get_fparam | 全局(usmart_str.h:26 声明) | 解析整行实参:循环调用 usmart_get_aparm,把每个参数按类型打包进 parm[] 并填 plentbl[] / parmtype,返回参数总数 |
本文件无 static 函数,也不定义全局变量。
4. 核心逻辑解析
4.1 文本解析状态机(三段式)
三个解析函数构成一条单向流水线,usmart.c 负责串联:
串口原始文本 "get_e_linker_sn(\"ABC\")"
│
├─ usmart_get_fname() ──► 裸函数名 "get_e_linker_sn"、形参个数、rval(void?)
│ 内部状态机:
│ ① 返回类型扫描(pcnt 高位置 0x80 表示已开始)
│ ② 函数名提取(用 offset 跳过返回类型,fover 计括号嵌套,nchar 处理字符串字面量)
│ ③ 形参个数统计(fover==1 时遇 ',' 递增 parmnum;若唯一形参是 void 则归零)
│
├─ usmart_strcmp() ──► 与 usmart_dev.funs[i].name 的裸名逐个比对,定位 usmart_dev.id
│
└─ usmart_get_fparam() ──► 逐个参数打包
循环 { usmart_get_aparm(str, tstr, &type) → 分支 case 0 / 1 / 0xFF }
case 0x00:usmart_str2num → *(u32*)(parm + parmpos(n)) = res;清 parmtype 位;plentbl[n]=4;n++
case 0x01:len = usmart_strlen+1;usmart_strcopy → parm;置 parmtype 位;plentbl[n]=len;n++
case 0xFF:返回 USMART_PARMERR
遇 ')' 或 '\0' 结束
状态迁移条件(usmart_get_fname,usmart_str.c:198):
| 阶段 | 迁移条件 | 代码位置 |
|---|---|---|
| 返回类型扫描 | 已收集 4 个字符且第 5 个字符不是 * → 结束(处理 void / char * 两种情况) | usmart_str.c:218-219 |
| 返回类型扫描 | 已收集 5 个字符(pcnt == 0x85)→ 结束 | usmart_str.c:223-225 |
| 跳过返回类型 | 扫描到 ' ' 或 '*' 后,若下一个非空格字符既不是 '(' 也不是 '*',记 offset = res | usmart_str.c:243-248 |
| 函数名 / 形参统计 | fover == 0 时把非空格字符写进 fname;遇 '(' 使 fover++ | usmart_str.c:262-263、usmart_str.c:276-283 |
| 形参计数 | fover == 1 时遇 ',' 使 pcnt++ 并置 temp = 1;每进一个新参数 parmnum++ | usmart_str.c:286-303 |
| 结束 | ')' 使 fover--,fover == 0 时跳出主循环 | usmart_str.c:264-272 |
| 错误 | 输入被 '\0' 截断或出现多余的 ')' | usmart_str.c:257-261、usmart_str.c:268-269 |
| void 归零 | parmnum == 1 且唯一形参名是 "void" → parmnum = 0 | usmart_str.c:307-312 |
nchar(usmart_str.c:209)是一个「是否处于字符串字面量内部」的标志,在遇到 '"' 时翻转(usmart_str.c:273-274),用于忽略字符串里的括号,避免 f("a(b") 被误判。
4.2 数值解析:usmart_str2num(usmart_str.c:103)
第一遍(字符白名单扫描,usmart_str.c:111-124):逐字符检查是否在 '0'~'9'、'A'~'F'、或在 bnum == 1 时的 'X' 之内;同时只要出现 'A'~'F' 就把 hexdec 从 10 改成 16。出现白名单外的字符立即返回 1。
第二遍(进制判定与转换,usmart_str.c:126-153):
| 情况 | 判据 | 返回 |
|---|---|---|
| 十六进制但太短 | hexdec == 16 && bnum < 3 | 2 |
十六进制但缺 0X 前缀 | hexdec == 16 && !(*p=='0' && *(p+1)=='X') | 3 |
| 十进制但长度为 0(纯空串/纯空格) | hexdec == 10 && bnum == 0 | 4 |
| 正常 | 逐位累加 t * usmart_pow(hexdec, bnum-1) | 0(结果写入 *res) |
return 4 具有特殊语义:在 usmart.c:203 / usmart.c:231 中,usmart_sys_cmd_exe 把 i == 4 解读为「调用方没有给数字参数」,于是把 hex / dec 命令降级为「仅切换显示进制」(usmart.c:210、usmart.c:238)。因此 hex 与 dec 是同一个函数里「有参 = 进制转换、无参 = 切换显示模式」的双重语义。
关键约束:白名单只接受大写字母,小写 0x1a 会在第一遍就返回 1;'X' 仅在 bnum == 1(即恰好是第二个字符)时被接受,因此 0XX1 会在第一遍失败。
4.3 单参数解析:usmart_get_aparm(usmart_str.c:324)
返回值为本参数在源串中消耗的字符数(i),并通过 *ptype 回传三类结果:
type 值 | 含义 | 处理 |
|---|---|---|
0 | 数字 | 逐字符拷进 fparm,小写 a~f/x 统一转大写(*fparm = *str - 0X20,usmart_str.c:342-345) |
1 | 字符串 | 遇 '"' 进入字符串模式(string = 1),循环里把字符原样拷出,遇到 '\\' 时跳过转义符并多计一个字符(usmart_str.c:369-373) |
0xFF | 非法 | 出现既不是数字、也不是 '"'、也不是空格/逗号的首字符(usmart_str.c:355-359) |
enout(usmart_str.c:327)是「本参数已结束」的局部标志:在 type == 0 或 type == 1 模式下遇到 ',' 后置位,下一次进入处理体时立即 break,从而把 i 停在逗号处。外层 usmart_get_fparam 用 str += i 推进指针,接着 usmart_get_aparm 再自己跳过 ','。
4.4 参数打包:usmart_get_parmpos 与 usmart_get_fparam
usmart_get_parmpos(num)(usmart_str.c:392)是纯前缀和:
u8 temp = 0;
for (i = 0; i < num; i++) temp += usmart_dev.plentbl[i];
return temp;
因为字符串参数长度可变,第 i 个参数的落点不能按下标乘 4 计算,必须走这个函数——这也是 plentbl[] 存在的唯一理由。
usmart_get_fparam(str, parn)(usmart_str.c:407)的完整流程:
- 清零整张
plentbl[](usmart_str.c:414)。 - 向前找
'(',找不到'\0'则返回USMART_FUNCERR(usmart_str.c:415-420)。 - 循环调用
usmart_get_aparm,对type做三分支:- 数字(
usmart_str.c:428-440):若文本非空,先usmart_str2num,失败(非 0)立即返回USMART_PARMERR;成功则把小端u32写入parm[pos],清parmtype对应位,plentbl[n] = 4,n++; - 字符串(
usmart_str.c:441-449):len = usmart_strlen(tstr) + 1(含'\0'),usmart_strcopy进parm[pos],置parmtype对应位,plentbl[n] = len,n++; - 非法(
usmart_str.c:450-451):返回USMART_PARMERR。
- 数字(
- 每加一个参数后检查
n > MAX_PARM→ 返回USMART_PARMOVER(usmart_str.c:438、usmart_str.c:447)。 - 遇
')'或'\0'结束,把总数写进*parn,返回USMART_OK。
4.5 与 uCOS-II 的配合
本文件完全不涉及定时器、信号量、消息邮箱或任务创建,是一组纯函数。它唯一的并发风险来自对全局 usmart_dev 的读写:usmart_get_parmpos 与 usmart_get_fparam 都在访问 usmart_dev.plentbl / parm / parmtype,而 usmart.c 的 usmart_scan() 同时挂在 task_usmart 任务(midware/debug/shell_usmart.c)和 TIMER3_IRQHandler 中断(common/usmart/usmart.c:43)上,两者无互斥,因此这一组全局状态是可被中断打断的。当前 DEBUG_SUMART == 0,整文件不编译,风险为潜伏状态。
4.6 关键全局变量的读写
| 全局变量 | 读/写点 | 触发条件 |
|---|---|---|
usmart_dev.plentbl[] | 写:usmart_str.c:414(清零)、usmart_str.c:436、usmart_str.c:445;读:usmart_str.c:397 | 每次 usmart_get_fparam 解析一行实参 |
usmart_dev.parm[] | 写:usmart_str.c:434(数字)、usmart_str.c:443(字符串);读:usmart_str.c:434、usmart_str.c:443 的偏移计算 | 同上 |
usmart_dev.parmtype | 写:usmart_str.c:435(清位)、usmart_str.c:444(置位) | 同上 |
5. 对外接口
导出(供外部调用)
| 符号 | 声明位置 | 使用者 |
|---|---|---|
usmart_strcmp | usmart_str.h:20 | usmart.c:151, 231, 294, 310 |
usmart_strcopy | usmart_str.h 未声明(仅 usmart_str.c:52 定义) | 仅 usmart_str.c:443 |
usmart_strlen | usmart_str.h 未声明(仅 usmart_str.c:70 定义) | 仅 usmart_str.c:442 |
usmart_pow | usmart_str.h:21 | usmart_str.c:148 |
usmart_str2num | usmart_str.h:22 | usmart_str.c:431、usmart.c:198, 226, 253 |
usmart_get_cmdname | usmart_str.h:23 | usmart.c:145 |
usmart_get_fname | usmart_str.h:24 | usmart.c:187, 287, 291, 327 |
usmart_get_aparm | usmart_str.h:25 | usmart_str.c:424、usmart.c:195, 223, 250 |
usmart_get_parmpos | usmart_str.h:19 | usmart_str.c:434, 443、usmart.c:334, 336 |
usmart_get_fparam | usmart_str.h:26 | usmart.c:304 |
usmart_search_nextc | usmart_str.h 未声明(仅 usmart_str.c:184 定义) | 仅 usmart_str.c:245;未加 static,导出为全局符号 |
本文件不导出任何全局变量。
被本文件调用(外部依赖)
| 符号 | 来源 |
|---|---|
usmart_dev | midware/debug/shell_usmart.c:32(经 usmart.h:66 的 extern 声明) |
MAX_PARM / PARM_LEN / USMART_* 错误码 | common/usmart/usmart.h |
u8 / u32 | usmart_str.h:14-15 → gd32f4xx.h / chip.h |
本文件不包含任何标准库头(无 string.h),所有字符串操作均为自实现。
6. 依赖关系
- 依赖:
usmart_str.h(原型与类型)、usmart.h(usmart_dev、MAX_PARM、PARM_LEN、USMART_*)、shell_usmart.h(DEBUG_SUMART开关),间接依赖gd32f4xx.h/chip.h。 - 被依赖:
common/usmart/usmart.c(唯一实际调用方,通过usmart.h:15的#include "usmart_str.h"拿到全部 11 个原型)。当前DEBUG_SUMART == 0,实际无任何模块会链接到本文件的符号。
7. 备注与风险点
usmart_get_fparam参数个数检查存在 off-by-one(越界写):plentbl[MAX_PARM]只有 10 个元素,但n++之后才做if(n > MAX_PARM)(usmart_str.c:438、usmart_str.c:447)。当解析第 11 个参数时,usmart_str.c:436/usmart_str.c:445会先写plentbl[10]——已越界;同样usmart_dev.parm[usmart_get_parmpos(n)]也会用越界的长度值算出错误偏移。正确判据应为n >= MAX_PARM。parm[]总长度无上限校验:usmart_get_fparam从不检查「已写入字节数 + 本参数长度 ≤ PARM_LEN」。一个接近 200 字节的字符串参数(tstr[PARM_LEN+1]能装下)在usmart_str.c:443的usmart_strcopy时会溢写usmart_dev.parm[](200 字节)。usmart_get_aparm的i返回值用u8计数:单行实参超过 255 字符时i回绕(usmart_str.c:326),str += i会倒退或错位。虽然收包侧usmart.c:415的data[50]会先越界,但两处都是隐患。usmart_search_nextc的空指针判断写错:while(*str==' '&&str!='\0')(usmart_str.c:187)中的str != '\0'是把指针与 0 比较,恒为真;正确写法应为*str != '\0'。此处因第一个条件已能终止循环而未暴露,属侥幸无害。usmart_strlen返回u8:字符串超过 255 字节时长度回绕(usmart_str.c:72),usmart_str.c:442的len = usmart_strlen(tstr) + 1随之失真,是第 2 条溢出的助推因素。usmart_strcopy参数命名与实际语义相反:原型与定义写作(u8*str1, u8*str2),但实现是*str2 = *str1(usmart_str.c:56),即 str1 是源、str2 是目的。usmart_str.h:20附近与其他函数挤在一起,易被误用为「拷贝到 str1」。usmart_get_fname的offset用u8存偏移(usmart_str.c:203):返回类型部分超过 255 字符时溢出,strtemp += offset + 1(usmart_str.c:252)会指到错误位置。fpname[6]容量与用法偏紧(usmart_str.c:206):该数组被两处复用——先存返回类型(最多 5 字符 +'\0'),后又复用存第一个形参的类型文本(usmart_str.c:292-296,有fplcnt < 5保护)。逻辑正确但复用方式易被后续修改破坏。usmart_str2num只认大写:白名单为'A'~'F'(usmart_str.c:113),小写十六进制与0x前缀(小写 x)都会被判为非法。虽然usmart_get_aparm会把小写转大写(usmart_str.c:342-345),但该函数若被其他模块直接调用就会拒绝小写输入。string标志对未闭合引号不报错:usmart_get_aparm中若字符串缺收尾'"',循环会一直走到'\0'才由usmart_str.c:334的(*str=='\0')&&string==0判断——因string仍为 1,break不触发,函数会越过串尾继续扫描。属潜在的越界读。usmart_str2num无符号数溢出:usmart_str2num返回res为u32,输入0XFFFFFFFF合法,但usmart_get_fparam之后按形参类型强转(usmart.c:353-389全转u32),符号/宽度语义由调用方函数决定,无校验。- 整文件当前不编译:
DEBUG_SUMART为 0(midware/debug/shell_usmart.h:13),上述问题在发布固件中均不生效;一旦把宏改为 1,第 1、2 条会立即构成内存破坏。 - 3 个函数未在头文件声明:
usmart_strcopy(usmart_str.c:52)、usmart_strlen(usmart_str.c:70)、usmart_search_nextc(usmart_str.c:184)在usmart_str.h中都没有原型(该头文件只声明了 8 个函数),却都未加static,属「有实现无契约」的导出符号。由于本文件被usmart.h:15整体包含,调用方若直接使用这三个函数只能靠隐式声明,在严格 C99/C11 编译器下会报错。 - 注释块只有
@Name行有内容:usmart_str.c的 11 个函数各自有一个 7 行的 Doxygen 风格注释块(usmart_str.c:25-31、45-51、63-69、82-88、96-102、155-161、177-183、191-197、317-323、385-391、400-406),其中@Name行与真实签名完全一致,但@Parameter、@Return、@Brief三行全部为空。对于usmart_get_aparm的ptype(0/1/0xFF 三态)、usmart_str2num的 4 个错误码、usmart_get_cmdname的nlen语义这类关键契约,注释没有提供任何信息,属纯占位模板遗留。