ARM V8A体系结构-第十八章 调试

概述

调试是软件开发的一个关键部分,通常被认为是整个过程中最耗时、最昂贵的部分。它使软件开发人员能够创建满足高性能、低功耗和可靠性三个关键标准的应用程序、中间件和平台软件。然而,bug可能很难检测、复制和修复。也很难预测解决缺陷所需的时间长度。当产品交付给客户时,解决问题的成本会显著增加。在许多情况下,当一个产品的销售时间窗口很小时,如果产品推迟,就会错过市场机会。因此,系统提供的调试工具对于任何开发人员来说都是一个重要的考虑因素。
许多使用ARM处理器的嵌入式系统的输入/输出设施有限。这意味着传统的桌面调试方法(例如使用printf())可能不合适。在过去的此类系统中,开发人员可能会使用昂贵的硬件工具,如逻辑分析仪或示波器来观察程序的行为。本书中描述的处理器是一个复杂的片上系统(SoC)的一部分,包含内存、缓存和许多其他模块。芯片外可能没有可见的处理器信号,因此无法通过连接逻辑分析仪(或类似设备)来监控行为。因此,ARM系统通常包括专用硬件,以提供用于调试的广泛控制和观察设施。
外部调试功能最初是在ARMv4体系结构处理器上引入的,以支持使用嵌入式和深度嵌入式处理器的开发人员,并已发展成为一系列广泛的调试和跟踪功能。ARMv6和ARMv7-a体系结构中最近增加了对富应用程序软件平台的支持,尤其是对自托管调试和性能评测的支持。
ARMv8处理器提供了硬件功能,使调试工具能够对核心活动提供重要级别的控制,并以非侵入性方式收集有关程序执行的大量数据。硬件功能分为两大类,入侵和非入侵。

1、ARM调试硬件

入侵式调试提供了一些工具,使您能够在C源代码级别或单步执行汇编语言指令来停止程序并逐行执行它们。这可以通过使用芯片JTAG引脚连接到核心的外部设备实现,也可以通过调试监控代码实现。

1.1 概述

调试器提供了控制程序执行的能力,使您能够将代码运行到某个点,停止核心,逐步执行代码,并恢复执行。您可以在特定指令上设置断点,使调试器在内核到达该指令时进行控制。这些工作使用两种不同的方法之一。软件断点通过将指令替换为HLT或BRK指令的操作码来工作。如果连接了外部调试器,并且相关的安全权限允许进入调试状态,HLT指令将导致内核进入调试状态。AArch64中的BRK指令生成同步调试异常,但不会导致内核进入调试状态。
显然,它们只能用于存储在RAM中的代码,但其优点是可以大量使用。调试软件必须跟踪其放置软件断点的位置以及这些地址上最初的操作码,以便在您想要执行断点指令时可以放回正确的代码。硬件断点使用内置在内核中的比较器,并在执行到达指定地址时停止执行。它们可以在内存中的任何位置使用,因为它们不需要更改代码,但硬件提供的硬件断点单元数量有限。
调试工具可以支持更复杂的断点,例如,在地址范围内的任何指令上停止,或者仅在特定的事件序列发生或硬件处于特定状态时停止。数据监视点在读取或写入特定的数据地址或地址范围时提供调试器控制。这些也可以称为数据断点。例如,Cortex-A57处理器在硬件资源中有六个硬件断点和四个监视点。
单步指的是调试器能够遍历一段代码,一次执行一条指令。可以参考函数调用来解释步进和步过之间的区别。如果跳过函数调用,整个函数将作为一个步骤执行,使您能够在不想单步执行的功能之后继续。单步执行意味着您只需单步执行函数。遇到断点或单步执行时,可以检查和更改ARM寄存器和内存的内容。更改内存的一种特殊情况是代码下载。调试工具通常允许您更改代码、重新编译,然后将新映像下载到系统中。

1.2 暂停或自托管调试

入侵性调试可分为暂停调试(也称为外部调试)和监视调试(也称为自托管调试)。在任何一种情况下,内核的调试逻辑都会生成一个调试事件,以响应某些情况,例如遇到断点。
该调试事件的处理是监视器调试与停止调试的区别。在停止调试时,调试事件会导致内核进入调试状态。在调试状态下,内核被暂停,这意味着它不再获取指令。相反,内核在通过JTAG或其他外部接口连接的不同主机上运行的调试器的指导下执行指令。
在监视器调试中,调试事件导致引发调试异常。该异常必须由在同一内核上运行的专用调试监视器软件处理。监视器调试以软件支持为前提。

