一份呕心沥血整理的bug list

 

1、 指针相关

 

(1)      指针类型转换出错

 

比如下面这个写串口的函数,长度len是一个int型指针:

 

图1 写串口API示例

 

在调试时发现当串口无回复重发时再次去写串口,传进去的长度变了,无法正常写串口

一份呕心沥血整理的bug list_第1张图片

 

图2串口调试log截图

 

想要写的数据是上面红框中,实际写的数据是下面红框中,检查发现是传进去的len指针类型不对,把一个char型的 len传进去了,下面的代码很容易出错:

 一份呕心沥血整理的bug list_第2张图片

 

但是Char型指针应该会自动强制转换为int型指针的啊?怎么还会出错呢?

 

         Int型指针转化为char型指针肯定不会出错,而char型指针转化为int指针可能(极大可能))出错,为什么呢?

         数据都是以一定的规则存储在对应内存区里,内存最小单元为1byte,数据最终对应的都是硬地址,char型数据占一个内存单元,int型数据占4个内存单元,short型数据占两个内存单元,char型数据的地址都是1的倍数,int型数据地址都是4的倍数,short型地址都是2的倍数,这样底层才能保证数据整齐,内存好管理。

一个int型指针转化为char型指针没问题,是因为4的倍数肯定也是1的倍数,因此int型地址可以随意转化成char型或者short型地址,而把一个char型指针转化成int型指针那极有可能出错,因为1的倍数不一定就是4的倍数,指针在获取数据时可能就会指不到那个位置(比如地址0x0002转成int地址就会出错),即使指到了那个位置获取到的数据也会不对,因为char型指针转为int型指针,相当于多要了3个单元的内存,你怎么知道那3个单元的内存里面原来没有存别的数据呢?

         一句话总结指针类型转化:长的指针类型可以转化为短的,短的不能转化为长的。

 

 

 

一份呕心沥血整理的bug list_第3张图片

 

图3 指针转换图

 

 

(2)      指针没有初始化

 

向没有初始化的指针去引用指针所指内存的值,或者去赋值,肯定会使系统挂掉。指针在没有指向有效的缓存地址时,是没有意义的。如下面代码就会使系统崩溃。

 

 

2、 内存相关

 

 

(1)      数组下标越界——一个“=”引发的血案

 

系统初始化时去flash里取数据存到用户的缓存中,用宏定义定义缓存的大小,调试时发现,把数据定义成1024byte,没问题,定义成384byte,一上电就挂掉了,为什么会这样?定的小点反而有问题?

 

图4 代码片段截图

 

数据对齐问题?内存不够溢出?

384改成256可以不?可以;

改成512可以不?不可以;

把起的线程一个个去掉,只剩下主线程,单步调试,并打印剩余内存,

发现系统起来时印出来剩余内存51K,当跑到cfg_init这个函数里面剩余内存打印出来就不对了,变成了几万!

仔细检查发现,有一个地方对数组的操作越界了,简单的说定义了数组a[10],却操作到了a[10],数组下标从0开始,因此a[10]数组元素是a[0]~a[9],没有a[10]。

 

一份呕心沥血整理的bug list_第4张图片

图5 越界代码片段截图

 

越界和溢出是C语言编程中两个不同概念,都是对内存的不当操作,那有什么区别?引用网友回答:溢出是空间不够,越界是访问了非法地址,越界就是你打水的时候拿错桶了,溢出就是你打水的时候水满出来了。

 

举个例子:

 

一份呕心沥血整理的bug list_第5张图片

 

(2)      堆栈溢出

 

堆和栈是两个不同概念,堆是程序员申请的内存空间,通过malloc和free函数申请和释放,而栈是函数运行时系统用到的内存空间,比如用于函数调用时压栈,函数运行时存放临时的变量等等,堆和栈其实都是内存没有区别,只是用途不同而被叫了不同名字而已;

 

堆栈溢出是程序调试运行过程中时刻要注意的问题,

 

栈溢出是指创建线程分配的栈空间,不够线程运行时用了,栈溢出分两种:

 

① 函数嵌套调用层数太多,导致一层层压栈栈空间不够,一般很少发生(反正我没遇到过);

② 函数运行过程中申请了很多局部变量,特别是很大的数组变量,导致栈空间耗完;

 

堆溢出见上面例子;

 

 

(3)      内存泄漏

内存泄漏指的是程序员在申请了内存用完之后内存没有释放掉,一般写代码的时候都会写了malloc后面再写free,没有谁那么粗心free都不写,需要注意的是写了malloc而且也写了free内存仍然泄漏的情况,malloc之后程序可能跑到的路径有多少种?每个路径都写free了吗?

下面的例子是一个线程向另一个线程发消息,当发送失败时由本线程释放掉内存,当发送成功时由另一个线程处理完数据释放掉。

