ARM Debug技术概述
Overview
Debug调试几乎是软件开发中最耗时的过程
,系统提供的调试工具对于任何开发人员来说都是非常重要的考虑因素。
许多使用ARM处理器的嵌入式系统的输入/输出设施有限。这个意味着传统的桌面调试方法(例如使用printf())可能不适合。在过去的系统中,开发人员可能使用昂贵的硬件工具,如逻辑分析仪或示波器来观察程序的行为。但现代处理器是一个复杂的SOC,通常包含内存、缓存、和许多其他逻辑单元。可能没有芯片外可见的处理器信号,因此无法通过连接逻辑分析器(或类似的)来监控行为。因此ARM系统通常包括专门的硬件,以提供广泛的控制和观察设施的调试。
ARM处理器提供的硬件特性使调试工具能够对core提供有效控制,调试ARM芯片,你应该理解这些概念:
- 理解
external, self-hosted, invasive, non-invasive debug
的区别 - 理解target连接到调试器时的状态
- 理解在建立调试连接之前,可能需要对一些target进行初始化的方法
Debug Features
- halt the core
- 恢复执行
- 访问修改memory
- 访问修改register
- instruction breakpoint
- 设置断点
- 单步执行
- data breakpoint
- 读指定数据地址时产生断点
- 写指定数据地址时产生断点
- 读 or 写指定数据地址时产生断点
Debug方式
External debug
外部调试是指调试器位于Target外部。例如,主机通过调试器连接独立开发板上的Armv8-A处理器,这就是外部调试。外部调试的另一个示例是一个调试器运行在一个处理器上,调试位于同一SoC上的另一个处理器。
外部调试通常在进行芯片验证和bringup时使用,或者在使用裸金属环境时使用。
通常,外部调试依赖于正在调试的Target上的物理连接器,例如JTAG或Serial Wire debug (SWD)。外部调试还需要在Target和运行调试器的主机之间连接一个调试探针probe。下图显示了调试探针在主机和调试目标之间的连接关系:
Self-hosted debug
当调试器和被调试的软件在同一个处理器上运行时,就会发生Self-hosted debug。gdbserver就是一个很好的例子,它用于Linux应用程序调试。Gdbserver是一个程序,它与Linux应用程序一起运行,并允许像GDB或Arm调试器这样的调试环境来调试应用程序。
自调试通常用于调试在操作系统下运行的应用程序,或者在物理调试连接器不可用时使用。
Invasive debug
侵入式调试会修改处理器的状态。
例如,当使用调试器设置断点时,处理器会在命中断点时停止。侵入式的调试方法还包括单步调试、 查看内存、设置断点和观察点以及查看寄存器
。
通常,使用侵入式调试技术引入的细微变化不会影响程序的执行。但是,一些bug,例如与时间相关的问题,可能对这些细微的更改非常敏感。这意味着侵入式调试技术不适合此类bug。
Non-invasive debug
相对于侵入式调试,非侵入式调试始终不会修改处理器状态。
例如,生成和收集trace数据通常不会影响处理器,所以trace被归类为非侵入式调试。其他非侵入式调试操作有PMU和采样PC指针。
软件类型
- Bare-metal and boot code
- OS kernel
- OS application and OS module or driver
Target类型
在调试时,重要的是要考虑目标的功能和相关性。有时候,在开发周期中,目标与最终的SoC设计只有粗略的相似性。这意味着,在速度、功能或性能等方面,早期目标和最终设计可能会有所不同。
- RTL simulation or emulation
- 处理器的精确模型
- debug非常慢
- FPGA
- 可以成为最终设计
- 调试速度比RTL simulation快,但是比最终目标慢
- 需要更多的步骤来建立调试连接
- Software model
- 时序不精确
- 软件模型的某些功能可能和硬件的不同
- 比RTL快,但是比FPGA慢
- 需要考虑模型是功能准确还是时序精确
- Development board
- 与最终目标相似或者相同
- 需要更多的步骤来建立调试连接
- 调试速度比模型快
- Final silicon
- 最终设计成品
- 可能没有用于调试的物理连接器
- 调试速度非常快
- 通常在项目后期可用
Target状态
- Is Core PowerDown
Debugger连不上下电状态的core。如果target是SMP系统,包含bootcode、firmware、os等软件阶段,当用不到某些core时,这些软件可能会让这些core处于下电状态,那么在这种情况下debugger是连不上core的debug逻辑部分的。 - Is Core Reset
Debugger连不上复位状态的core。在如果某些core还没有被master core解复位的话,debugger也是控制不了core的。 - Different security state
ARMv8 v9是支持安全和非安全状态的。如果安全态被硬件lock,那么debugger是访问不了的。 - Different exception level
如果EL3的exception level被lock住,debugger也是访问不了的
初始化Target
一些target需要特殊步骤才能建立debug连接。这时候就要问设计者或者用户手册来寻找建立debugger连接的前置条件。下面是一些可能的通用操作:
- 上电并且解复位core(Powering up or taking a core or processor out of reset)
- 一些设计者会增加开关来开启debug功能(Unlocking scan chains or devices)
- 一些设计者可能会通过跳帽来控制GPIO作为debug引脚(Multiplexed signals)
- 一些设计者仅提供了连线,没有预留debug口(Missing connectors)
Debug event
处理器的debug logic负责产生debug event。debug event是处理器的一部分,它通知debugger有事件产生。Breakpoints, the BRK and HLT instructions, and Watchpoints are all synchronous debug events。
Software debug events:
- Breakpoint debug events.
- Watchpoint debug events.
- Software Step debug events.
- Software Breakpoint Instruction debug events.
- Vector Catch debug events
断点类型
软件断点
你可以在指定代码位置设置断点,core运行到断点时,调试器会获得core的控制权。软件断点的工作原理是用HLT或BRK指令的操作码替换指令:
HLT指令
:如果连接了外部调试器,并且相关的安全权限允许进入调试状态,那么HLT
指令将导致core进入调试状态。
BRK指令
:在AArcg64状态下执行BRK
指令,会产生一个同步debug异常,但是不会导致core进入调试状态。
代码存储在RAM时可以使用软件断点,优点是断点数量基本不受限制。
调试软件必须跟踪它在哪里放置了软件断点,以及在这些地址上最初放置了哪些操作码,以便在执行断点指令时,它可以将正确的代码放回。
硬件断点
硬件断点使用内置在core中的比较器
,并在执行到达指定地址时停止执行。这些断点可以在内存中的任何地方使用,因为它们不需要替换代码,但是硬件提供了有限数量的硬件断点单元。
单步执行
step in与step over区别在于函数调用。
- step over
step over函数调用,整个函数会作为一步全部执行,而不会进入函数内部 - step in
step in函数调用,会进入函数内部,进行单步调试
调用栈call back
应用程序代码使用调用堆栈来传递参数、存储本地数据和存储返回地址。每个函数推送到堆栈上的数据被组织到一个堆栈框架中。当调试器停止一个core时,它能够分析堆栈上的数据,为你提供一个调用堆栈,也就是当前的函数调用列表。
在多线程应用程序中,每个线程都有自己的栈。
semihosting debug
半宿主是一种机制,使在嵌入式系统(也称为目标)上运行的代码能够与主机的I/O通信并使用主机的I/O,例如键盘输入和显示输出等。其实最简单的就是在host PC侧使用串口工具进入显示和命令输入。
Reference
Learn the architecture - Before debugging on Armv8-A: https://developer.arm.com/documentation/102408/0100/?lang=en
Learn the architecture - AArch64 self-hosted debug: https://developer.arm.com/documentation/102120/0100?lang=en