气候已然入秋了,诱人的夜色带着阵阵凉意降临到喧闹了一天的大地上,洁白的月光裹挟着点点星光洒满窗台,是时中夜,寂然无声,周围的全部显得慈祥而又安静,我似乎听得见一旁熟睡的妻儿那漫长的呼吸声和带着愉快节奏的心跳声,是的,我又睡不着觉了,每逢作业中遇到一个一时找不出来的bug时,我总会“废寝不忘食”一番。
佛陀在金刚经上说,菩萨应无所住而生其心,便是说处理任何作业都不要执着,不要“住”在这个作业上,执着杯水车薪,就拿我来说吧,假使不纠结在这个bug上,好好睡觉养足了精力,第二天歇息充沛的大脑或许就忽然开了窍很顺畅地把bug找出来了,像现在这样躺在床上想入非非,看不了代码,思绪还不时地跑飞,清楚便是浪费时刻嘛,歇息欠好,第二天状况必定也欠好,处理问题的才能必定大不如常。从理性上来说,应该看穿这种执着,放下bug,而生起睡觉的心,可是,理性的我仍是执着地“住”在bug上,哎,看穿和放下哪有那么简单!
1
这次遇到的bug现象再清楚不过,笔者做的一款BCM产品,上电后代码跑着跑着就飞了,飞得我心有余悸,飞得我手足无措。
其实,Bug的体现十分有规矩:产品上电后大约八分多钟的姿态就重启复位了,一分不多,一分不少,照常理,规矩性重现的bug最好捉了,可是也不尽然。由于产品代码被划分红那么多模块,每个模块都有或许出问题,这种不知道怎样操作来着就跑飞了的bug其实并欠好处理。
2
传闻高人们捉Bug最喜爱直接剖析代码,领悟高的人犁庭扫穴,三下两下就能找到问题源头,领悟低的人在代码的迷雾中上下求索,终究也能拨云见雾找到真凶。而小白们捉Bug的首要手法便是调试和测验了。
笔者对着代码模糊半响,总算意识到所谓“软件一哥”之语仅仅掩耳盗铃,所以老老实实回归测验一途。
笔者之前都是上电后就开端做各种测验,做哪些测验也只管随心,并无一定之规。依照“宁可错杀一千,不行放过一人”的准则,这些测验触及的操作都有导致跑飞的严重嫌疑,假使真真地都细究起来,不免有鲁迅先生《狂人日记》中觉得事事可疑,人人可怖的狂生的愚笨之嫌。所以,为了逐渐缩小对bug的包围圈,笔者采用了毛主席闻名的游击战战略:“敌动我不动、敌不动我动”,已然不知道终究是什么操作形成的跑飞,那么现在什么操作也不做,我不动,看敌动否?
笔者在代码中加了一段测验程序,一旦跑飞复位,一个led灯就会闪耀一下。在测验台上拨拉几下开关,设置了产品的运转环境之后,洒家就静心屏息,正襟危坐,随同那潺潺不息的时刻之水神游天外起来。
笔者一边品茶消磨时刻,一边调查着led灯的现象,时不时抬起手腕看看手机上的时刻。八分钟过去了,‘不急,时刻未到’,十分钟过去了,’不急,或许每次跑飞的时刻都不大相同’,十五分钟过去了,‘咦?见鬼了,就加了那么一点测验程序,代码就不跑飞了?’二十分钟过去了,洒家开端着急起来,’之前每次都跑飞的,现在怎样回事?’
“哎呦,写Bug呐!”一位了解的搭档从我身边飘过,轻松的戏弄就像飞刀相同,直入我心。
笔者怔怔地看着测验台和慵懒地躺在一边的电路板,刚加上的那个测验led灯直愣愣地杵在电路板上,半吐半吞。“必定是测验条件发生了改变!”我下意识地想到,“发生了什么改变呢?”串接在电路板上的万用表静静显现着作业电流-1.6毫安,一点点没有招摇之意,这是低功耗状况下的休眠电流,整车厂要求低于3mA,我从4mA一路做下来,减到1.6mA,‘先给自己点个赞。’我打着闲茬,顺手打开了测验台的ACC开关,让BCM脱离了低功耗状况。
“我这次什么也没有操作,莫非是之前进行的某个操作导致了跑飞?那就费事大了,鬼知道其时进行过哪些操作?”我持续剖析,“又或者是操作次第,不是单个操作引发跑飞,而是某几个操作连起来导致的?那就更费事了!”我痛苦地思索道。
滴答,滴答,时刻无情地消逝着,我半躺在转椅上,任由思绪纷飞。电路和程序是历来都不会扯谎的,我置疑过天,置疑过地,唯一没有置疑过你,我盯着那个测验led灯,小声地喃喃自语。
Led灯像一个静静的美男子,全然不顾我的胡说八道,默然静立。忽然,就如电光火石一般,Led闪耀了一下,当它那炫美的蓝色余晖还在我眼前流连,我现已惊天动地地意识到,必定对错休眠状况下才会跑飞的,我抬了抬手腕,看了看手机上的时刻,算上上电后进入休眠状况的一分钟,加上方才脱离低功耗后运转的七分多钟,正好八分钟多!
3
依照这个思路,我从头设置了测验台,使得产品不会进入休眠状况,果不其然,每隔八分多钟led灯都会规矩地闪上那么一下,守时守信,从不失约。
那么,已然没有做任何操作,首要就可以把一切操控模块的嫌疑扫除去,剩余的便是通讯模块了。这款产品有两路CAN总线通讯,一路LIN总线通讯。持续调试程序,先把CAN总线通讯屏蔽掉,测验发现led持续雷打不动地规矩闪耀,扫除CAN通讯嫌疑。然后试着把LIN通讯屏蔽掉,Led总算不再嬉闹了,它挑选了在机器的国际中如如不动,直入三昧之境。
那么,LIN通讯程序错在哪儿了呢?这块程序大致包含时刻槽调度程序、报文接纳中止服务程序、报文解析程序三大块,为了进一步缩小嫌疑,我把canoe撤掉,这时LIN总线上没有了实践的LIN通讯,便不会履行报文接纳ISR和解析程序,如此这般,一通测验下来,这个Bug依然故我,顽固而刚强,便是不愿往生极乐。问题了然了,必定是时刻槽调度程序出了问题。
这段程序说来也很简单,以20ms为时刻槽长度,在每个时槽抵达时发送报文头,然后依据是发送数据场仍是接纳数据场设置相关寄存器。传闻剖析程序不贴代码的都是耍流氓,我就先上为敬,把代码贴上来。
void l_ifc_tx(l_sch_table_item *sch_item)
{
l_u32buffer_data;
buffer_data= *(sch_item->data);
BLIN.linflex->BDRM.R= buffer_data;
buffer_data= *(++sch_item->data);
BLIN.linflex->BDRL.R= buffer_data;
sch_item->data–;
BLIN.linflex->BIDR.B.DFL= sch_item->datalen – 1;
BLIN.linflex->BIDR.B.CCS= 0;
BLIN.linflex->BIDR.B.ID= sch_item->id;
if(l_SEND== sch_item->mode){
BLIN.linflex->BIDR.B.DIR= 1;
}else{
BLIN.linflex->BIDR.B.DIR= 0;
}
BLIN.linflex->LINCR2.B.HTRQ= 1;
}
其间,l_sch_table_item这个结构体的界说如下:
typedef struct
{
l_ifc_handle handle;
l_u8 id;
l_Resp_mode mode;
l_u8 datalen;
l_u32 *data;
l_u8 timelen;
}l_sch_table_item;
就路还家,知道问题出在哪段程序里就算找到Bug的家门了,胆大心细眼又尖的高手或许现已发现了Bug地点,没错,便是这句:
buffer_data =*(++sch_item->data);
依照C言语运算符的优先级,上述句子的履行次第是,先找到sch_item的成员变量data,然后对data履行++,最终履行*(新地址)的操作。
居然那么随意地对指针进行了算术运算!!!这种行云流水的洒脱用在日常生活上却是很美,用在编程上便是自讨苦吃了。看看这条句子,sch_item->data是个指针,在这里的++运算使得它每隔20ms都会累加一次,意味着1秒钟累加50次,一分钟累加3000次,八分钟累加24K次,这款产品的RAM为24KB,八分钟就累加到RAM空间之外,便是无效的地址空间了,加上程序上电开端履行第一次通讯调度的时刻,正好是8分多钟!
跋文
嵌入式软件范畴的编程宝典MISRA C的规矩101明确指出,不能对指针进行算术运算,意图是为了避免指针指向无效的内存空间,言之凿凿,十分了解MISRA C的笔者却不闻不问,拿豆包不妥干粮,哎,不听老人言,吃亏在眼前,早干嘛去了?!