ARM64 Debug

前言

调试是软件开发的重要组成部分,通常被认为是开发过程中耗时最长、因此成本最高的环节。它使得软件开发人员能够创建满足高性能、低功耗和可靠性这三个关键标准的应用程序、中间件和平台软件。然而,错误可能难以检测、重现和修复。预测解决缺陷所需的时间长度也可能具有一定的困难。当产品交付给客户时,解决问题的成本将显著增加。在许多情况下,如果产品的销售时间窗口很小,如果产品交付延迟,就会错过市场机会。因此,系统提供的调试功能对于任何开发人员来说都是至关重要的考虑因素。

在使用ARM处理器的嵌入式系统中,通常存在输入/输出设施有限的情况。这意味着传统的桌面调试方法(如使用printf())可能不适用。在过去的这种系统中,开发人员可能会使用昂贵的硬件工具,如逻辑分析仪或示波器来观察程序的行为。本书中描述的处理器是复杂的片上系统(System-on-Chip,SoC)的一部分,包含内存、缓存和许多其他模块。可能没有片外可见的处理器信号,因此无法通过连接逻辑分析仪(或类似设备)来监视行为。因此,ARM系统通常包含专用硬件,用于提供广泛的控制和观察功能,以进行调试。

外部调试功能首次在ARMv4架构处理器上引入,以支持使用嵌入式和深度嵌入式处理器的开发人员,并逐步发展为一个广泛的调试和追踪功能组合。对于丰富的应用软件平台的支持,特别是自托管调试和性能分析的支持,是ARMv6和ARMv7-A架构中较新的添加功能。

ARMv8处理器提供硬件功能,使得调试工具能够对核心活动提供重要的控制,并且能够非侵入性地收集大量关于程序执行的数据。这里有两个广泛的硬件功能类别,即侵入性和非侵入性。

(1)Invasive debug
影响系统状态的调试方法称为侵入式调试。侵入式方法涉及停止执行、修改寄存器或使用核心读取和写入内存。自托管调试和外部调试都是侵入式调试的例子。

自托管调试是一种侵入式调试方法,它使用处理器内部的调试逻辑来监视和控制程序的执行。通过自托管调试,可以暂停程序的执行,检查和修改寄存器的值,以及读取和写入内存。

外部调试也是一种侵入式调试方法,它涉及使用外部调试工具和接口来连接到处理器,以进行更高级别的调试和分析。通过外部调试,可以停止程序的执行,修改寄存器和内存,并观察程序的状态和执行过程。

侵入性调试功能:这些功能允许调试工具直接干预和控制处理器的执行。其中一些功能包括:

断点:调试工具可以设置断点,使得程序执行到指定的位置时暂停。
单步执行:调试工具可以逐条执行处理器指令,以便开发人员可以逐步跟踪代码的执行。
寄存器访问:调试工具可以读取和修改处理器的寄存器状态,以检查和修改程序执行的上下文信息。
内存访问:调试工具可以读取和修改处理器访问的内存数据,以帮助分析和调试程序的行为。

(2)Non-invasive debug
不影响系统状态的调试方法称为非侵入式调试。非侵入式调试特性允许观察数据或程序流程,但不允许直接修改数据或程序流程。

非侵入式调试方法旨在提供对程序执行过程的观察和分析,而不会对系统状态产生任何影响。这种调试方法通常用于收集信息、诊断问题或进行性能分析,而不会对正在运行的程序进行修改。

非侵入性调试功能:这些功能允许调试工具在不中断程序执行的情况下收集有关程序执行的数据。其中一些功能包括:

• 性能监控单元(Performance Monitoring Unit,PMU),不使用中断
• 追踪(Trace)
• 基于程序计数器(PC)采样的性能分析扩展(PC Sample-based Profiling Extension)

性能监控单元(PMU)是一种硬件组件,用于收集和记录处理器运行时的性能数据,例如指令执行次数、缓存命中率等,而无需中断程序的执行。通过分析这些性能数据,开发人员可以获得关于程序性能瓶颈和优化机会的有用信息。

追踪(Trace)是一种记录程序执行过程中关键事件和指令流的方法。追踪技术可以提供详细的程序执行信息,包括指令的执行顺序、分支情况、函数调用等,以帮助开发人员理解程序的行为和调试问题。

基于程序计数器(PC)采样的性能分析扩展是一种基于采样的性能分析方法。它通过定期采样程序执行时的程序计数器值,以获取程序在不同代码路径上的执行频率信息。通过分析采样数据,可以识别程序中的热点代码和性能瓶颈,以便进行优化。

对于线上的生产环境,侵入性调试工具会对生产环境产生干扰。非侵入性调试工具对于生产环境上调试具有很大的观测意义,不影响生产环境。

一、ARM debug hardware

侵入性调试提供了一些设施,使您能够停止程序并逐行进行调试,无论是在C源代码级别还是逐步执行汇编语言指令。这可以通过连接到芯片JTAG引脚的外部设备或通过调试监视器代码实现。

1.1 简介

调试器提供了控制程序执行的能力,使您能够运行代码到某个特定点,暂停核心,逐步执行代码,并恢复执行。您可以在特定指令上设置断点,当核心执行到该指令时,调试器将接管控制权。这些功能通过两种不同的方法实现。软件断点通过将指令替换为HLT或BRK指令的操作码来工作。

HLT指令会在外部调试器连接且相关安全权限允许进入调试状态时,导致核心进入调试状态。调试状态是一种特殊模式,允许调试器对核心的执行进行更大程度的控制,并访问其内部寄存器和内存。

另一方面,AArch64(64位Arm架构)中的BRK指令会生成一个同步的调试异常,但不会直接导致核心进入调试状态。相反,它触发一个调试异常,可以由异常处理机制来处理。这个异常可以被外部调试器拦截,然后控制执行并执行调试操作。

显然,这些断点方法只能用于存储在RAM中的代码,但它们的优点是可以大量使用。调试软件必须跟踪已放置软件断点的位置以及原始地址中的操作码,这样在您想要执行断点指令时,它可以恢复正确的代码。
硬件断点使用内核内置的比较器,在执行到指定地址时停止执行。它们可以在内存中的任何位置使用,因为它们不需要对代码进行更改,但硬件提供的硬件断点单元数量有限。
软件断点的缺点是需要在执行断点指令之前替换目标指令,而且需要额外的内存空间来存储原始的指令操作码。因此,软件断点的数量可以相对较大,但在替换指令和管理内存方面需要额外的开销。
硬件断点的优点是可以在内存中的任何位置使用,而不需要修改代码。然而,硬件断点的数量是受限的,因为处理器内核只提供有限数量的硬件断点单元。这意味着在调试过程中,开发人员可能需要仔细选择和管理硬件断点的使用。
因此,软件断点适用于需要大量断点的情况,但需要额外的管理和内存开销。硬件断点适用于需要在内存中任意位置设置断点的情况,但数量受限。选择断点方法应根据具体的调试需求和资源限制进行权衡。

调试工具可以支持更复杂的断点,例如,在一系列地址中停止在任何指令上,或者仅在特定事件序列发生或硬件处于特定状态时停止。数据观察点(Data watchpoints)在特定数据地址或地址范围被读取或写入时给调试器提供控制。这些也可以被称为数据断点。
例如,Cortex-A57处理器在硬件资源中提供了六个硬件断点和四个观察点(watchpoints)。可以通过查看调试ID寄存器(DBGDIDR)来获取特定实现的这些值。
调试工具能够提供更高级的断点功能,允许开发人员根据特定的条件或事件设置断点。这些功能可以通过调试器的命令和设置进行配置,以满足特定的调试需求。

单步执行(Single step)是调试器逐条指令地执行代码的能力。Step-In和Step-Over的区别可以通过参考函数调用来解释。如果选择Step-Over(跳过步入),整个函数调用将作为一个步骤执行,使您能够在不逐步执行函数内部的情况下继续执行。而选择Step-In(步入)意味着您将逐步执行函数内部的指令。
具体来说,当调试器执行到函数调用时,选择Step-Over时,调试器会执行整个函数调用作为一个步骤,不会逐步执行函数内部的指令,直接跳到函数调用后的下一条指令。这对于不希望逐步执行函数内部的开发人员来说很有用。
相反,如果选择Step-In,调试器将进入函数内部,逐条执行函数内的指令。这样可以逐步跟踪函数内部的执行过程,方便调试和理解代码的细节。

在遇到断点或单步执行时,您可以检查和更改ARM寄存器和内存的内容。更改内存的特例是代码下载。调试工具通常允许您更改代码,重新编译,然后将新的映像下载到系统中。
当程序执行到断点处时,调试器会暂停程序执行,此时您可以查看和修改寄存器的值以及内存中的数据。寄存器存储了程序执行过程中的关键状态和数据,您可以检查这些寄存器的值以了解程序的状态或调试问题。您还可以在调试器中直接更改寄存器的值,以便在继续执行时修改程序的行为或观察不同的执行路径。
同样地,调试器还允许您查看和更改内存中的数据。您可以查看特定内存地址处的数据,并在需要时修改它们。这对于调试和修复内存相关的问题非常有用。
除了寄存器和内存的检查和更改,调试工具还提供了代码下载的功能。这意味着您可以更改代码,重新编译,并将新的二进制映像下载到目标系统中。这允许您在调试过程中进行代码的迭代和更新,以便修复问题、改进性能或添加新功能。

1.2 Halting or Self-hosted debug

侵入式调试(Invasive debug)可以分为 停机调试 – halting debug(也称为外部调试 – external debug)和监视器调试 – monitor debug(也称为自托管调试 – self-hosted debug)两种类型。无论哪种情况,核心的调试逻辑会在某种情况下生成调试事件,例如命中断点。对于这个调试事件的处理是监视器调试和停机调试之间的区别所在。

在停机调试中,调试事件导致核心进入调试状态。在调试状态下,核心停止执行指令,不再获取指令。相反,核心根据通过JTAG或其他外部接口连接的不同主机上运行的调试器的指导执行指令。

在监视器调试中,调试事件引发调试异常。该异常必须由在同一核心上运行的专用调试监视器软件处理。监视器调试需要软件支持。

external debug(halting debug):调试状态(Debug state)是外部调试模式的基础。
self-hosted debug(monitor debug):调试异常(Debug exceptions)是自托管调试模式的基础。

1.3 Debug events

处理器的调试逻辑负责生成调试事件。调试事件是被调试的进程的某个部分,它导致系统通知调试器。调试事件可以是同步的或异步的。调试事件包括诸如断点单元将指令的地址与其寄存器中存储的地址进行匹配等事件。断点、BRK指令、HLT指令和观察点都属于同步的调试事件。处理器将调试事件转换为以下几种操作之一:
(1)调试异常(Debug exception)。
调试异常是自托管调试模型的基础。
(2)进入特殊的调试状态(Debug state)。
调试状态是外部调试模型的基础。
(3)忽略调试事件。
(4)挂起调试事件,并稍后将其转换为一个操作。
(5)根据外部调试状态和控制寄存器(EDSCR)的设置,进入两种调试模式之一:
— 监视器调试模式(Monitor debug mode)。
— 停机调试模式(Halting Debug mode)。

将调试事件转换为异常或进入调试状态取决于调试逻辑的配置和调试事件的类型。例如,某些调试事件永远不会导致进入调试状态,而另一些调试事件永远不会引发调试异常。调试事件永远不会同时转换为调试异常和进入调试状态。

有时,尽管调试逻辑进行了配置,处理器仍无法将调试事件转换为这些操作之一。这是因为这样做会违反处理器的安全模型。如果处理器在安全状态下执行,并且连接到处理器的外部调试器不被信任,处理器将不允许进入调试状态。

Software debug events

Software debug events are:

• Breakpoint debug events.
• Watchpoint debug events.
• Software Step debug events.
• Software Breakpoint Instruction debug events.
• Vector Catch debug events.

除了在下面的“使用外部调试的断点和观察点”部分中描述的情况之外,断点和观察点调试事件还有以下行为:
• 如果对当前安全状态和异常级别启用了调试异常,它们会生成调试异常并发送到调试异常目标异常级别。
• 只有在启用调试异常时,才会生成软件步进调试事件。
• 软件断点指令调试事件总是会生成调试异常。

(1)Breakpoint debug event
地址断点通过将系统寄存器中的值与指令地址进行比较来生成调试事件。
某些断点是上下文感知的,可以编程为上下文断点,与上下文标识(Context ID)或(在非安全状态下)虚拟机标识符(VMID)的值进行比较。
断点可以被编程为仅在特定模式、异常级别和安全状态下匹配。地址断点可以与上下文断点关联。
处理器中的断点数量是由具体实现定义的。

(2)Watchpoint debug event
地址观察点通过将系统寄存器中的值与由加载和存储指令生成的数据地址进行比较来生成调试事件。
观察点可以被编程为仅在特定模式、异常级别和安全状态下匹配。地址观察点可以与上下文断点关联。
观察点还可以被编程为根据访问类型进行匹配;即仅匹配加载操作、仅匹配存储操作,或者同时匹配加载和存储操作。观察点不会匹配指令获取操作。
处理器中的观察点数量由具体实现定义。

(3)Software Step debug event
软件步进调试事件用于单步执行指令,即执行一条指令,然后将控制权返回给调试器。要进行单步执行指令的操作,可以按照以下步骤进行:

调试器软件启用软件步进功能。
调试器软件将程序计数器(PC)设置为要执行的指令地址。
处理器执行该单条指令。
在下一条指令上触发软件步进异常。

然而,在执行指令的过程中,可能会生成其他同步异常。

(4)Software breakpoint instruction debug event
A64指令集定义了软件断点指令。

BRK #<immediate>

A32和T32指令集也定义了软件断点指令。

BKPT #<immediate>

软件断点指令会生成同步调试异常,无法被屏蔽。

(5)Vector Catch debug event
Vector Catch调试事件只在AArch32阶段1的转换机制中生成,并且只会生成调试异常。Vector Catch异常仅从AArch32状态生成。

1.4 Debug basics

1.4.1 Breakpoints

Armv8-A处理器提供了在代码或感兴趣的指令上设置断点的能力。设置断点是一种常见且有用的调试工具,用于帮助确定执行过程中出现意外或不正确行为的原因。

大多数能够调试Arm架构处理器的调试器都提供两种类型的断点:软件断点和硬件断点。以下表格比较了不同的断点类型:
在这里插入图片描述
(1)软件断点通过修改断点位置处的指令来实现软件断点。修改断点位置处的指令,可能改变代码行为。由于增加了软件断点指令,会引入一定的开销。可以设置多个软件断点。
(2)硬件断点通过利用处理器提供的专用断点寄存器或资源来实现硬件断点。不修改原始代码,确保代码行为保持不变。对性能影响很小,因为硬件断点是在专用寄存器或资源中实现的。受处理器提供的硬件断点寄存器数量的限制。

