4. Trace32命令系统:命令语法规则、常用调试命令、脚本语言基础、变量与表达式
Trace32的命令系统,说白了就是你和调试器对话的语言。刚开始接触时,我也觉得它有点「反人类」——怎么全是缩写?怎么连个图形界面都没有?但用久了你会发现,这套命令系统才是真正高效的东西。尤其是做车载ECU调试,动不动就要重复几百次同样的操作,写个脚本一键搞定,比点鼠标舒服太多了。
这一章,我把命令系统拆成四个部分来讲:语法规则、常用命令、脚本基础、变量与表达式。咱们一步步来。
4.1 命令语法规则:先看懂「句子结构」
Trace32的命令,本质上就是「动词 + 对象 + 参数」的结构。举个例子:
Data.Set EAX %Long 0x12345678
这句命令的意思是:设置(Data.Set)寄存器EAX(对象)为32位长整型(%Long)值0x12345678(参数)。
我个人习惯把命令分成三个层级来记:
| 层级 | 说明 | 示例 |
|---|---|---|
| 命令前缀 | 表示操作类别 | Data、Break、Trace、List |
| 操作动词 | 表示具体动作 | Set、View、List、Delete |
| 参数/修饰符 | 限定操作范围或格式 | %Long、0x1000、/Spotter |
这里有个容易踩坑的地方——大小写。Trace32的命令是大小写不敏感的,但参数里的符号和地址是敏感的。我曾经因为把 0x8000 写成了 0X8000,折腾了半小时才发现问题。嗯,别学我。
Help 命令。比如输入 Help Data.Set,Trace32会直接弹出这个命令的完整语法说明。我调试时经常开着Help窗口,边查边写。
4.2 常用调试命令:这些你每天都会用
做ECU调试,最常用的命令其实就那么十几个。我把它们分成三类:
4.2.1 断点类
Break.Set 0x8000 // 在地址0x8000设断点
Break.Set func_main /Program // 在函数func_main入口设断点
Break.Delete // 删除所有断点
Break.List // 列出当前所有断点
你想想看,车载ECU的代码动不动就几十万行,手动找函数地址?不现实。用 /Program 修饰符直接按函数名设断点,省事多了。
4.2.2 运行与暂停类
Go // 全速运行
Go 0x9000 // 运行到地址0x9000停下
Step // 单步执行(进入函数内部)
Next // 单步执行(跳过函数调用)
Break // 强制暂停目标
我记得有一次调试一个CAN通信的bug,程序跑到某个中断里就出不来了。我用了 Break 强制暂停,然后用 List.Stack 看调用栈,一下子就定位到了死循环的位置。
4.2.3 数据查看与修改类
Data.View 0x8000++0x100 // 查看地址0x8000开始的256字节
Data.Set 0x8000 %Word 0x55AA // 将0x8000地址的16位数据改为0x55AA
Register.View // 查看所有寄存器
Register.Set R0 %Long 0x0 // 将R0寄存器清零
Data.Set,轻则命令无效,重则导致总线错误。我刚开始用Trace32时就犯过这个错,直接把ECU搞挂了,只能重新上电。
4.3 脚本语言基础:让调试自动化
Trace32的脚本语言叫 PRACTICE。说实话,这名字起得挺实在——就是让你多练习、多实践。脚本文件后缀通常是 .cmm,用文本编辑器就能写。
一个最简单的脚本长这样:
// 文件: init_debug.cmm
PRINT "开始初始化调试环境..."
Break.Delete
Data.Set 0x8000 %Word 0x55AA
Go
PRINT "初始化完成,目标已运行"
在Trace32的命令行里输入 DO init_debug.cmm,脚本就会逐行执行。
我个人习惯把脚本分成三个部分:
- 头部:清空断点、重置状态、打印日志
- 主体:设置断点、加载符号、配置触发条件
- 尾部:启动运行、保存结果、清理环境
这样做的好处是,每个脚本都可以独立运行,不会互相干扰。我在项目里维护了十几个这样的脚本,覆盖了从启动到休眠的各个阶段。
4.3.1 流程控制
脚本里少不了判断和循环。PRACTICE的语法和C语言有点像,但更简洁:
// IF-ELSE 示例
IF (R0 == 0x0)
(
PRINT "R0 为 0,进入异常处理"
Go 0xFFFF0000
)
ELSE
(
PRINT "R0 非 0,继续运行"
Go
)
// WHILE 循环示例
WHILE (R0 < 0x100)
(
PRINT "R0 = %R0"
Step
)
你可能会问:为什么用圆括号 () 而不是花括号 {}?嗯,这是PRACTICE的语法特点,习惯了就好。
4.4 变量与表达式:让脚本「活」起来
没有变量的脚本,就像没有调料的菜——能吃饱,但不好吃。Trace32支持多种变量类型:
| 变量类型 | 定义方式 | 示例 |
|---|---|---|
| 全局变量 | GLOBAL <name> |
GLOBAL &count |
| 局部变量 | LOCAL &name |
LOCAL &temp |
| 地址变量 | ENTRY &addr |
ENTRY &func_addr |
变量名前面加 & 符号,这是PRACTICE的规矩。赋值用 =,引用时直接写 &变量名:
LOCAL &i
&i = 0
WHILE (&i < 10)
(
PRINT "循环次数: &i"
&i = &i + 1
)
表达式方面,Trace32支持算术运算、逻辑运算、位运算,甚至可以直接读取内存值参与计算:
LOCAL &val
&val = Data.Long(0x8000) // 读取0x8000处的32位值
&val = &val & 0xFF // 只保留低8位
PRINT "低8位值为: &val"
4.5 知识结构总览
下面这张图,把本章的知识点串在了一起。你可以把它当作一个「命令系统地图」:
从这张图可以看得很清楚:语法规则是骨架,常用命令是血肉,脚本和变量表达式则是让整个系统「活起来」的关键。四者缺一不可。
Help 看看。然后照着本章的例子,一个一个试。试错了也没关系——又不会把板子烧了,对吧?