这个对话框,大家应该都不陌生。程序员做开发时经常会见到,用户肯定也曾被它骚扰过。很显然,这是软件的BUG所致。软件中存在的BUG肯定是会出现的,只是时间的问题,或早或晚,有些很幸运在测试时就会被发现,那些不幸的就成了客户抱怨的缘由。所以,我们不能抱有侥幸心理,而应该想办法来解决这类问题。
对于软件的BUG来说,扼杀于摇篮当然是最根本的解决办法。尽量编写没有BUG的代码,必要时主动在代码中添加一些异常处理,让程序决断如何处理。很多语言都提供了异常处理的机制,但BUG似乎只能减少,而不可消灭。在嵌入式系统中,由于硬件及工作状态的差异,有些貌似万无一失的代码也常常出现BUG。所以,编写一个完全没有BUG的软件,是我们的理想与追求,但同时也需要我们做最坏的准备。也许某个三更半夜,就会被一个莫名的BUG折腾得焦头烂额。
产品发布之前,会有很系统的测试,包括硬件和软件。对一个产品来说,测试的重要性不言而喻。尽可能在测试阶段发现所有的问题,而不要将这些问题留给客户。前两天听开发组的同事惊呼“这个BUG也被你们发现了,你们的测试功夫实在了得”。原来是他调用一函数时用错了一个参数,测试组的同事愣是把它揪了出来。另外一个关于汉字排序出错的BUG,也被测试组发现了。但要不要改,开发组的几个人争得面红耳赤。最后老邓都有些火了,他的意思是即使BUG再不易重现,客户也许从不会碰到,但只要确实有问题,就得Fix掉。另外的人觉得Fix这个BUG,太繁琐,不值得花太多时间。最后估计还是得听老邓的,发现了的BUG一定彻底解决!但测试也只能是解决一部分问题,毕竟还有可能存在暂时没有发现的BUG。这些BUG最终会在客户手上爆发。
产品发布之后,平静了一段时间,终于有一天,客户抱怨来了,软件有BUG,出现系统崩溃的情况。询问再三,却没有更多的有利于Debug的信息。这不是客户的责任,毕竟他们不是专业的测试人员。要重现这个BUG也非常很困难。不过,幸运的是针对这种情况,WinCE提供了类似于桌面Windows的一套错误报告系统。通过它,我们能把一些软件BUG出现时的情况记录下来,有利于分析BUG的诱因,从而解决BUG。我发现M8似乎就采用了类似的机制,在Disk目录下,会看到一些LOG文件和edb_err.txt文件。
综上,解决软件BUG的问题,有三道关口,第一关,编写代码时,止于源头,第二关, 系统测试时,斩立决,第三关,用户发现后,须立等可取。这三点说起来简单,实际上都有很大的学问,即所谓道,是需要花大量时间,研习许多资料才能了解的。
以上说的是正道,下面再说点旁门左道,也是我目前碰到的问题。产品发布了,有少数用户报告有BUG,甚至发来如上所示的图。系统本身是有有看门狗的,但等看门狗复位系统的时间太长,需要几分钟。由于种种原因,现在也不可能到代码中去找错,有些部分甚至是没有源码的。目前的处理机制是,出现如上所示的对话框后,点击“OK”按钮,程序会关闭,然后另外的程序会再运行该出错关闭的程序。所以,我需要做的事情就是如何跳过这个提示对话框,让出错程序直接关闭。简单分析之后,有两个想法,一个是修改WinCE内核,修改异常处理部分的代码,出现此类异常时,直接关闭对应的进程。另一个是写个应用程序,模拟用户点击“OK”按钮的操作。第一种方法似乎可行,在Windows XP中可禁用错误报告,也可禁用通知,但在WinCE下没找到该开关。所以需要修改内核代码,并重新编译,这感觉有点不妥。第二种方法,直截了当,担心的问题是系统额外的消耗。很显然这个程序得一直运行着,并检测出错窗口,一旦出错,马上将其关掉。如果这个程序的系统消耗很小,那也不失为一个缓兵之计。当然,要从根本上解决问题,还得走正道。
简单写了个Savior.exe,在模拟器上测试了一下,系统消耗基本可以忽略,下图为证,包括了异常出现前后的两个时间段,可以看到,都在0.5%以下。如果能暂时缓解一下问题,这点消耗是完全可以接受的。
在模拟器中,配合Crash.exe测试了一下,基本实现了左道的功能。Savior.exe的源代码如下:
Code
#include<windows.h>
//定义出错类型,列举所有出错提示框的信息
const TCHAR *szErrorInfo[] =
{
_T("致命的应用程序错误"),
_T("应用程序错误"),
_T("Fatal Application Error"),
_T("Application Error"),
NULL,
};
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
HANDLE hMutex = CreateMutex(NULL,FALSE,_T("SAVIOR"));
if (hMutex)
{
if(ERROR_ALREADY_EXISTS == GetLastError())
{
return FALSE;
}
}
TCHAR szPath[256];
while (1)
{
HWND hWnd = NULL;
for (int i = 0;szErrorInfo[i];i++)
{
if (szErrorInfo[i])
{
hWnd = FindWindow(NULL,szErrorInfo[i]);
if (hWnd)
{
//查找到出错提示对话框
DWORD dwProcessID;
//获取出错进程ID
GetWindowThreadProcessId(hWnd,&dwProcessID);
//获取出错进程句柄
HMODULE hProc = (HMODULE)OpenProcess(0,FALSE,dwProcessID);
if (hProc)
{
//获取出错进程对应的EXE
GetModuleFileName(hProc,szPath,255);
}
//关闭出错提示对话框
SendMessage(hWnd,WM_CLOSE, 0, 0);
Sleep(3000);
//重新启动出错应用程序
PROCESS_INFORMATION pi;
if (CreateProcess(szPath,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,&pi))
{
CloseHandle(pi.hProcess);
}
}
}
Sleep(100);
}
Sleep(1500);
}
return TRUE;
}
由于Crash.exe是一启动就出错的程序,所以,两个程序一起测试时,会陷入一个循环,出错重启,还出错还重启。在实际的项目中应该避免该情况,可以增加一个黑名单的功能,如果重启次数超过5次,就将该程序列入黑名单,关闭后不再重启。另外,针对出错信息形形色色,可以将其写入到文件或注册表中,便于后续增加错误信息的定义。左道的实际效果如何,还需拿到真机上测试。在某些无头设备中,用户没有办法点击弹出的出错提示框时,左道也能发挥作用,似乎还有些不可替代。
WinCE下应用程序错误的解决之道,漫漫,可正,可邪,须上下求索。