当前位置:首页 > 芯闻号 > 充电吧
[导读]做为程序员,最怕什么?Bug?大家都清楚,调试期的 Bug 并不可怕,那怕是那些神龙见首不见尾的 INT(随机、没有规律) Bug。做为嵌入式程序员,也是一样的。一般来说嵌入式系统都提供了异常分析的方

做为程序员,最怕什么?Bug?大家都清楚,调试期的 Bug 并不可怕,那怕是那些神龙见首不见尾的 INT(随机、没有规律) Bug。

做为嵌入式程序员,也是一样的。一般来说嵌入式系统都提供了异常分析的方法,特别是强大的调试工具,这些工具使用在 PC 上编程使用的工具是一样的,例如:Visual Studio 系列。但是一些专用的、或小的嵌入式系统,可能会提供专用的调试工具。虽然从功能上来说,没有微软提供的 VS 功能强大,使用起来也不太方便,但也会提供类似的调试功能。这里我主要讨论的还是微软提供的工具。
目前,在车载与 PND 市场,使用 WinCE 系统的比较多。在 WinCE6.0 系统中,如果应用发生较严重的错误时,一般都会弹出系统标准的、令人十分讨论的应用错误对话框。大概提示:XXX.exe出现严重错误,必须被关闭。
如何解决此类问题呢?
只要能接上调试串口,或与调试工具连接,如VS2008等,获取出错时的异常信息后,就可以来分析异常可能的原因。
但如果设备已经处于量产状态,无法连接输出 LOG 的串口和调试 USB 口时,如何能捕捉到异常信息呢?
在无法彻底解决此类问题的情况下,有人就想能不能不让系统显示那个错误对话框。为了能使应用“优美”的退出(网络上的说法),即程序退出时不出现述的错误对话框,有人曾试着去修改 WinCE 提供的内核代码,但这部分应该是属于未开源的部分。所以此方法也行不通的!


解决此类问题的根本办法当然是提高编码的质量,然后加强质量保证(即测试),尽量将 Bug 消灭在研发阶段。因为研发阶段,有大量的调试工具可以使用,如下述的第一种方法。
在没有调试工具可以依赖时,有没有办法获取到异常信息呢?方法当然是有的,如下述第二种和第三种方法。为什么要说第一种方法呢,因为它提供的信息是最基础的,是后面两种方法都要用到的基础。在这里,我重点推荐的是第三种方法。因为它的处理比较独立、在 WinCE 系统中比较有效、且方便集成到已有代码中,实现异常捕获。


第一种方法:如果有输出 LOG 串口可说时,串口输出的异常信息,加 MAP 文件一起分析错误的出处,可以到函数一级。所以要求在调试时一定要将对应版本的 MAP 文件一起保留,用于后继异常问题的分析。

对于如下的测试代码:


void TestCrashFunc(void)
{
  int *pNullPoint = NULL;

  RETAILMSG(1,(L"-----------------------------%drn",pNullPoint));
  *pNullPoint = 0;
  RETAILMSG(1,(L"-----------------------------%d,%drn",pNullPoint,*pNullPoint));
}

void CallCrashFunc(void)
{
  TestCrashFunc();
}

void CSmartDeviceMFCDlg::OnTimer(UINT_PTR nIDEvent)
{
  // TODO: 在此添加消息处理程序代码和/或调用默认值
  if(1 == nIDEvent)
  {
    KillTimer(1);
    CallCrashFunc();
    
    // 其它的功能
  }
  CDialog::OnTimer(nIDEvent);
}


在 WinCE6.0 和 WinCE7.0 下运行时,串口的输出内容基本上是相同的,但在 WinCE7.0 下没有出错的对话框。
串口中输出的 Crash 信息如下:



Exception 'Data Abort' (0x4): Thread-Id=0780000a(pth=c08e24e0), Proc-Id=077e000a(pprc=c088da7c) 'SmartDeviceMFC.exe', VM-active=077e000a(pprc=c088da7c) 'SmartDeviceMFC.exe'
PC=00011738(SmartDeviceMFC.exe+0x00001738) RA=4002ac4c(coredll.dll+0x0001ac4c) SP=0004f6a8, BVA=00000000
Exception 'Raised Exception' (0x116): Thread-Id=0780000a(pth=c08e24e0), Proc-Id=00400002(pprc=8360b5e0) 'NK.EXE', VM-active=077e000a(pprc=c088da7c) 'SmartDeviceMFC.exe'
PC=eff6ed60(k.coredll.dll+0x0001ed60) RA=8052a62c(kernel.dll+0x0000e62c) SP=d9bbf3b4, BVA=ffffffff


