中文 英语

调试嵌入式应用程序

软件和硬件的相互依赖使嵌入式设计中的调试复杂化。帮助减少调试时间的新方法正在成熟。

受欢迎程度

随着硬件和软件之间可观察到的和可能的交互的数量不断增长,以及越来越多的功能被塞进芯片、包和系统中,调试嵌入式设计变得越来越困难。但在这方面似乎也有一些进步,涉及多种技术,包括硬件跟踪、基于扫描链的调试,以及更好的仿真模型。

其中一些是由于新的工具,一些是现有工具的组合,还有一些涉及方法的变化,其中工具在设计-制造流程的不同时间以不同的组合使用。

公司首席执行官Shubhodeep Roy Choudhury表示:“基于触发事件跟踪和捕获内部信号,并将其存储在跟踪缓冲区中,然后可以从调试端口读取,从而实现数据的无缝收集,而不会中断系统的正常执行。Valtrix系统.“可能需要大量硬件仪表,但将触发事件放置在故障点附近可以对问题产生很大的可见性。”

最大的挑战之一是在嵌入式设备上运行的软件。微软首席执行官西蒙•大卫曼表示:“测试这款软件真的很难治之软件.“我记得作为一名工程师,我试图接触这些原型。你必须分享它们,因为你永远都不够。使用仿真改变了为嵌入式世界开发软件的能力,因为如果您可以为您的平台构建一个良好的仿真模型,这意味着您可以测试它,运行它,并将其放入回归场,实现持续集成等等。你得到了更高质量的软件,因为你有更多的机会去验证它。但是当涉及到调试时,就像今天的模拟一样,它比在原型或硬件上调试要有效得多,并且您可以获得一些很大的好处——版本控制、可控性、可观察性以及抽象和在您想要的地方停止的能力——然后看到所有东西。通过模拟器,你可以获得其他方法无法获得的可观察性。”

为了提高性能和降低功耗,在设计中加入了不同类型的芯片和存储器,这就变得更加复杂了。

“在异构系统中,典型的系统将有来自多个不同供应商的IP块,以及在某些情况下,由SoC开发人员内部小组提供的IP块,以及来自非传统IP供应商的IP块,”at IP Group的产品营销总监George Wall表示节奏.“如果他们从另一家SoC公司获得了硬件模块的许可,那么每家公司都有自己的一套标准和实现方式。所以这是一个非常多样化的设备。如何在调试级别集成所有这些?这是一个挑战。我们确实在调试端支持开放标准,就像许多其他商业供应商一样。但内部知识产权和来自非传统领域的知识产权,可能来自那些不支持这些标准的公司。”

这增加了另一层复杂性,因为工程团队正在处理一些黑盒。他们不知道里面装的是什么,也不太想摆弄它们。沃尔说:“他们只是想确保外部可见,这样他们就能看到正在发生的事情。”“真的没有行业标准说,‘好吧,这里有适当的曝光度。’所以这是一个挑战。”


图1:用于嵌入式系统设计的Cadence/Green Hills工具集成。来源:节奏

增加可见性
但大卫曼说,新的方法可以创造新的效率。“我们看到工程师们试图让他们的固件与他们的操作系统一起运行,写跟踪和抽象的东西,不仅是为了监视变量级别或功能级别,而是为了深入操作系统并观察操作系统在做什么。他们可以追踪到。这意味着他们将获得数千行函数跟踪或调度器跟踪,而不是十亿行指令跟踪,因此他们可以观察正在发生的事情,并在高层次上可视化其中的一些内容。一旦出了问题,他们就能深入挖掘。例如,一个用户实现了断言来监视操作系统,如果发生了什么事情,它就会出错。它在一个跟踪中保持一个滚动缓冲区,例如,10,000条指令和1,000个函数构建。当它停止时,他们可以回头看看当时发生了什么。”