一份呕心沥血整理的bug list_第6张图片

 

图6 malloc代码片段截图

 

 

         当然有时候内存泄漏也有可能是厂商提供的SDK底层的内存泄漏,在8711和4004 SDK上均出现过内存泄漏的问题。

 

3、 操作系统相关

 

问:在一个平台测试稳定跑了很长时间的代码,移植到另一个平台会出问题吗?

答:不可能,就算两个平台非常相似,也不可能一点错误都不出;

 

举几个平台相关的bug:

①RTL8711 /古北8782往已经声明到还没有创建的队列里面发消息不会使系统崩溃只是返回错误,而古北7687会挂掉;

②古北8782往已经关闭的socket里面写数据,会返回错误,而古北7687则会使整个系统挂掉;

③RTL8711/古北8782 在malloc一段缓存用完后free,不小心多free了一次,没有出问题,而古北7687一段缓存free了两次则是整个系统挂掉了。

所以没有完美的系统,没有毫无bug的系统,只有相对稳定和不稳定,代码经过很多平台的使用考验才慢慢趋于稳定的。

 

(1)      消息队列相关

 

① 读队列前必须把用于装消息的消息清空,但注意不能把队列清空!

有些系统比如threadX,队列变量是一个指向结构体的指针,这个结构体里面存着队列相关的变量,如队列ID,前一个消息指针,当前消息指针,前一个队列指针等等,清0后会出现指针错误;

 

② 一个队列声明了但还没有创建(即还没有定义),可以去读写队列吗?

 

答:有些平台读写没有问题,有些平台却有问题,比如高通4004+threadX平台就会出错,而RTL8711+freeRTOS 却没有问题,古北7687+freeRTOS也会出错;原因是操作系统底层保护机制不一样,有些系统读写队列API也许会首先判断该队列是否已经被创建,有些系统也许没有,嵌入式中用的系统常常都是裁剪过的,功能不一定完整,所以保险的做法是用户自己判断队列是否已经创建。

 

③ 创建队列时要仔细阅读API使用说明

 

创建队列的API形参一般有:队列栈空间大小、队列名字、消息大小、队列深度(即可装多少条消息)不同操作系统会有差别,以freeRTOS 和threadX系统为例,图7为threadX系统与freeRTOS创建队列API,可以看到threadX系统创建一个队列要传进去的参数很多,包括队列指针、名称、消息大小、队列栈指针(用户需要自己定义一个缓存用户队列栈空间)、队列栈大小,而freeRTOSmart系统创建队列只需要两个参数:队列大小(即能装多少条消息)和消息大小。

a、threadX系统创建队列API

 

b、freeRTOS系统创建队列API

 

图7 队列创建API对比

 

         另外值得注意的是,形参的取值范围,在freeRTOS中,消息大小可以是任意大小,随便定义多少byte都可以,而在threadX中消息大小只有以下4中取值:1个 word、4个word、8个word、16个word,这就意味着使用过程中不是那么灵活,比如实际使用过程中消息大小是20byte,那么创建队列时消息大小就只能选8个word(32byte),

队列栈的大小也要定义对,队列栈的大小也要定义定对,如果是10条8个word大小的消息,队列栈大小就是320byte,详见图8。

         threadX实际调试时,由于一开始队列用错,造成了很多诡异的现象,比如发现一旦队列收到消息去读队列,读串口函数的形参就莫名其妙被改变了。

 

一份呕心沥血整理的bug list_第7张图片

 

图8 threadX系统创建队列API说明截图

 

 

④对API封装注意事项

         在实际时使用时,往往不会直接用操作系统API原型,而是先封装成统一的格式后再用封装过的API,与硬件相关的函数也一样先封装一下,相当于一个平台适配层。这样做的目的是易于移植,把代码从一个平台移植到另一个平台时,应用层代码不用变,只需要修改适配层就好了。

         从freeRTOS平台移植到threadX平台,同样的封装格式,只修改封装函数里面内容,发现系统并不能如预期跑起来,出现了一些很诡异的错误,咨询FAE,说是最好封装成跟原来一样的格式。如图9,可能是4004threadX平台底层函数调用有一些限制。

一份呕心沥血整理的bug list_第8张图片

 

图9 两种封装格式对比

 

(2)      线程相关

在4004+threadX系统调试时,一开始只是创建了一个线程,线程里面只是一句打印语句和延时,但是系统就是死活跑不起来,检查发现是线程入口函数格式定义有问题,函数的定义形势必须跟他规定的保持一致。

 


 

 

虽然参数 which应用层没有用到,可能底层会用到呢。图10为线程创建函数说明和使用示例。

 

 

 

 

一份呕心沥血整理的bug list_第9张图片

一份呕心沥血整理的bug list_第10张图片

 