1.4.2 Watchpoints

Armv8-A处理器提供了在感兴趣的内存值上设置监视点(watchpoints)的能力。监视点的行为和实现与硬件断点非常相似。调试器将使用核心或处理器中的一些比较器来设置监视点。

当监视点被触发时,调试器会将核心置于调试状态。通常情况下,当核心恢复正常执行时,监视点将保持有效,直到禁用或删除监视点为止。由于依赖于调试逻辑,监视点可以设置在任何类型的内存中的任何内存地址上。

与硬件断点类似,Armv8-A架构并没有规定一个Armv8-A处理器实现中存在多少监视点比较器。这意味着每个Armv8-A处理器实现可能具有不同数量的监视点比较器可用。核心的参考手册应该包含有关可用的监视点比较器数量的信息。

1.4.3 Vector catch

如果您有使用早于Armv8-A的Arm处理器的经验,您可能会了解到一种称为Vector Catch的机制。Vector Catch允许调试器根据地址或异常匹配来捕获异常。通常,调试器会为每个允许的异常提供启用和禁用Vector Catch的方式。如果对于特定异常启用了Vector Catch,调试器通常会在该异常发生时暂停执行。

然而,Armv8-A架构在处理器元素(PE)使用AArch64翻译机制时不允许使用Vector Catch异常。这意味着在AArch64执行模式下,也就是Armv8-A处理器使用的执行模式下,不支持Vector Catch。

此外,Vector Catch在Armv8-A架构中已经被弃用,这意味着不再建议使用,并且在未来的Arm架构版本中可能不会出现。

二、External debug

在一个复杂的系统中,需要在使用任何标准接口进行调试之前,确保系统的大部分硬件和软件功能正常运行非常重要。在调试系统时,不能依赖于正在调试的系统本身。为此,您需要可靠的外部调试功能,即基于硬件辅助的运行控制调试和跟踪功能。所有这些功能可以在不需要在平台上运行软件的情况下进行控制,但通常这些功能在产品设计早期阶段就非常需要。

自托管工具通常需要多层软件支持,这使得对软件的某些部分进行调试变得困难,或者对于诊断某些类型的错误来说,调试可能过于侵入性。低成本的外部调试接口,例如串行线调试(Serial Wire Debug,SWD),还有助于扩展外部调试适用的应用范围。请参考8.1CoreSight。

外部调试模型是在将被调试的 Processing Element (PE)托管在外部调试器时使用的。外部调试器可以通过以下操作来控制PE:

在发生调试事件时停止程序执行
检查架构寄存器的状态
修改架构寄存器的状态
在PE上运行指令以访问内存

通过这些操作,外部调试器可以暂停程序的执行,检查和修改寄存器的值,并执行指令以访问和修改内存。这使得外部调试器能够对被调试的PE进行全面的控制和调试,以便进行故障排除、性能优化和软件开发等操作。

外部调试器通过配置调试事件来控制处理器核心进入调试状态。调试事件可以通过编程调试逻辑或通过向核心发送一个调试停机请求信号来配置。下面的图表说明了外部调试器的设置:
在这里插入图片描述

2.1 External debug interface

在调试状态下,处理器单元(PE)执行指令传输寄存器(ITR – Instruction Transfer Register)中的指令。外部调试器使用外部调试接口将指令插入到ITR中。调试通信通道(DCC – Debug
Communication Channel)允许PE与外部调试器之间进行通信。下面的图表说明了使用外部调试器接口的Arm核心的调试设置:
在这里插入图片描述

2.2 Debug state

调试状态(Debug state)是外部调试模式的基础。外部调试器通过编程调试逻辑在发生调试事件时导致PE进入调试状态。外部调试也称为停机调试模式(halting debug mode)。当PE进入调试状态时,以下顺序发生:
(1)PE停止执行由程序计数器指示的指令。相反,调试器通过外部调试接口控制PE。
(2)调试器使用指令传输寄存器(ITR)通过外部调试接口向PE传递指令,以在调试状态下执行。
通过ITR执行的指令,调试器可以读取/写入架构寄存器,例如通用寄存器、系统寄存器和浮点寄存器。
通过ITR执行的指令,调试器可以读取/写入内存位置。
调试通信通道(DCC)的数据传输寄存器可以通过外部调试接口和系统寄存器接口访问。DCC在PE和外部调试器之间交换数据起着重要作用。
(3)在调试状态下,PE无法响应中断。
在调试状态下,PE停止正常执行程序,并通过调试器控制进行调试操作。调试器可以通过ITR向PE发送指令,读取和修改寄存器的值,以及访问内存。调试通信通道(DCC)用于在PE和外部调试器之间进行数据交换。此模式允许调试器完全控制PE的执行,以进行调试、故障排除和性能优化等操作。

Debug state entry and exit:

(1)当PE进入调试状态时:
PE在进入调试状态之前的PSTATE保存在调试保存的程序状态寄存器(DSPSR – Debug Saved Program Status Register)中。
首选重启地址存储在调试链接寄存器(DLR – Debug Link Register)中。
PE停止执行由程序计数器(Program Counter)指示的代码。
中断不会被处理。
外部调试器接管PE的控制。

(2)当PE处于调试状态时,外部调试器可以:
查看和修改内存位置和架构寄存器的内容,包括DLR和DSPSR。
使用ITR向PE传递指令,在调试状态下执行。
使用调试通信通道(DCC)向PE传递数据,并从PE接收数据。
通过使用DCPS和DRPS指令改变异常级别。
通过触发重启请求退出调试状态。

(3)当外部调试器发出重启请求时,PE将退出调试状态。当PE退出调试状态时,PE会:
将程序计数器设置为DLR中的地址。
从DSPSR中恢复PSTATE。
开始执行由程序计数器指示的指令。

2.3 Debug Access Port

在外部调试模型中,使用外部调试接口访问调试寄存器。访问外部调试接口的方式由具体实现定义。然而,大多数Armv8-A系统都包括一个调试访问端口(Debug Access Port,DAP),用于通过片外外部调试器访问外部调试接口。而片上外部调试器,例如使用第二个PE来调试一个PE的情况,使用内存映射接口来访问外部调试接口。

下图说明了使用DAP访问外部调试接口:
在这里插入图片描述
外部调试寄存器,也称为停机模式调试寄存器,通常以ED为前缀,例如EDSCR。其中两个重要的外部调试寄存器是:
(1)EDSCR:外部调试状态和控制寄存器(External Debug Status and Control Register)
(2)EDECR:外部调试执行控制寄存器(External Debug Execution Control Register)

2.4 External debug events

这节我们描述了导致PE进入调试状态的调试事件。以下是每个事件的摘要:

• 外部调试请求调试事件(External debug request debug event):这个事件是处理器的输入触发事件。当这个事件被触发时,PE进入调试状态。

• HLT指令调试事件(Halt instruction debug event):每当PE执行HLT指令时,就会生成这个事件。

• 停机步进调试事件(Halting step debug event):这个事件在PE执行指令或异常后立即生成,此时PE不处于调试状态。

• 异常捕获调试事件(Exception catch debug event):这个事件在PE执行异常或异常返回后立即生成。

• 复位捕获调试事件(Reset catch debug event):这个事件在复位后,在PE开始执行复位后的指令之前立即生成。

• 软件访问调试事件(Software access debug event):当PE尝试访问调试系统寄存器(例如断点和观察点控制寄存器)时,会生成这个事件。

• 操作系统解锁捕获调试事件(OS unlock catch debug event):当操作系统的锁定状态从锁定变为解锁时,会生成这个事件。

• 断点事件(Breakpoint event):每当PE尝试从特定地址执行指令时,就会生成这个事件。

• 观察点事件(Watchpoint event):每当PE从特定地址访问数据时,就会生成这个事件。

断点和观察点是自托管调试器和外部调试器之间共享的资源。如果启用了停机模式调试,断点事件或观察点事件会导致PE进入调试状态。否则,如果启用了自托管调试,将生成调试异常。

其中断点(Breakpoint)和观察点(Watchpoint)的区别:
功能:断点用于在特定指令地址上暂停程序的执行,而观察点用于在数据访问或修改时暂停程序的执行。

触发条件:断点的触发条件是指令地址的匹配,当程序执行到设置的断点地址时触发断点事件。观察点的触发条件是数据访问或修改,当程序访问或修改设置的观察点地址时触发观察点事件。

总的来说,断点用于控制程序的执行流程,而监视点用于跟踪程序的数据访问和修改。

watchpoint是一种特殊的断点,当内存中一个指定的地址被读、写或者修改的时候就暂停的断点,通常称之为:条件断点或数据断点或观察点。

External debug request event

外部调试请求事件是处理器的一个输入触发事件。当这个事件被触发时,PE进入调试状态。调试器通过断言外部调试请求来强制PE进入调试状态,然后外部调试器控制PE。交叉触发接口寄存器用于生成外部调试请求事件。

如果在PE已经处于调试状态时断言了外部调试请求,请求将被忽略。要退出调试状态,外部调试器向处理器断言重启请求。重启请求是一个输入信号,当重启请求被触发时,处理器退出调试状态。

Halt instruction debug event

停机指令调试事件允许外部调试器在PE尝试执行HLT指令时接管PE的控制权。HLT指令是用于停机的软件断点指令。当HLT指令被提交执行时,如果EDSCR.HDE为1,则生成停机指令调试事件。在执行HLT指令时,PE进入调试状态并将控制权交给外部调试器。

调试器通常将程序指令替换为HLT指令以触发停机指令调试事件。在此事件之后,外部调试器控制PE。

要退出调试状态,外部调试器向处理器断言重启请求。

Halting step debug event

停机步进调试事件允许外部调试器控制正在调试的程序。这发生在PE执行每个单独的指令或异常之后,而且PE不处于调试状态。当启用停机步进调试事件时,每当PE中的指令或异常被返回时,PE都会进入调试状态。

编程停机步进调试事件的步骤:
(1)当PE处于调试状态时,外部调试器通过将EDECR.SS写入1来启用停机步进。
(2)外部调试器向PE发出信号,要求其退出调试状态。
(3)在退出调试状态后,PE执行由程序计数器(PC)指向的指令。在执行下一条指令之前,PE进入调试状态,将控制权交给外部调试器。

要退出调试状态,外部调试器向处理器断言重启请求。

Exception catch debug event

异常捕获调试事件允许外部调试器在PE执行异常或异常返回时接管控制权。对于每个异常级别,ED异常捕获控制寄存器(EDECCR)包括独立的控制位用于异常和异常返回。在EDECCR中设置位将在PE执行具有在EDECCR中指示的目标异常级别的异常或异常返回时,强制PE进入调试状态。

当异常捕获调试事件在异常入口时生成时,PE在异常入口的一部分进入调试状态,而在执行异常处理程序的第一条指令之前。当异常捕获调试事件在异常返回时生成时,PE在执行异常返回地址处的第一条指令之前进入调试状态。

要退出调试状态,外部调试器向处理器断言重启请求。

Reset catch debug event

重置捕获调试事件允许外部调试器在重置后立即接管PE的控制权。要启用重置捕获调试事件,在外部调试执行控制寄存器(EDECR)或交叉触发接口设备控制寄存器(CTIDEVCTL)中设置Reset Catch debug Event(RCE)位。

当外部调试器设置RCE位时,重置捕获调试事件在PE重置时生成。在重置后的第一条指令执行之前,PE进入调试状态并将控制权交给外部调试器。重置可以是PE的热重置或冷重置。

要退出调试状态,外部调试器向处理器断言重启请求。

Software access debug event

软件访问调试事件允许外部调试器在PE软件尝试访问以下寄存器时接管控制权:

调试断点值寄存器(DBGBVRn_EL1),包括来自AArch32状态的DBGBXVRn
调试断点控制寄存器(DBGBCRn_EL1)
调试观察点值寄存器(DBGWVRn_EL1)
调试观察点控制寄存器(DBGWCRn_EL1)

外部调试器可以通过在外部调试状态和控制寄存器(EDSCR)中设置TDA位来启用软件访问调试事件。

当外部调试器设置TDA位时,PE进入调试状态,并在PE软件尝试访问以下任何调试系统寄存器时,将控制权提供给外部调试器:

AArch64:DBGBCR<n>_EL1,DBGBVR<n>_EL1,DBGWCR<n>_EL1,DBGWVR<n>_EL1。
AArch32:DBGBCR<n>,DBGBVR<n>,DBGBXVR<n>,DBGWCR<n>,DBGWVR<n>

对上述寄存器的内存映射访问不会生成软件访问调试事件。

要退出调试状态,外部调试器向处理器断言重启请求。

OS unlock catch debug event

一些调试寄存器的内容在PE断电时会丢失。为了在重置后启用处理器的调试能力,必须在断电之前将调试寄存器的内容保存到非易失性存储器中,并在系统解除重置时进行恢复。Armv8-A架构通过操作系统的保存机制实现调试寄存器内容的保存,并通过操作系统的恢复机制实现调试寄存器内容的恢复。

PE软件执行保存和恢复操作。在保存调试寄存器内容时,PE软件锁定操作系统锁,禁止外部调试访问调试寄存器。在恢复调试寄存器内容后,PE软件解锁操作系统锁。

操作系统解锁捕获调试事件允许外部调试器在操作系统恢复序列之后立即接管PE的控制权。此事件在重置后立即在恢复调试寄存器内容后强制PE进入调试状态。

控制位EDECR.OSUCE启用操作系统解锁捕获调试事件。

Breakpoint event

断点事件在PE尝试执行特定地址的指令时生成。硬件断点寄存器可以编程为程序的地址。当PE尝试执行来自编程地址的指令时,将生成断点事件。

当生成断点事件时,如果满足以下条件,它会生成进入调试状态的事件:

EDSCR.HDE == 1。对于这些事件启用了暂停调试。
认证信号表明调试器已经根据PE的当前安全状态进行了足够的身份验证。

断点设置使用硬件寄存器,因此通常称为硬件断点。

编程断点事件的步骤:

外部调试器将指令的地址编程到断点值寄存器DBGBVR中。
外部调试器设置使能位DBGBCR.E以启用断点事件。
Watchpoint event

当PE从特定地址或地址范围访问数据存储器时,会生成观察点事件。硬件观察点寄存器可以编程为数据存储器位置的地址。当PE尝试从编程地址访问数据时,将生成观察点事件。

当生成观察点事件时,如果满足以下条件,PE将进入调试状态:

