电子说
调试是软件开发的一个重要组成部分,通常是最消耗时间的(也因此非常昂贵)。错误可以是很难察觉、重现和修复的,而且也难以预料解决一个缺陷需要多长的时间。
在产品交付给客户后,解决问题的成本显著增加。在很多情况下,一个产品的销售只有一个很小的时间窗口,如果产品晚了,它可能错过市场的机会。因此,对于任何开发人员,系统所提供的调试工具是要考虑的一个重要因素。
许多实用ARM处理器的嵌入式系统只有有限的输入/输出设备。这意味着可能无法使用传统的台式机的调试方法(如实用printf()函数)。
像很多IoT设备,都没有屏显或者串口。
在过去,在这样的系统中,开发人员可能会使用昂贵的硬件工具,如逻辑分析仪和示波器来观察程序的行为。
本书所描述的处理器是有高速缓存的一个复杂的片上系统,包含内存和许多其他功能模块。在片外也许无法看到处理器的内部信号,因此无法通过连接逻辑分析仪等设备来监测它的行为。
出于这个原因,ARM系统一般包含专门的硬件,以便为调试提供广泛的控制和观测模块。
ARM调试硬件
Cortex-A系列处理器提供的硬件功能可以使调试工具提供可以明显提高处理器控制活动和非入侵性地收集大量的有关程序执行数据的水平。我们可以将硬件的功能分为两类:入侵性和非入侵性。
入侵性调试提供了能够停止程序并单步执行的工具(在C源代码或通过汇编语言指令单步执行),它可以通过一个外部设备连接到处理器芯片的JTAG引脚或(较少见)系统ROM中的调试监视代码。
JTAG是Joint Test Action Group的简写,是IEEE 1149.1规范,它最初设计用来测试电路板上的电子设备,但现在广泛用于连接处理器调试。
一个典型的JTAG连接有5个引脚:2个输入、1个时钟、1个复位和1个输出。
调试器提供了控制执行程序的能力,使我们能够运行到某一点的代码时暂停处理器、单步执行代码和继续执行,也可以在指定的指令上设置断点(当处理器到达这条指令时,让调试器控制它)。这些工作可以使用两种不同的方法。
(1)采用BKPT指令替代需要停止的指令可以产生软件断点。显然,这只能用于存储在RAM中的代码,但优点是可以设置非常多的软件断点。该调试器软件必须跟踪记录放置软件断点的位置以及该位置的原始操作码,这样当希望执行该断点处的指令时,它可以放回原始代码。
(2)硬件断点,使用处理器内置的比较器,当执行到指定地址时自动停止执行。这可以用在代码空间中的任何地方,因为它们不需要修改代码,但硬件只能提供数量有限的硬件断点单元(Cortex-A系列通常只有4个)。调试工具可以支持更复杂的断点(例如在一个地址范围内的任意指令,或仅当指定序列事件发生或硬件在指定的状态时停止任意指令)。数据观察点让调试器可以控制当一个特定的数据地址或地址范围被读取或写入访问时,产生调试控制。这些也可以称为数据断点。
调试事件是指进程的某些部分正在被调试导致系统通知调试器的事件。调试事件可以是同步或异步的,断点、BKPT指令和观察点都是同步调试事件。当任何这些事件之一发生时,该处理器能以以下方式之一响应。
● 它可以忽略调试事件。
● 它可以引发一个调试异常。
● 根据调试状态和控制寄存器(Debug Status and Control Register, DSCR)的设置,它会进入两种调试模式:
✧ 监视调试模式;
✧ 停止调试模式。
上述两种调试模式都是侵入式调试的例子。
(1)停止调试模式。在停止调试模式中,一个调试事件会使处理器进入调试状态,该处理器被停止,并从系统的其他部分分离出来。这意味着,调试器可以显示处理器所能看到的内存,内存管理和缓存操作的影响将变得可见。
在调试状态下,处理器从程序计数器指定的位置停止执行指令,并通过外部调试接口控制,尤其是使用错误指令转移寄存器(Debug Instruction Transfer Register, DBGITR),这启用了外部控制器,例如调试器来查询处理器的现场,并控制所有后续的指令执行。处理器和系统状态都是可以被修改的,因为处理器已经停止,它无法处理任何中断,直到调试器重新启动代码执行。
(2)监视调试模式。在监控调试模式中,调试事件会产生一个调试异常,无论是关于指令的执行产生的预取中止异常,还是数据访问时产生的数据中止异常,这些都必须由软件调试监视器处理。由于处理器仍然在运行,中断仍然可以被响应。
ARM跟踪硬件
非侵入性调试(在ARM文档中通常称为跟踪)可以在执行时观察处理器的行为,它可以记录内存访问的执行(包括地址和数据),并生成一个程序运行的跟踪,查看外设访问,堆和栈访问以及变量的改变。
对于许多实时体统,**不能使用入侵性调试的方法。**例如,考虑一个引擎管理系统,也许能够在特定的地方停止处理器,但引擎将继续运行,所以将无法做有用的调试,即使是在不太繁重的实时要求环境中,跟踪也是非常有用的。
非侵入性调试,或称为“跟踪”,是一种在不干扰或影响目标系统运行的前提下,观察和记录目标系统运行时行为的方法。这种方法特别适用于实时系统或那些不能接受中断的系统。
侵入式调试是一种在调试过程中需要直接干预和介入目标系统运行的调试方式。这种调试方式需要在目标系统的源代码级别或汇编语言指令中停止程序并逐行执行它们。通过使用芯片JTAG引脚连接到内核的外部设备,或者通过调试监控代码,开发人员可以实现对目标系统的直接控制和干预。
侵入式调试的优点在于能够深入了解目标系统的底层细节和运行状态,帮助开发人员准确地定位和解决问题。但是,这种调试方式也存在一些缺点。首先,它需要对目标系统进行直接干预和介入,可能会对系统的正常运行造成干扰或破坏。其次,侵入式调试需要对目标系统的源代码或汇编语言指令进行操作,这需要较高的技术水平和经验,并且可能会引入新的错误或问题。
因此,在选择调试方式时,需要根据实际情况进行权衡和选择。对于一些复杂或关键的系统,侵入式调试可能是必要的,但在大多数情况下,非侵入式调试或跟踪可能是更合适的选择。
通常,跟踪由连接到处理器的外部硬件模块提供,这就是所谓的嵌入式跟踪宏单元(Embedded Trace Macrocell, ETM)或程序跟踪宏单元(Program Trace Macrocell, PTM),是基于ARM处理器系统的一个可选部分,SoC在设计时可以去除这个模块以降低成本。
这个模块可以观察(但不影响)处理器的行为,并能监视指令的执行和数据的访问。
关于捕获跟踪,有两个主要的问题。
第一个问题与如今非常高的处理器主频有关,即使几秒的操作也可能执行数万亿周期。显然,要查询如此多的信息将是非常困难的。
第二个问题是,如今的处理器在一个周期中可能执行一个或多个64 bit缓存访问,要记录数据地址和数据值就需要非常大的宽带。
这就带来一个的问题,芯片上可能只能提供少数引脚,并且这些输出引脚只能用一个比处理器主频更低的时钟。如果处理器以速度为1 GHz的时钟,每个周期生成100 bit的信息,但芯片只能以200MHz的速度输出4 bit的跟踪信息。
为了解决这个问题,跟踪宏单元将尝试压缩信息,以减少所需的宽带。然而,处理这些问题的主要方法是控制跟踪模块,以便只收集所选择的跟踪信息。
例如,我们可能只跟踪执行流,而不记录数据值,或我们只跟踪一个特定外设或特定函数执行的数据访问。
由于处理器的速度和复杂性,捕获其所有操作的信息是非常困难的和带宽密集型的。因此,通常需要采用特定的策略和方法来仅捕获所需的信息,从而有效地解决调试和故障排除问题。
此外,将跟踪信息存储在芯片的内存缓冲区(嵌入式跟踪缓冲区(ETB))是常见的办法,这将缓解芯片获取信息的速度问题,但在硅片面积(对应芯片的价格)会有额外的成本,并且会限定一个固定的可被捕获的跟踪信息量。
B就是buffer
ETB以循环方式存储压缩的跟踪信息,连续的捕获跟踪信息直到停止。ETB的大小根据芯片的实现不同而变化,但通常8KB或16KB的缓冲区就足以容纳对几千行程序的跟踪。
在程序出现故障时,如果启动了跟踪缓冲区,则可以看到部分程序的历史记录,通过程序的历史记录,很容易通过程序查回去,看看在故障点前发生了什么。
这对于调查间歇和实时故障特别有用,而这对传统的需要停止和启动处理器执行的调试方法是比较困难的。
使用硬件跟踪可以显著地减少寻找故障所需的时间,跟踪信息精确地显示执行了什么,何时产生的以及访问了什么数据。
这种技术可以用于生成程序的calltrace,即记录程序执行过程中函数或方法的调用序列。
通过calltrace,可以了解在故障点前程序执行的具体流程和函数调用关系。这有助于分析问题的根本原因,确定是哪个函数或方法导致了故障,以及故障是如何产生的。
ARM的CoreSight技术通过ETM提供了扩展功能,在特定的系统中它的存在和功能是由芯片设计者定义的。
最近在找一个课程,看看能不能让我去学习一下Verilog的实现,到时候有机会的话,可以分享的更加深入一点。
CoreSight提供了一个非常强大的调试功能,它可以调试多处理器系统(非对称和SMP),共享调试和跟踪的引脚,在一段跟踪时间内,具有对处理器的完全控制。
嵌入式交叉触发机制让工具可以同步控制多个内核,例如,当一个内核遇到一个断点,也将停止所有其他的内核。
商业的调试工具可以使用跟踪数据提供诸如处理器寄存器、内存及外设的实时视图等功能,允许用户对程序流进行单步向前或向后执行。
分析工具可以利用这些数据显示该程序在哪里花费的时间以及存在的性能瓶颈。
代码覆盖工具可以使用跟踪数据提供图探究调用。
OS信息调试器可以使用跟踪信息(在某些情况下,额外的代码插装)提供高级别的系统上下文信息。
像DS、劳特巴赫这些其实都是细分赛道,做的很深,国内这样的企业好少~~~期待。
在这里,我们列出了一些可用的CoreSight组件,并简要说明其用途。
先整个图,看着会舒服点
画的真漂亮哈哈哈
(1)调试访问端口(DAP)。
DAP是ARM的CoreSight系统的一个可选部分,不是每个设备都包含DAP,它使外部调试器直接访问系统的内存空间,而无须使处理器进入调试状态。在没有DAP情况下,要读取或写入可能需要调试器停止处理器,并执行加载或存储指令。DAP提供了一个外部调试工具来访问系统中的所有的JTAG扫描链(因此可以访问处理器的所有调试和跟踪寄存器)
(2)嵌入式交叉触发(ECT)。
ECT模块是CoreSight的一个组件,可以包含在CoreSight系统中。其目的是将系统中的多个设备的调试功能链接在一起。例如,我们可以有两个相互独立的运行内核,在一个内核的运行程序上设置一个断点时,如果可以在一个内核在断点处停止时,让另一个内核也停止下来(无论当前正在执行什么指令),那么将对调试工作非常有帮助。ECT中的交叉触发矩阵和接口可以在内核和跟踪宏单元之间传递调试状态和控制信息。
(3)AHB跟踪宏单元。
AMBA AHB跟踪宏单元可以让调试器看到系统内存总线上发生了什么。此信息不是直接从处理器ETM得到的,因为集成的内核无法确定这个数据来自缓存还是外部存储器。
(4)CoreSight串行线。
CoreSight串行线调试通过使用调试访问端口(DAP)给出了一个2引脚的连接,它的功能相当于5引脚的JTAG接口。
(5)系统跟踪宏单元(STM)。
它为多处理器和进程提供了printf()风格的调试方法。系统中任何主设备运行的软件都能够通过使用非常简单的代码片段访问STM通道,而无须知道其他软件的使用,它会使能内核和用户代码的仪器化软件的时间标记。时间标记的信息提供了相对于先前的事件的一个变化偏移量,这是非常有用的。
(6)跟踪内存控制器(TMC)。
正如已经描述的,给芯片封装添加额外的引脚会显著地增加其成本。在多个内核设备(或其他模块能产生跟踪信息)的情况下,为经济考虑很可能排除提供多个跟踪端口的可能性。CoreSight跟踪内存控制器可用于将多个跟踪源合并到一个单一的总线上。控制可以在多个输入源之间进行优先级考虑和选择。跟踪信息可以使用专门的跟踪端口导出,通过JTAG或SWI串行线接口或通过复用SoC的I/O端口,可以存储在一个ETB或系统内存中。
程序员应该查阅所使用的设备的文档,以确定哪些跟踪功能和工具可以使用。
调试监视器
ARM架构为外部调试器提供了多种访问的功能,这些功能模块也可用于处理器的代码,即所谓的调试监视器,它驻留在目标系统。
监视系统是比较便宜的,因为它们可能并不需要额外的硬件。然而,它们需要占用系统的存储器空间,而且只有当目标系统本身实际运行时才能使用。
如果系统连起码的正确启动都无法成功的话,它们在系统上没有什么价值。
处理器的断点和观察点硬件可用于调试监视器。
当选择监视模式调试时,断点单元可以通过ARM处理器上运行的代码进行编程。如果执行了BKPT指令,或一个硬件断点单元匹配,系统在监视模式下会有不同的行为。
相比在外部硬件调试器控制下停止处理器,在监视模式下处理器会产生一个中止异常,并且它可以识别这是不是由调试事件产生的中止,然后调用监视代码。
举个例子,假设有一个ARM处理器在运行一个程序,并且开发者想要在某个特定的代码行上设置一个断点。在监视模式下,开发者可以在目标系统上运行一个特殊的指令来编程断点单元。当程序执行到这一行时,断点单元会触发并产生一个异常。处理器可以识别这个异常是由断点引起的,然后执行预定义的监视代码。这个监视代码可以用于收集调试信息、记录程序状态或执行其他必要的调试任务。
总的来说,监视系统是一种用于处理器调试的机制,允许开发者在不停止系统运行的情况下检查程序状态和行为。通过使用断点和观察点硬件,开发者可以在目标系统上设置断点、观察点或执行其他调试操作,从而更好地理解程序的执行流程和潜在问题。
很多编写源码的IDE工具大概就是利用这个操作来实现的。所以说做编译器真的有点东西。
调试Linux应用程序
Linux是一个多任务的操作系统,其每一个进程都有自己的进程地址空间,并完成私有页表的映射,这会让调试一些问题比较麻烦。
我们可以大致定义两个不同的调试Linux系统所使用的方法。
(1)Linux应用程序,通常使用运行在目标上的GDB调试服务器与主计算机通信来进行调试,通常通过以太网。
在调试会话发生时,内核将继续正常运行,这种调试方法不会使用所提供的内置硬件调试设备,目标系统永久处于运行状态,服务器接收到主机调试器的请求,然后接收命令,并提供数据传回主机。
主机调试器将加载请求发送给GDB服务器,GDB服务器通过启动一个新的进程来运行需要调试的应用程序并做出响应。
在开始执行前,它使用系统调用ptrace()控制应用程序的进程,这个进程中的所有信号被转发到GDB服务器。
任何发送到应用程序的信号将会进入GDB服务器,它可以处理信号或将其转发给正在被调试的应用程序。
为了设置断点,GDB服务器会在代码中所要求的位置插入一个产生SIGTRAP信号的代码。
当执行该代码时,GDB服务器被调用,然后执行调试器任务,如检查调用栈信息,变量或寄存器内容。
(2)对于内核调试,需要使用JTAG调试器。
当一个断点被执行,系统就停止。
这是最简单的检查问题的方法,如设备驱动程序加载或不正确操作,或内核引导失败等问题。
日常的常用场景
另一种常见的方法是通过调用printk()函数。
strace工具可以显示关于用户系统调用的信息。
Kgdb是一个Linux内核的源代码级调试器,它与GDB协同工作在单独的机器上工作,可以用来检查堆栈跟踪和内核状态视图(如PC值、定时器内容和内存)。
设备/dev/kmem可以提供内核内存的在线访问。
当然,Linux-aware JTAG调试器也可以用来调试线程,它通常只会停止所有的进程,无法在停止一个单独的线程或进程同时让其他线程运行。
断点可以设置在所有的线程或一个指定的线程上。
由于存储器映射取决于哪个进程是活动的,因此软件断点通常只能设置在一个特定进程进行映射时。
ARM DS-5调试器可以通过gdbserver调试应用程序,通过JTAG调试Linux内核和Linux内核模块。
还有Trace32工具。
小结
最后这里拿几个典型的核来说说调试架构。
Corte-M3
Corte-M3系列微控制器的总体结构图
Corte-M3的调试系统基于ARM公司的“CoreSight(内核景象)”调试架构,对处理器上总线逻辑的控制使用另外的总线接口,即通过所谓的“调试访问端口(Debug Access Port,DAP)”,把JTAG或串行线协议都转换成DAP总线接口协议,再控制DAP来执行调试动作。
CoreSight架构的另一部分用于跟踪,在跟踪踪过程中,由跟踪源产生的数据被裹成数据包,然后被送到“高级跟踪总线(Advanced Tracking Bus,ATB)”上进行传送。
如果某SoC含有多个跟踪源(如多核系统),则需要一种硬件(在CoreSight架构中,这种硬件被称为ATB funnel)水平的ATB归并器(merger),把各ATB数据流归并成一条。
归并后的数据流都送往TPIU(Trace Port Interface Unit,跟踪端口接口单元),TPIU再把数据导出到片外的跟踪硬件设备数据总线和指令总线通过总线接口与代码存储器,外部存储器,外部设备和内部设备进行连接。
下面再详细展开说说:
Cortex-M3在内核水平上搭载了若干种调试相关的特性。
最主要的就是程序执行控制,包括
停机(Halting)、
单步执行(Stepping)、
指令断点、
数据观察点、
寄存器和存储器访问、
性能速写(Profiling),
以及各种跟踪机制。
Cortex-M3的调试系统基于ARM最新的CoreSight架构。不同于以往的ARM处理器,内核本身不再含有JTAG接口。取而代之的是CPU提供称为“调试访问接口(DAP)”的总线接口。
通过这个总线接口,可以访问芯片的寄存器,也可以访问系统存储器,甚至可以在内核运行时访问。
对此总线接口的使用,是由一个调试端口(DP)设备完成的。DPs不属于Cortex-M3内核,但它们是在芯片的内部实现的。
目前,可用的DP包括SWJ-DP(既支持传统的JTAG调试,也支持新的串行线调试协议),另一个SW-DP则去掉了对JTAG的支持。
另外,也可以使用ARM CoreSignt产品家族的JTAG-DP模块。
这下就有3个DP可以选了,芯片制造商可以从中选择一个,以提供具体的调试接口(通常都是选SWJ-DP)。
Cortex-M3还能挂载一个所谓的“嵌入式跟踪宏单元(ETM)”。
ETM可以不断地发出跟踪信息,这些信息通过一个被称为“跟踪端口接口单元(TPIU)”的模块送到内核的外部,再在芯片外面使用一个“跟踪信息分析仪”,就可以把TIPU输出的“已执行指令信息”捕捉到,并且送给调试主机——也就是PC。
在Cortex-M3中,调试动作能由一系列的事件触发,包括断点、数据观察点、fault条件或者是外部调试请求输入的信号。
当调试事件发生时,Cortex-M3可能会停机,也可能进入调试监视器异常handler。具体如何反应,则根据与调试相关寄存器的配置。
与调试相关的还有其他的绝活。现在要介绍的是“指令追踪宏单元(ITM)”,它也有自己的办法把数据送往调试器。
通过把数据写到ITM的寄存器中,调试器能够通过跟踪接口来收集这些数据,并且显示或者处理它。此法不但容易使用,而且比JTAG的输出速度更快。
所有这些调试组件都可以由DAP总线接口来控制,Cortex-M3内核提供DAP接口。此外,运行中的程序也能控制它们,所有的跟踪信息都能通过TPIU进行访问。
Zynq-7000 AP SoC
Zynq-7000 AP SoC提供标准的JTAG(IEEE 1149.1)调试接口,包括一个在PS内部的ARM调试接口(DAP)和一个在PL内部的标准JTAG测试接口(TAP)。
ARM DAP是ARM CoreSight调试框架的一部分,允许用户使用行业标准的第三方调试工具。
Xilinx TAP控制器在标准的JTAG功能上添加了一些支持PL的特性,包括PL调试、熔丝/BBRAM编程、片上XADC访问等。
更重要的是,它通过共享跟踪缓冲及PL和PS的交叉触发接口,达到允许在使用DAP调试ARM上的软件的同时,使用TAP调试PL硬件。
JTAG调试接口的关键特性:
支持JTAG 1149.1边界扫描;
两个符合1149.1标准的TAP控制器——JTAG TAP控制器和ARM DAP;
每个Zynq-7000家族的设备都有唯一的IDCODE;
支持IEEE 1532在线编程(ISE),如熔丝编程、BRAM编程和XADC访问;
板级Flash编程;支持Xilinx Chipscope调试;
使用ARM DAP的ARM CoreSight调试控制中心;
通过DAP-AP端口实现系统地址空间直接访问;
使用MIO或者EMIO的外部跟踪接口。
之前的文章都是基于Corte-A系列。
调试安全
最后这里再啰嗦一点关于安全的知识。
联合测试行动小组(JTAG)国际标准测试协议已被业界广泛应用。JTAG接口的主要功能在于将固件下载到设备芯片,并在程序运行时对软件执行过程进行分析监测,监测内容包括CPU中的寄存器和内存中存储的值,以实现对芯片内部测试及系统仿真、调试。
芯片厂商通常将JTAG接口作为片上系统的一部分。例如,ARM公司在其处理器芯片的CoreSight调试架构中集成了这一接口。
JTAG的使用提高了移动终端固件开发的便利性。但由于JTAG接口通常是开放的,因此,攻击者可以找到芯片上JTAG接口的引脚并接入工具,实现对移动终端的攻击。
例如,可以破坏或禁用系统组件,提取固件代码或加/解密密钥,也可以插入未经授权的功能函数,获得攻击系统的后门,传输攻击程序及分析设备漏洞等。由于通过JTAG可以同时访问内存和处理器的信息,因此,基于JTAG接口实施攻击比通过其他接口进行攻击更为直接。
我们可以联合采用基于电路特性隐匿与基于认证的安全防护方法抵御基于JTAG接口的攻击。
(1)基于电路特性隐匿的JTAG接口攻击防护
这种防护的核心技术思想是在芯片的硬件电路上进行必要的修改,使攻击者难以从物理层面接入。
例如,修改芯片上JTAG的默认输入电压,使得攻击者难以使用有效的电压激活JTAG接口;
修改引脚映射,即修改芯片上面JTAG引脚的布局,使攻击者难以辨认哪些管脚被用于JTAG;
熔丝的办法,即如果固件已经被下载到芯片中,则特殊熔丝会被熔断,并以删除输入/输出端口的方式来禁用JTAG端口,使得开发者无法再使用JTAG。
(2)基于认证的JTAG接口攻击防护
这种防护方法通常需要外置电路芯片,以完成JTAG的认证逻辑。
例如,设置安全级别的方式,在JTAG接口引脚外继续外接一个微控制器,通过“验证-挑战”的方式对接入的JTAG器件的权限进行认证。
通过接收固定的字符,微控制器将会验证当前的JTAG端口连接请求是否符合身份。在通过验证后,微控制器将把当前外部器件发送的调试指令转发给实际芯片的JTAG端口。
另外,也可以在使用JTAG接口之前,强制要求需要输入密码或者使用JTAG带有密钥进行身份认证。
最后也可以设计专属的门控认证。
其实一直都是停留于使用者的角度来认识Coresight以及调试软件与工具,期待站在设计者的角度来认识Coresight。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !