作者:Surest
一、背景介绍
VxWorks提供了一种错误检测及报告的机制帮助开发者调试软件,6.9版本中称为edr功能。在创建内核时,在workbench组件编辑component configuration中可以找到如下组件
这个edr功能的特性:在RAM中保留一块内存区域,热启动时不会擦除该块区域的内容,此区域用做edr记录的空间。VxWorks内核在处理CPU、用户以及地址等各种各样的异常时,会在EDR中记录异常发生的任务、地址、堆栈调用、异常触发时间,然后再进行保护机制例如热启动等,当重启发生后,我们可以通过VxWorks提供的show routine在shell中进行查看发生的故障,进行问题回溯。
edr功能记录的种种信息对于我们评估驱动程序、应用程序、调试bug等具有非常大的帮助,但是edr毕竟是在RAM中记录,一旦冷启动,所有的记录信息都会消失。
基于想要保存edr中各种重要的记录的想法,我们可以设置一些列的辅助手段,将EDR中记录的内容写到非易失存储器当中。
二、edr 概要
edr中记录的异常种类极多,但是按照严重级别可以分为几类,如图所示发生异常的事项(Table 14-1),严重级别(Table 14-2),可以注意到有一个envent type是user,这表示edr功能运行开发者在运行时自动触发异常记录,api如下所示。
一般来说我们程序遇到的几项异常都涵盖在当中,异常记录完毕后,shell中通过edrShow就能查看发生的异常内容,edr记录的格式。
三 辅助手段
前面说过我们可以通过辅助手段将异常记录导入到非易失存储器,下面来介绍我实践过的做法。
首先,edr功能存在一个vxWorks中最常见的一个功能,钩子函数。edrErrorInjectPrePostHookAdd(FUNC(int post))这是我使用的函数,功能是在内核发现异常创建记录时会调用一次钩子,异常记录完成时调用一次钩子,通过函数指针中的post确定是创建前还是创建后。
钩子通知我们异常记录完成后,接下来就是获取异常记录。这个东西是在内核里搞得,我们怎么弄出来?内核也是人写的,比如说edrShow可以把所有异常记录都记下来,显示出来,那么肯定有办法把异常记录形成字符串。
接下来我在workBench中找到了edrShow的实现,当调用钩子时,我把edrShow中流程走一遍,将形成的字符串不进行打印输出直接写文件保存在硬件设备中的/ata0:0(也可以是其他文件系统设备、裸层flash)。
最后,把具体的代码中贴出来。我是在钩子函数中通过信号量通知任务进行异常记录,记录完成后进行重启操作。
while( 1 ) { semTake( pTempDevId->semCIdEdrFile, WAIT_FOREVER ); /* clock_gettime(CLOCK_REALTIME, &tp); nowSec = tp.tv_sec; gmtime_r(&nowSec, &timeBuffer); timeLen = strftime(datetime, 64, "%Y-%m-%d %H:%M:%S", &timeBuffer);*/ if ((pLog = ( EDR_ERR_LOG *) edrLogBaseGet ()) == NULL) { FUNC_LOG ( "edrShow: cannot get address of persistent memory region\n" ); return (ERROR); } if ( ( recordNum = edrErrLogNodeCount (pLog)) == ERROR ) { FUNC_LOG ("edrShow: the ED&R log is corrupt, unable to display records.\n"); return (ERROR); } if (edrErrLogIterCreate (pLog, &iter, recordNum - 1, 1 ) ) { EDR_ERR_LOG_NODE * pNode; while ((pNode = edrErrLogIterNext (&iter)) != NULL) { EDR_ERROR_RECORD * pRecInLog = (EDR_ERROR_RECORD *) pNode->data; EDR_ERROR_RECORD * pER = (EDR_ERROR_RECORD *) recordBuf; UINT32 genCount = pNode->genCount; memcpy (pER, pRecInLog, pRecInLog->size); /* ensure node wasn't updated during copy */ if ( genCount == pNode->genCount) { /* skip empty (recovered) records */ if (pRecInLog->size == 0) { FUNC_LOG ("Incomplete record!\n"); } /* only display records meeting the display criteria */ if ( pRecInLog->size != 0 ) { unsigned int uiTempKind = pER->kind & EDR_FACILITY_MASK; if( uiTempKind == EDR_FACILITY_KERNEL || uiTempKind == EDR_FACILITY_INTERRUPT || uiTempKind == EDR_FACILITY_USER ) { edrErrorRecordDecode (pER, edrFileBuf, EDR_WIDTH ); if( fprintf (fpVm,"%s", edrFileBuf ) < 0 ) { FUNC_LOG ("Incomplete record!\n"); } fprintf( fpVm,"%s\n\n"); memset( edrFileBuf, 0, EDR_WIDTH ); fclose( fpVm ); reboot( 0 ); } } } else { fprintf ( fpVm, "Record was updated while trying to be displayed!\n" ); } } } }
在发生异常时,系统自动重启完成整个系统工作恢复,同时能够在文件系统中找到文件相应异常的记录,通过具体信息调试程序中出现的问题。