1.3 调试事件

处理器的调试逻辑负责生成调试事件。调试事件是正在调试的进程的某个部分,它会导致系统通知调试器。调试事件包括诸如断点单元之类的事件,该断点单元将指令地址与其寄存器中存储的地址相匹配。它们可以是同步的,也可以是异步的。断点、BRK和HLT指令以及监视点都是同步调试事件。处理器将调试事件转换为多个操作之一,即:

  • 调试异常。调试异常是自托管调试模型的基础。
  • 输入特殊调试状态。调试状态是外部调试模型的基础。
  • 忽略调试事件
  • 挂起调试事件并稍后将其转换为激活
  • 根据外部调试状态和控制寄存器(EDSCR)的设置,输入两种调试模式之一:监视器调试模式、暂停调试模式

调试事件转换为异常或进入调试状态取决于调试逻辑的配置和调试事件的类型。例如,某些调试事件从未导致条目进入调试状态,而其他事件从未导致调试异常。调试事件永远不会同时转换为调试异常和调试状态条目。有时,处理器无法将调试事件转换为这些操作之一,尽管调试逻辑进行了配置。这是因为这样做会破坏处理器的安全模型。如果处理器在安全状态下执行,并且附加到它的外部调试器不受信任,则处理器不允许进入调试状态。

软件调试事件
包含:

  • 断点调试事件
  • 监视点调试事件
  • 软件单步调试事件
  • 软件断点指令调试事件
  • 向量捕获调试事件

除了下面外部调试使用断点和观察点中描述的情况外,断点和观察点调试事件:

  • 如果为当前安全状态和异常级别启用,则生成调试异常到调试异常目标异常级别
  • 仅当启用调试异常时,才会生成软件单步调试事件
  • 软件断点指令调试事件始终生成调试异常

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

监视点调试事件
地址监视点通过比较系统寄存器中保存的值与加载和存储指令生成的数据地址来生成调试事件。监视点可编程为仅在特定模式、异常级别和安全状态下匹配。地址监视点可以链接到上下文断点。监视点也可以编程为与访问类型相匹配;即,仅匹配加载、仅匹配存储或同时匹配加载和存储。监视点与指令获取不匹配。处理器中监视点的数量由实现定义。

软件单步调试事件
软件单步调试事件用于单步执行一条指令,即执行一条指令,然后将控制权返回给调试器。要单步执行指令:

  1. 调试器单步调试使能
  2. 调试器软件将PC设置为要步进的指令
  3. 处理器执行单个指令
  4. 下一条指令会出现软件步骤异常

然而,当指令被步进时,可能会生成另一个同步异常。

软件断点指令调试事件
A64指令集定义了软件断点指令:BRK #< immediate >
A32和T32指令集定义软件断点指令:BKPT #< immediate >
软件断点指令生成无法屏蔽的同步调试异常。

向量捕获调试事件
向量捕获调试事件仅在AArch32阶段1转换机制中生成,并且仅生成调试异常。向量捕获异常仅从AArch32状态生成。

1.4 外部调试

一个复杂的系统在使用任何标准接口进行调试之前,需要其大部分硬件和软件都能正常工作。在系统上进行调试而不依赖正在调试的系统是非常重要的。为此,您需要可靠的外部调试,即硬件辅助、运行控制调试和跟踪功能。所有这些都可以控制,无需在平台上运行软件,但通常在产品设计周期的早期就需要这样做。自托管工具通常需要多层软件支持,这使得调试软件的某些部分变得困难,或者使得调试对于诊断某些类型的bug来说过于侵入性。低成本的外部调试接口,如串行线调试(SWD),也有助于扩展具有吸引力的外部调试应用程序的范围。

1.5 暂停调试模式

在暂停调试模式下,调试事件会导致core进入调试状态。内核被停止并与系统的其余部分隔离。这意味着调试器显示内核看到的内存,并且内存管理和缓存操作的效果变得可见。在调试状态下,内核停止从程序计数器指示的位置执行指令,而是通过外部调试接口进行控制。这使外部代理(如调试器)能够询问核心上下文并控制所有后续指令的执行。核心和系统状态都可以修改。由于内核已停止,因此在调试器重新启动执行之前,不会处理任何中断。
停止调试的基本原则在ARMv7-A中保持不变,即:

  • 当编程为停止调试时,调试事件会导致进入特殊调试状态
  • 在调试状态下,内核不会从内存中获取指令,而是从特殊的指令传输寄存器中获取指令。
  • 数据传输寄存器用于在主机和目标之间移动寄存器和内存内容

