Pichsakul Promrungsee | Dreamstime.com
编程促销

跟踪和调试以提高效率

2020年11月24日
调试嵌入式软件可能是一项耗时的活动,从追踪特定的错误到标准的项目活动。本文将介绍一些策略,这些策略可能不会消除调试的所有麻烦,但至少会使神奇的部分最小化。

本系列文章在提高软件代码质量在我们的话题系列图书馆

新编写的软件很少是100%无bug的,这对任何人来说都不足为奇。但是,可以在一开始就采取步骤,以帮助减少代码中可能出现的问题的数量。或者,换句话说,这样你要做的调试就少了。明确的出发点是为代码卫生制定一些基本规则:

  • 使用像MISRA和CERT C这样的编码标准。争取遵从MISRA有助于避免C和c++中固有的一些缺陷。CERT C可以将安全透视图添加到要避免的事项列表中。第一个推论是密切注意编译器警告。第二个推论是使用一个自动的静态检查器来检查您的合规性。
  • 使用您自己的或别人的硬件抽象层。避免在代码中直接操作硬件的内联代码。例如,如果需要启动计时器,请调用HAL函数来设置和启动计时器,而不是直接操作计时器寄存器。通过注意这个建议,您将认识到许多优点,其中之一是在代码库的不同位置处理多个不同的计时器调用时,几乎完全避免了复制/粘贴错误和打字错误。此外,编译器通常可以在优化方面做得更好,甚至可以内联代码,因此您可以获得性能和代码大小。这个建议的一个必然结果是保持HAL的单个功能尽可能小——避免创造“瑞士军刀”(大型功能和许多责任)。这些小型、单一用途的函数不仅更容易理解和维护,而且通常也更容易让编译器进行真正的优化,这可能看起来有点违反直觉。
  • 多考虑一下如何使用内存。例如,您真的需要动态内存管理吗?堆栈真的是存储复杂数据结构的好地方吗?功能安全和高完整性软件的标准通常强烈反对动态内存管理和在堆栈上存储复杂或大的数据结构,这是有充分理由的。
  • 如果你的工具链支持最坏情况堆栈深度分析在美国,研究和使用这些功能的投资将很快得到回报。

打印tf还是不打印tf不应该是问题

首先要认识到(或记住)的一件事是,如果您正在开发和调试嵌入式软件,您很可能在这样一个环境中执行代码,在目标上执行代码是通过调试器完成的。例如,如果您在IDE中工作,那么最简单的执行程序的方法就是启动调试器。

让我们深入了解细节,看看断点的力量。但是,首先,让我们稍微介绍一下作为调试工具的printf。

不使用printf的最重要原因是,在代码中添加printf语句会极大地影响代码的编译方式。不仅printf是一个函数调用,而且调用的参数也必须考虑在内。这意味着堆栈和寄存器的使用看起来完全不同,许多编译器优化将不会被执行,特别是当语句位于一个紧循环中时。

如果您的代码很复杂,或者依赖于C/ c++标准中已定义或未定义的C/ c++行为,那么这样的场景可能会产生不可预知的后果。可能发生的情况是,在向代码中添加printf时,代码运行得非常好,但在删除打印输出时,代码就会中断,反之亦然?顺便说一下,这是争取遵守MISRA的一个很好的理由。

另一个很好的原因是,printf是一个很弱的工具,因为它只能显示数据。第三个原因是,要更改打印输出的行为或添加更多的打印语句,您需要重新构建应用程序并将其下载到目标程序中。最后,在某些时候,您将不得不遍历代码库并删除添加的所有语句,即使它们都被#ifdefs保护。

断点的力量

因此,让我们暂时停止说教,看看可用的不同类型的断点。断点,以其最简单的形式,是在特定的源语句处的停止符号,以确保在到达正确的位置时,执行无条件地中断。一个好的调试器可以让你检查变量、寄存器、调用堆栈以及内存的内容。这样的代码断点本身非常有用,但它也可以与一个表达式相关联,该表达式的真值决定执行是否停止。

这使您可以在每次执行通过断点位置时关注有趣的情况,而不是检查有趣的变量。例如,如果您想要更仔细地查看循环索引变量的特定值范围内发生了什么,您可以设置表达式只在索引在该范围内时停止,而不是每次碰到该代码时都停止。当然,您也可以根据范围内的任何变量构造更复杂的stop表达式。

有时候你真的需要看到一个或多个表达式的值。这可以很容易地通过使用日志断点来实现,该断点的唯一目的是在调试日志窗口中打印消息而不停止执行。它本质上是一个调试器提供的printf,可以与布尔表达式结合,以确定是否应该生成消息。

一种非常强大的断点类型是数据断点。这将触发特定变量或内存位置被访问。如果您试图弄清楚为什么特定位置的数据值不是所期望的,那么它会非常有用。