图 10 threadX系统创建线程API说明截图

 

 

(3)      flash读写

首先要了解的是芯片的flash结构,留给用户用的是哪一段,厂商提供的API怎么用,写flash的API是只能一次写一个块还是每个word都可以单独写?擦的时候是一次擦多大?(一般是一次擦4K);另外要考虑的是芯片flash擦写次数是有寿命的,每次提交数据,应该先把要更新的数据全部更新到自己定义的缓存中,再擦flash一次写进去,而不是每写一个擦写一下flash;所以cfg模块设计时提供给别的模块的API应该是设置数据一个函数,提交数据到flash一个函数,这样在用户有多项数据要写入时,先多次调用调用设置数据的函数一项一项设置数据,然后再调用一次提交数据的函数,这样就仅仅擦写了一次flash;

其次是要考虑互斥和同步,在开发中发现偶尔有设备重新注册的bug出现,原因是存在

flash中的设备账号丢失了,分析log发现重新注册前,用户进行复位设备操作,然后wifi板执行进行相关网络通信处理,此时开启了超时计时器,而网络通信数据处理和超时判断在两个不同的线程中,在通信线程中收到网络来的数据进行处理后,把一个标志写入flash的要提交的时候,刚好超时计时器也到了,此时刚好内核时间片转到超时处理的线程,也要把同一个标志设成不同的值写入flash,进行提交,可能是前一个线程提交到一半内核转过去了造成flash中数据丢失了,解决的办法是在进行读写操作时加互斥锁,保证一个线程读或写时,不产生上下文切换内核被其他线程抢占,全部操作完才退出,不切换到其他线程去,所以flash读写应跟厂商了解清楚提供的API底层有没有加互斥,如果加了那就不用再在上层加。

 

(4)串口驱动

首先,通信的双方必须遵守约定好的通信时序,比如wifi板发送数据两个数据包帧之

间不能超过约定的频率否则会造成电控板处理不过来串口死掉;Wifi板读串口的频率则根据电控板收到数据处理后发送回包的延时时间来确定,读的太频繁会浪费内核资源,读的太慢,又会造成底层驱动缓存中的串口数据被新数据覆盖,造成数据丢失;而驱动层的缓存大小要根据需求定义合适的大小,太大了会浪费内存资源;

数据帧的处理上,要考虑从底层驱动读到的数据每次都是完整的还是有时候是片段

分几次到达的?大多数时候读到的都是完整的,少数时候一个完整数据包会分几次读到,这跟底层串口产生读中断的机制及驱动层的实现有关,所以最好开一个FIFO缓存(环形缓存)对收到数据帧进行累计循环处理;

串口标准上,开发过程中发现同样的一块8711wifi板跟空调电控板能通跟冰箱电控

板却不能通,发数据无响应,而4004wifi板跟空调电控板以及冰箱电控板都能通,为什么会这样?异步串口通信中标准的1byte的数据电平是:1bit起始位低电平+8bit的数据电平+1bit的校验位电平(奇校验和偶校验,如果配置无校验则没有)+1bit停止位(高电平)(如图1)每1bit占的长度则可根据设置的波特率算出来,波特率越大,长度越短。

 

一份呕心沥血整理的bug list_第11张图片

图11  串口1byte数据电平格式

 

用示波器测量发现冰箱的电控板的TX口(即wifi板的RX口)在无数据时是低电平(而标准的电平应该保持高电平),结合8711串口驱动代码,8711在写串口时首先会去判断读串口结束了没(判断方式是当前系统tick值与最近一次串口读中断tick值差值是否大于一个阈值),如果没有结束则一直等,冰箱电控板在空闲时保持低电平导致8711误认为是数据起始位一直有数据一直产生读中断,写串口时一直阻塞在等待读数据完成无法去写,所以无法通信;

那为什么4004的跟冰箱电控板可以通?他的串口驱动是怎么实现的?可能原因是:

a、持续的低电平在4004底层没有产生串口中断,其产生中断的机制更加完备,可能起始位低电平和停止位高电平都同时具备才能产生中断,这样持续的低电平无法使其产生中断,8711的在空闲时串口会读到一大堆的0而4004的不会读到0,可以断定持续低电平在4004中确实没有产生串口中断;

b、驱动层实现方式不一样,写串口的时候不去等读串口完成,直接写,全双工的板子可以这样做;

 

 

4、  编译相关

 

(1)位移运算

 

有些编译器会自动优化一些运算,导致计算结果不正确

 

 

 

 

上面这个宏定义先左移n位再右移m位,Quacomm4004+threadX平台编译器会直接右移m-n就完事;

 

(2)经常出现的告警和错误

implicit declaration of function

 

一份呕心沥血整理的bug list_第12张图片

 

你可能感兴趣的:(工作总结,C语言编程)