外部调试器与处理器之间的握手与external debug events
- 一,External Debug的使能
- 二,外部调试器和CPU之间的握手
- 三,外部调试事件 External debug events
- 1. External debug request event
- 2. Halt instruction debug event
- 3. Halting step debug event
- 4. Exception catch debug event
- 5. Reset catch debug event
- 6. Software access debug event
- 7. OS unlock catch debug event
- 8. Breakpoint event
- 9. Watchpoint event
- 四,Embedded Cross Trigger简介
一,External Debug的使能
ARM的外部调试模型并没有一个全局的使能控制bit,而是单独对每个外部调试事件,有对应的控制bit可以配置,从而控制该调试事件是否能将处理器停止。
比如 断点事件(Breakpoints event)和观察点事件(watchpoints)是self-host 调试模型和外部调试模型共享的资源,将 EDSCR(外部调试控制和状态寄存器)的HDE(Halting debug enable)字段设成1, 可以使能Breakpoint, Watchpoint 以及Halt Instruction 等debug events将处理器停止。
此外,外部调试器需要自我鉴权(authenticate itself),如果没有自我鉴权,让CPU停止运行的调试事件可能会被禁止。鉴权操作可能以多级鉴权的形式存在,比如当处理器处于Non-secure下时,需要一级鉴权来使能停止处理器的行为;当处理器处于secure下时,可能又需要另一级鉴权。
二,外部调试器和CPU之间的握手
当处理器进入调试状态(debug state)后,外部调试器将接管并控制处理器。外部调试器也同样可以让处理器离开debug state,让其继续正常执行程序。
在处理器和外部调试器之间控制信息传送的机制称为握手,一个典型的握手操作有如下:
- 首先外部调试器会配置处理器的调试逻辑单元(debug logic),当程序执行到某个特定点上时,进而触发调试事件。比如外部调试器可以在程序中设置断点,当处理器执行到该位置时,CPU停止并进入debug state。
- 当CPU进入debug state时,相关外部调试寄存器会显示当前处理器的具体状态,比如EDSCR寄存器的STATUS字段:
- 外部调试器将持续监控处理器的相关外部调试寄存器,判断其是否处于debug state。
- 当调试结束时,外部调试器将会发起一个restart 请求,让处理器离开debug state。
三,外部调试事件 External debug events
接下来,本文将介绍可以让处理器进入调试模式的各种外部调试事件(external debug events)。
1. External debug request event
对于处理器来讲,一个外部调试请求事件是一个输入的触发事件。当处理器收到该事件,将会进入debug state。外部调试器一般通过写 CTI 寄存器( Cross Trigger Interface Registers),来触发一个External debug request event,强制让处理器进入debug state,进而控制处理器。
如果处理器已经处于debug state,此时收到的External debug request将会被忽略。外部调试器如果想让处理器离开debug state继续执行程序,也可以通过写CTI寄存器触发一个restart request事件,当该事件被处理器收到,处理器将离开debug state。
2. Halt instruction debug event
当处理器主动执行 HLT 指令时, 处理器会进入debug state,并会触发一个 halt instruction debug event,该事件将允许外部调试器接管处理器。HLT指令同样也是用于停止处理器的软件断点指令。如果EDSCR的HDE为1,HLT指令将被允许执行,Halt instruction debug event也会被允许产生。一旦处理器执行HLT指令,处理器将进入debug state,并将控制器交给外部调试器。此外,AArch64状态下的semihosting的实现,也是通过HLT指令来实现,详情介绍参考博客:什么是Semihosting(半主机)- ARM处理器与主机之间IO通信机制
HLT指令的一大用途就是用来打断点。外部调试器比如要在程序中的某个位置A处打断点,调试器可以将地址A处的指令替换成HLT指令,一旦程序执行到了地址A,HLT指令将会触发一个Halt instruction debug event,之后外部调试器便可以控制处理器了。
如果想要离开debug state,外部调试器也可以触发一个restart 事件让处理器继续运行。
3. Halting step debug event
Halting step debug event可以让外部调试器对程序进行单步调试:让处理器在不处于debug state状态下单步执行指令或者异常。换句话说,就是执行指令的时候处理器不处于debug state,当该指令执行完成又进入debug state,从而达到单步调试的目的。具体实现有如下步骤:
-
当处理器处于debug 状态下时,外部调试器可以通过将EDECR寄存器的SS字段写1, 将单步调试功能enable。需要注意的是,当处理器不处于debug state时,改变EDECR.SS的值,其行为是CONSTRAINED UNPREDICTABLE,可能会有如下行为发生:
- EDECR.SS的值将变成 UNKNOWN
- 单步调试的状态机的状态也将变成UNKNOWN。
- 当处理器被reset后,EDECR.SS的值以及单步调试的状态机的状态也将变成UNKNOWN。
-
外部调试器会发出信号让处理器离开debug state。
-
在离开debug state之后,处理器将执行PC寄存器中地址上指向的指令,并且处理器将会在执行下一条指令之前再次进入debug state,随即又把控制权交给外部调试器。
单步调试的流程可以总结为如上三个步骤,处理器在单步调试过程中,不断地在debug state和正常状态中切换,其中是有一个状态机:the Halting Step state machine来维护该操作的。
4. Exception catch debug event
当处理器进入异常或者从异常退出时,异常捕获调试事件可以让外部调试器接管处理器。
对于每个异常等级,在ED Exception Catch Control Register (EDECCR)中都有对应的控制bit,通过配置这些控制位(每个目标异常等级和Secure状态都有不同的控制位),当处理器在对应异常等级和安全状态下,进入异常或者异常返回时,处理器会进入debug state:
- 当处理器进入异常,并产生一个异常捕获事件时,处理器会在执行异常处理函数的第一条指令之前,先进入debug state。
- 当处理器从异常退出,并产生一个异常捕获事件时,处理器会在执行异常返回的第一条指令之前,进入debug state。
下图为EDECCR, External Debug Exception Catch Control Register寄存器的控制位示意图,其中NS表示Non-secure,S表示Secure;E表示进入异常,R表示异常返回。最后的数字0 1 2 3表示异常等级。所以该寄存器可以控制所有状态下的异常捕获。
5. Reset catch debug event
当发生reset时,异常捕获事件可以允许外部异常调试器立刻接管处理器。
如果想要使能Reset catch debug event,可以配置以下两个寄存器的Reset Catch debug Event (RCE) 控制位:
- External Debug Execution Control Register (EDECR)
- Cross Trigger Interface Device Control register (CTIDEVCTL)
对处理器来讲,无论是Warm reset 还是 Cold reset,只要当RCE控制位被置上,当reset发生后,处理器就会在执行第一条指令之前,进入debug state,并将控制权交给外部调试器。
6. Software access debug event
A software access debug event allows the external debugger to take control of the PE when the PE
software tries to access the following registers:
当External Debug Status and Control Register (EDSCR)寄存器的TDA控制位被设为1,处理器执行的软件试图访问以下寄存器时:
- Debug Breakpoint Value Registers (DBGBVRn_EL1), including DBGBXVRn from AArch32 state
- Debug Breakpoint Control Registers (DBGBCRn_EL1)
- Debug Watchpoint Value Registers (DBGWVRn_EL1)
- Debug Watchpoint Control Registers (DBGWCRn_EL1)
将会触发software access debug event,进而处理器会进入debug state。
关于TDA控制位,参考如下:
当TDA为1时,software access debug event可以软件通过访问以下debug寄存器的方式触发,进而进入debug state:
- AArch64: DBGBCR_EL1, DBGBVR_EL1, DBGWCR_EL1, DBGWVR_EL1.
- AArch32: DBGBCR, DBGBVR, DBGBXVR, DBGWCR, DBGWVR.
需要注意的是,如果是通过内存映射的方式来访问上述寄存器,是不会产生software access debug event的。
7. OS unlock catch debug event
在处理器电源断电时,一些调试寄存器内容将会丢失。为了在reset后恢复处理器的调试能力,必须将调试寄存器内容保存到非易失性内存中,并在系统复位后restore。Armv8-A体系结构支持使用操作系统保存机制(OS save mechanism)保存调试寄存器内容,并使用操作系统恢复机制(OS restore mechanism)恢复调试寄存器内容。
处理器软件负责执行保存和恢复。在保存调试寄存器内容时,软件会给 OS lock上锁,以便禁止外部调试访问调试寄存器。恢复调试寄存器内容后,PE软件会解锁 OS lock。
OS unlock catch debug event允许外部调试器在OS restore sequence后立即控制处理器。此事件强制处理器在reset后恢复调试寄存器器的内容后,并立即进入调试状态。
EDECR寄存器的OSUCE控制位可以控制 OS unlock catch debug event是否能被触发。
8. Breakpoint event
外部调试器可以先在硬件断点(Hardware breakpoint)寄存器里写入断点的地址。当处理器执行到该地址的指令时,如果满足以下条件,将生成一个断点事件(Breakpoint event):
-
EDSCR.HDE ==1, 表示 Halting debug is enabled 。
-
鉴权信号表明当前的调试器已经为当前安全状态下的处理器完成了自我鉴权。
上文提到过可以通过HLT指令设置software Breakpoint,而这种通过配置硬件寄存器的方式实现的Breakpoint,则称为硬件断点(hardware breakpoint)。
参考下图的示例,以下是设置硬件断点的流程: -
首先外部调试器需要将需要打断点的指令所在的地址写入DBGBVR(Breakpoint Value Register) 寄存器。比如想在0x9014地址上设置一个断点,则将该地址写入DBGBVR1_EL1中。
-
然后外部调试器将对应的断点控制寄存器DBGBCR1_EL1的E控制位写1,表示使能了断点事件。
-
如果处理器执行到了0x9014上的指令,则会触发Breakpoint event。
断点控制寄存器(Debug Breakpoint Control Registers)DBGBCR_EL1包含针对断点的控制位,主要是 E 位:
如果E控制位为0,那么将不能产生Breakpoint 事件。此外,在reset后,DBGBCRn.E的值是未知的,所以外部调试器在进行调试之前,必须确保它有个确定的值。
断点值寄存器DBGBVR_EL1保存用于对应断点匹配的值。该值可以是:
- 一个指令的虚拟地址(An instruction virtual address)
- 一个或两个上下文ID(One or two Context IDs)
- 一个VMID值
- 一个上下文ID值和一个VMID值的组合(A concatenation of both a Context ID value and a VMID value)
The range of n for DBGBCRn is 0 to 5.
Armv8-A架构提供了2-16个可实现的硬件断点,可以通过寄存器ID_AA64DFR0_EL1的BRPs字段来知道具体实现了多少个断点单元。比如在Cortex-A55架构下,最多支持6个硬件断点。
可以通过配置单个调试断点控制寄存器DBGBCR_El1.E的E位来启用或禁用单个断点单元。每个断点单元都有一个相应的控制寄存器。根据实现的断点数量,寄存器的编号将按照如下方法进行:
- DBGBCR0_EL1 and DBGBVR0_EL1 are for breakpoint number zero.
- DBGBCR1_EL1 and DBGBVR1_EL1 are for breakpoint number one.
- DBGBCR2_EL1 and DBGBVR2_EL1 are for breakpoint number two.
- DBGBCR_EL1 and DBGBVR_EL1 are for breakpoint number n.
9. Watchpoint event
A watchpoint event is generated when a PE accesses data memory from a particular address
or address range. Hardware watchpoint registers can be programmed with the address of a
data memory location. When the PE attempts to access data from the programmed address, a
watchpoint event is generated.
通过配置硬件监视点(Hardware watchpoint)寄存器,写入需要被监控的数据所在的内存地址,当处理器从该特定地址或地址范围访问数据内存时,就会生成监视点事件。当生成监视点事件(Watchpoint event)时,PE将进入调试状态。同Breakpoint event一样,也需要满足一下两个条件:
- EDSCR.HDE ==1, 表示 Halting debug is enabled 。
- 鉴权信号表明当前的调试器已经为当前安全状态下的处理器完成了自我鉴权。
Watchpoint 永远不会在指令获取(instruction fetch)时生成监视点调试事件。使能Hardware watchpoint的流程:
- 首先外部调试器需要将数据的地址写入到到监视点值寄存器DBGWVR(Watchpoint Value Register)中。如下图所示,外部调试器要监控内存地址0x20004上的数据是否被访问,则将该地址写入DBGWVR1_EL1中
- 然后外部调试器通过设置监视点控制寄存器(Watchpoint Control Register)DBGWCR.E为1,来启用监视点事件,即将对应的DBGWCR1_EL1的E控制位写成1。
观察点可以是:
- 对内存地址的只读访问。
- 写访问。
- 读写一起。
Armv8架构提供了2-16硬件监视点实现。可以通过查看ID_AA64DFR0_EL1的WRPs字段,看具体实现了多少个监视点单元。比如在Cortex-A55架构下,最多支持4个硬件断点。
可以通过编程单个调试监视点控制寄存器DBGWCR_EL1.E的E位来启用或禁用单个监视点单元。每个监视点单元都有一个相应的控制寄存器。根据实现的观察点寄存器编号可以找到对应关系:
- DBGWCR0_EL1 and DBGWVR0_EL1 are for watchpoint number zero.
- DBGWCR1_EL1 and DBGWVR1_EL1 are for watchpoint number one.
- DBGWCR2_EL2 and DBGWVR2_EL1 are for watchpoint number two.
- DBGWCR_EL1 and DBGWVR_EL1 are for watchpoint number n.
四,Embedded Cross Trigger简介
嵌入式交叉触发器(Embedded Cross Trigger,ECT)是Armv8- A系统中的外部调试器用于生成调试事件的一种机制。ECT还支持将调试事件从一个代理路由到系统中的另一个代理。外部调试器可以使用ECT来生成debug request或restart request。这些事件会触发处理器之间的路由事件。例如,这些事件可以将一个debug request 事件从一个处理器路由到其他处理器。这样的话,当系统中的一个处理器停止时,系统中的其他所有处理器都可以停止。
Armv8-A 架构下的ECT包括如下三部分:
- Cross Trigger Interface (CTI)
- Input and output triggers,输入和输出触发器。
- Cross Trigger Matrix (CTM)
CTI提供了跨Armv8-A系统的各个组件路由触发事件的能力。例如,将触发事件路由到系统中的中断控制器(GIC),trace units或者其他处理器。如果系统中存在多个CTI模块,则可以使用ETM在CTI模块之间传递事件。CTM使用如下的IO 通道在CTM模块之间传递事件:
- Input triggers – Trigger event inputs from the PE to the CTI. 即从处理器发出,到CTI的触发事件。
- Output triggers – Trigger event outputs from the CTI to the PE.即从CTI发出,到处理器的触发事件。
- Input channels – Channel event inputs from the CTM to the CTI. These are directly accessible
by the registers CTIAPPPULSE, CTIAPPSET, and CTIAPPCLEAR, which are present in the ECT
programmers model. - Output channels – Channel event outputs from the CTI to the CTM.
在Armv8-A架构中,有一些CTI的output 触发事件已经被固定用途,比如:
- Output trigger event 0 – Debug request trigger event, 调试请求触发事件,用于让处理器强制进入debug state。该事件对CTI来讲是output trigger,对处理器来讲是input trigger。换句话说,该事件由CTI发出,由处理器接收。CTI 会发起debug request让处理器强制进入debug state, 直到外部调试器确认了该事件。如果处理器此时已经处于debug state,那么处理器会忽略该事件,但是CTI还会继续发起该请求,直到外部调试器将该信号清除(ACK)。
- Output trigger event 1 – Restart request trigger event. 也是一个从CTI发出,由处理器接受的事件。该事件可以强制让处理器离开debug state,继续运行程序。
- Input trigger event 0 – Cross-halt Request trigger event. 这是一个来自处理器的,发送给CTI的trigger事件,该事件可以被路由到系统中的其他agents。比如由一个处理器发出一个debug request到CTI,经过CTI路由到其他处理器,从而让其他处理器停止工作,进入debug state。