4. CoreSight组件之ETM:触发逻辑、ATB总线与Trace数据格式化
ETM,全称是Embedded Trace Macrocell。说白了,它就是芯片内部的一个“硬件探针”。你想想看,CPU跑得飞快,软件断点根本跟不上。这时候ETM就派上用场了。它能实时抓取CPU执行的指令流和数据流,然后通过ATB总线送出去。
我个人习惯把ETM理解成一个“黑匣子”。飞机上有黑匣子,记录飞行数据。ETM就是SoC的黑匣子,记录程序执行轨迹。调试的时候,我们回放这些轨迹,就能知道程序到底是怎么跑的,哪里出了问题。
4.1 ETM的触发逻辑:什么时候开始抓?
ETM不是一直抓数据的。那样数据量太大,根本存不下。它需要一套触发逻辑,来决定“什么时候开始抓”、“抓到什么时候停”。
触发逻辑的核心,是资源选择器和触发条件的组合。我画了一张图,帮你理清这个关系。
触发逻辑的配置,通常通过ETM的寄存器来完成。核心寄存器包括:
| 寄存器 | 功能 | 我常用的配置 |
|---|---|---|
| ETMCR | 控制寄存器,使能ETM | 设置bit[0]为1启动 |
| ETMTRIGGER | 触发事件寄存器 | 配置地址匹配+数据匹配 |
| ETMTSSCR | 时间戳控制 | 周期采样模式 |
| ETMFFCR | FIFO控制,防溢出 | 设置阈值,避免丢数据 |
我的经验:配置触发条件时,别一上来就设得太复杂。我习惯先设一个简单的地址断点,确认ETM能正常抓数据。然后再加计数器、加过滤条件。一步步来,出问题了也好定位。
4.2 ATB总线协议:数据怎么传出去?
ETM抓到了Trace数据,接下来要把它送出去。这就用到了ATB总线——Advanced Trace Bus。它是CoreSight体系内部专用的总线,专门用来传输Trace数据。
ATB总线其实不复杂。它跟AHB有点像,但更简单。核心信号就这几个:
- ATCLK:时钟,跟CPU时钟可以不同步
- ATDATA:数据总线,宽度可配(8/16/32位)
- ATID:源ID,标识数据来自哪个ETM
- ATVALID:发送端有效信号
- ATREADY:接收端准备好信号
ATB的握手协议很简单:发送端拉高ATVALID,接收端拉高ATREADY,一个数据就传过去了。如果接收端忙,ATREADY拉低,发送端就得等。
⚠️ 注意:ATB总线上可以挂多个ETM。每个ETM有唯一的ATID。我曾经遇到一个问题:两个ETM配了相同的ATID,结果Trace数据混在一起,根本没法解析。排查了好久才发现是ID冲突。所以,ATID的分配一定要仔细核对。
ATB总线上还有一个重要的概念——数据包。ETM产生的Trace数据不是裸数据,而是打包成ATB包。包的类型包括:
- 原子包:最基本的指令执行信息
- 数据包:包含数据读写地址和值
- 同步包:用于对齐和同步
- 触发包:标记触发事件发生
4.3 Trace数据的产生与格式化:抓到的数据长什么样?
ETM抓到的原始数据,不能直接用。它需要经过格式化,变成调试工具能识别的格式。这个过程,我称之为“从硬件信号到调试信息的翻译”。
Trace数据的产生流程,大致是这样的:
- 指令跟踪:ETM监控CPU的指令执行流水线
- 分支记录:只记录分支指令(跳转、调用、返回)
- 压缩编码:用变长编码压缩数据,减少带宽
- 打包输出:封装成ATB包,加上时间戳
这里有个关键点——分支跟踪。ETM不会记录每一条指令,那样数据量太大了。它只记录“分支”信息。比如程序从地址A跳到了地址B,ETM就记下这个跳转。调试工具根据分支信息,结合程序镜像,就能重建出完整的执行路径。
核心原理:ETM输出的Trace数据,本质上是“程序执行路径的增量信息”。它不告诉你每条指令是什么,只告诉你“从哪里跳到了哪里”。调试工具拿到这个增量信息,再结合ELF文件,就能还原出完整的指令流。
Trace数据的格式,ARM定义了一套标准——ETMv4架构的包格式。常见的包类型有:
| 包类型 | 编码 | 含义 | 长度 |
|---|---|---|---|
| Atom | 0b1xxx | 原子执行信息 | 1字节 |
| Branch Address | 0b01xx | 分支目标地址 | 2-8字节 |
| Data Trace | 0b001x | 数据读写信息 | 可变 |
| Timestamp | 0b0001 | 时间戳 | 2-8字节 |
| Exception | 0b0000 | 异常事件 | 2字节 |
举个例子。假设程序执行了这样一段代码:
0x1000: MOV R0, #1
0x1004: ADD R1, R0, #2
0x1008: BL func
0x100C: SUB R2, R1, #3
ETM抓到的Trace数据,可能只有一条:Branch Address: 0x2000(跳转到func函数)。调试工具收到这个包,就知道程序在0x1008处调用了func,目标地址是0x2000。然后工具去ELF文件里查0x2000处的指令,就能继续跟踪了。
避坑指南:我曾经调试一个死循环问题。程序在某个循环里出不来了。用ETM抓Trace,发现分支地址一直在两个值之间来回跳。一看代码,原来是循环条件写错了。如果没有ETM,这种问题靠断点根本查不出来——断点一停,循环状态就变了。
Trace数据的格式化,还有一个重要环节——时间戳。ETM可以配置为周期性地插入时间戳包。这样调试工具就能知道每条指令执行的精确时间。对于分析性能瓶颈,时间戳非常有用。
嗯,到这里,ETM的核心机制就讲清楚了。触发逻辑决定抓什么,ATB总线决定怎么传,格式化决定数据长什么样。这三者配合起来,ETM才能成为一个强大的调试工具。