3. TRACE32脚本语言PRACTICE入门

在上一节中,我们已经能够通过 TRACE32 的图形界面手动查看任务列表、切换任务上下文并读取寄存器。但当需要反复检查多个任务、批量采集状态或把诊断动作固化下来时,手工操作既慢又容易遗漏。PRACTICE 是 TRACE32 自带的脚本语言,它把调试器命令、变量、条件判断和循环组织成可重复执行的脚本,是构建自动化调试流程的基础。本节从语法要素入手,最终完成一个能读取任务状态的脚本。

PRACTICE 脚本的基本语法

PRACTICE 脚本通常保存为 .cmm 文件,通过 DO <文件名> 执行,或在 TRACE32 命令区直接输入命令逐条运行。脚本由一行行命令组成,命令之间用换行分隔,也可以用分号分隔同一行的多条命令。注释使用 // 表示行注释,/* ... */ 表示块注释。命令不区分大小写,但为了可读性,通常把命令关键字大写、变量名小写。

PRACTICE 的命令分为几类:调试器操作命令(如 Break、Go、Step)、数据访问命令(如 Data.Long、Register)、系统命令(如 PRINT、AREA)以及脚本控制命令(如 IF、WHILE、GOTO)。理解这些命令的返回值是编写自动化脚本的关键,因为很多控制逻辑依赖命令执行结果。

变量与数据类型

PRACTICE 支持多种变量类型,最常用的是整型、字符串和浮点型。变量声明使用 LOCAL 或 GLOBAL 关键字,例如:

LOCAL &taskCount      // 整型变量
LOCAL &taskName        // 字符串变量
LOCAL &cpuLoad         // 浮点变量
GLOBAL &logFile        // 全局变量,跨脚本可见

变量名以 & 开头,赋值使用 = 或 :=。整型变量可以直接参与算术运算,字符串变量用双引号括起。PRACTICE 还支持数组和结构体,但对于任务状态读取,整型和字符串已经足够。需要注意的是,变量在使用前最好显式声明,避免与系统保留字冲突。

流程控制:条件与循环

条件判断使用 IF ... ELSE IF ... ELSE ... ENDDO 结构。例如,判断当前是否处于停止状态:

IF STATE.RUN()
(
    PRINT "目标正在运行,先停止"
    Break
)
ELSE
(
    PRINT "目标已停止,可以读取状态"
)

循环使用 WHILE ... ENDDO 或 REPEAT ... UNTIL。在任务诊断中,常用循环遍历任务列表。PRACTICE 提供了 Task.List 等命令,但更通用的做法是通过 Data.Long 读取内核数据结构,或者使用 Task.Select 配合任务编号循环。下面是一个遍历任务编号的示例框架:

LOCAL &i
&i = 0
WHILE &i < 8
(
    PRINT "检查任务槽位 " &i
    &i = &i + 1
)

这里 &i < 8 是循环条件,&i = &i + 1 是步进。实际任务数量应从内核的任务控制块中动态获取,而不是硬编码,这一点在后面的脚本中会体现。

宏定义与参数传递

PRACTICE 的宏通过 ENTRY 和 ENDPART 定义,可以带参数。宏适合把重复的调试动作封装起来。例如,定义一个打印任务名称和状态的宏:

ENTRY &printTask &taskId
(
    PRINT "任务ID: " &taskId
    Task.Select &taskId
    PRINT "任务名: " TASK.NAME()
    PRINT "状态: " TASK.STATE()
)
ENDPART

调用时使用 &printTask 3。宏内部可以访问全局变量,也可以声明局部变量。宏定义让脚本结构更清晰,也便于后续扩展成任务状态批量采集工具。

编写第一个自动化调试脚本:读取任务状态

现在把上述要素组合起来,编写一个读取任务状态的脚本。假设目标系统使用常见的 RTOS,任务控制块数组的基地址可以通过符号 taskTable 获取,每个任务控制块大小为 64 字节,任务名字段偏移 0,状态字段偏移 16,优先级字段偏移 20。这些参数是示例假设,实际使用时需要根据目标系统的符号表和结构体定义调整。

// task_status.cmm
// 读取任务状态并打印

LOCAL &base
LOCAL &count
LOCAL &i
LOCAL &name
LOCAL &state
LOCAL &prio