从对应的 MAP 文件中查到是 TestCrashFunc 函数出错(PC 指针 0x00001738 + MAP 文件中的 Preferred load address 偏移量),此例中出错时位置为:0x00001738 + 00010000 = 00011738:



SmartDeviceMFC
 Timestamp is 539fa9e3 (Tue Jun 17 10:37:23 2014)
 Preferred load address is 00010000
......
 0001:000006a8       ?InitInstance@CSmartDeviceMFCApp@@UAAHXZ 000116a8 f   SmartDeviceMFC.obj
 0001:00000708       ?OnCbnDropdownCombo1@CSmartDeviceMFCDlg@@QAAXXZ 00011708 f   SmartDeviceMFCDlg.obj
 0001:00000714       ?TestCrashFunc@@YAXXZ      00011714 f   SmartDeviceMFCDlg.obj
 0001:0000075c       ?BeginModalState@CWnd@@UAAXXZ 0001175c f i SmartDeviceMFCDlg.obj
 0001:00000768       ?EndModalState@CWnd@@UAAXXZ 00011768 f i SmartDeviceMFCDlg.obj
 0001:00000774       ??_GCComboBox@@UAAPAXI@Z   00011774 f i SmartDeviceMFCDlg.obj


由此可见 WinCE7.0 系统对这种对空指针赋值等异常是做了一些处理的,至少不再弹出那个令人十分讨厌的对话框,也不影响后继其它功能的执行。在 WinCE6.0 下如果出现类似的对话框,则应用就会退出。


第二种方法:使用 __try 和 __except。在开源的多媒体播放器 TCPMP 中,就有如下的用法。
先定义两个宏,然后将重要的处理线程代码包含在定义的这两个宏中,以捕捉两个宏之间代码出现的异常。这样做有一个缺点:但代码量很大时,就需要增加很多对这两个宏的调用。



#define SAFE_BEGIN __try {
#define SAFE_END ;} __except (SafeException(_exception_info())) {}


可以看到 WinCE 下的使用方法,与 PC 上 SEH(Structured Exception Handling)是一样的。如下所示:



__try 
{
   // guarded code
}
__except ( expression )
{
   // exception handler code
}


以下是 TCPMP 中一个关键线程的异常处理代码(TCPMP 线程的代码,没有完整的给出,有兴趣的童鞋请自己去看 TCPMP 的源代码),其中两个定义的异常处理宏,将线程的所有代码包含在内。



static int ProcessThread(player_base* p)
{
  int Result = ERR_NONE;

#ifdef MULTITHREAD
  SAFE_BEGIN

  while (p->Wnd)
  {
    ......
    if (p->RunProcess)
    {
      processstate State;
      State.Fill = p->Fill;

      p->Timer->Get(p->Timer,TIMER_TIME,&State.Time,sizeof(tick_t));

      //DEBUG_MSG1(DEBUG_PLAYER,T("Process Time:%d"),State.Time);

      Result = p->Format->Process(p->Format,&State);

      if (Result == ERR_SYNCED)
      {
        ......
      }
      else if (p->Fill && (Result == ERR_END_OF_FILE || Result == ERR_BUFFER_FULL
        || (Result == ERR_NEED_MORE_DATA && (p->NoMoreInput || State.BufferUsedAfter >= p->CurrBufferSize2-2))))
      {
        ......
      }

      ......
    }
    ......
  }

  SAFE_END
  return 0;
}


此种实现方法,最最关键是 SafeException() 函数中分析与记录异常信息的办法。
但由于在 TCPMP 中,获取异常的信息与 TCPMP 的软件框架结合在一起。需要移植此部分代码到其它工程时,需要将有用的代码分离出来,其实这个也比较简单。
只要将与 EXCEPTION_POINTERS 相关的代码拿出来即可。