这可以在硬件上实现,但并不容易。他说:“你必须把它全部装进去。”“有了模拟器,工程团队可以编写自己的,扩展它,并构建自己的工具,以获得更好的可视性,他们可以将断言放入,以更好地监控事情,并指出可能不是致命的,但仍会影响性能的错误。这是关于可见性和可观察性。如果你正确地使用模拟器,就不会出现黑箱。”

调试在通用硬件上运行的软件应用程序与在嵌入式应用程序上运行的软件应用程序之间的最大区别在于它运行在独特的硬件上。

“一般来说,这是专为特定应用而设计的东西,而不是基因计算节点,”Sam Tennent说Synopsys对此.“关键在于依赖硬件的软件之间的交互,这是软件必须了解硬件的最底层。在此之上还有一些层,这些层是从硬件中抽象出来的。我们对与硬件对话的这一层以及由此产生的具体问题很感兴趣,这与你可能在高级软件中看到的问题不同。”

调试工程师需要对硬件有一点了解,他说。“他们需要意识到设备注册、中断等事情——所有这些发生在硬件层面的事情都会影响软件的运行。他们不仅需要了解软件领域发生了什么,还需要了解硬件领域发生了什么。他们需要能够将这些事情联系起来。”

虚拟原型是解决这个问题的一种方法。“事实上,虚拟原型使用的是硬件的抽象模型,这意味着你可以看到硬件层发生的事情,同时也可以看到软件层发生的事情。Tennent说。“通常情况下,你可以把这些事情联系起来。你可以在你的软件中查看一个事件,或者你可以查看你的软件例程,看看它们是如何与硬件交互的,例如,如果你知道硬件产生了中断,你可以跟踪它,你可以确切地看到软件对它的反应。当你在这个级别调试问题时,这真的很有用。”

纯软件模拟器对于更高级别的调试仍然是有用的,但通常它们不用于建模诸如硬件寄存器之类的东西。Tennent说:“他们有一个高级的API,软件正在使用,但他们没有在寄存器级别建模。”“这意味着你在这个层面上遇到的任何问题都不会被这些软件模拟器这样的东西所发现。”

Davidmann指出,当处理器模型被封装在Verilog中时,工程团队就可以使用Cadence、Siemens EDA和Synopsys工具进行调试。“他们可以在我们的调试器中调试软件堆栈,并在一个模拟中进行所有调试。例如,由于他们是单步进,他们可以看到Cadence设备中的波形,并可以在一个模拟中查看硬件和软件。它不是一个调试器,因为从概念上讲,硬件中有信号和线路,但它可以在一次运行中完成,并且是同步的,这样工程师就可以点击软件中的一个点,看看那个时间点的波形在做什么。”

Cadence’s Wall建议工程团队预先考虑如何确保在系统上运行的固件的调试。他说:“考虑一下固件和SoC中其他设备之间将发生的交互类型。”“想想如何获得这些互动的可见性。CPU级别的一种常用方法是实现跟踪功能,其中跟踪输出至少可以告诉您在某些事情发生时正在运行哪些代码。为了确保这些交互的可见性,在系统级别上还需要做很多工作。可以添加特殊的可见寄存器,以定期检查提供系统状态的嵌入式固件。在系统中的其他块中实现跟踪检测的其他技术可以由运行在处理器上的固件控制或启用。因此,如果它在与某个特定块交互时遇到困难,它可以打开该块的跟踪,然后从内存位置读取跟踪以了解问题。”

改进底层软件调试
取决于应用程序是在裸金属级别,还是具有RTOS,也会产生不同的影响。

微软的嵌入式软件主管Haris Turkmanovic说:“基于这两个分支的应用程序总是使用一些集成开发环境进行调试,这是调试某些东西时的主要入口。Vtool.“调试过程有多复杂取决于集成开发环境有多复杂。如果有一个开发良好的集成嵌入式环境,调试就会容易得多。”