// 获取任务表基地址和任务数量
&base = ADDRESS("taskTable")
&count = Data.Long(ADDRESS("taskCount"))

PRINT "任务总数: " &count
PRINT "----------------------------------------"
PRINT "ID  任务名        状态    优先级"
PRINT "----------------------------------------"

&i = 0
WHILE &i < &count
(
    &name = Data.String(&base + &i * 64 + 0, 16)
    &state = Data.Long(&base + &i * 64 + 16)
    &prio = Data.Long(&base + &i * 64 + 20)
    PRINT &i "   " &name "   " &state "   " &prio
    &i = &i + 1
)

PRINT "----------------------------------------"
PRINT "任务状态读取完成"

脚本执行后,会在 TRACE32 的输出窗口看到类似下面的结果(以下为示例输出,非实测数据):

任务总数: 5
----------------------------------------
ID  任务名        状态    优先级
----------------------------------------
0   IDLE          1       31
1   MAIN          2       10
2   UART_RX       2       12
3   SPI_TX        3       15
4   MONITOR       1       20
----------------------------------------
任务状态读取完成

其中状态值 1 表示就绪,2 表示运行,3 表示阻塞,具体编码需要对照目标 RTOS 的定义。脚本的关键点在于:用 ADDRESS() 获取符号地址,用 Data.Long() 和 Data.String() 按偏移读取字段,用 WHILE 循环遍历任务表。如果任务数量在运行时会变化,可以在循环中重新读取 taskCount,或者加入边界检查防止越界。

调试脚本时,可以先用 PRINT 输出中间变量,确认地址和偏移正确后再批量读取。如果 Data.String 读出的名字乱码,通常是偏移或长度不对,需要检查结构体定义。

脚本执行流程与数据流

为了更直观地理解脚本从启动到输出结果的过程,下面用一张数据流图展示脚本各阶段的数据来源、处理动作和输出。图中左侧是输入(符号表、内存数据),中间是脚本的处理步骤,右侧是输出(打印结果)。

PRACTICE 任务状态读取脚本 — 数据流 输入源 符号表 taskTable 符号 taskCount 目标内存 TCB 数组 脚本处理步骤 1. ADDRESS("taskTable") 取基址 → &base 2. Data.Long(taskCount) 取数量 → &count 3. 初始化 &i = 0 循环计数器 4. WHILE &i < &count 按偏移读 name/state/prio &i = &i + 1 5. PRINT 格式化输出 任务ID/名称/状态/优先级 输出结果 任务总数 任务名/状态/优先级 完成提示 循环 关键点:符号地址 → 内存偏移读取 → 循环遍历 → 格式化输出;偏移量需与目标 RTOS 结构体定义一致

从图中可以看到,脚本的核心是“符号地址 → 内存偏移读取 → 循环遍历 → 格式化输出”这条数据链。输入来自符号表和目标内存,处理步骤按顺序执行,循环部分会反复读取每个任务控制块,最终把结果打印到输出窗口。理解这条链路后,修改偏移量、增加字段或改变输出格式都只是局部调整。

常见问题与调试建议

初次编写 PRACTICE 脚本时,最容易遇到三类问题。第一类是地址或偏移错误,表现为读出的任务名乱码或状态值明显不合理。此时应先用 Data.Long 读取几个已知字段验证,再逐步调整偏移。第二类是循环边界问题,如果 taskCount 读取失败返回 0,循环不会执行,脚本会直接结束;如果返回值过大,可能越界访问导致目标异常。建议在循环中加入 IF &i >= 64 THEN Break 之类的保护。第三类是脚本执行时机问题,如果目标正在运行,内存内容可能变化,读取到的任务状态不一致。通常先 Break 停止目标,再执行读取脚本。

在真实目标上运行脚本前,先在 TRACE32 的模拟环境或已知安全的测试目标上验证。直接对运行中的系统做批量内存读取,虽然通常安全,但如果脚本误写了内存地址,可能造成目标崩溃。

掌握本节内容后,你已经能够用 PRACTICE 脚本完成基本的任务状态采集。下一步可以在此基础上增加条件判断,例如只打印阻塞状态的任务,或者把结果写入文件供后续分析。脚本的变量、循环和宏定义是后续所有自动化调试功能的基础,建议多动手修改示例中的偏移量和输出格式,加深对 PRACTICE 数据访问方式的理解。