376 lines
11 KiB
C++
376 lines
11 KiB
C++
|
/******************************************************************************
|
||
|
|
||
|
Copyright (c) 2000 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
frhang.cpp
|
||
|
|
||
|
Abstract:
|
||
|
Implements hang reporting
|
||
|
|
||
|
Revision History:
|
||
|
created derekm 07/07/00
|
||
|
|
||
|
******************************************************************************/
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include "dbghelp.h"
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// exported functions
|
||
|
|
||
|
// **************************************************************************
|
||
|
EFaultRepRetVal APIENTRY ReportHang(DWORD dwpid, DWORD dwtid, BOOL f64bit,
|
||
|
HANDLE hNotify)
|
||
|
{
|
||
|
USE_TRACING("ReportHang");
|
||
|
|
||
|
EXCEPTION_POINTERS ep;
|
||
|
CPFFaultClientCfg oCfg;
|
||
|
EXCEPTION_RECORD er;
|
||
|
EFaultRepRetVal frrvRet = frrvErrNoDW;
|
||
|
SDWManifestBlob dwmb;
|
||
|
SSuspendThreads st;
|
||
|
SMDumpOptions smdo;
|
||
|
CONTEXT cxt;
|
||
|
HRESULT hr = NOERROR;
|
||
|
HANDLE hProc = NULL, hth = NULL;
|
||
|
LPWSTR wszStage1, wszStage2, wszCorpPath, wszHdr;
|
||
|
LPWSTR wszFiles = NULL, wszDir = NULL, wszManifest = NULL;
|
||
|
LPWSTR pwszAppCompat = NULL;
|
||
|
WCHAR wszExe[MAX_PATH], wszAppName[MAX_PATH];
|
||
|
WCHAR wszAppVer[MAX_PATH];
|
||
|
WCHAR *pwszApp, *pwsz;
|
||
|
WCHAR wszBuffer[512];
|
||
|
DWORD dw, cch, cchDir, cchSep;
|
||
|
BOOL fMSApp = FALSE, fThreadsHeld = FALSE;
|
||
|
BYTE *pbBuf = NULL;
|
||
|
|
||
|
ZeroMemory(&st, sizeof(st));
|
||
|
|
||
|
VALIDATEPARM(hr, (dwpid == 0 || dwtid == 0));
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
TESTHR(hr, oCfg.Read(eroPolicyRO));
|
||
|
if (FAILED(hr))
|
||
|
goto done;
|
||
|
|
||
|
if (oCfg.get_TextLog() == eedEnabled)
|
||
|
{
|
||
|
HANDLE hFaultLog = INVALID_HANDLE_VALUE;
|
||
|
|
||
|
// assume system is on a local drive with a base path of "X:\"
|
||
|
GetSystemDirectoryW(wszBuffer, sizeofSTRW(wszBuffer));
|
||
|
wszBuffer[3] = L'\0';
|
||
|
wcscat(wszBuffer, c_wszLogFileName);
|
||
|
hFaultLog = CreateFileW(wszBuffer, GENERIC_WRITE,
|
||
|
FILE_SHARE_WRITE | FILE_SHARE_READ,
|
||
|
NULL, OPEN_ALWAYS, 0, NULL);
|
||
|
if (hFaultLog != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
SYSTEMTIME sst;
|
||
|
DWORD cb, cbWritten;
|
||
|
char szMsg[512];
|
||
|
|
||
|
GetSystemTime(&sst);
|
||
|
GetModuleFileNameW(NULL, wszExe, sizeofSTRW(wszExe));
|
||
|
cb = wsprintf(szMsg,
|
||
|
"%02d-%02d-%04d %02d:%02d:%02d Hang fault for %ls\r\n",
|
||
|
sst.wDay, sst.wMonth, sst.wYear, sst.wHour, sst.wMinute,
|
||
|
sst.wSecond, wszExe);
|
||
|
SetFilePointer(hFaultLog, 0, NULL, FILE_END);
|
||
|
WriteFile(hFaultLog, szMsg, cb, &cbWritten, NULL);
|
||
|
CloseHandle(hFaultLog);
|
||
|
}
|
||
|
|
||
|
wszBuffer[0] = L'\0';
|
||
|
}
|
||
|
|
||
|
// see if we're disabled. We do not allow notify only mode. It's really
|
||
|
// pointless to do so, given that the user explicitly wanted the app
|
||
|
// terminated and the 'Do you really want to kill this app' dialog that
|
||
|
// had to have popped up told him we thot it was hung.
|
||
|
if (oCfg.get_DoReport() == eedDisabled)
|
||
|
{
|
||
|
DBG_MSG("DoReport disabled");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
if (oCfg.get_ShowUI() == eedDisabled)
|
||
|
{
|
||
|
LPCWSTR wszULPath = oCfg.get_DumpPath(NULL, 0);
|
||
|
|
||
|
// check and make sure that we have a corporate path specified. If we
|
||
|
// don't, bail
|
||
|
if (wszULPath == NULL || *wszULPath == L'\0')
|
||
|
{
|
||
|
DBG_MSG("ShowUI disabled and no CER path");
|
||
|
goto done;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// find out what the exe path is
|
||
|
hProc = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE,
|
||
|
dwpid);
|
||
|
if (hProc == NULL)
|
||
|
{
|
||
|
if (ERROR_ACCESS_DENIED == GetLastError())
|
||
|
{
|
||
|
DBG_MSG("Could not open process: ACCESS_DENIED");
|
||
|
}
|
||
|
else
|
||
|
DBG_MSG("Could not open process");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
TESTHR(hr, GetExePath(hProc, wszExe, sizeofSTRW(wszExe)));
|
||
|
if (FAILED(hr))
|
||
|
goto done;
|
||
|
|
||
|
// check to see if the hung thread is DW or other part of our reporting chain
|
||
|
// if so, no point in reporting it...
|
||
|
for(pwsz = wszExe + wcslen(wszExe);
|
||
|
pwsz >= wszExe && *pwsz != L'\\';
|
||
|
pwsz--);
|
||
|
if (*pwsz == L'\\')
|
||
|
pwsz++;
|
||
|
if (_wcsicmp(pwsz, L"dwwin.exe") == 0 ||
|
||
|
_wcsicmp(pwsz, L"dumprep.exe") == 0)
|
||
|
{
|
||
|
DBG_MSG("We are hung- BAIL OUT!!");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
if (FreezeAllThreads(dwpid, 0, &st) == FALSE)
|
||
|
{
|
||
|
DBG_MSG("Could not freeze all threads");
|
||
|
goto done;
|
||
|
}
|
||
|
fThreadsHeld = TRUE;
|
||
|
|
||
|
// if we can't collect info on this puppy, then we'd just be notifying & I
|
||
|
// went over that case above.
|
||
|
if (oCfg.ShouldCollect(wszExe, &fMSApp) == FALSE)
|
||
|
goto done;
|
||
|
|
||
|
if (CreateTempDirAndFile(NULL, NULL, &wszDir) == 0)
|
||
|
goto done;
|
||
|
|
||
|
for (pwszApp = wszExe + wcslen(wszExe);
|
||
|
*pwszApp != L'\\' && pwszApp > wszExe;
|
||
|
pwszApp--);
|
||
|
if (*pwszApp == L'\\')
|
||
|
pwszApp++;
|
||
|
|
||
|
cchDir = wcslen(wszDir);
|
||
|
cch = cchDir + sizeofSTRW(c_wszManFileName) + 4;
|
||
|
__try { wszManifest = (LPWSTR)_alloca(cch * sizeof(WCHAR)); }
|
||
|
__except(EXCEPTION_STACK_OVERFLOW) { wszManifest = NULL; }
|
||
|
if (wszManifest == NULL)
|
||
|
{
|
||
|
SetLastError(ERROR_OUTOFMEMORY);
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
wcscpy(wszManifest, wszDir);
|
||
|
wszManifest[cchDir] = L'\\';
|
||
|
wszManifest[cchDir + 1] = L'\0';
|
||
|
wcscat(wszManifest, c_wszManFileName);
|
||
|
|
||
|
cchDir = wcslen(wszDir);
|
||
|
cch = 2 * cchDir + wcslen(pwszApp) + sizeofSTRW(c_wszACFileName) +
|
||
|
sizeofSTRW(c_wszDumpSuffix) + 4;
|
||
|
__try { wszFiles = (LPWSTR)_alloca(cch * sizeof(WCHAR)); }
|
||
|
__except(EXCEPTION_STACK_OVERFLOW) { wszFiles = NULL; }
|
||
|
if (wszFiles == NULL)
|
||
|
{
|
||
|
SetLastError(ERROR_OUTOFMEMORY);
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
wcscpy(wszFiles, wszDir);
|
||
|
wszFiles[cchDir] = L'\\';
|
||
|
wszFiles[cchDir + 1] = L'\0';
|
||
|
wcscat(wszFiles, pwszApp);
|
||
|
wcscat(wszFiles, c_wszDumpSuffix);
|
||
|
|
||
|
// build a exception context...
|
||
|
hth = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE,
|
||
|
dwtid);
|
||
|
if (hth == NULL)
|
||
|
goto done;
|
||
|
|
||
|
ZeroMemory(&cxt, sizeof(cxt));
|
||
|
cxt.ContextFlags = CONTEXT_CONTROL;
|
||
|
TESTBOOL(hr, GetThreadContext(hth, &cxt));
|
||
|
if (FAILED(hr))
|
||
|
goto done;
|
||
|
|
||
|
ZeroMemory(&er, sizeof(er));
|
||
|
er.ExceptionCode = 0xcfffffff;
|
||
|
#ifdef _X86_
|
||
|
// this cast is ok to do cuz we know we're on an x86 machine
|
||
|
er.ExceptionAddress = (PVOID)cxt.Eip;
|
||
|
|
||
|
#elif _IA64_
|
||
|
// this cast is ok to do cuz we know we're on an ia64 machine
|
||
|
er.ExceptionAddress = (PVOID)cxt.StIIP;
|
||
|
#endif
|
||
|
|
||
|
ep.ExceptionRecord = &er;
|
||
|
ep.ContextRecord = &cxt;
|
||
|
|
||
|
// generate the minidump
|
||
|
ZeroMemory(&smdo, sizeof(smdo));
|
||
|
smdo.cbSMDO = sizeof(smdo);
|
||
|
#ifdef MANIFEST_HEAP
|
||
|
smdo.ulThread = c_ulThreadWriteDefault;
|
||
|
smdo.ulThreadEx = c_ulThreadWriteDefault;
|
||
|
smdo.ulMod = c_ulModuleWriteDefault;
|
||
|
#else
|
||
|
smdo.ulThread = ThreadWriteThread | ThreadWriteContext | ThreadWriteStack;
|
||
|
smdo.ulMod = ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteDataSeg;
|
||
|
#endif
|
||
|
smdo.dwThreadID = dwtid;
|
||
|
smdo.dfOptions = dfCollectSig;
|
||
|
smdo.pvFaultAddr = (UINT64)er.ExceptionAddress;
|
||
|
#if defined(_X86_) || defined(_IA64_)
|
||
|
smdo.pEP = (UINT64)&ep;
|
||
|
smdo.fEPClient = FALSE;
|
||
|
#endif
|
||
|
smdo.wszModFullPath[0] = L'\0';
|
||
|
wcscpy(smdo.wszAppFullPath, wszExe);
|
||
|
wcscpy(smdo.wszMod, L"hungapp");
|
||
|
#ifdef MANIFEST_HEAP
|
||
|
if (InternalGenFullAndTriageMinidumps(hProc, dwpid, wszFiles,
|
||
|
NULL, &smdo, f64bit) == FALSE)
|
||
|
#else
|
||
|
if (InternalGenerateMinidump(hProc, dwpid, wszFiles, &smdo) == FALSE)
|
||
|
#endif
|
||
|
goto done;
|
||
|
|
||
|
ThawAllThreads(&st);
|
||
|
fThreadsHeld = FALSE;
|
||
|
|
||
|
// if the app requested it, notify it of that we're done fetching the
|
||
|
// dump
|
||
|
if (hNotify != NULL)
|
||
|
SetEvent(hNotify);
|
||
|
|
||
|
// log an event- don't care if it fails or not.
|
||
|
TESTHR(hr, LogHang(smdo.wszApp, smdo.rgAppVer, smdo.wszMod, smdo.rgModVer,
|
||
|
smdo.pvOffset, f64bit));
|
||
|
|
||
|
// generate all the URLs & file paths we'll need for reporting
|
||
|
TESTHR(hr, BuildManifestURLs(smdo.wszApp, smdo.wszMod, smdo.rgAppVer,
|
||
|
smdo.rgModVer, smdo.pvOffset,
|
||
|
f64bit, &wszStage1, &wszStage2,
|
||
|
&wszCorpPath, &pbBuf));
|
||
|
if (FAILED(hr))
|
||
|
goto done;
|
||
|
|
||
|
TESTHR(hr, GetVerName(smdo.wszAppFullPath, wszAppName,
|
||
|
sizeofSTRW(wszAppName)));
|
||
|
if (FAILED(hr))
|
||
|
goto done;
|
||
|
|
||
|
wszAppName[sizeofSTRW(wszAppName) - 1] = L'\0';
|
||
|
|
||
|
// we created the wszDump buffer above big enuf to hold both the
|
||
|
// dumpfile path as well as the app compat filename. So make
|
||
|
// use of that right now.
|
||
|
cchSep = wcslen(wszFiles);
|
||
|
pwszAppCompat = wszFiles + cchSep + 1;
|
||
|
wcscpy(pwszAppCompat, wszDir);
|
||
|
pwszAppCompat[cchDir] = L'\\';
|
||
|
pwszAppCompat[cchDir + 1] = L'\0';
|
||
|
wcscat(pwszAppCompat, c_wszACFileName);
|
||
|
|
||
|
// if we succeed, turn the NULL following the dump file path into
|
||
|
// the DW separator character
|
||
|
TESTBOOL(hr, GetAppCompatData(wszExe, smdo.wszModFullPath, pwszAppCompat));
|
||
|
if (SUCCEEDED(hr))
|
||
|
wszFiles[cchSep] = DW_FILESEP;
|
||
|
|
||
|
// get the header text
|
||
|
dw = LoadStringW(g_hInstance, IDS_HHDRTXT, wszBuffer,
|
||
|
sizeofSTRW(wszBuffer));
|
||
|
if (dw == 0)
|
||
|
goto done;
|
||
|
|
||
|
cch = dw + wcslen(wszAppName) + 1;
|
||
|
__try { wszHdr = (LPWSTR)_alloca(cch * sizeof(WCHAR)); }
|
||
|
__except(EXCEPTION_STACK_OVERFLOW) { wszHdr = NULL; }
|
||
|
if (wszHdr == NULL)
|
||
|
{
|
||
|
SetLastError(ERROR_OUTOFMEMORY);
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
swprintf(wszHdr, wszBuffer, wszAppName);
|
||
|
|
||
|
ZeroMemory(&dwmb, sizeof(dwmb));
|
||
|
dwmb.wszTitle = wszAppName;
|
||
|
dwmb.wszHdr = wszHdr;
|
||
|
dwmb.nidErrMsg = IDS_HERRMSG;
|
||
|
dwmb.wszStage1 = wszStage1;
|
||
|
dwmb.wszStage2 = wszStage2;
|
||
|
dwmb.wszBrand = c_wszDWBrand;
|
||
|
dwmb.wszFileList = wszFiles;
|
||
|
dwmb.fIsMSApp = fMSApp;
|
||
|
dwmb.wszCorpPath = wszCorpPath;
|
||
|
dwmb.wszEventSrc = c_wszHangEventSrc;
|
||
|
|
||
|
// check and see if the system is shutting down. If so, CreateProcess is
|
||
|
// gonna pop up some annoying UI that we can't get rid of, so we don't
|
||
|
// want to call it if we know it's gonna happen.
|
||
|
if (GetSystemMetrics(SM_SHUTTINGDOWN))
|
||
|
goto done;
|
||
|
|
||
|
frrvRet = StartDWManifest(oCfg, dwmb, wszManifest);
|
||
|
|
||
|
done:
|
||
|
// preserve the error code so that the following calls don't overwrite it
|
||
|
dw = GetLastError();
|
||
|
|
||
|
if (fThreadsHeld)
|
||
|
ThawAllThreads(&st);
|
||
|
|
||
|
if (hProc != NULL)
|
||
|
CloseHandle(hProc);
|
||
|
if (hth != NULL)
|
||
|
CloseHandle(hth);
|
||
|
if (wszFiles != NULL)
|
||
|
{
|
||
|
if (pwszAppCompat != NULL)
|
||
|
{
|
||
|
wszFiles[cchSep] = L'\0';
|
||
|
DeleteFileW(pwszAppCompat);
|
||
|
}
|
||
|
#ifdef MANIFEST_HEAP
|
||
|
DeleteFullAndTriageMiniDumps(wszFiles);
|
||
|
#else
|
||
|
DeleteFileW(wszFiles);
|
||
|
#endif
|
||
|
}
|
||
|
if (wszManifest != NULL)
|
||
|
DeleteFileW(wszManifest);
|
||
|
if (wszDir != NULL)
|
||
|
{
|
||
|
DeleteTempDirAndFile(wszDir, FALSE);
|
||
|
MyFree(wszDir);
|
||
|
}
|
||
|
|
||
|
if (pbBuf != NULL)
|
||
|
MyFree(pbBuf);
|
||
|
|
||
|
SetLastError(dw);
|
||
|
|
||
|
return frrvRet;
|
||
|
}
|