int SafeException(void* p)
{
  EXCEPTION_POINTERS* Data = (EXCEPTION_POINTERS*)p;

  // 删除了无关的代码 - 此部分代码是 TCPMP 中的代码,所以未做排版。
  {
    {
      const uint8_t* ContextRecord = (const uint8_t*) Data->ContextRecord;
      EXCEPTION_RECORD* Record = Data->ExceptionRecord;

      switch (Record->ExceptionCode)
      {
      case STATUS_ACCESS_VIOLATION:   Name = T("Access violation"); break;
      case STATUS_BREAKPOINT:       Name = T("Breakpoint"); break;
      case STATUS_DATATYPE_MISALIGNMENT:  Name = T("Datatype misalignment"); break;
      case STATUS_ILLEGAL_INSTRUCTION:  Name = T("Illegal instruction"); break;
      case STATUS_INTEGER_DIVIDE_BY_ZERO: Name = T("Int divide by zero"); break;
      case STATUS_INTEGER_OVERFLOW:   Name = T("Int overflow"); break;
      case STATUS_PRIVILEGED_INSTRUCTION: Name = T("Priv instruction"); break;
      case STATUS_STACK_OVERFLOW:     Name = T("Stack overflow"); break;
      default:              Name = T("Unknown"); break;
      }

      if (Record->ExceptionCode == STATUS_ACCESS_VIOLATION)
      {
        if (Record->ExceptionInformation[0])
          Name = T("Write to");
        else
          Name = T("Read from");
      }
      
      //...... 关键是处理 EXCEPTION_POINTERS 结构体相关的成员
      // 其它一些相关的,如可执行程序文件名等,根据需要来获取
  }
}


第三种方法:使用函数 AddVectoredExceptionHandler()。
在 WinCE 下使用此函数,需要包含头文件: TlHelp32.h 和库文件: toolhelp.lib。由于此函数属于 WinCE 示公开的 API,所以帮忙只要以 PC 上为准。
使用此函数,是向 WinCE 系统注册一个矢量异常处理程序,但有异常发生时会调用此处理程序。
函数的原型如下(MSDN),各参数具体的含义,请参考 MSDN。这里就不做翻译了。



PVOID WINAPI AddVectoredExceptionHandler(__in ULONG FirstHandler, __in PVECTORED_EXCEPTION_HANDLER VectoredHandler);

以下代码,演示了如何使用 AddVectoredExceptionHandler() 函数:
(1) AddVectoredExceptionHandler(1,MyVectoredExceptionHandler);


(2) 定义异常处理程序

LONG WINAPI MyVectoredExceptionHandler(struct _EXCEPTION_POINTERS *pExceptionInfo)
{
  typedef ULONG (WINAPI *lpGetThreadCallStack)(HANDLE,ULONG,LPVOID,DWORD,DWORD);
  /* 在使用时,必须包含一些头文件。这些头文件,需要从 WinCE 的安装目录中获得。
  OS Versions: Windows CE 5.0 and later.
  Header: Pkfuncs.h.
  */
  typedef struct _CallSnapshotEx
  {
    DWORD dwReturnAddr;
    DWORD dwFramePtr;
    DWORD dwCurProc;
    DWORD dwParams[4];
  }CallSnapshotEx;
  // 打印 Dump 信息 
  ......
  // 打印 SP 堆栈
  ......
  ULONG *punSp = (ULONG *)pExceptionInfo->ContextRecord->Sp;
  // 获取线程堆栈调用
  HMODULE hCore = LoadLibrary(L"coredll.dll");
  if(NULL != hCore)
  {
    lpGetThreadCallStack pGetThreadCallStack = (lpGetThreadCallStack)GetProcAddress(hCore,L"GetThreadCallStack");
    if(NULL != pGetThreadCallStack)
    {
    }
  }
  // 获取进程内 dll 信息
  MODULEENTRY32 CurrentModule;
  HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,GetCurrentProcessId());
  if((HANDLE)-1 != hSnapShot)
  {
    // 调用  Module32First  和 Module32Next 完成 Module 枚举
  }
}


是否还需要其它信息来分析程序出现异常的原因,可以参考 MSDN 中对结构 _EXCEPTION_POINTERS 中各成员的说明。然后将有用的信息,写到 SD 卡等可永久存贮的设备中。这样就不必为担心找不到用于分析异常的资源,且此记录的文件中的信息远大于串口输出的异常信息。同时,也可以根据需要输入一些应用(进程)的相关信息。
可以将此异常捕获功能的代码,封装成一个 LIB 来供使用程序调用。