EDSCR.HDE == 1。对于这些事件启用了暂停调试。
认证信号表明调试器已经根据PE的当前安全状态进行了足够的身份验证。

观察点在指令获取时不会生成观察点调试事件。

以下是按顺序编程观察点事件的步骤:

外部调试器将数据的地址编程到观察点值寄存器DBGWVR中。
外部调试器通过设置使能位DBGWCR.E,在观察点控制寄存器DBGWCR中启用观察点。

2.5 Halting debug mode

在停止调试模式(halting debug mode)下,调试事件会导致核心进入调试状态。核心被暂停执行,并与系统的其余部分隔离开来。这意味着调试器显示的是核心所见的内存,并且内存管理和缓存操作的效果变得可见。在调试状态下,核心停止执行由程序计数器指示的指令,并通过外部调试接口进行控制。这使得外部代理(如调试器)能够查询核心上下文并控制所有后续指令的执行。核心和系统状态都可以被修改。由于核心停止执行,直到调试器重新启动执行之前,不会处理任何中断。

停止调试的基本原则与 ARMv7-A 相比没有改变,包括:
(1)当设置为停止调试时,调试事件会导致进入特殊的调试状态(Debug state)。
(2)在调试状态下,核心不会从内存中获取指令,而是从特殊的指令传输寄存器(Instruction Transfer Register)中获取。
(3)数据传输寄存器用于在主机和目标之间移动寄存器和内存内容。

外部调试器的一个重要特点是它可以与正在调试的进程或处理器同时(可能是独立地)运行,并且必须能够在设备复位时进行调试。因此,在 ARMv8-A 中,外部调试器也使用外部身份验证接口。

三、Self-hosted debug

ARM架构提供了广泛的功能,可以由外部调试器访问。其中许多功能也可以被运行在核心上的软件使用,这些软件是驻留在目标系统上的调试监视器。调试监视器系统可以廉价实现,因为它们可能不需要任何额外的硬件。然而,它们会占用系统中的内存空间,并且只能在目标系统实际运行时使用。在无法正确启动的系统上,它们几乎没有任何价值。

为了帮助开发人员创建应用程序,平台需要开发工具,这些工具通常至少部分运行在应用处理器本身上,而不需要昂贵的接口硬件来连接第二台主机计算机。ARMv8-A架构进一步改进了对这种自托管形式调试的架构支持。在现有的桌面平台上,自托管是软件开发的主要方法。

自托管调试功能是指在处理器内部使用调试逻辑进行调试的特性,其中包括调试寄存器、调试模式和调试异常等。这些功能使得开发人员能够在执行软件时监视和控制 CPU 的行为和系统环境。

3.1 Self-hosted debug models

自托管调试(Self-hosted debug)模式是在被调试的处理器上托管调试器时使用的模式。调试异常(Debug exceptions)是自托管调试模式的基础。调试器会编程调试逻辑以生成调试事件,然后这些调试事件会触发调试异常。

调试异常是同步异常,会被路由到托管调试器所在的异常级别(EL,Exception level)。异常级别通常被称为ELx。ELx调试目标异常级别是托管调试器所在的异常级别。调试器代码在ELx上执行,类似于异常处理器代码。

ELx的可能取值为EL1或EL2。当EL3使用AArch32时,也可以将调试异常路由到EL3。

自托管调试支持以下调试模式,取决于调试器所托管的位置和其能力:

• Application debugging
• Kernel debugging
• OS debugging
• Hypervisor debugging

3.1.1 Application debugging

应用程序调试(Application debugging)允许在EL0上执行的应用程序代码被在EL1上执行的调试器进行调试。调试异常可以从应用程序执行(EL0)中生成,并由操作系统(EL1)处理。应用程序调试允许操作系统通过将调试器代码作为异常处理器代码托管在EL1上来调试其应用程序。

在应用程序调试中,当应用程序执行时发生调试事件,调试逻辑会生成调试异常。这些调试异常被路由到操作系统所在的EL1级别。操作系统的调试器代码会充当异常处理器,用于处理这些调试异常。

通过应用程序调试,操作系统能够在EL1级别上托管调试器,并通过处理调试异常来调试其应用程序。这使得操作系统能够监视和控制应用程序的执行,以进行调试、问题排查和性能分析等操作。

下图显示了应用程序调试:
在这里插入图片描述

3.1.2 Kernel debugging

内核调试(Kernel debugging)允许在EL0和EL1上执行的代码通过在EL1上执行的调试器进行调试。调试异常可以从EL0和EL1中生成,并在EL1级别进行处理。内核调试允许操作系统通过将调试器代码作为异常处理器代码托管在EL1级别来调试其自身的软件和应用程序。

内核调试的目的是使操作系统能够调试其自身的内核代码以及在EL0和EL1级别上运行的应用程序。调试器在EL1级别执行,并监视和控制来自内核和应用程序执行的调试事件和异常。

下图显示了内核调试的示意图:
在这里插入图片描述

3.1.3 Operating system debugging

操作系统调试(OS debugging)允许在EL0和EL1上执行的代码通过在EL2上执行的调试器进行调试。调试异常可以从EL0和EL1中生成,并在EL2级别进行处理。操作系统调试允许虚拟化监控程序(hypervisor)调试客户操作系统。操作系统调试使得虚拟化监控程序能够通过将调试器代码作为异常处理器代码托管在EL2级别上,来调试在EL0和EL1级别上执行的代码。

下图显示了操作系统调试的示意图:
在这里插入图片描述

3.1.4 Hypervisor debugging

虚拟化监控程序(Hypervisor)调试允许在EL0、EL1和EL2上执行的代码通过在EL2上执行的调试器进行调试。调试异常可以从EL0、EL1和EL2中生成,并在EL2级别进行处理。虚拟化监控程序调试允许虚拟化监控程序调试其自身的软件。虚拟化监控程序调试允许在EL0、EL1和EL2上执行的代码进行调试,通过将调试器代码作为异常处理器代码托管在EL2级别来实现。

下图显示了虚拟化监控程序调试的示意图:
在这里插入图片描述

自托管调试(Self-hosted debug),也称为监视器模式调试(monitor mode debug),是一种调试模式,其中调试器在特权监视器模式下运行。在这种模式下,调试器可以直接访问特定的调试寄存器,用于控制和监视调试过程。

当处于AArch64状态时,使用系统寄存器接口访问调试寄存器;而当处于AArch32状态时,使用协处理器接口访问调试寄存器。

在自托管调试模式下,专用于此模式的调试寄存器以"MD"(Monitor Debug)为前缀。例如,MDSCR_EL1代表监视器调试系统控制寄存器。

还有一些寄存器是自托管调试模式和外部调试模式共享使用的。这些共享寄存器以"DBG"(Debug)为前缀。例如,DBGBVR0_EL1是其中一个共享寄存器,表示EL1中的调试断点值寄存器0。

以下是一些重要的监视器调试寄存器:
MDSCR_EL1(监视器调试系统控制寄存器):该寄存器控制监视器调试模式的行为和配置。
MDCR_EL2(监视器调试配置寄存器 EL2):该寄存器是针对EL2的,用于控制EL2中的监视器调试模式的配置。

3.5 Debug exceptions

