3. TRACE32命令系统与调试对象

TRACE32 的调试能力并不是靠某一个“万能命令”完成的,而是由一套分层、可组合的命令系统支撑。理解这套命令系统,关键不是背命令表,而是先分清“命令作用于什么对象、返回什么结果、结果如何被脚本消费”。本节围绕四件事展开:命令分类、寄存器与内存访问、符号与调试信息加载、返回值处理。

一、常用调试命令的分类逻辑

TRACE32 命令按作用对象大致可分为四类,这个分类直接决定了脚本中命令的书写顺序和参数形式。

  • 运行控制类:Go、Break、Step、StepOver、Wait。它们改变 CPU 的执行状态,通常不返回数据,而是改变调试会话的“当前状态”。
  • 数据访问类:Data.List、Data.Set、Register.View、Register.Set。它们读写寄存器、内存、外设地址,是脚本获取现场信息的主要手段。
  • 符号与调试信息类:Data.Load、Data.LOAD.Elf、Symbol.SourcePATH、List.auto。它们把可执行文件、符号表、源码路径与调试器关联起来。
  • 脚本与流程类:PRINT、IF、WHILE、GOSUB、RETURN、ENTRY。它们不直接操作目标,而是组织命令的执行顺序和结果判断。

分类的意义在于:运行控制类命令往往改变状态,数据访问类命令产生可读取的值,符号类命令决定“地址能否翻译成名字”,脚本类命令负责把这些值串成判断逻辑。脚本出错时,先判断错误发生在哪一类,比逐行猜测更高效。

二、CPU 寄存器与内存访问

寄存器访问使用 Register 命令族。查看单个寄存器可用 Register.View R0,批量查看可用 Register.View 配合窗口或 PRINT。修改寄存器用 Register.Set R0 0x12345678。需要注意:寄存器宽度与目标架构相关,ARM Cortex-M 的 R0 是 32 位,而 AArch64 的 X0 是 64 位,脚本中若用固定掩码会出错。

内存访问使用 Data 命令族。读取一段内存的典型写法是:

Data.List 0x20000000 /Long 16
Data.Set 0x20000010 %Long 0xDEADBEEF
Data.List 0x20000010 /Long 1

这里 /Long 表示按 32 位字长解释,16 表示读取 16 个单元。地址、字长、数量三者必须匹配,否则读出的值会与预期错位。对于外设寄存器,建议使用 /Long 或 /Word 明确宽度,避免默认字长带来的歧义。

在脚本中访问内存前,先确认目标是否处于停止状态。若 CPU 正在运行,读到的值可能处于变化中,脚本判断会不稳定。必要时先 Break,再读取。

三、符号与调试信息加载

符号加载决定调试器能否把地址显示为函数名、变量名和源码行。常用命令是 Data.Load 或 Data.LOAD.Elf,加载时通常需要指定文件路径和加载选项。例如:

Data.LOAD.Elf demo.elf /NoClear
Symbol.SourcePATH C:\project\src
List.auto

/NoClear 表示加载符号时不自动清除已有符号,适合分模块加载;Symbol.SourcePATH 告诉调试器源码根目录,使源码行能正确关联;List.auto 让列表窗口自动跟随当前 PC。若符号加载后仍显示地址而非函数名,优先检查三件事:ELF 是否包含调试段、加载地址与链接地址是否一致、源码路径是否可访问。

四、命令返回值处理

TRACE32 脚本中,命令的返回值通常通过 PRINT 或函数形式获取。例如 PRINT Register(R0) 可输出 R0 的值,PRINT Data.Long(0x20000000) 可读取一个 32 位内存值。返回值可以参与条件判断:

IF Register(PC) == 0x08000100
(
    PRINT "Hit entry point"
    Go
)
ELSE
(
    PRINT "PC = " Register(PC)
)

返回值处理的关键是类型与比较方式。寄存器值通常按无符号整数处理,若需要按有符号解释,应显式转换。内存读取要指定字长,否则默认宽度可能与预期不同。对于返回字符串的命令,比较时应使用字符串比较而非数值比较。

TRACE32 命令系统与调试对象关系图 命令分类 运行控制类 Go / Break / Step 数据访问类 Register / Data 符号调试类 Data.Load / Symbol 脚本流程类 IF / WHILE / PRINT 调试对象 CPU 寄存器 R0-R15 / PC / SP 内存空间 RAM / Flash / 外设 符号表 函数 / 变量 / 源码行 执行状态 Running / Stopped 返回值处理与脚本判断 PRINT Register(R0) 读取寄存器值 → 参与比较 示例:0x20000000 PRINT Data.Long(addr) 按字长读取内存 → 判断内容 示例:0xDEADBEEF IF 条件判断 比较返回值 → 分支执行 命中 / 未命中 / 异常 结果输出与记录 PRINT 日志 / 写入文件 供后续脚本消费 命令分类决定操作方式 → 调试对象决定数据来源 → 返回值处理决定脚本判断逻辑

五、脚本中的典型组合与判断依据

把上述四部分串起来,一个可复用的脚本片段通常包含:加载符号、设置断点、运行到断点、读取寄存器与内存、根据返回值判断、输出结果。例如判断某函数是否被调用,可以先在函数入口设断点,运行后检查 PC 是否等于函数地址,再读取参数寄存器。若返回值符合预期,则记录“命中”;否则记录“未命中”并继续运行。

判断依据要明确:寄存器比较用精确值或范围,内存比较用字长和掩码,符号比较用名称或地址。脚本中不要依赖“看起来对”的模糊判断,而应把每个条件的阈值、字长、比较方式写清楚。这样即使目标程序变化,脚本仍能给出可解释的结果。

TRACE32 命令对大小写和参数格式敏感。例如 /Long 与 /long 在不同版本中可能表现不同,建议统一使用官方文档中的写法,并在脚本中保持一致的命名风格。

掌握命令分类、寄存器与内存访问、符号加载和返回值处理这四块,就具备了编写可判断、可复用调试脚本的基础。后续更复杂的自动化流程,都是在这四块之上组合出来的。