嵌入的掩盖错误

开发任何软件时都不可避免会使用插头。 在嵌入中,他们慷慨的5美分也会引发硬件问题,但这是一首单独的歌。 但是当您陷入一个看似空旷的地方时,纯粹是编程伏击...对我来说,它们有三种。

最简单的方法是在对铁的库的手册,标准或配置程序不完全了解时。 在这里很清楚:并不是所有的举动,耐心和工作都已经用尽,还有另外五到两个实验,它就会变成现实。 示波器和科学技术人员可以提供帮助。


选择分频器配置CAN总线

更糟糕的是,当问题是拼写错误或逻辑错误时,您将无法看到空白点,除非您亲眼目睹并逐步调试二十次。 然后,天亮了,额头son作响,哭了起来,“好吧,你真是个混蛋!” 可以用

还有令人沮丧的第三种观点:一个小故障在一个外国图书馆中根深蒂固,在与铁的交界处爬行。 莎士比亚的激情激发了监视器的稳定光。 “为什么,它不能,系统无法以这种方式运行,因为它永远不会! 好吧,真的! 啊?! 不行 接收,签名。

结果,现实比预期的更广泛,更广泛。 几个例子:

历史第一 MicroSD闪存驱动器和DMA工作


回忆


您需要将数据转储到SD卡上的文件中。 当然,我既没有时间也不想自己编写文件系统和SDIO驱动程序,因此我选择了完成的库。 我将其设置为铁,一切正常。 首先。 然后事实证明,数据被疯狂地记录:卷是准确的,但是在文件本身中,三对字节的单独对被复制,然后消失,没有任何规律性。 不好!

实验开始。 我正在写测试数据-一切正常。 我正在写战斗-某种魔鬼。 我更改数据缓冲区的大小,刷新的频率,数据模板都没有用。 在缓冲区本身中,一切总是非常出色,内存中的数据随处可见。 而且,尽管如此,闪存驱动器上的故障还是在这里。

挖了几天狗。

诊断


问题出在库与DMA设备的交互中。

SD卡具有特殊性:它们只能以512字节的块写入。 为此,该库将数据缓冲在512字节数组中,并在填充后通过DMA从那里刷新到闪存。 但是!

如果我将大于<512xN +库缓冲区中的空白空间>字节的片段传输到记录,则该库(显然是为了不来回推动内存)是这样做的:它补充其缓冲区,并将其写入闪存,接下来的512xN字节会从缓冲区直接抛出到我的DMA中! 好吧,如果有东西未完成-它会再次复制到自己的东西,直到下一次。

一切都很好,但是DMA控制器要求将数据放在4字节边界对齐的内存中。 库缓冲区始终如此对齐,语言保证了这一点。 但是用什么地址,在复制了一部分数据之后,剩下的只有一个小字节的512xN就是从我开始的-天知道。 而且库根本不检查此:将地址原样传递给DMA控制器。

“他们送了一些笨拙的东西……一只狗陪着他。” 控制器以静默方式重置发送地址的低2位。 并开始传输。


该地址最初不是4的倍数,而是由倍数-瞧,从我的库缓冲区中最多将最后三个字节重新写入我的文件中,并且缓冲区中相同数量的字节将丢失而不会丢失。 结果,数据总量是正确的,操作可以顺利进行,但是磁盘却毫无意义。

治疗方法


在调用硬件记录功能之前,我必须立即添加另一个缓冲区。 如果写地址不是4的倍数,则首先将数据复制到该地址。 同时,由于合理选择了缓冲区大小,平均速度有所提高。 当然,它占用了内存,但是,当您有足够的空间使用时,有4个kb的充分理由-无限192字节!

历史2号 Rantime和一堆


序言


在下一次更改之后,程序开始崩溃,并且以某种方式下降得很厉害,从而将处理器扔到了Hard Fault处理程序中。 然后他就在开始之后甚至在执行到达main()之前就将其扔到了那里,也就是说,我的代码中没有一行能够执行。

第一个印象是“海狸死了,芯片需要更换”。 然后程序员给了橡树。 但是,不能,旧版本的固件可以稳定运行,但是新版本的固件在启动和我的代码之间的某些晦涩的组装深度中会稳定下降。 我没有任何假设,这是什么异端。

第一章


帮助互联网观看如何至少获得一些其他信息。 解析硬默认的后果的过程被谷歌搜索:寄存器状态,转储堆栈。 多皮尔。 用过的

原来,它是由于总线上的操作错误而崩溃的。 我认为这又是一种不平衡的访问方式-与第一个故事中的问题类型相同,但是从不同的角度来看。 但是最相反的是错误发生的地方。 它出现在运行时库中,也就是代码中,从理论上讲,它就像阳光明媚的猫一样被舔lick。

分析的继续表明,故障是尝试初始化局部静态变量的结果。

抒情离题
顺便说一句,考虑到反汇编的代码,我同时找到了一个问题的答案,这个问题我有时会问自己,但太懒了,无法立即使用google:当2个或更多线程可以尝试同时初始化这样的变量时,情况如何解决。 事实证明,在这种情况下,编译器使用信号量安排初始化,从而确保一次仅一个线程将通过整个过程,其余线程将等待直到第一个线程完成。 自C ++ 11起,此行为已标准化。 你知道吗 我不知道

第二章


一旦运行时参与了变量的构造,他也将在程序完成时调用析构函数(即使程序从未真正完成工作,这是微控制器的绝对标准)。 为此,他需要在某个地方存储有关他设法初始化的所有变量的信息。

正是在将此类信息存储在某种内部列表中的地方,运行时也下降了。 因为malloc()函数为该列表的元素分配了内存,并且根据标准,该函数会产生保证至少在8字节边界对齐的块,因此在成功调用了n次之后,它会产生一个在此边界未对齐的块。



新固件代码的更改破坏了malloc?! 但这怎么可能呢? 我没有完全重新定义malloc;我自己在其他任何地方都不需要它!

在编译器选项中很有用,用于查找一些关键字,帮助,但是到处都清楚地指出: malloc()保证沿着基本边界对齐的内存输出。 如果没有足够的内存,则为null指针

第三章


很长时间以来,我毫无意义地陷入了代码之中,设置了断点,遭受了痛苦,一无所知,直到某个时候它没有戳开,我仔细查看了malloc返回的地址。 在此之前,整个分析是要查看地址的最后一位是否为0x4。 现在,他开始完全比较由连续调用malloc发出的其他地址。

哦,一个奇迹!

所有成功的呼叫均从RAM空间发出地址(对于此地址,其地址为0x20000000和更早的地址),从一个呼叫到另一个呼叫依次增加。 而第一个失败的返回0x00000036。 也就是说,该地址不仅是未对齐的,而且根本不在RAM的地址空间中! 处理器试图在那里写一些东西,然后自然掉下来。

而且,令人惊讶的是,即使malloc()按照标准运行并在没有足够空间的情况下返回0,从程序崩溃的角度来看,这也不会做任何更改(除非可以早点弄清该错误的原因)。 仍然不检查malloc返回的值,但立即生效。 这是在运行时。

结语


增加了配置文件中的堆大小,并且所有内容均已修复。

但是在那一刻之前,我什至没有考虑它的体积。 我想,地狱是否向我投降了。 无论如何,我所有的变量和对象都是静态的或在堆栈上。 因此,由于惯性,我在其下保留了0x300字节,因为在所有模板项目中分配了堆下的某些卷。 但是,不,C ++运行时仍需要根据控制器的标准动态分配内存,并且数量相当可观。

生活和学习。

Source: https://habr.com/ru/post/zh-CN453944/


All Articles