671 lines
19 KiB
C++
671 lines
19 KiB
C++
/****************************************************************************
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
dumprep.cpp
|
|
|
|
Abstract:
|
|
hang manager intermediate app
|
|
*** IMPORTANT NOTE: this links with the single threaded CRT static lib. If
|
|
it is changed to be multithreaded for some odd reason,
|
|
then the sources file must be modified to link to
|
|
libcmt.lib.
|
|
|
|
Revision History:
|
|
|
|
DerekM created 08/16/00
|
|
|
|
****************************************************************************/
|
|
|
|
#include "stdafx.h"
|
|
#include "malloc.h"
|
|
#include "faultrep.h"
|
|
#include "pfrcfg.h"
|
|
|
|
enum EOp
|
|
{
|
|
eopNone = 0,
|
|
eopHang,
|
|
eopDump,
|
|
eopEvent
|
|
};
|
|
|
|
enum ECheckType
|
|
{
|
|
ctNone = -1,
|
|
ctKernel = 0,
|
|
ctUser,
|
|
ctShutdown,
|
|
ctNumChecks
|
|
};
|
|
|
|
struct SCheckData
|
|
{
|
|
LPCWSTR wszRegPath;
|
|
LPCWSTR wszRunVal;
|
|
LPCWSTR wszEventName;
|
|
LPCSTR szFnName;
|
|
BOOL fUseData;
|
|
BOOL fDelDump;
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// constants
|
|
|
|
const char c_szKSFnName[] = "ReportEREventDW";
|
|
const char c_szUserFnName[] = "ReportFaultFromQueue";
|
|
|
|
SCheckData g_scd[ctNumChecks] =
|
|
{
|
|
{ c_wszRKKrnl, c_wszRVKFC, c_wszMutKrnlName, c_szKSFnName, FALSE, FALSE },
|
|
{ c_wszRKUser, c_wszRVUFC, c_wszMutUserName, c_szUserFnName, TRUE, TRUE },
|
|
{ c_wszRKShut, c_wszRVSEC, c_wszMutShutName, c_szKSFnName, FALSE, FALSE },
|
|
};
|
|
|
|
#define EV_ACCESS_ALL GENERIC_ALL | STANDARD_RIGHTS_ALL
|
|
#define EV_ACCESS_RS GENERIC_READ | SYNCHRONIZE
|
|
|
|
#define pfn_VALONLY pfn_REPORTEREVENTDW
|
|
#define pfn_VALDATA pfn_REPORTFAULTFROMQ
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// globals
|
|
|
|
BOOL g_fDeleteReg = TRUE;
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// misc
|
|
|
|
// **************************************************************************
|
|
LONG __stdcall ExceptionTrap(_EXCEPTION_POINTERS *ExceptionInfo)
|
|
{
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
#ifdef MANIFEST_HEAP
|
|
BOOL
|
|
DeleteFullAndTriageMiniDumps(
|
|
LPCWSTR wszPath
|
|
)
|
|
//
|
|
// We create a FullMinidump file along with triage minidump in the same dir
|
|
// This routine cleans up both those files
|
|
//
|
|
{
|
|
LPWSTR wszFullMinidump = NULL;
|
|
DWORD cch;
|
|
BOOL fRet;
|
|
|
|
fRet = DeleteFileW(wszPath);
|
|
cch = wcslen(wszPath) + sizeofSTRW(c_wszHeapDumpSuffix);
|
|
__try { wszFullMinidump = (WCHAR *)_alloca(cch * sizeof(WCHAR)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW) { wszFullMinidump = NULL; }
|
|
if (wszFullMinidump)
|
|
{
|
|
LPWSTR wszFileExt = NULL;
|
|
|
|
// Build Dump-with-heap path
|
|
wcsncpy(wszFullMinidump, wszPath, cch);
|
|
wszFileExt = wszFullMinidump + wcslen(wszFullMinidump) - sizeofSTRW(c_wszDumpSuffix) + 1;
|
|
if (!wcscmp(wszFileExt, c_wszDumpSuffix))
|
|
{
|
|
*wszFileExt = L'\0';
|
|
}
|
|
wcsncat(wszFullMinidump, c_wszHeapDumpSuffix, cch);
|
|
|
|
|
|
fRet = DeleteFileW(wszFullMinidump);
|
|
|
|
} else
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
return fRet;
|
|
}
|
|
#endif // MANIFEST_HEAP
|
|
|
|
// **************************************************************************
|
|
void DeleteQueuedEvents(HKEY hkey, LPWSTR wszVal, DWORD cchMaxVal,
|
|
ECheckType ct)
|
|
{
|
|
DWORD cchVal, dw;
|
|
HKEY hkeyRun = NULL;
|
|
HRESULT hr = NOERROR;
|
|
|
|
USE_TRACING("DeleteQueuedEvents");
|
|
|
|
VALIDATEPARM(hr, (hkey == NULL || wszVal == NULL));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
for(;;)
|
|
{
|
|
cchVal = cchMaxVal;
|
|
dw = RegEnumValueW(hkey, 0, wszVal, &cchVal, NULL, NULL,
|
|
NULL, NULL);
|
|
if (dw != ERROR_SUCCESS && dw != ERROR_NO_MORE_ITEMS)
|
|
{
|
|
SetLastError(dw);
|
|
goto done;
|
|
}
|
|
|
|
if (dw == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
|
|
RegDeleteValueW(hkey, wszVal);
|
|
if (ct == ctUser)
|
|
{
|
|
#ifdef MANIFEST_HEAP
|
|
DeleteFullAndTriageMiniDumps(wszVal);
|
|
#else
|
|
DeleteFileW(wszVal);
|
|
#endif // !MANIFEST_HEAP
|
|
}
|
|
}
|
|
|
|
// gotta delete our value out of the Run key so we don't run
|
|
// unnecessarily again...
|
|
dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_wszRKRun, 0,
|
|
KEY_ALL_ACCESS, &hkeyRun);
|
|
if (dw != ERROR_SUCCESS)
|
|
goto done;
|
|
|
|
RegDeleteValueW(hkeyRun, g_scd[ct].wszRunVal);
|
|
|
|
done:
|
|
if (hkeyRun != NULL)
|
|
RegCloseKey(hkeyRun);
|
|
|
|
return;
|
|
}
|
|
|
|
// **************************************************************************
|
|
void ReportEvents(HMODULE hmod, ECheckType ct)
|
|
{
|
|
EFaultRepRetVal frrv;
|
|
pfn_VALONLY pfnVO = NULL;
|
|
pfn_VALDATA pfnVD = NULL;
|
|
EEventType eet = eetKernelFault;
|
|
HRESULT hr = NOERROR;
|
|
HANDLE hmut = NULL;
|
|
LPWSTR wszVal = NULL;
|
|
LPBYTE pbData = NULL, pbDataToUse = NULL;
|
|
EEnDis eedReport, eedUI;
|
|
DWORD cchVal = 0, cchMaxVal = 0, cbMaxData = 0, cVals = 0;
|
|
DWORD dw, cbData = 0, *pcbData = NULL;
|
|
DWORD dwType;
|
|
HKEY hkey = NULL;
|
|
|
|
USE_TRACING("ReportEvents");
|
|
|
|
VALIDATEPARM(hr, ((ct <= ctNone && ct >= ctNumChecks) || hmod == NULL));
|
|
|
|
if (FAILED(hr))
|
|
return;
|
|
|
|
// assume hmod is valid cuz we do a check in wWinMain to make sure it is
|
|
// before calling this fn
|
|
if (g_scd[ct].fUseData)
|
|
pfnVD = (pfn_VALDATA)GetProcAddress(hmod, g_scd[ct].szFnName);
|
|
else
|
|
pfnVO = (pfn_VALONLY)GetProcAddress(hmod, g_scd[ct].szFnName);
|
|
if (pfnVD == NULL && pfnVO == NULL)
|
|
return;
|
|
|
|
dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_wszRPCfg, 0, KEY_READ, &hkey);
|
|
if (dw != ERROR_SUCCESS)
|
|
return;
|
|
|
|
cbData = sizeof(eedUI);
|
|
dw = RegQueryValueExW(hkey, c_wszRVShowUI, 0, NULL, (PBYTE)&eedUI,
|
|
&cbData);
|
|
if (dw != ERROR_SUCCESS)
|
|
{
|
|
RegCloseKey(hkey);
|
|
return;
|
|
}
|
|
|
|
cbData = sizeof(eedReport);
|
|
dw = RegQueryValueExW(hkey, c_wszRVDoReport, 0, NULL, (PBYTE)&eedReport,
|
|
&cbData);
|
|
RegCloseKey(hkey);
|
|
hkey = NULL;
|
|
if (dw != ERROR_SUCCESS)
|
|
return;
|
|
|
|
if (eedUI != eedEnabled && eedUI != eedDisabled &&
|
|
eedUI != eedEnabledNoCheck)
|
|
eedUI = eedEnabled;
|
|
|
|
if (eedReport != eedEnabled && eedReport != eedDisabled)
|
|
eedReport = eedEnabled;
|
|
|
|
// only want one user at a time going thru this
|
|
hmut = OpenMutexW(SYNCHRONIZE, FALSE, g_scd[ct].wszEventName);
|
|
VALIDATEPARM(hr, (hmut == NULL));
|
|
if (FAILED(hr))
|
|
return;
|
|
|
|
// the default value above is eetKernelFault, so only need to change if
|
|
// it's a shutdown
|
|
if (ct == ctShutdown)
|
|
eet = eetShutdown;
|
|
|
|
__try
|
|
{
|
|
__try
|
|
{
|
|
// give this wait five minutes. If the code doesn't complete by
|
|
// then, then we're either held up by DW (which means an admin
|
|
// aleady passed thru here) or something has barfed and is holding
|
|
// the mutex.
|
|
dw = WaitForSingleObject(hmut, 300000);
|
|
if (dw != WAIT_OBJECT_0 && dw != WAIT_ABANDONED)
|
|
__leave;
|
|
|
|
dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, g_scd[ct].wszRegPath, 0,
|
|
KEY_ALL_ACCESS, &hkey);
|
|
if (dw != ERROR_SUCCESS)
|
|
__leave;
|
|
|
|
// determine how big the valuename is & allocate a buffer for it
|
|
dw = RegQueryInfoKeyW(hkey, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
&cVals, &cchMaxVal, &cbMaxData, NULL, NULL);
|
|
if (dw != ERROR_SUCCESS || cVals == 0 || cchMaxVal == 0)
|
|
__leave;
|
|
|
|
cchMaxVal++;
|
|
|
|
// get us some buffers to hold the data bits we're interested in...
|
|
wszVal = (LPWSTR)MyAlloc(cchMaxVal * sizeof(WCHAR));
|
|
if (wszVal == NULL)
|
|
__leave;
|
|
|
|
// if we're completely disabled, then nuke all the queued stuff
|
|
// and bail
|
|
if (eedUI == eedDisabled && eedReport == eedDisabled)
|
|
{
|
|
DeleteQueuedEvents(hkey, wszVal, cchMaxVal, ct);
|
|
__leave;
|
|
}
|
|
|
|
if (g_scd[ct].fUseData)
|
|
{
|
|
pbData = (LPBYTE) MyAlloc(cbMaxData);
|
|
if (pbData == NULL)
|
|
__leave;
|
|
|
|
pbDataToUse = pbData;
|
|
pcbData = &cbData;
|
|
}
|
|
|
|
do
|
|
{
|
|
cchVal = cchMaxVal;
|
|
cbData = cbMaxData;
|
|
dw = RegEnumValueW(hkey, 0, wszVal, &cchVal, NULL, &dwType,
|
|
pbDataToUse, pcbData);
|
|
if (dw != ERROR_SUCCESS && dw != ERROR_NO_MORE_ITEMS)
|
|
__leave;
|
|
|
|
if (dw == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
|
|
if (g_scd[ct].fUseData)
|
|
{
|
|
// if the type isn't REG_BINARY, then someone wrote an
|
|
// invalid blob to the registry. We have to ignore it.
|
|
if (dwType == REG_BINARY)
|
|
frrv = (*pfnVD)(wszVal, pbData, cbData);
|
|
else
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
frrv = frrvOk;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
frrv = (*pfnVO)(eet, wszVal, NULL);
|
|
}
|
|
|
|
// if the call succeeds (or the data we fed to it was invalid)
|
|
// then nuke the reg key & dump file
|
|
if (GetLastError() == ERROR_INVALID_PARAMETER ||
|
|
(g_fDeleteReg && frrv == frrvOk))
|
|
{
|
|
dw = RegDeleteValueW(hkey, wszVal);
|
|
if (dw != ERROR_SUCCESS && dw != ERROR_FILE_NOT_FOUND &&
|
|
dw != ERROR_PATH_NOT_FOUND)
|
|
__leave;
|
|
|
|
if (g_scd[ct].fDelDump && g_fDeleteReg)
|
|
{
|
|
#ifdef MANIFEST_HEAP
|
|
DeleteFullAndTriageMiniDumps(wszVal);
|
|
#else
|
|
DeleteFileW(wszVal);
|
|
#endif // !MANIFEST_HEAP
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// don't delete the Run key if we got an error and didn't
|
|
// delete the fault key
|
|
if (frrv != frrvOk)
|
|
__leave;
|
|
}
|
|
}
|
|
while(1);
|
|
|
|
RegCloseKey(hkey);
|
|
hkey = NULL;
|
|
|
|
// gotta delete our value out of the Run key so we don't run
|
|
// unnecessarily again...
|
|
dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_wszRKRun, 0,
|
|
KEY_ALL_ACCESS, &hkey);
|
|
if (dw != ERROR_SUCCESS)
|
|
__leave;
|
|
|
|
RegDeleteValueW(hkey, g_scd[ct].wszRunVal);
|
|
}
|
|
|
|
__finally
|
|
{
|
|
}
|
|
}
|
|
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
|
|
if (hmut != NULL)
|
|
{
|
|
ReleaseMutex(hmut);
|
|
CloseHandle(hmut);
|
|
}
|
|
if (hkey != NULL)
|
|
RegCloseKey(hkey);
|
|
if (pbData != NULL)
|
|
MyFree(pbData);
|
|
if (wszVal != NULL)
|
|
MyFree(wszVal);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// wmain
|
|
|
|
// **************************************************************************
|
|
int __cdecl wmain(int argc, WCHAR **argv)
|
|
{
|
|
EFaultRepRetVal frrv = frrvErrNoDW;
|
|
SMDumpOptions smdo, *psmdo = &smdo;
|
|
ECheckType ct = ctNone;
|
|
HMODULE hmod = NULL;
|
|
HANDLE hevNotify = NULL, hproc = NULL, hmem = NULL;
|
|
LPWSTR wszDump = NULL;
|
|
WCHAR wszMod[MAX_PATH];
|
|
DWORD dwpid, dwtid;
|
|
BOOL f64bit = FALSE;
|
|
int i;
|
|
EOp eop = eopNone;
|
|
HRESULT hr = NOERROR;
|
|
|
|
// we don't want to have any faults get trapped anywhere.
|
|
SetErrorMode(SEM_NOGPFAULTERRORBOX | SEM_NOALIGNMENTFAULTEXCEPT |
|
|
SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
|
|
SetUnhandledExceptionFilter(ExceptionTrap);
|
|
|
|
INIT_TRACING
|
|
|
|
USE_TRACING("DumpRep.wmain");
|
|
|
|
VALIDATEPARM(hr, (argc < 2 || argc > 8));
|
|
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
dwpid = _wtol(argv[1]);
|
|
|
|
ZeroMemory(&smdo, sizeof(smdo));
|
|
|
|
for (i = 2; i < argc; i++)
|
|
{
|
|
if (argv[i][0] != L'-')
|
|
continue;
|
|
|
|
switch(argv[i][1])
|
|
{
|
|
// debug flag to prevent deletion of reg entries
|
|
case L'E':
|
|
case L'e':
|
|
#if defined(NO_WAY_DEBUG) || defined(NO_WAY__DEBUG)
|
|
g_fDeleteReg = FALSE;
|
|
#endif
|
|
break;
|
|
|
|
// user or kernel faults or shutdowns
|
|
case L'K':
|
|
case L'k':
|
|
case L'U':
|
|
case L'u':
|
|
case L'S':
|
|
case L's':
|
|
if (eop != eopNone)
|
|
goto done;
|
|
|
|
eop = eopEvent;
|
|
|
|
// to workaround the desktop hanging while all Run processes
|
|
// do their thing, we spawn a another copy of ourselves and
|
|
// immediately exit.
|
|
if (argv[i][2] != L'G' && argv[i][2] != L'g')
|
|
{
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFOW si;
|
|
|
|
GetModuleFileNameW(NULL, wszMod, sizeofSTRW(wszMod));
|
|
if (argv[i][1] == L'K' || argv[i][1] == L'k')
|
|
wcscat(wszMod, L" 0 -KG");
|
|
else if (argv[i][1] == L'U' || argv[i][1] == L'u')
|
|
wcscat(wszMod, L" 0 -UG");
|
|
else
|
|
wcscat(wszMod, L" 0 -SG");
|
|
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
|
|
if (CreateProcessW(NULL, wszMod, NULL, NULL, FALSE, 0, NULL,
|
|
NULL, &si, &pi))
|
|
{
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
else
|
|
{
|
|
if (argv[i][1] == L'K' || argv[i][1] == L'k')
|
|
ct = ctKernel;
|
|
else if (argv[i][1] == L'U' || argv[i][1] == L'u')
|
|
ct = ctUser;
|
|
else
|
|
ct = ctShutdown;
|
|
}
|
|
break;
|
|
|
|
// hangs
|
|
case L'H':
|
|
case L'h':
|
|
if (i + 1 >= argc || eop != eopNone)
|
|
goto done;
|
|
|
|
eop = eopHang;
|
|
|
|
#ifdef _WIN64
|
|
if (argv[i][2] == L'6')
|
|
f64bit = TRUE;
|
|
#endif
|
|
dwtid = _wtol(argv[++i]);
|
|
|
|
if (argc > i + 1)
|
|
{
|
|
hevNotify = OpenEventW(EVENT_MODIFY_STATE | SYNCHRONIZE,
|
|
FALSE, argv[++i]);
|
|
}
|
|
break;
|
|
|
|
// dumps
|
|
case L'D':
|
|
case L'd':
|
|
if (i + 3 >= argc || wszDump != NULL || eop != eopNone)
|
|
goto done;
|
|
|
|
eop = eopDump;
|
|
|
|
ZeroMemory(&smdo, sizeof(smdo));
|
|
smdo.ulMod = _wtol(argv[++i]);
|
|
smdo.ulThread = _wtol(argv[++i]);
|
|
wszDump = argv[++i];
|
|
|
|
if (argv[i - 3][2] == L'T' || argv[i - 3][2] == L't')
|
|
{
|
|
if (i + 1 >= argc)
|
|
goto done;
|
|
|
|
smdo.dwThreadID = _wtol(argv[++i]);
|
|
smdo.dfOptions = dfFilterThread;
|
|
}
|
|
else if (argv[i - 3][2] == L'S' || argv[i - 3][2] == L's')
|
|
{
|
|
if (i + 2 >= argc)
|
|
goto done;
|
|
|
|
smdo.dwThreadID = _wtol(argv[++i]);
|
|
smdo.ulThreadEx = _wtol(argv[++i]);
|
|
smdo.dfOptions = dfFilterThreadEx;
|
|
}
|
|
else if (argv[i - 3][2] == L'M' || argv[i - 3][2] == L'm')
|
|
{
|
|
HANDLE hmemRemote = NULL;
|
|
LPVOID pvMem = NULL;
|
|
|
|
if (i + 1 >= argc)
|
|
goto done;
|
|
|
|
hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwpid);
|
|
if (hproc == NULL)
|
|
goto done;
|
|
#ifdef _WIN64
|
|
hmemRemote = (HANDLE)_wtoi64(argv[++i]);
|
|
#else
|
|
hmemRemote = (HANDLE)_wtol(argv[++i]);
|
|
#endif
|
|
VALIDATEPARM(hr, (hmemRemote == NULL));
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
if (DuplicateHandle(hproc, hmemRemote, GetCurrentProcess(),
|
|
&hmem, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS) == FALSE)
|
|
goto done;
|
|
|
|
pvMem = MapViewOfFile(hmem, FILE_MAP_WRITE | FILE_MAP_WRITE,
|
|
0, 0, 0);
|
|
if (pvMem == NULL)
|
|
goto done;
|
|
|
|
psmdo = (SMDumpOptions *)pvMem;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// if we didn't get an operation, no point in doing anything else...
|
|
if (eop == eopNone)
|
|
goto done;
|
|
|
|
GetSystemDirectoryW(wszMod, sizeofSTRW(wszMod));
|
|
wcscat(wszMod, L"\\faultrep.dll");
|
|
|
|
hmod = LoadLibraryExW(wszMod, NULL, 0);
|
|
|
|
VALIDATEPARM(hr, (hmod == NULL));
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
switch(eop)
|
|
{
|
|
// user or kernel faults:
|
|
case eopEvent:
|
|
ReportEvents(hmod, ct);
|
|
break;
|
|
|
|
// dumps
|
|
case eopDump:
|
|
{
|
|
pfn_CREATEMINIDUMPW pfnCM;
|
|
|
|
VALIDATEPARM(hr,(wszDump == NULL));
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
pfnCM = (pfn_CREATEMINIDUMPW)GetProcAddress(hmod,
|
|
"CreateMinidumpW");
|
|
VALIDATEPARM(hr, (pfnCM == NULL));
|
|
if (SUCCEEDED(hr))
|
|
frrv = (*pfnCM)(dwpid, wszDump, psmdo) ? frrvOk : frrvErr;
|
|
|
|
break;
|
|
}
|
|
|
|
// hangs
|
|
case eopHang:
|
|
{
|
|
pfn_REPORTHANG pfnRH;
|
|
|
|
pfnRH = (pfn_REPORTHANG)GetProcAddress(hmod, "ReportHang");
|
|
VALIDATEPARM(hr, (pfnRH == NULL));
|
|
|
|
if (SUCCEEDED(hr))
|
|
frrv = (*pfnRH)(dwpid, dwtid, f64bit, hevNotify);
|
|
|
|
break;
|
|
}
|
|
|
|
// err, shouldn't get here
|
|
default:
|
|
break;
|
|
}
|
|
|
|
done:
|
|
if (hmod != NULL)
|
|
FreeLibrary(hmod);
|
|
if (hproc != NULL)
|
|
CloseHandle(hproc);
|
|
if (hmem != NULL)
|
|
CloseHandle(hmem);
|
|
if (hevNotify != NULL)
|
|
CloseHandle(hevNotify);
|
|
if (psmdo != NULL && psmdo != &smdo)
|
|
UnmapViewOfFile((LPVOID)psmdo);
|
|
|
|
TERM_TRACING;
|
|
|
|
return frrv;
|
|
}
|