EMS 知识库
首页 / knowledge/代码解析/common/usmart

usmart_str.c

源文件路径: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 = resusmart_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 = 0usmart_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 < 32
十六进制但缺 0X 前缀hexdec == 16 && !(*p=='0' && *(p+1)=='X')3
十进制但长度为 0(纯空串/纯空格)hexdec == 10 && bnum == 04
正常逐位累加 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)的完整流程:

  1. 清零整张 plentbl[](usmart_str.c:414)。
  2. 向前找 '(',找不到 '\0' 则返回 USMART_FUNCERR(usmart_str.c:415-420)。
  3. 循环调用 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。
  4. 每加一个参数后检查 n > MAX_PARM → 返回 USMART_PARMOVER(usmart_str.c:438、usmart_str.c:447)。
  5. 遇 ')' 或 '\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_strcmpusmart_str.h:20usmart.c:151, 231, 294, 310
usmart_strcopyusmart_str.h 未声明(仅 usmart_str.c:52 定义)仅 usmart_str.c:443
usmart_strlenusmart_str.h 未声明(仅 usmart_str.c:70 定义)仅 usmart_str.c:442
usmart_powusmart_str.h:21usmart_str.c:148
usmart_str2numusmart_str.h:22usmart_str.c:431、usmart.c:198, 226, 253
usmart_get_cmdnameusmart_str.h:23usmart.c:145
usmart_get_fnameusmart_str.h:24usmart.c:187, 287, 291, 327
usmart_get_aparmusmart_str.h:25usmart_str.c:424、usmart.c:195, 223, 250
usmart_get_parmposusmart_str.h:19usmart_str.c:434, 443、usmart.c:334, 336
usmart_get_fparamusmart_str.h:26usmart.c:304
usmart_search_nextcusmart_str.h 未声明(仅 usmart_str.c:184 定义)仅 usmart_str.c:245;未加 static,导出为全局符号

本文件不导出任何全局变量。

被本文件调用(外部依赖)

符号来源
usmart_devmidware/debug/shell_usmart.c:32(经 usmart.h:66 的 extern 声明)
MAX_PARM / PARM_LEN / USMART_* 错误码common/usmart/usmart.h
u8 / u32usmart_str.h:14-15 → gd32f4xx.h / chip.h

本文件不包含任何标准库头(无 string.h),所有字符串操作均为自实现。

6. 依赖关系

7. 备注与风险点

  1. 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。
  2. parm[] 总长度无上限校验:usmart_get_fparam 从不检查「已写入字节数 + 本参数长度 ≤ PARM_LEN」。一个接近 200 字节的字符串参数(tstr[PARM_LEN+1] 能装下)在 usmart_str.c:443 的 usmart_strcopy 时会溢写 usmart_dev.parm[](200 字节)。
  3. usmart_get_aparm 的 i 返回值用 u8 计数:单行实参超过 255 字符时 i 回绕(usmart_str.c:326),str += i 会倒退或错位。虽然收包侧 usmart.c:415 的 data[50] 会先越界,但两处都是隐患。
  4. usmart_search_nextc 的空指针判断写错:while(*str==' '&&str!='\0')(usmart_str.c:187)中的 str != '\0' 是把指针与 0 比较,恒为真;正确写法应为 *str != '\0'。此处因第一个条件已能终止循环而未暴露,属侥幸无害。
  5. usmart_strlen 返回 u8:字符串超过 255 字节时长度回绕(usmart_str.c:72),usmart_str.c:442 的 len = usmart_strlen(tstr) + 1 随之失真,是第 2 条溢出的助推因素。
  6. usmart_strcopy 参数命名与实际语义相反:原型与定义写作 (u8*str1, u8*str2),但实现是 *str2 = *str1(usmart_str.c:56),即 str1 是源、str2 是目的。usmart_str.h:20 附近与其他函数挤在一起,易被误用为「拷贝到 str1」。
  7. usmart_get_fname 的 offset 用 u8 存偏移(usmart_str.c:203):返回类型部分超过 255 字符时溢出,strtemp += offset + 1(usmart_str.c:252)会指到错误位置。
  8. fpname[6] 容量与用法偏紧(usmart_str.c:206):该数组被两处复用——先存返回类型(最多 5 字符 + '\0'),后又复用存第一个形参的类型文本(usmart_str.c:292-296,有 fplcnt < 5 保护)。逻辑正确但复用方式易被后续修改破坏。
  9. usmart_str2num 只认大写:白名单为 'A'~'F'(usmart_str.c:113),小写十六进制与 0x 前缀(小写 x)都会被判为非法。虽然 usmart_get_aparm 会把小写转大写(usmart_str.c:342-345),但该函数若被其他模块直接调用就会拒绝小写输入。
  10. string 标志对未闭合引号不报错:usmart_get_aparm 中若字符串缺收尾 '"',循环会一直走到 '\0' 才由 usmart_str.c:334 的 (*str=='\0')&&string==0 判断——因 string 仍为 1,break 不触发,函数会越过串尾继续扫描。属潜在的越界读。
  11. usmart_str2num 无符号数溢出:usmart_str2num 返回 res 为 u32,输入 0XFFFFFFFF 合法,但 usmart_get_fparam 之后按形参类型强转(usmart.c:353-389 全转 u32),符号/宽度语义由调用方函数决定,无校验。
  12. 整文件当前不编译:DEBUG_SUMART 为 0(midware/debug/shell_usmart.h:13),上述问题在发布固件中均不生效;一旦把宏改为 1,第 1、2 条会立即构成内存破坏。
  13. 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 编译器下会报错。
  14. 注释块只有 @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 语义这类关键契约,注释没有提供任何信息,属纯占位模板遗留。