在自托管调试模式中,调试逻辑被配置为在调试事件发生时引发调试异常。处理单元(Processing Element,PE)只能在调试事件上生成调试异常。调试器程序被安装为异常处理器代码,它是处理调试异常并对调试系统寄存器进行编程的更高级别的系统软件。调试异常是同步异常,被路由到托管调试器的异常等级(Exception Level,EL)。托管调试器的异常等级称为调试目标异常等级(debug target Exception level,ELx)。调试器代码在ELx上执行,类似于异常处理器代码。ELx的可能取值为EL1或EL2。当EL3使用AArch32时,可以将调试异常路由到EL3。

在将调试异常传递到ELx时,调试器代码可以从以下内容中解释异常的原因:

编码在ESR_ELx寄存器中的事件类型和综合码(syndrome)。
对于观察点(Watchpoint)异常,FAR_ELx指示被观察的地址。

自托管调试支持以下调试异常:

断点指令 -- 软件断点(Breakpoint instruction):每当PE执行BRK指令时生成此事件。
断点 -- 硬件断点(Breakpoint):每当PE尝试执行特定地址处的指令时生成此事件。
观察点(Watchpoint):每当PE访问特定地址处的数据时生成此事件。
软件步进(Software step):在PE执行指令后立即生成此事件。

断点和观察点是自托管调试和外部调试器共享的资源。如果启用了停机模式调试(halt mode debug),则观察点或断点事件会使PE进入调试状态。否则,如果启用了自托管调试,将生成调试异常。

3.5.1 Breakpoint Instruction exception

当执行断点指令BRK时,处理单元(PE)会发生断点指令异常(Breakpoint Instruction exception)。断点指令异常无法屏蔽(mask),这意味着当PE尝试执行BRK指令时,断点异常总是会被生成。

断点指令异常也被称为软件断点。调试器代码可以将程序中的指令替换为BRK指令,以在需要停止程序执行的地方设置断点。当发生异常时,调试器代码会在从调试异常返回之前将BRK指令恢复为原始指令。

下图说明了当PE在EL0执行BRK指令时发生的断点指令异常:
在这里插入图片描述

3.5.2 Breakpoint exception

当处理单元(PE)尝试执行特定地址处的指令时,会生成断点异常(Breakpoint exception)。可以使用硬件断点寄存器来编程应用代码的地址。当PE尝试执行来自已编程地址的指令时,会生成断点异常。由于断点设置使用了硬件寄存器,因此断点异常通常称为硬件断点

以下是编程断点异常的步骤:
调试器将指令的地址编程到断点值寄存器DBGBVR中。
调试器通过设置断点控制寄存器DBGBCR中的使能位DBGBCR.E来启用断点,以生成断点异常。

下图说明了当PE尝试执行已设置断点的指令时发生的断点异常:
在这里插入图片描述
根据AArch64体系结构,可以实现2到16个硬件断点。支持多少个硬件断点取决于具体的实现选择。根据硬件断点单元的可用性,可以在实现上同时设置相同数量的硬件断点。寄存器ID_AA64DFR0_EL1.BRPs指示实现中有多少个断点单元。

可以通过编程各个调试断点控制寄存器(DBGBCR_EL1.E)的E位来启用或禁用单个断点单元。

每个断点单元都有一个对应的控制寄存器。根据实现的断点数量,寄存器按照相应顺序编号,例如:
• DBGBCR0_EL1和DBGBVR0_EL1用于断点0。
• DBGBCR1_EL1和DBGBVR1_EL1用于断点1。
• DBGBCR2_EL1和DBGBVR2_EL1用于断点2。
• DBGBCR_EL1和DBGBVR_EL1用于断点n。

3.5.3 Watchpoint exception

当处理单元(PE)从特定地址或地址范围访问数据时,将生成一个监视点异常(Watchpoint exception)。硬件监视点寄存器可以被编程为存储应用程序数据的地址。当PE尝试从编程的地址访问数据时,会触发监视点异常。需要注意的是,在指令获取期间,监视点不会生成监视点调试事件。

以下是编程监视点异常所需的步骤:
(1)调试器将数据的地址编程到监视点数值寄存器(DBGWVR)中。
(2)调试器通过设置使能位(DBGWCR.E)来在监视点控制寄存器(DBGWCR)中启用监视点,以生成监视点异常。

下图说明了当PE尝试执行已设置监视点的指令时,监视点异常的情况:
在这里插入图片描述
DBGWCR具有控制功能,允许屏蔽DBGWVR的低位在地址比较中。这意味着可以监视一段地址范围。

监视点可以有以下特点:

• 可以设置为仅在读访问、仅在写访问或两种类型的访问发生时生成监视点调试事件。
• 可以与关联上下文断点链接,这样只有在地址匹配发生时PE处于特定上下文时才生成监视点调试事件。

AArch64体系结构提供了可实现2到16个硬件监视点的功能。特定实现支持的硬件监视点单元数量是实现的选择。根据硬件监视点单元的可用性,可以在实现上同时设置相同数量的监视点。寄存器ID_AA64DFR0_EL1.WRPs显示实现的监视点单元数量。

可以通过编程各个调试监视点控制寄存器(DBGWCR_EL1.E)的E位来启用或禁用单个监视点单元。每个监视点单元都有一个对应的控制寄存器。根据实现的监视点数量,寄存器按照相应顺序编号,例如:

• DBGWCR0_EL1和DBGWVR0_EL1用于监视点0。
• DBGWCR1_EL1和DBGWVR1_EL1用于监视点1。
• DBGWCR2_EL2和DBGWVR2_EL1用于监视点2。
• DBGWCR<n>_EL1和DBGWVR<n>_EL1用于监视点n。

3.5.4 Software Step exception

软件步进事件允许调试器在调试程序(也称为被调试对象)执行的每条指令之后获取控制权。当启用软件步进时,每当从被调试对象中执行一条指令时,都会生成一个软件步进异常。

以下是编程软件步进异常所需的步骤:
(1)调试器将MDSCR_EL1.SS写入1,以启用软件步进异常。
(2)调试器将SPSR_ELx.SS位设置为1。
(3)调试器将要执行的指令的地址编程到异常链接寄存器(ELR)中。
(4)调试器执行ERET指令。
(5)在ERET指令上,处理单元从ELR中编程的地址执行一条指令,并在下一条指令到达ELx时触发单步异常,将控制权返回给调试器。

调试器只能在使用AArch64的异常级别中执行软件步进。然而,被步进的指令可以在任何执行状态下执行,因此可以从任何执行状态触发软件步进异常。

只有在启用调试异常时才会生成软件步进异常。如果禁用调试异常,则软件步进处于非活动状态。

四、Debugging Linux applications

Linux是一个多任务操作系统,每个进程都有自己的进程地址空间,并带有私有的转换表映射。这可能使得某些问题的调试变得相当棘手。

广义上讲,在Linux系统中有两种不同的调试方法。

通常使用在目标上运行的GDB调试服务程序与主机计算机进行通信,通常通过以太网进行通信,来调试Linux应用程序。在调试会话进行时,内核继续正常运行。这种调试方法不能访问内置的硬件调试功能。目标系统始终处于运行状态。GDB调试服务程序接收来自主机调试器的连接请求,然后接收命令并将数据返回给主机。

主机调试器向GDB调试服务程序发送加载请求,GDB调试服务程序通过启动一个新进程来运行要调试的应用程序。在执行开始之前,它使用系统调用ptrace()来控制应用程序进程。这个进程的所有信号都会转发到GDB调试服务程序。发送给应用程序的信号实际上会发送给GDB调试服务程序,它可以处理该信号或将其转发给正在调试的应用程序。