那么调试的过程是怎样的呢?“你首先需要知道会发生什么,”Turkmanovic说。“如果这个期望没有得到满足,你就知道你有问题了。这种期望基于不同的值,这些值是记忆的一部分。当你调试一些东西时,你需要进入内存,查看内存内容,看看它是如何改变的,一步一步地通过你的代码。每个调试过程由迭代组成。您一步一步地检查代码,观察内存内容,看看是否有什么出乎意料的行为。这样你就能发现问题所在了。基本上,您正在观察内存,以查看内容是否按照预期写入。如果不是,那么就定位问题。 If you have a big system, you can divide it into parts and look at each part separately. This process can be easier if there is some kind of operating system, because embedded platforms that run embedded systems are very complex. They usually have a memory protection unit that allows you, for example, to divide a memory into multiple regions. If you want to access part of the code from one region to another region, the MPU will notice. The second approach when you have an operating system is to use the built-in functions, which will monitor the execution of your program. If you try to do something that was not planned, this function will be called, and breakpoints there can catch the error.”

提高调试过程的效率需要一种系统的方法。

土库曼诺维奇说:“这种系统方法的切入点是知道你的期望是什么,以及限制是什么。”“如果你没有一个系统的调试方法,如果你只是通过猜测来进行调试,你就会陷入一个无休止的循环,调试永远不会结束。如果你没有一个期望,如果你没有一个系统的方法,调试是非常困难的。例如,如果你试图以比系统更快的速度获取数据,你总是会遇到错误,因为你不能做一些不可能的事情。”

在使用自动生成的代码和/或自定义配置的情况下,处理器核心验证的自动化尤为重要。形式化验证技术在这里发挥了关键作用。

形式验证提供了比模拟更快的运行时间,允许模拟许可证被释放用于集成测试等其他任务,”Rob van Blommestein,营销主管指出OneSpin,西门子业务。“设置也更快更简单。RISC-V创建自定义指令的灵活性为仿真创建了验证障碍。形式化技术可以很容易地应用于验证自定义扩展和指令。在测试平台的开发过程中,不需要付出任何努力,就可以正式地实现对所有角落用例的完全覆盖。未指定的行为,如未记录的指令,也可以使用形式化方法来发现。在整个验证过程中,工程团队将能够了解与ISA需求相关的覆盖进度。可以实现验证和覆盖的直接可追溯性。”

正式验证技术中的新技术也有助于验证断言集足以覆盖RISC-V核心设计,并确保没有未经验证的RTL代码。

“设计中的任何额外功能,包括硬件木马,都会被检测到并报告为违反ISA。这包括系统地发现任何隐藏的指令或指令的意外副作用。总的来说,正式的培训可以用更少的努力带来更好的结果。”van Blommestein补充道。

对于嵌入式Linux代码,情况变得更加复杂。Vtool的嵌入式软件Linux主管Gradimir Ljubibratic说:“我们有多线程、多处理器系统,使用某种调试器来进行调试并不容易,这些调试器可以一步一步地进行,检查内存或其他东西。”“在Linux世界中,我们主要依赖于实时的调试块和调试事件。我们可以看到系统发生了什么,系统如何波动,不同的组件如何相互作用,等等。在我们尝试在实际系统上测试所有内容之前,我们使用单元测试来测试系统的不同小组件。我们目前正在实施持续集成测试,以帮助我们在开发的早期阶段检测错误。此外,如果我们有某种堆栈溢出或内存违规等情况,可以使用不同的工具进行内存分析,以检查代码的情况。这主要与用户空间开发和应用程序开发有关。”

