Why Software Crash?
1. Preface
我们有一个软件在特定的model上运行有时会挂掉。但是在别的model上就没有问题。而且它还是有机率的,连续试个几百次才有可能出现一次。
2. Detail Info
具体情况是这样的,这是一只监控系统系统信息的软体,它能够检测到系统的各种信息,在检测到变化后显示出来。频繁的开盖合盖的时候这个bug有可能会发生。可是越是想让它出现,它就是不出现L,没有点耐心,不试个几百次别想看到。一旦出现,它将呈现崩溃状,直接死给你看J,下图1就是它的死状:
图 1
3. Why&How
从它的尸体我是看不出有什么状况了,那么该怎么办呢?最初我打算使用log的形式记录软件的执行状况,待到它死掉就可以看出死的原因,试验了几组我就发现这个方法不可取,代码太多了,我除非在每一个方法甚至每一行都抛debug 信息,这样才能确定原因,可是如果这么做,我都要疯了L!冷静J!经过几番思量我想到了WinDbg,用它来分析尸体挺合适。我build一个debug版本的程序,将symbol配置好以后,让测试的同事帮忙试出Bug。几个小时后bug出现了,我立马赶过去祭出winDbg,挂上即将死掉的程序,程序被断下我输入go,该程序挣扎了一下就挂了。我一路call stack 小心的检查调用栈上的函数的运行状况,最后给我发现一个通过WMI(WMI的相关内容将会在WMI ACPI部分详细介绍)获取亮度信息的函数有重大的作案嫌疑,它的pclsObj的指针指向了一堆乱七八糟的地方,程序挂掉之前的最后一步在pclsObj->Release()。代码如下所示:
int XYZ::GetWMIBrightness(void) { HRESULT hres; BSTR bstrWQL = SysAllocString(L"WQL"); BSTR bstrQuery = SysAllocString(L"SELECT * FROM WmiMonitorBrightness"); IEnumWbemClassObject* pEnumerator = NULL; hres = pSvc->ExecQuery( bstrWQL, bstrQuery, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if (FAILED(hres)) { AfxMessageBox("Query for operating system name failed."); pSvc->Release(); pLoc->Release(); CoUninitialize(); return 0; // Program has failed. } // Step 7: ----------------------------------------------- // Get the data from the query in step 6 ------------------ IWbemClassObject *pclsObj; ULONG uReturn = 0; CString csTemp; int iResult = 0; while (pEnumerator) { HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if(0 == uReturn) { break; } VARIANT vtProp; // Get the value of the Name property hr = pclsObj->Get(L"CurrentBrightness", 0, &vtProp, 0, 0); csTemp.Format(" Brightness : %d", vtProp.bVal); iResult = vtProp.bVal; VariantClear(&vtProp); } // Cleanup // ======== pSvc->Release(); pLoc->Release(); pEnumerator->Release(); pclsObj->Release(); CoUninitialize(); return iResult; // Program successfully completed. }
仔细分析了一下发现这段程序对变量IWbemClassObject *pclsObj的使用有点问题,它没有给它初始化为NULL,然后就通过pEnumerator->Next()去获得一个包含亮度信息的对象,这里存在隐患,在释放内存的时候也没有去检查pclsObj是否为NULL就直接Release();这时我猜测Release()会将内存释放,那么一个无效的地址就可能导致了程序的崩溃。
后来我将代码做了修改将pclsObj 初始化为NULL,IWbemClassObject *pclsObj = NULL;
而且在释放内从时加入了下述判断:
// Peter hu ++ #define SAFE_RELEASE(punk) / if ((punk) != NULL) / { (punk)->Release(); (punk) = NULL; } // Peter hu --
如此修改以后导入测试,跑了几天再也没有出现上述状况,可是问题还没有结束,如果单纯是这样那么每一个model都会出现这样的问题,为什么只有该model会有呢?有人说是硬件平台的差异,我不太相信。开盖合盖和brightness有什么关系呢?我试了试别的model,发现它们在开盖合盖时并不会去读亮度信息,那么为什么该model会呢?进一步分析,我发现该model会在开盖合盖时EC会送亮度变化的scancode出来,这就导致了该软体去读取亮度信息。那么读亮度信息为什么会挂了呢?我猜测原因是当盖子打开有时panel还没有被点亮,driver还没有准备好亮度信息,所以没有读到该对象,另外再加上代码中的缺陷,于是bug出现了。
Peter