外部调试器的一个重要特征是,它是并行运行的,并且(可能)独立于正在调试的进程或处理器,并且必须能够在设备重置之外进行调试。因此,外部身份验证接口也用于ARMv8-A中的外部调试器。

1.6 自托管调试

我们已经了解了ARM体系结构如何提供外部调试器可以访问的广泛功能。其中许多功能还可以由运行在core上的软件使用,core是驻留在目标系统上的调试监视器。监控系统可能很便宜,因为它们可能不需要任何额外的硬件。但是,它们会占用系统中的内存空间,并且只能在目标系统本身实际运行时使用。它们在至少不能正确引导的系统上没有什么价值。为了帮助开发人员创建应用程序,平台需要经常在应用程序处理器本身上运行(至少部分运行)的开发工具,而不需要昂贵的接口硬件来连接第二台主机。ARMv8-A体系结构完善了对这种自托管调试形式的体系结构支持。在现有的桌面平台上,自托管是软件开发的常用方法。

1.7 调试linux应用程序

Linux是一种多任务操作系统,其中每个进程都有自己的进程地址空间,并具有专用转换表映射。这会使某些问题的调试变得相当棘手。一般来说,Linux系统中使用了两种不同的调试方法。Linux应用程序通常使用运行在目标上的GDB调试服务器进行调试,该服务器通常通过以太网与主机通信。在调试会话发生时,内核继续正常运行。此调试方法不提供对内置硬件调试工具的访问。
目标系统永久处于运行状态。服务器从主机调试器接收连接请求,然后接收命令并将数据提供回主机。主机调试器向GDB服务器发送加载请求,GDB服务器通过启动新进程来运行正在调试的应用程序来响应。在执行开始之前,它使用系统调用ptrace()来控制应用程序进程。
来自该进程的所有信号都被转发到GDB服务器。而发送到应用程序的信号会转到GDB服务器,GDB服务器可以处理该信号或将其转发给正在调试的应用程序。为了设置断点,GDB服务器在代码中所需的位置插入生成SIGTRAP信号的代码。执行此操作时,将调用GDB服务器,然后可以执行经典的调试器任务,例如检查调用堆栈信息、变量或寄存器内容。

1.8 调试linux内核

对于内核调试,使用基于JTAG的调试器。执行断点时,系统将停止。这是检查设备驱动程序加载、错误操作或内核引导失败等问题的最简单方法。另一种常用方法是通过printk()函数调用。strace工具显示有关用户系统调用的信息。Kgdb是Linux内核的源代码级调试器,它在单独的机器上与GDB一起工作,支持检查堆栈跟踪和查看内核状态(如PC值、计时器内容和内存)。device/dev/kmem支持对内核内存的运行时访问。当然,可以使用支持Linux的JTAG调试器来调试线程。通常只能停止所有进程;不能停止单个线程或进程而让其他线程或进程继续运行。可以为所有线程设置断点,也可以仅在特定线程上设置断点。由于内存映射取决于哪个进程处于活动状态,因此通常只能在映射特定进程时设置软件断点。ARM DS-5调试器能够使用gdbserver调试Linux应用程序,并使用JTAG调试Linux内核和Linux内核模块。DS-5调试器的调试和跟踪功能将在下一节中介绍。

1.9 调用栈

应用程序代码使用调用堆栈传递参数、存储本地数据和存储返回地址。每个函数推送到堆栈上的数据被组织到堆栈框架中。当调试器停止内核时,它可能能够分析堆栈上的数据,以提供调用堆栈,即导致当前情况的函数调用列表。这在调试时非常有用,因为它使您能够确定应用程序达到特定状态的原因。要重建调用堆栈,调试器必须能够确定堆栈上的哪些条目包含返回地址信息。如果构建代码时包含调试器信息(DWARF调试表),或者通过遵循应用程序推送到堆栈上的一系列帧指针,则这些信息可能包含在调试器信息(DWARF调试表)中。
要做到这一点,必须将代码构建为使用帧指针。如果这两种类型的信息都不存在,则无法构造调用堆栈。在多线程应用程序中,每个线程都有自己的堆栈。因此,调用堆栈信息仅与正在检查的特定线程相关。

1.10 半托管调试

半宿主是一种机制,它允许在ARM目标上运行的代码使用运行调试器的主机上提供的工具。
这方面的示例可能包括键盘输入、屏幕输出和磁盘I/O。例如,您可以使用此机制启用C库函数,如printf()和scanf(),以使用主机的屏幕和键盘。开发硬件通常没有完整的输入和输出设施,但半托管使主机能够提供这些设施。半托管由一组定义的软件指令实现,这些指令生成异常。应用程序调用适当的半宿主调用,然后调试代理处理异常。调试代理提供与主机的所需通信。semihosting使用的ARMv8处理器规范与实现ARMv7的处理器规范不同。DS-5调试器通过在AArch64中拦截HLT 0xF000来处理半宿主。当然,在开发环境之外,在主机上运行的调试器通常不会连接到系统。因此,开发人员有必要重新确定使用半宿主的任何C库函数的目标,例如,使用fputc()。这将涉及将使用SVC调用的库代码替换为可以输出字符的代码。

2、ARM跟踪硬件

非侵入性调试,允许在执行时观察核心行为。虽然有不同种类的非侵入性调试,但本节将特别介绍跟踪和跟踪硬件。可以记录执行的内存访问(包括地址和数据值),并生成程序的实时跟踪,查看外围访问、堆栈和堆访问以及变量的更改。对于许多实时系统,不可能使用侵入式调试方法。例如,考虑一个引擎管理系统,在该系统中,虽然可以在特定点停止核心,但引擎会不断移动,并且无法进行有用的调试。
即使在实时性要求较低的系统中,跟踪也非常有用。跟踪通常由连接到核心的内部硬件块提供。这被称为嵌入式跟踪宏单元(ETM),是大多数基于ARM处理器的系统的一部分。在某些情况下,每个核心有一个ETM。系统芯片设计人员可以从他们的硅中省略这一块,以降低成本。这些块观察但不影响核心行为,并能够监视指令执行和数据访问。捕获跟踪有两个主要问题。首先,在当前非常高的核心时钟速度下,即使几秒钟的操作也可能意味着数万亿次的执行周期。显然,要理解这一数量的信息是极其困难的。
第二个相关问题是,当前的内核可能会在每个周期执行一个或多个64位缓存访问,记录数据地址和数据值可能需要很大的带宽。这就产生了一个问题,通常,芯片上只能提供几个引脚,并且这些输出可以以远低于内核时钟的速率进行切换。如果内核以1GHz的速度每周期生成100位信息,但芯片只能以200MHz的速度输出4位跟踪,那么就存在问题。为了解决后一个问题,跟踪宏单元尝试压缩信息以减少所需的带宽。然而,处理这些问题的主要方法是控制跟踪块,以便只收集选定的跟踪信息。例如,您可能只跟踪执行,而不记录数据值。
此外,通常将跟踪信息存储在片上存储器缓冲器(嵌入式跟踪缓冲器(ETB))中。这缓解了从芯片上快速获取信息的问题,但在硅面积(因此芯片价格)方面有额外的成本,并且还对可捕获的跟踪量提供了固定限制。ETB以循环方式存储压缩的跟踪信息,持续捕获跟踪信息,直到停止。ETB的大小因芯片实现而异,但8或16KB的缓冲区通常足以容纳数千行程序跟踪。当程序失败时,如果启用了跟踪缓冲区,则可以看到程序历史的一部分。有了此程序历史记录,可以更轻松地回顾您的程序,查看故障点之前发生的情况。这对于调查间歇和实时故障特别有用,这些故障可能难以通过需要停止和启动核心的传统调试方法识别。使用硬件跟踪可以显著减少查找这些故障所需的时间,因为跟踪可以准确显示执行的内容、时间安排以及发生的数据访问。

2.1 CoreSight