Taimoor Mirza, EPS IDE团队的工程经理西门子EDA表示同意。“在现代软件开发中,必须覆盖多个soc上多个核上的多个线程,并且每个组件都需要与系统中的其他组件通信,传统的调试技术很快就达到了极限。这些对于跟踪单个线程/核心上的故障非常重要和必要,但是为了对调试的全系统理解,需要扩展视图。这就是分析和概要工具发挥作用的地方,可以帮助分析和理解复杂的系统行为,并帮助跟踪这类问题。用户可以获得整个系统行为的概览,从而很容易发现出现问题的区域,例如网络问题、操作系统的调度问题、设备驱动程序中的问题。工具还可以导入外部记录数据,并与软件跟踪同步显示。”


图2:使用复杂测试台进行调试。来源:西门子EDA

Mirza说,跟踪点也可以添加到代码中以扩展可用性,而api允许创建了解这些跟踪点的自定义代理,以便更好地支持用户提供在哪里搜索问题的提示。此外,IDE固件开发人员可以添加检测,并在自己的代理中使用它,以帮助最终用户发现高级代码的问题。一旦问题领域被归零,用户现在可以使用特定于问题的技术来尝试获得更多细节。”

例如,如果用户在Linux内核或内核模块中发现问题,可以使用工具来帮助调试Linux内核或内核模块。如果用户在使用RTOS时遇到问题,可以使用RTOS感知功能提供有关该问题的附加信息。

米兹拉说,总的来说,有一些技巧和技巧可以帮助解决任何情况。例如,通过使用-O0 -fno-inline(禁用优化和内联)使代码更易于调试,这样您就可以自然地逐级检查所有代码。您还可以使用-Og而不是-O0来专门优化调试。本质上,这要求优化器协助调试能力,而不是妨碍它或禁用它。

还有许多其他可用的技术。此外,调试团队可以使用静态分析工具,如Klocwork、valgrind套件等,这些工具伴随着学习曲线,有时会给出假阳性,但它们可以发现您甚至没有发现的问题,所以最好在开发早期使用它们,然后持续使用。

在模拟器上早期运行项目可以实现更好的远程和并行开发,以及更好的高级测试自动化。此外,优化编辑-构建-调试周期在以后确实会有回报。还可以创建开发脚本、调整makefile和调整IDE,以自动实现最快的构建和加载。这些类型的调整会对调试进度和整个上市时间产生很大的影响。

安全问题
对于开发人员来说,安全性也正在成为一个更大的话题。“在安全需求和调试可见性之间存在着一种天然的紧张关系,”Wall说。“SoC设计人员在SoC运行时想要获得的信息也有潜在的脆弱性,但对黑客来说很有价值。你不能孤立地考虑这些方面。你必须预先考虑所有这些部分将如何相互作用。它必须事先设计好架构。”

Synopsys软件完整性组的首席安全顾问Mike Fabian说:“我工作过的大多数组织都没有关注更好的调试实践。”弹性、质量和安全都是处于不同成熟度级别的组织的目标和目标。他们专注于使用最新进展更早地发现错误,以确保常规发布具有弹性并满足可接受的安全级别。需要强制使用经过审查的设计蓝图、明确的SDK/框架/编码标准、供应链调查、积极的机制来保护客户隐私,以及自动化治理和技术护栏来避免早期可预防的错误。在开发周期的后期调试一个问题,假设调试意味着“这段代码没有按照预期的方式工作”,就是这些过程和控制的失败。更快更早地发现漏洞更具有成本效益。”

结论
最后,因为后期的bug总是会给进度带来风险,除了调试技术之外,还应该高度重视刺激和测试生成器。Valtrix公司的Roy Choudhury表示:“在设计生命周期的早期启用软件驱动的刺激和实际用例,可以增加发现复杂漏洞的机会。“由于应用程序软件的开发并不是本着寻找设计漏洞的心态,而且通常调试起来很复杂,因此使用刺激生成器可以更好地运行系统,并利用系统中现有的调试基础设施始终是一个好主意。”

相关的
调试:日程杀手
花在调试上的时间是不可预测的。它消耗了开发周期的大部分时间,并且会打乱进度,但是良好的实践可以将其最小化。



留下回复


(注:此名称将公开显示)

Baidu