为了设置断点,GDB调试服务程序在代码中所需的位置插入生成SIGTRAP信号的代码。当它被执行时,将调用GDB调试服务程序,然后可以执行经典的调试器任务,例如检查调用堆栈信息、变量或寄存器内容。

五、Debugging Linux kernel

对于内核调试,通常使用基于JTAG的调试器。当执行断点时,系统会停止。这是检查设备驱动程序加载、错误操作或内核启动失败等问题的最简单方法。另一种常见方法是通过printk()函数调用进行调试。strace工具显示有关用户系统调用的信息。

Kgdb是Linux内核的源代码级调试器,它与独立的GDB一起工作,可以检查堆栈跟踪并查看内核状态(如PC值、计时器内容和内存)。设备/dev/kmem允许在运行时访问内核内存。

当然,可以使用支持Linux的JTAG调试器来调试线程。通常只能暂停所有进程;无法暂停单个线程或进程并让其他线程继续运行。可以为所有线程设置断点,也可以仅在特定线程上设置断点。由于内存映射取决于哪个进程处于活动状态,通常只能在特定进程映射时设置软件断点。ARM DS-5调试器能够使用gdbserver调试Linux应用程序,并使用JTAG调试Linux内核和Linux内核模块。

六、The call stack

应用程序代码使用调用堆栈来传递参数、存储局部数据和存储返回地址。每个函数在堆栈上推送的数据都组织成一个堆栈帧。当调试器停止一个核心时,它可以分析堆栈上的数据,以提供一个调用堆栈,即导致当前情况的函数调用列表。这在调试过程中非常有用,因为它使您能够确定应用程序为什么会达到特定的状态。

为了重建调用堆栈,调试器必须能够确定堆栈上的哪些条目包含返回地址信息。如果代码是使用了调试器信息(DWARF调试表)构建的,这些信息可能包含在其中,或者通过跟踪应用程序在堆栈上推送的一系列帧指针来获取。为此,代码必须构建为使用帧指针。如果这两种类型的信息都不存在,就无法构建调用堆栈。

在多线程应用程序中,每个线程都有自己的堆栈。因此,调用堆栈信息只与正在检查的特定线程相关。

七、Semihosting debug

Semihosting是一种机制,使在ARM目标上运行的代码能够使用在运行调试器的主机计算机上提供的功能。其中的例子可能包括键盘输入、屏幕输出和磁盘I/O。例如,您可以使用这种机制来使C库函数(如printf()和scanf())能够使用主机的屏幕和键盘。开发硬件通常没有完整的输入和输出设备,但是使用Semihosting可以让主机计算机提供这些功能。

Semihosting通过一组定义的软件指令来实现,这些指令会生成一个异常。应用程序调用适当的Semihosting调用,然后调试代理处理异常。调试代理提供与主机的必要通信。

在ARMv8处理器上使用的Semihosting规范与实现ARMv7的处理器不同。DS-5调试器通过拦截AArch64中的HLT 0xF000来处理Semihosting。

当然,在开发环境之外,通常不会将运行在主机上的调试器连接到系统上。因此,开发人员需要重新定位使用Semihosting的任何C库函数,例如,使用fputc()。这将涉及替换使用SVC调用的库代码,以便能够输出一个字符。

一些可用于Armv8-A的调试器提供了一种称为半主机(semihosting)的机制。半主机使运行在目标设备上的代码能够与主机计算机上的输入/输出(I/O)设施进行通信和使用。一个很好的例子是,在目标设备上运行的程序使用printf()函数输出一条消息,而这条消息会出现在调试器控制台中,而不是目标输出设备或控制台上。

半主机的工作原理是,将程序与一组支持半主机的库进行构建,这些库中的I/O函数包含特殊的SVC或HLT指令。当调试器遇到这些特殊的SVC或HLT指令时,调试器将接管控制,并执行程序所需的I/O操作。

下图说明了如果目标程序使用printf()函数输出"Hello!"时,半主机的工作原理:
在这里插入图片描述
当您使用的目标设备没有所需的全部I/O功能,或者只具备部分所需的I/O功能时,半主机非常有用。

由于调试器执行I/O操作,使用半主机可能会导致与不使用半主机执行程序相比的一些差异。例如,半主机在执行半主机操作时会有效地暂停执行。这可能会对对时间要求严格的系统造成不必要的时序变化。

在大多数情况下,使用半主机不会对目标执行产生任何明显的影响。然而,如果在I/O操作周围遇到无法解释的行为,您应该考虑半主机可能产生的影响。

八、ARM trace hardware

非侵入式调试(Non-invasive debug)允许在核心执行时观察其行为。尽管有不同类型的非侵入式调试方法,本节重点介绍追踪(trace)和追踪硬件(trace hardware)。通过追踪,可以记录内存访问(包括地址和数据值),并生成程序的实时追踪,以观察外设访问、堆栈和堆内存访问以及变量的变化。对于许多实时系统而言,使用侵入式调试方法是不可行的。举个例子,考虑一个发动机管理系统,在该系统中,虽然可以在特定点停止核心,但发动机仍在运转,因此无法进行有用的调试。即使在对实时要求不那么严格的系统中,追踪也非常有用。

追踪通常由与核心连接的内部硬件模块提供。这被称为嵌入式追踪宏单元(Embedded Trace Macrocell,ETM),并且是大多数基于ARM处理器的系统的一部分。在某些情况下,每个核心都有一个ETM。片上系统设计人员可以在其芯片中省略此模块以降低成本。这些模块观察但不影响核心行为,并能够监视指令执行和数据访问。

捕获追踪信息面临两个主要问题。第一个问题是,在当前非常高的核心时钟速度下,即使是几秒钟的操作也意味着数万亿个周期的执行。显然,要理解这么庞大的信息量将非常困难。

第二个相关问题是当前的处理器核心每个周期可能执行一个或多个64位的缓存访问,而记录数据地址和数据值可能需要较大的带宽。这就带来了一个问题,通常芯片上只提供了少量引脚用于输出追踪信息,并且这些输出引脚的切换速率可能远远低于核心的时钟频率。如果每个周期核心生成100位的追踪信息,时钟频率为1GHz,但芯片只能以200MHz的速度输出4位的追踪信息,那就会出现问题。

为了解决后一个问题,追踪宏单元尝试对信息进行压缩,以减少所需的带宽。然而,处理这些问题的主要方法是控制追踪模块,以便只收集选定的追踪信息。例如,可以只追踪执行过程,而不记录数据值。

另外,通常将追踪信息存储在芯片上的内部存储缓冲区中,称为嵌入式追踪缓冲(Embedded Trace Buffer,ETB)。这样可以缓解以高速从芯片外部获取信息的问题,但也带来了额外的成本,包括芯片的硅面积(从而影响芯片价格)以及对可捕获的追踪信息量的固定限制。

ETB(嵌入式追踪缓冲区)以循环方式存储压缩的追踪信息,持续地捕获追踪数据直到停止。ETB的大小因芯片实现而异,但通常8KB或16KB的缓冲区足以容纳几千行程序追踪信息。

当程序发生故障时,如果启用了追踪缓冲区,您可以查看程序历史的一部分。有了这个程序历史记录,您可以更容易地回溯程序,在故障点之前查看发生了什么。这对于调查间歇性和实时故障特别有用,这些故障通过传统的需要停止和启动核心的调试方法很难确定。硬件追踪的使用可以显著减少查找这些故障所需的时间,因为追踪显示了确切的执行情况、时序和数据访问。