ARM CoreSight™ 技术扩展了ETM提供的功能。同样,它在特定系统中的存在和功能由系统设计者定义。CoreSight提供了许多非常强大的调试工具。它支持多核系统(非对称和SMP)的调试,这些系统可以共享调试访问和跟踪引脚,并完全控制在何时跟踪哪些核。嵌入式交叉触发机制使工具能够以同步方式控制多个核,因此,例如,当一个核碰到断点时,所有其他核也将停止。分析工具可以使用这些数据来显示程序在哪里花费时间以及存在哪些性能瓶颈。代码覆盖率工具可以使用跟踪数据来提供调用图探索。支持操作系统的调试器可以使用跟踪,在某些情况下,还可以使用额外的代码插装来提供高级系统上下文信息。以下是一些可用CoreSight组件的简要说明:

  • 调试访问端口(DAP):DAP是ARM CoreSight系统的可选部分。并非每个设备都包含DAP。它允许外部调试器直接访问系统的内存空间,而无需将内核置于调试状态。要在没有DAP的情况下读取或写入内存,可能需要调试器停止内核并让它执行加载或存储指令。DAP允许外部调试工具访问系统中的所有JTAG扫描链,从而调试和跟踪可用内核和其他组件的配置寄存器。
  • 嵌入式交叉触发器(ECT):ECT块是CoreSight组件,可包含在CoreSight系统中。其目的是将系统中多个设备的调试功能链接在一起。例如,您可以有两个彼此独立运行的内核。当您在一个内核上运行的程序上设置断点时,可以指定当该内核在断点处停止时,另一个内核也必须停止(无论它当前执行的是什么指令),这将非常有用。ECT中的交叉触发矩阵和接口使调试状态和控制信息能够在内核和跟踪宏单元之间传播。ARMv8处理器系统中始终需要交叉触发块,因为它提供了在处理器进入暂停模式后重新启动处理器执行的唯一方法。
  • CoreSight串行线:CoreSight串行线调试使用调试访问端口(DAP)提供2针连接,该端口在功能上相当于5针JTAG接口。
  • 系统跟踪宏单元(STM):这为多核(和进程)执行printf()风格的调试提供了一种方法。系统中任何主机上运行的软件都可以访问STM通道,而不必知道其他用户的使用情况,只需使用非常简单的代码片段。这使得内核和用户空间代码的时间戳软件插装成为可能。时间戳信息给出了与以前事件相关的增量,非常有用。
  • 跟踪内存控制器(TMC):如前所述,在封装IC中添加额外的引脚可以显著增加其成本。在单个设备上有多个内核(或其他能够生成跟踪信息的块)的情况下,经济上可能会排除提供多个跟踪端口的可能性。CoreSight跟踪内存控制器是一个跟踪漏斗,能够将多个跟踪源组合到系统内存中的一条总线中。提供的控件用于在这些多个输入源之间启用、优先排序和选择。跟踪信息可以使用专用跟踪端口、JTAG或串行线接口或通过重新使用SoC的I/O端口从芯片外导出。跟踪信息可以存储在ETB或系统内存中。

3、DS-5 debug and trace

DS-5调试器为使用基于ARM体系结构的处理器在硬件目标和模型上调试应用程序提供了强大的工具。您可以完全控制执行流程,以便快速隔离和更正错误。
DS-5调试器提供了广泛的调试功能,例如:

  • 加载镜像和符号表
  • 运行镜像
  • 断点和观测点
  • 源码和指令级别单步
  • 控制变量和寄存器值
  • 观测调用栈
  • 支持处理异常和Linux信号
  • 调试多线程linux和安卓app
  • Linux、内核和Android模块的调试、引导代码和内核移植
  • 应用程序回放,允许您通过Linux和Android应用程序进行向后和向前调试

调试器支持一组全面的DS-5调试器命令,这些命令可以在Eclipse IDE、脚本文件或命令行控制台中执行。此外,还有一小部分CMM风格的命令足以运行目标初始化脚本。DS-5调试器支持使用JTAG进行裸机调试、使用gdbserver进行Linux应用程序调试、使用JTAG进行Linux内核调试和内核模块调试。对裸机SMP系统的调试和跟踪支持,包括交叉触发和依赖核心的视图和断点、PTM跟踪,以及高达4 GB的DSTREAM跟踪。以下各节将介绍此支持。此外,DS-5调试器支持ARM CoreSight ETM、PTM、ETB和STM,以提供非侵入性程序跟踪,使您能够在指令发生时查看指令(以及相关的源代码)。它还提供了调试时间敏感问题的能力,否则传统的侵入式步进技术将无法解决这些问题。

TD

你可能感兴趣的:(arm)