相对于 PC Windows 下感知程序崩溃(其实就是运行时的严重错误)的方法,WinCE 还是比较少的,且真正被用的更是少之又少。
PC 下除了以上第二和第三种方法外,还有以下 3 个核心的函数可以感知程序的异常,分别是:
SetUnhandledExceptionFilter(HandleException),功能是确定出现没有控制的异常发生时调用的函数为 HandleException;函数在 WinCE 上是不可用的,无100%替代函数。
_set_invalid_parameter_handler(HandleInvalidParameter),功能是确定出现无效参数调用发生时调用的函数为 HandleInvalidParameter;
_set_purecall_handler(HandlePureVirtualCall),功能是确定纯虚函数调用发生时调用的函数为 HandlePureVirtualCall。


充分利用 Bug 的解决方法,是一个程序员成长的必由之路。因为程序员的工作不只是编码,还包括前期设计与后期的产品问题的修复等。
好的应用,就应该像 TCPMP 一样,在异常来临时能正确的提示用户,这样的程序的崩溃也朝“优美”迈进了一步。同时,也提供的开发人员分析异常的信息:记录在文件中。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

当地时间5月15日下午,台积电位于美国亚利桑那州北凤凰城的厂区,突然发生爆炸,造成至少1人重伤。目前,现场详细情况仍待进一步确认。

关键字: 台积电 半导体 晶圆厂

贝克曼库尔特目前已成为MeMed Key免疫分析平台和MeMed BV检测技术的授权经销商 在原有合作的基础上,继续开发适用于贝克曼库尔特免疫分析仪的MeMed BV检测 加州布瑞亚和以色列海法2024年5月16日...

关键字: BSP IO 检测技术 免疫分析仪

英国英泰力能的燃料电池是可产业化的产品解决方案 英国首个专为乘用车市场开发的燃料电池系统 在 157kW 功率下,此燃料电池比乘用车的其他发动机更为强大 &...

关键字: ENERGY INTELLIGENT 氢燃料电池 BSP

率先上市的新平台利用创新的生成和运营人工智能技术为公司的全套 CPM 解决方案提供动力 纽约2024年5月16日 /美通社/ -- 全球专业信息、软件和服务领先者威科集团今天宣布推出人工智能驱动的C...

关键字: 人工智能 智能驱动 TI GE

引领供应链数字化转型新潮流 上海2024年5月16日 /美通社/ -- 5月14日,"第七届亚太智慧供应链与物流创新博览会"在上海顺利举办,作为中国和亚太区最大规模,最有影响力的顶流供应链物流盛会,...

关键字: 数字化 软件 供应链管理 控制

深爱人才,共赴"芯"程 深圳2024年5月15日 /美通社/ -- 5月11日,深圳国资国企"博士人才荟"半导体与集成电路产业专场活动在深圳市重投天科半导体有限公司(简...

关键字: 半导体 集成电路产业 BSP 人工智能

武汉2024年5月15日 /美通社/ -- 北京时间4月26日-5月4日,2024 VEX 机器人世界锦标赛于美国得克萨斯州达拉斯市举办。本届 VEX 世锦赛为期九天,设有 VIQRC 小学组/初中组、V5RC 初中组/...

关键字: 机器人 BSP RC POWERED

上海2024年5月15日 /美通社/ -- 由生成式人工智能(AI)驱动的临床阶段生物医药科技公司英矽智能宣布,与复星医药(600196.SH;02196.HK)合作开发的潜在"全球首创"候选药物IS...

关键字: ISM BSP PC 人工智能

深圳2024年5月15日 /美通社/ -- 近日,国际公认的测试、检验和认证机构SGS为深圳市英威腾光伏科技有限公司(以下简称"英威腾光伏")的XG系列光伏并网逆变器XG50-60KTR和XG100-...

关键字: 英威腾 光伏并网逆变器 测试 新能源

香港2024年5月15日 /美通社/ -- 在人工智能技术的引领下,蓝帽子互动娱乐公司(交易代码:BHAT)今天宣布,一位由公司自主研发的AI数字人"艾琳...

关键字: NAS 人工智能 DAQ ASDA
关闭
关闭