8.1 CoreSight

ARM CoreSight™ 技术扩展了 ETM(嵌入式跟踪模块)所提供的功能。它在特定系统中的存在和功能由系统设计人员定义。CoreSight 提供了一系列非常强大的调试功能。它可以进行多核系统(包括非对称和SMP)的调试,这些系统可以共享调试访问和追踪引脚,并完全控制在哪些时间追踪哪些核心。嵌入式交叉触发机制使工具能够以同步的方式控制多个核心,例如当一个核心遇到断点时,所有其他核心也将停止。

在这里插入图片描述

Profiling工具可以使用数据显示程序花费时间的位置和存在的性能瓶颈。代码覆盖率工具可以使用追踪数据提供调用图探索功能。操作系统感知的调试器可以利用追踪数据,并在某些情况下使用额外的代码插装来提供高级系统上下文信息。以下是一些可用的CoreSight组件的简要描述:
(1)Debug Access Port (DAP)
调试访问端口(Debug Access Port,DAP)是ARM CoreSight系统的可选部分。并非每个设备都包含DAP。它使得外部调试器可以直接访问系统的内存空间,而无需将核心置于调试状态。如果没有DAP,要读取或写入内存可能需要调试器停止核心,并让其执行Load或Store指令。DAP使外部调试工具能够访问系统中的所有JTAG扫描链,从而调试和追踪可用核心和其他组件的配置寄存器。

(2)Embedded Cross Trigger (ECT)
嵌入式交叉触发器(Embedded Cross Trigger,ECT)是CoreSight系统中的一个组件。它的作用是将系统中多个设备的调试能力连接在一起。例如,系统中可以有两个独立运行的核心。当在一个核心上设置断点时,如果能够指定当该核心在断点处停止时,另一个核心也必须停止(无论它当前正在执行的指令是什么),那将非常有用。ECT中的交叉触发矩阵和接口使得调试状态和控制信息能够在核心和追踪宏单元之间传播。

(3)CoreSight Serial Wire
CoreSight Serial Wire Debug(SWD)是通过使用等效于5针JTAG接口的调试访问端口(Debug Access Port,DAP)来提供2针连接的调试功能。

SWD是一种更简化的调试接口,通常用于资源受限的嵌入式系统。它使用两个引脚进行通信:SWDIO(Serial Wire Debug I/O)和SWCLK(Serial Wire Debug Clock)。SWDIO用于数据传输和调试信号,而SWCLK用于时钟同步。

通过SWD接口,可以实现与目标设备的调试通信,包括读取/写入寄存器、操纵断点和监控程序执行等功能。它提供了与JTAG接口类似的调试功能,但使用的引脚数量更少,适用于资源有限或空间受限的应用场景。

SWD是Serial Wire Debug的简称,翻译成中文是”串行线调试”。 SWD是ARM目前支持的两种调试端口之一,另一个调试端口叫做JTAG Debug Port,也就是我们常用的J-link上面的调试端口(JTAG模式下)。基于ARM CoreSight调试构架,SWD可以通过传输数据包来读写芯片的寄存器。SWD是用于访问ARM调试接口的双线协议。它是ARM调试接口规范(ARM Debug Interface Architecture Specification)的一部分,是JTAG的替代品。

(4)System Trace Macrocell (STM)
系统追踪宏单元(System Trace Macrocell,STM)提供了一种多个核心(和进程)执行类似printf()的调试操作的方式。系统中的任何主设备上运行的软件都可以访问STM通道,而无需意识到其他设备的使用情况,只需使用非常简单的代码片段即可。这使得内核和用户空间代码都能够进行带有时间戳的软件插装。时间戳信息提供了与前一事件的时间差,非常有用。

(5)Trace Memory Controller (TMC)
追踪存储器控制器(Trace Memory Controller,TMC)是CoreSight的一个组件,用于解决在单个设备上具有多个核心(或其他能够生成追踪信息的模块)时,增加芯片引脚会显著增加成本的问题。TMC是一个追踪漏斗,具有将多个追踪源组合成系统内存中的单个总线的能力。提供了控制功能,可以启用、优先级排序和选择多个输入源之间的追踪信息。追踪信息可以通过专用的追踪端口、通过JTAG或串行线接口导出到芯片外部,或者通过重新使用SoC的输入/输出端口。追踪信息可以存储在ETB(Embedded Trace Buffer)中或系统内存中。

参考资料

ARM Cortex-A Series Programmer’s Guide for ARMv8-A

Learn the architecture - Before debugging
Learn the architecture - Debugger usage
Learn the architecture - AArch64 external debug
Learn the architecture - AArch64 self-hosted debug

反汇编Linux/Windows OS运行的32位/64位程序/动态库文件,CPU类型:ARM PowerPC MIPS X86 操作菜单选择:文件解析 Alx+P ELF文件解析 Alt+E 另有CORE文件解调用栈、文本比较等功能。V2.0.3相对上一版本,完善ARM64、X8664、PPC64反汇编、ko反汇编,完善反汇编文本文件比较、IQ数据解析,修复小BUG;V2.0.2相对上一版本,完善ARM64、X8664反汇编、ARM64位core文件调用栈,完善文本文件比较、增加高亮、查找功能,修复小BUG;V2.0.0相对上一版本,完善ARM64反汇编、ARM64位core文件调用栈,完善C++符号名字解析,支持工具运行在英文版OS;V1.26.01相对上一版本,增加ARM64反汇编、64位core文件解析;V1.26.00相对上一版本,增强EXE反汇编,增加dbx老邮件解析、二维码功能;V1.25.07相对上一版本,增加二进制反汇编、IQ数据解析功能,完善MIPS反汇编,修复小BUG;V1.25.05相对上一版本,增加内存数据按数据类型解析功能;V1.25.04相对上一版本,完善X86反汇编,修复小BUG;V1.25.02相对上一版本,COREDUMP统计、匹配目标文件等相关功能优化,修复小BUG;V1.25.00相对上一版本,相关功能支持动态库文件,查询代码支持无符号目标文件+有符号目标文件,COREDUMP统计、与问题单关联、目标文件/CORE文件/问题单同步;V1.24.02相对上一版本,针对进程主动捕捉异常的信息定制处理进一步完善COREDUMP文件解析与应用,增强软件管理;V1.24.01相对上一版本,进一步完善COREDUMP文件解析与应用,提供部分ARM Thumb指令反汇编;V1.24.00相对上一版本,进一步完善COREDUMP文件解析与应用,提供堆栈调用关系分析;V1.23.03相对上一版本,提供32位X86反汇编;V1.23.02相对上一版本,提供源代码行查询指令地址,OBJ/COREDUMP文件解析,sprintf函数参数特定检查,完善文件拖放操作,修复小BUG;V1.23.01相对上一版本,提供ELF文件指令修改,修复ARM MLS指令反汇编错误等BUG;V1.23.00相对上一版本,提供程序地址对应源代码行查询,修复MIPS调试信息错误;V1.22相对上一版本,修复MIPS小端字节序反汇编错误,网上最新版本提示;V1.21相对上一版本,菜单调整,完善64位ELF文件解析,解析调试信息;另部分增强功能的菜单操作设有密码,如有兴趣欢迎咨询。欢迎大家反馈相关软件使用过程中的问题!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值