为什么需要这样做?可能有几个原因,但这些问题的来源之一是指针。如果你使用指针,很有可能在某些时候你会得到一些错误的指针运算。虽然读取或写入错误的地址可能不会使程序失败,但它可能会产生非常奇怪的结果。这些类型的问题可能很难调试,因为实际的错误和您体验到效果的地方通常没有任何关系。

将数据断点(或任何类型的断点,就此而言)与调用堆栈窗口相结合会非常有启示性(图1).调用堆栈窗口将显示您来自哪里。它还提供了在调用链上下移动和检查参数值的机会。


其中一些类型的断点可能并不总是可用的,这取决于运行程序的确切设备和/或特定的调试探测。

一些目标支持实时读取内存,因此调试器可以在使用标准调试探针执行期间连续显示变量值和其他信息。

启蒙之路

如果您能忍受一些额外的流行词和形容词,那么让我们来谈谈一个真正令人惊叹的调试工具。跟踪是一种记录设备上的执行和其他类型的数据流的方法,比如中断信息和其他硬件事件(图2).例如,在一个时间轴上查看合并的事件数据可以很好地揭示系统的行为:中断是否在应该触发的时候触发,它如何与其他活动相关联?


使跟踪比常规调试更复杂的是,有许多不同类型的跟踪技术,以及访问跟踪数据的不同方法。此外,您可能需要启用跟踪的探测。以最好的方式利用跟踪的力量意味着在项目开始时考虑你需要做什么来使用它:

  • 需要考虑的一件事是设备的选择。它有跟踪功能吗?如果有,是什么类型的?该设备有跟踪和无跟踪版本吗?如果是这样,您可以用trace构建电路板的开发版本,然后在没有trace的情况下进行生产,以降低成本。
  • 跟踪还可以是分析和代码覆盖数据的推动者,因此提前考虑您在该领域的需求是有益的。

高质量的跟踪工具旨在消除跟踪复杂性的痛苦,并使用所有可用的跟踪信息,但您仍然需要理解硬件方面的需求。然而,在前期投入一些时间和资源作为调试和代码质量工具进行跟踪,将在遇到第一个棘手的问题时获得回报。

提高效率之路

本文中的一些主题可能看起来微不足道,但棘手问题的最佳解决方案通常属于这一类。找到软件问题的根本原因可能需要几天甚至几周的时间,也可能是一个快速而简单的过程。减少困难的一种方法是花点时间考虑如何结合调试器和跟踪工具的特性最好地使用您的代码库知识,而不是总是使用printf语句。随着时间的推移,这种工作方式会提高生产力和效率,更不用说内心的平静了。

更多信息请参阅提高软件代码质量在我们系列系列图书馆

Anders Holmberg是嵌入式开发工具公司的总经理IAR系统


从我们的合作伙伴

WavePulser 40iX高速互连分析仪

无与伦比的特征的观点。s参数(频域)和阻抗剖面(时域)的组合在一个单一的采集wi…

实时操作系统(RTOS)及其应用

2021年2月25日,
什么是操作系统。实时操作系统(RTOS)是一个轻量级的操作系统,用于在资源和时间限制下简化多任务和任务集成……

复杂嵌入式系统中动力轨序列测试

2021年9月20日
嵌入式计算系统通常需要多个电源电压来为微处理器、内存和其他车载设备提供电源。其他……

物联网设备调试工具和技术的专业指南

2021年3月23日
嵌入式系统的开发,其中软件和硬件必须很好地配合,已经变得极其复杂和具有挑战性,甚至…

协同处理器体系结构:一种用于快速成型的嵌入式系统体系结构

2021年7月6日
编者注:尽管它以数字处理性能和吞吐量而闻名,但协处理器体系结构提供了嵌入式系统…

通用DIN外壳,几乎任何应用

通用DIN外壳几乎任何应用Barry Manz DIN rail是基于一个如此优雅的概念,其增长正在扩大超过一个岑…

声音你的意见!

本网站要求您注册或登录后发表评论。
目前还没有任何评论。想开始对话吗?

从我们的合作伙伴

WavePulser 40iX高速互连分析仪

无与伦比的特征的观点。s参数(频域)和阻抗剖面(时域)的组合在一个单一的采集wi…

实时操作系统(RTOS)及其应用

什么是操作系统。实时操作系统(RTOS)是一个轻量级的操作系统,用于在资源和时间限制下简化多任务和任务集成……

复杂嵌入式系统中动力轨序列测试

嵌入式计算系统通常需要多个电源电压来为微处理器、内存和其他车载设备提供电源。其他……

物联网设备调试工具和技术的专业指南

嵌入式系统的开发,其中软件和硬件必须很好地配合,已经变得极其复杂和具有挑战性,甚至…

协同处理器体系结构:一种用于快速成型的嵌入式系统体系结构

编者注:尽管它以数字处理性能和吞吐量而闻名,但协处理器体系结构提供了嵌入式系统…
Baidu