You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1189 lines
30 KiB
1189 lines
30 KiB
/*++
|
|
|
|
Copyright (c) 1991-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
savedump.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the code to recover a dump from the system paging
|
|
file.
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <savedump.h>
|
|
|
|
// Flag for testing behavior.
|
|
BOOL g_Test = FALSE;
|
|
|
|
// MachineCrash key information.
|
|
ULONG g_McTempDestination;
|
|
WCHAR g_McDumpFile[MAX_PATH];
|
|
|
|
// CrashControl key information.
|
|
ULONG g_CcLogEvent;
|
|
ULONG g_CcSendAlert;
|
|
ULONG g_CcOverwrite;
|
|
ULONG g_CcReportMachineDump;
|
|
WCHAR g_CcMiniDumpDir[MAX_PATH];
|
|
WCHAR g_CcDumpFile[MAX_PATH];
|
|
|
|
// Dump information.
|
|
DUMP_HEADER g_DumpHeader;
|
|
WCHAR g_DumpBugCheckString[256];
|
|
WCHAR g_MiniDumpFile[MAX_PATH];
|
|
PWSTR g_FinalDumpFile;
|
|
|
|
HRESULT
|
|
FrrvToStatus(EFaultRepRetVal Frrv)
|
|
{
|
|
switch(Frrv)
|
|
{
|
|
case frrvOk:
|
|
case frrvOkManifest:
|
|
case frrvOkQueued:
|
|
case frrvOkHeadless:
|
|
return S_OK;
|
|
default:
|
|
return LAST_HR();
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
GetRegStr(HKEY Key,
|
|
PWSTR Value,
|
|
PWSTR Buffer,
|
|
ULONG BufferChars,
|
|
PWSTR Default)
|
|
{
|
|
ULONG Length;
|
|
ULONG Error;
|
|
ULONG Type;
|
|
HRESULT Status;
|
|
|
|
//
|
|
// We want to only return valid, terminated strings
|
|
// that fit in the given buffer. If the registry value
|
|
// is not a string, has a bad size or fills the buffer
|
|
// without termination it can't be returned.
|
|
//
|
|
|
|
Length = BufferChars * sizeof(WCHAR);
|
|
Error = RegQueryValueEx(Key, Value, NULL, &Type, (LPBYTE)Buffer, &Length);
|
|
if (Error != ERROR_SUCCESS)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(Error);
|
|
}
|
|
else if ((Type != REG_SZ && Type != REG_EXPAND_SZ) ||
|
|
(Length & (sizeof(WCHAR) - 1)) ||
|
|
(Length == BufferChars * sizeof(WCHAR) &&
|
|
Buffer[Length / sizeof(WCHAR) - 1] != 0))
|
|
{
|
|
Status = E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
if (Length < BufferChars * sizeof(WCHAR))
|
|
{
|
|
// Ensure that the string is terminated.
|
|
Buffer[Length / sizeof(WCHAR)] = 0;
|
|
}
|
|
|
|
Status = S_OK;
|
|
}
|
|
|
|
if (Status != S_OK)
|
|
{
|
|
// Set to default.
|
|
|
|
if (!Default || wcslen(Default) >= BufferChars)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
StringCchCopy(Buffer, BufferChars, Default);
|
|
Status = S_OK;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
ExpandRegStr(HKEY Key,
|
|
PWSTR Value,
|
|
PWSTR Buffer,
|
|
ULONG BufferChars,
|
|
PWSTR Default,
|
|
PWSTR ExpandBuffer,
|
|
ULONG ExpandChars)
|
|
{
|
|
HRESULT Status;
|
|
ULONG Length;
|
|
|
|
if ((Status = GetRegStr(Key, Value, Buffer, BufferChars, Default)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
Length = ExpandEnvironmentStrings(Buffer, ExpandBuffer, ExpandChars);
|
|
return Length > 0 && Length <= ExpandChars ? S_OK : E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT
|
|
GetRegWord32(HKEY Key,
|
|
PWSTR Value,
|
|
PULONG Word,
|
|
ULONG Default,
|
|
BOOL CanDefault)
|
|
{
|
|
ULONG Length;
|
|
ULONG Error;
|
|
ULONG Type;
|
|
HRESULT Status;
|
|
|
|
Length = sizeof(*Word);
|
|
Error = RegQueryValueEx(Key, Value, NULL, &Type, (LPBYTE)Word, &Length);
|
|
if (Error != ERROR_SUCCESS)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(Error);
|
|
}
|
|
else if (Type != REG_DWORD ||
|
|
Length != sizeof(*Word))
|
|
{
|
|
Status = E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
Status = S_OK;
|
|
}
|
|
|
|
if (Status != S_OK)
|
|
{
|
|
// Set to default.
|
|
|
|
if (!CanDefault)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
*Word = Default;
|
|
Status = S_OK;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
GetRegMachineCrash(void)
|
|
{
|
|
ULONG Error;
|
|
HKEY Key;
|
|
|
|
Error = RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
SUBKEY_CRASH_CONTROL L"\\MachineCrash",
|
|
&Key);
|
|
if (Error != ERROR_SUCCESS)
|
|
{
|
|
// If the key doesn't exist we just go with the defaults.
|
|
return S_OK;
|
|
}
|
|
|
|
GetRegWord32(Key, L"TempDestination", &g_McTempDestination, 0, TRUE);
|
|
|
|
GetRegStr(Key, L"DumpFile",
|
|
g_McDumpFile, RTL_NUMBER_OF(g_McDumpFile),
|
|
L"");
|
|
|
|
RegCloseKey(Key);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
GetRegCrashControl(void)
|
|
{
|
|
HRESULT Status;
|
|
ULONG Error;
|
|
HKEY Key;
|
|
WCHAR TmpPath[MAX_PATH];
|
|
PWSTR Scan;
|
|
|
|
Error = RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
SUBKEY_CRASH_CONTROL,
|
|
&Key);
|
|
if (Error != ERROR_SUCCESS)
|
|
{
|
|
// If the key doesn't exist we just go with the defaults.
|
|
return S_OK;
|
|
}
|
|
|
|
GetRegWord32(Key, L"LogEvent", &g_CcLogEvent, 0, TRUE);
|
|
GetRegWord32(Key, L"SendAlert", &g_CcSendAlert, 0, TRUE);
|
|
GetRegWord32(Key, L"Overwrite", &g_CcOverwrite, 0, TRUE);
|
|
GetRegWord32(Key, L"ReportMachineDump", &g_CcReportMachineDump, 0, TRUE);
|
|
|
|
if ((Status = ExpandRegStr(Key, L"MiniDumpDir",
|
|
TmpPath, RTL_NUMBER_OF(TmpPath),
|
|
L"%SystemRoot%\\Minidump",
|
|
g_CcMiniDumpDir,
|
|
RTL_NUMBER_OF(g_CcMiniDumpDir))) != S_OK)
|
|
{
|
|
g_CcMiniDumpDir[0] = 0;
|
|
goto Exit;
|
|
}
|
|
|
|
// Remove any trailing slash on the directory name.
|
|
Scan = g_CcMiniDumpDir + wcslen(g_CcMiniDumpDir);
|
|
if (Scan > g_CcMiniDumpDir && *(Scan - 1) == L'\\')
|
|
{
|
|
*--Scan = 0;
|
|
}
|
|
|
|
if ((Status = ExpandRegStr(Key, L"DumpFile",
|
|
TmpPath, RTL_NUMBER_OF(TmpPath),
|
|
L"%SystemRoot%\\MEMORY.DMP",
|
|
g_CcDumpFile,
|
|
RTL_NUMBER_OF(g_CcDumpFile))) != S_OK)
|
|
{
|
|
g_CcDumpFile[0] = 0;
|
|
goto Exit;
|
|
}
|
|
|
|
Status = S_OK;
|
|
|
|
Exit:
|
|
RegCloseKey(Key);
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
GetDumpInfo(void)
|
|
{
|
|
HANDLE File;
|
|
ULONG Bytes;
|
|
BOOL Succ;
|
|
HRESULT Status;
|
|
|
|
if (!g_McDumpFile[0])
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
File = CreateFile(g_McDumpFile,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
if (File == INVALID_HANDLE_VALUE)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
Succ = ReadFile(File,
|
|
&g_DumpHeader,
|
|
sizeof(g_DumpHeader),
|
|
&Bytes,
|
|
NULL);
|
|
|
|
CloseHandle(File);
|
|
|
|
if (Succ &&
|
|
Bytes == sizeof(g_DumpHeader) &&
|
|
g_DumpHeader.Signature == DUMP_SIGNATURE &&
|
|
g_DumpHeader.ValidDump == DUMP_VALID_DUMP)
|
|
{
|
|
#ifdef _WIN64
|
|
Status =
|
|
StringCchPrintf(g_DumpBugCheckString,
|
|
RTL_NUMBER_OF(g_DumpBugCheckString),
|
|
L"0x%08x (0x%016I64x, 0x%016I64x, 0x%016I64x, 0x%016I64x)",
|
|
g_DumpHeader.BugCheckCode,
|
|
g_DumpHeader.BugCheckParameter1,
|
|
g_DumpHeader.BugCheckParameter2,
|
|
g_DumpHeader.BugCheckParameter3,
|
|
g_DumpHeader.BugCheckParameter4);
|
|
#else
|
|
Status =
|
|
StringCchPrintf(g_DumpBugCheckString,
|
|
RTL_NUMBER_OF(g_DumpBugCheckString),
|
|
L"0x%08x (0x%08x, 0x%08x, 0x%08x, 0x%08x)",
|
|
g_DumpHeader.BugCheckCode,
|
|
g_DumpHeader.BugCheckParameter1,
|
|
g_DumpHeader.BugCheckParameter2,
|
|
g_DumpHeader.BugCheckParameter3,
|
|
g_DumpHeader.BugCheckParameter4);
|
|
#endif
|
|
|
|
// This check and message are here just to make
|
|
// it easy to catch cases where the message outgrows
|
|
// the buffer. It is highly unlikely that this will happen.
|
|
if (Status != S_OK)
|
|
{
|
|
KdPrint(("SAVEDUMP: g_DumpBugCheckString too small\n"));
|
|
Status = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ZeroMemory(&g_DumpHeader, sizeof(g_DumpHeader));
|
|
Status = E_NOINTERFACE;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
SetSecurity(HANDLE FileHandle)
|
|
{
|
|
PSID LocalSystemSid = NULL;
|
|
PSID AdminSid = NULL;
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
SID_IDENTIFIER_AUTHORITY WorldAuthority = SECURITY_WORLD_SID_AUTHORITY;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|
BYTE SdBuffer[SECURITY_DESCRIPTOR_MIN_LENGTH];
|
|
PACL Acl;
|
|
BYTE AclBuffer[1024];
|
|
HANDLE Token = NULL;
|
|
PTOKEN_OWNER TokOwner;
|
|
ULONG TryLen = 256;
|
|
ULONG RetLen;
|
|
NTSTATUS NtStatus;
|
|
|
|
NtStatus = RtlAllocateAndInitializeSid(&NtAuthority, 1,
|
|
SECURITY_LOCAL_SYSTEM_RID,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
&LocalSystemSid);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
NtStatus = RtlAllocateAndInitializeSid(&NtAuthority, 2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0, 0, 0, 0, 0, 0, &AdminSid);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
SecurityDescriptor = (PSECURITY_DESCRIPTOR)SdBuffer;
|
|
|
|
//
|
|
// You can be fancy and compute the exact size, but since the
|
|
// security descriptor capture code has to do that anyway, why
|
|
// do it twice?
|
|
//
|
|
|
|
Acl = (PACL)AclBuffer;
|
|
|
|
NtStatus = RtlCreateSecurityDescriptor(SecurityDescriptor,
|
|
SECURITY_DESCRIPTOR_REVISION);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
NtStatus = RtlCreateAcl(Acl, sizeof(AclBuffer), ACL_REVISION);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Current user, Administrator and System have full control
|
|
//
|
|
|
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &Token) &&
|
|
!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &Token))
|
|
{
|
|
NtStatus = STATUS_ACCESS_DENIED;
|
|
goto Exit;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
TokOwner = (PTOKEN_OWNER)malloc(TryLen);
|
|
if (!TokOwner)
|
|
{
|
|
NtStatus = STATUS_NO_MEMORY;
|
|
goto Exit;
|
|
}
|
|
|
|
if (GetTokenInformation(Token, TokenOwner, TokOwner, TryLen, &RetLen))
|
|
{
|
|
NtStatus = RtlAddAccessAllowedAce(Acl, ACL_REVISION,
|
|
GENERIC_ALL | DELETE |
|
|
WRITE_DAC | WRITE_OWNER,
|
|
TokOwner->Owner);
|
|
break;
|
|
}
|
|
else if (RetLen <= TryLen)
|
|
{
|
|
NtStatus = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
free(TokOwner);
|
|
TryLen = RetLen;
|
|
}
|
|
|
|
free(TokOwner);
|
|
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
NtStatus = RtlAddAccessAllowedAce(Acl, ACL_REVISION,
|
|
GENERIC_ALL | DELETE |
|
|
WRITE_DAC | WRITE_OWNER,
|
|
AdminSid);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
NtStatus = RtlAddAccessAllowedAce(Acl, ACL_REVISION,
|
|
GENERIC_ALL | DELETE |
|
|
WRITE_DAC | WRITE_OWNER,
|
|
LocalSystemSid);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
NtStatus = RtlSetDaclSecurityDescriptor(SecurityDescriptor, TRUE, Acl,
|
|
FALSE);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
NtStatus = RtlSetOwnerSecurityDescriptor(SecurityDescriptor, AdminSid,
|
|
FALSE);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
NtStatus = NtSetSecurityObject(FileHandle,
|
|
DACL_SECURITY_INFORMATION,
|
|
SecurityDescriptor);
|
|
|
|
Exit:
|
|
if (AdminSid)
|
|
{
|
|
RtlFreeSid(AdminSid);
|
|
}
|
|
if (LocalSystemSid)
|
|
{
|
|
RtlFreeSid(LocalSystemSid);
|
|
}
|
|
if (Token)
|
|
{
|
|
CloseHandle(Token);
|
|
}
|
|
|
|
return NT_SUCCESS(NtStatus) ? S_OK : HRESULT_FROM_NT(NtStatus);
|
|
}
|
|
|
|
HRESULT
|
|
CreateMiniDumpFile(PHANDLE MiniFileHandle)
|
|
{
|
|
INT i;
|
|
SYSTEMTIME Time;
|
|
HRESULT Status;
|
|
HANDLE FileHandle;
|
|
|
|
if (!g_CcMiniDumpDir[0])
|
|
{
|
|
// Bad minidump directory.
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
//
|
|
// If directory does not exist, create it. Ignore errors here because
|
|
// they will be picked up later when we try to create the file.
|
|
//
|
|
|
|
CreateDirectory(g_CcMiniDumpDir, NULL);
|
|
|
|
//
|
|
// Format is: Mini-MM_DD_YY_HH_MM.dmp
|
|
//
|
|
|
|
GetLocalTime(&Time);
|
|
|
|
for (i = 1; i < 100; i++)
|
|
{
|
|
if ((Status = StringCchPrintf(g_MiniDumpFile,
|
|
RTL_NUMBER_OF(g_MiniDumpFile),
|
|
L"%s\\Mini%2.2d%2.2d%2.2d-%2.2d.dmp",
|
|
g_CcMiniDumpDir,
|
|
(int)Time.wMonth,
|
|
(int)Time.wDay,
|
|
(int)Time.wYear % 100,
|
|
(int)i)) != S_OK)
|
|
{
|
|
g_MiniDumpFile[0] = 0;
|
|
return Status;
|
|
}
|
|
|
|
FileHandle = CreateFile(g_MiniDumpFile,
|
|
GENERIC_WRITE | WRITE_DAC,
|
|
0,
|
|
NULL,
|
|
CREATE_NEW,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (FileHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (FileHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
// We failed to create a suitable file name.
|
|
g_MiniDumpFile[0] = 0;
|
|
return E_FAIL;
|
|
}
|
|
|
|
if ((Status = SetSecurity(FileHandle)) != S_OK)
|
|
{
|
|
CloseHandle(FileHandle);
|
|
DeleteFile(g_MiniDumpFile);
|
|
g_MiniDumpFile[0] = 0;
|
|
return Status;
|
|
}
|
|
|
|
*MiniFileHandle = FileHandle;
|
|
return S_OK;
|
|
}
|
|
|
|
#define COPY_CHUNK (1024 * 1024)
|
|
|
|
HRESULT
|
|
CopyAndSecureFile(PWSTR Source,
|
|
PWSTR Dest,
|
|
HANDLE DestHandle,
|
|
BOOL Overwrite,
|
|
BOOL DeleteDest)
|
|
{
|
|
HRESULT Status;
|
|
HANDLE SourceHandle = INVALID_HANDLE_VALUE;
|
|
PUCHAR Buffer = NULL;
|
|
|
|
Buffer = (PUCHAR)malloc(COPY_CHUNK);
|
|
if (!Buffer)
|
|
{
|
|
Status = E_OUTOFMEMORY;
|
|
goto Exit;
|
|
}
|
|
|
|
SourceHandle = CreateFile(Source,
|
|
GENERIC_READ,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (SourceHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
Status = LAST_HR();
|
|
goto Exit;
|
|
}
|
|
|
|
if (DestHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
DestHandle = CreateFile(Dest,
|
|
GENERIC_WRITE | WRITE_DAC,
|
|
0,
|
|
NULL,
|
|
Overwrite ? CREATE_ALWAYS : CREATE_NEW,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (DestHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
Status = LAST_HR();
|
|
goto Exit;
|
|
}
|
|
|
|
DeleteDest = TRUE;
|
|
|
|
if ((Status = SetSecurity(DestHandle)) != S_OK)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
ULONG Req, Done;
|
|
|
|
if (!ReadFile(SourceHandle, Buffer, COPY_CHUNK, &Done, NULL))
|
|
{
|
|
Status = LAST_HR();
|
|
break;
|
|
}
|
|
else if (Done == 0)
|
|
{
|
|
// End-of-file.
|
|
Status = S_OK;
|
|
break;
|
|
}
|
|
|
|
Req = Done;
|
|
if (!WriteFile(DestHandle, Buffer, Req, &Done, NULL))
|
|
{
|
|
Status = LAST_HR();
|
|
break;
|
|
}
|
|
else if (Done < Req)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(ERROR_HANDLE_DISK_FULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
if (DeleteDest)
|
|
{
|
|
CloseHandle(DestHandle);
|
|
if (Status != S_OK)
|
|
{
|
|
DeleteFile(Dest);
|
|
}
|
|
}
|
|
if (SourceHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(SourceHandle);
|
|
}
|
|
free(Buffer);
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
MoveDumpFile(void)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (g_DumpHeader.Signature != DUMP_SIGNATURE)
|
|
{
|
|
// Dump file is not present or invalid, so there's nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// If the dump file needs to be copied, copy it now.
|
|
//
|
|
|
|
if (!g_McTempDestination)
|
|
{
|
|
g_FinalDumpFile = g_McDumpFile;
|
|
}
|
|
else
|
|
{
|
|
if (!g_Test)
|
|
{
|
|
//
|
|
// Set the priority class of this application down to the Lowest
|
|
// priority class to ensure that copying the file does not overload
|
|
// everything else that is going on during system initialization.
|
|
//
|
|
// We do not lower the priority in test mode because it just
|
|
// wastes time.
|
|
//
|
|
|
|
SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS);
|
|
SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_LOWEST);
|
|
}
|
|
|
|
if (g_DumpHeader.DumpType == DUMP_TYPE_FULL ||
|
|
g_DumpHeader.DumpType == DUMP_TYPE_SUMMARY)
|
|
{
|
|
if (!g_CcDumpFile[0])
|
|
{
|
|
// Invalid dump file registry entry.
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if ((Status = CopyAndSecureFile(g_McDumpFile,
|
|
g_CcDumpFile,
|
|
INVALID_HANDLE_VALUE,
|
|
g_CcOverwrite ? TRUE : FALSE,
|
|
TRUE)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
g_FinalDumpFile = g_CcDumpFile;
|
|
}
|
|
else if (g_DumpHeader.DumpType == DUMP_TYPE_TRIAGE)
|
|
{
|
|
HANDLE MiniFile;
|
|
|
|
if ((Status = CreateMiniDumpFile(&MiniFile)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if ((Status = CopyAndSecureFile(g_McDumpFile,
|
|
NULL,
|
|
MiniFile,
|
|
FALSE,
|
|
TRUE)) != S_OK)
|
|
{
|
|
g_MiniDumpFile[0] = 0;
|
|
return Status;
|
|
}
|
|
|
|
g_FinalDumpFile = g_MiniDumpFile;
|
|
}
|
|
|
|
DeleteFile(g_McDumpFile);
|
|
g_McDumpFile[0] = 0;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConvertDumpFile(void)
|
|
{
|
|
HRESULT Status;
|
|
IDebugClient4 *DebugClient;
|
|
IDebugControl *DebugControl;
|
|
HANDLE MiniFile;
|
|
|
|
//
|
|
// Produce a minidump by conversion if necessary.
|
|
//
|
|
|
|
if (!g_FinalDumpFile ||
|
|
(g_DumpHeader.DumpType != DUMP_TYPE_FULL &&
|
|
g_DumpHeader.DumpType != DUMP_TYPE_SUMMARY))
|
|
{
|
|
// No dump or not a convertable dump.
|
|
return S_OK;
|
|
}
|
|
|
|
if ((Status = CreateMiniDumpFile(&MiniFile)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if ((Status = DebugCreate(__uuidof(IDebugClient4),
|
|
(void **)&DebugClient)) != S_OK)
|
|
{
|
|
goto EH_File;
|
|
}
|
|
if ((Status = DebugClient->QueryInterface(__uuidof(IDebugControl),
|
|
(void **)&DebugControl)) != S_OK)
|
|
{
|
|
goto EH_Client;
|
|
}
|
|
|
|
if ((Status = DebugClient->OpenDumpFileWide(g_FinalDumpFile, 0)) != S_OK)
|
|
{
|
|
goto EH_Control;
|
|
}
|
|
|
|
if ((Status = DebugControl->
|
|
WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) != S_OK)
|
|
{
|
|
goto EH_Control;
|
|
}
|
|
|
|
Status = DebugClient->
|
|
WriteDumpFileWide(NULL, (ULONG_PTR)MiniFile, DEBUG_DUMP_SMALL,
|
|
DEBUG_FORMAT_DEFAULT, NULL);
|
|
|
|
EH_Control:
|
|
DebugControl->Release();
|
|
EH_Client:
|
|
DebugClient->EndSession(DEBUG_END_PASSIVE);
|
|
DebugClient->Release();
|
|
EH_File:
|
|
CloseHandle(MiniFile);
|
|
if (Status != S_OK)
|
|
{
|
|
DeleteFile(g_MiniDumpFile);
|
|
g_MiniDumpFile[0] = 0;
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
VOID
|
|
LogEvent(ULONG Id,
|
|
WORD StringCount,
|
|
PWSTR* Strings)
|
|
{
|
|
HANDLE LogHandle;
|
|
BOOL Retry;
|
|
DWORD Retries;
|
|
|
|
//
|
|
// Attempt to register the event source.
|
|
// Savedump runs early in the startup process so
|
|
// it's possible that the event service hasn't started
|
|
// yet. If it appears that the service hasn't started,
|
|
// wait a bit until it comes around. If it hasn't
|
|
// come around after a reasonable amount of time bail out.
|
|
//
|
|
|
|
Retries = 0;
|
|
|
|
do
|
|
{
|
|
LogHandle = RegisterEventSource(NULL, L"Save Dump");
|
|
|
|
//
|
|
// Retry on specific failures that indicate the event
|
|
// service hasn't started yet.
|
|
//
|
|
|
|
if (LogHandle == NULL &&
|
|
Retries < 20 &&
|
|
(GetLastError () == RPC_S_SERVER_UNAVAILABLE ||
|
|
GetLastError () == RPC_S_UNKNOWN_IF))
|
|
{
|
|
Sleep(1500);
|
|
Retry = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Retry = FALSE;
|
|
}
|
|
|
|
Retries++;
|
|
}
|
|
while (LogHandle == NULL && Retry);
|
|
|
|
if (!LogHandle)
|
|
{
|
|
KdPrint(("SAVEDUMP: Unable to register event source, %d\n",
|
|
GetLastError()));
|
|
return;
|
|
}
|
|
|
|
if (!ReportEvent(LogHandle,
|
|
EVENTLOG_INFORMATION_TYPE,
|
|
0,
|
|
Id,
|
|
NULL,
|
|
StringCount,
|
|
0,
|
|
(LPCWSTR *)Strings,
|
|
NULL))
|
|
{
|
|
KdPrint(("SAVEDUMP: Unable to report event, %d\n", GetLastError()));
|
|
}
|
|
|
|
DeregisterEventSource(LogHandle);
|
|
}
|
|
|
|
void
|
|
LogCrashDumpEvent(void)
|
|
{
|
|
LPWSTR StringArray[2];
|
|
WORD StringCount;
|
|
DWORD EventId;
|
|
|
|
//
|
|
// Set up the parameters based on how much information
|
|
// is available.
|
|
//
|
|
|
|
StringCount = 0;
|
|
|
|
if (g_DumpBugCheckString[0])
|
|
{
|
|
StringArray[StringCount++] = g_DumpBugCheckString;
|
|
}
|
|
if (g_FinalDumpFile)
|
|
{
|
|
StringArray[StringCount++] = g_FinalDumpFile;
|
|
}
|
|
|
|
//
|
|
// Report the appropriate event.
|
|
//
|
|
|
|
if (g_FinalDumpFile)
|
|
{
|
|
EventId = EVENT_BUGCHECK_SAVED;
|
|
}
|
|
else if (g_DumpBugCheckString[0])
|
|
{
|
|
EventId = EVENT_BUGCHECK;
|
|
}
|
|
else
|
|
{
|
|
EventId = EVENT_UNKNOWN_BUGCHECK;
|
|
}
|
|
|
|
LogEvent(EventId, StringCount, StringArray);
|
|
}
|
|
|
|
void
|
|
SendCrashDumpAlert(void)
|
|
{
|
|
PADMIN_OTHER_INFO AdminInfo;
|
|
DWORD AdminInfoSize;
|
|
DWORD Length;
|
|
DWORD i;
|
|
ULONG Error;
|
|
UCHAR VariableInfo[4096];
|
|
|
|
//
|
|
// Set up the administrator information variables for processing the
|
|
// buffer.
|
|
//
|
|
|
|
AdminInfo = (PADMIN_OTHER_INFO)VariableInfo;
|
|
AdminInfo->alrtad_numstrings = 0;
|
|
AdminInfoSize = sizeof(*AdminInfo);
|
|
|
|
//
|
|
// Format the bugcheck information into the appropriate message format.
|
|
//
|
|
|
|
if (g_DumpBugCheckString[0])
|
|
{
|
|
Length = (wcslen(g_DumpBugCheckString) + 1) * sizeof(WCHAR);
|
|
if (AdminInfoSize + Length > sizeof(VariableInfo))
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
RtlCopyMemory((PCHAR)AdminInfo + AdminInfoSize,
|
|
g_DumpBugCheckString, Length);
|
|
|
|
AdminInfo->alrtad_numstrings++;
|
|
AdminInfoSize += Length;
|
|
}
|
|
|
|
|
|
//
|
|
// Set up the administrator alert information according to the type of
|
|
// dump that was taken.
|
|
//
|
|
|
|
if (g_FinalDumpFile)
|
|
{
|
|
Length = (wcslen(g_FinalDumpFile) + 1) * sizeof(WCHAR);
|
|
|
|
if (AdminInfoSize + Length > sizeof(VariableInfo))
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
AdminInfo->alrtad_errcode = ALERT_BugCheckSaved;
|
|
RtlCopyMemory((PCHAR)AdminInfo + AdminInfoSize,
|
|
g_FinalDumpFile, Length);
|
|
AdminInfo->alrtad_numstrings++;
|
|
AdminInfoSize += Length;
|
|
}
|
|
else
|
|
{
|
|
AdminInfo->alrtad_errcode = ALERT_BugCheck;
|
|
}
|
|
|
|
//
|
|
// Get the name of the computer and insert it into the buffer.
|
|
//
|
|
|
|
Length = (sizeof(VariableInfo) - AdminInfoSize) / sizeof(WCHAR);
|
|
if (!GetComputerName((LPWSTR)((PCHAR)AdminInfo + AdminInfoSize),
|
|
&Length))
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
Length = (Length + 1) * sizeof(WCHAR);
|
|
AdminInfo->alrtad_numstrings++;
|
|
AdminInfoSize += Length;
|
|
|
|
//
|
|
// Raise the alert.
|
|
//
|
|
|
|
i = 0;
|
|
|
|
do
|
|
{
|
|
Error = NetAlertRaiseEx(ALERT_ADMIN_EVENT,
|
|
AdminInfo,
|
|
AdminInfoSize,
|
|
L"SAVEDUMP");
|
|
if (Error)
|
|
{
|
|
if (Error == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
if (i++ > 20)
|
|
{
|
|
break;
|
|
}
|
|
if ((i & 3) == 0)
|
|
{
|
|
KdPrint(("SAVEDUMP: Waiting for alerter...\n"));
|
|
}
|
|
|
|
Sleep(15000);
|
|
}
|
|
}
|
|
} while (Error == ERROR_FILE_NOT_FOUND);
|
|
|
|
if (Error != ERROR_SUCCESS)
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
return;
|
|
|
|
Error:
|
|
KdPrint(("SAVEDUMP: Unable to raise alert\n"));
|
|
}
|
|
|
|
VOID
|
|
__cdecl
|
|
wmain(int Argc,
|
|
PWSTR Argv[])
|
|
{
|
|
int Arg;
|
|
BOOL Report = TRUE;
|
|
|
|
for (Arg = 1; Arg < Argc; Arg++)
|
|
{
|
|
if (Argv[Arg][0] == L'-' || Argv[Arg][0] == L'/')
|
|
{
|
|
switch (Argv[Arg][1])
|
|
{
|
|
case L't':
|
|
case L'T':
|
|
#if DBG
|
|
g_Test = TRUE;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetRegMachineCrash() != S_OK ||
|
|
GetRegCrashControl() != S_OK)
|
|
{
|
|
LogEvent(EVENT_UNABLE_TO_READ_REGISTRY, 0, NULL);
|
|
}
|
|
|
|
GetDumpInfo();
|
|
|
|
if (MoveDumpFile() != S_OK)
|
|
{
|
|
LogEvent(EVENT_UNABLE_TO_MOVE_DUMP_FILE, 0, NULL);
|
|
}
|
|
|
|
if (ConvertDumpFile() != S_OK)
|
|
{
|
|
LogEvent(EVENT_UNABLE_TO_CONVERT_DUMP_FILE, 0, NULL);
|
|
}
|
|
|
|
//if (WatchdogEventHandler(TRUE) == S_OK)
|
|
//{
|
|
// // Note: Bugcheck EA will be reported in the watchdog code since
|
|
// // we need to send minidump and wdl files together and we want to have
|
|
// // a single pop-up only.
|
|
// Report = FALSE;
|
|
//}
|
|
|
|
if (Report)
|
|
{
|
|
// The default behavior is to report a minidump
|
|
// even if the machine dump was not a minidump in
|
|
// order to minimize the amount of data sent.
|
|
// If the system is configured to report the
|
|
// machine dump go ahead and send it regardless
|
|
// of what kind of dump it is.
|
|
PWSTR ReportDumpFile = g_CcReportMachineDump ?
|
|
g_FinalDumpFile : g_MiniDumpFile;
|
|
|
|
if (ReportDumpFile && ReportDumpFile[0])
|
|
{
|
|
// Report bugcheck to Microsoft Error Reporting.
|
|
if (FrrvToStatus(ReportEREvent(eetKernelFault,
|
|
ReportDumpFile,
|
|
NULL)) != S_OK)
|
|
{
|
|
LogEvent(EVENT_UNABLE_TO_REPORT_BUGCHECK, 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Knock down reliability ShutdownEventPending flag. We must always try
|
|
// to do this since somebody can set this flag and recover later on
|
|
// (e.g. watchdog's EventFlag cleared). With this flag set savedump
|
|
// will always run and we don't want that.
|
|
//
|
|
// Note: This flag is shared between multiple components.
|
|
// Only savedump is allowed to clear this flag, all other
|
|
// components are only allowed to set it to trigger
|
|
// savedump run at next logon.
|
|
//
|
|
|
|
HKEY Key;
|
|
|
|
if (RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
SUBKEY_RELIABILITY,
|
|
&Key) == ERROR_SUCCESS)
|
|
{
|
|
RegDeleteValue(Key, L"ShutdownEventPending");
|
|
RegCloseKey(Key);
|
|
}
|
|
|
|
//
|
|
// If there was a dump produced we may need to log an event
|
|
// and send an alert.
|
|
// We delay these time consuming opertaions till the end. We had the
|
|
// case where SendCrashDumpAlert delayed PC Health pop-ups few minutes.
|
|
//
|
|
|
|
BOOL HaveCrashData =
|
|
g_McDumpFile[0] ||
|
|
g_DumpBugCheckString[0] ||
|
|
g_FinalDumpFile;
|
|
|
|
if (HaveCrashData && g_CcLogEvent)
|
|
{
|
|
LogCrashDumpEvent();
|
|
}
|
|
|
|
//
|
|
// This function will fill the BugCheckString for DirtyShutdown UI based
|
|
// on the flag set by EventLog service during startup time, in some case
|
|
// Eventlog service might start after savedump, so we will need to run this
|
|
// function after the first event was logged by savedump.
|
|
// if g_CcLogEvent == FALSE, it is OK not set the string since the user
|
|
// are not interested about the BugCheck info at all.
|
|
//
|
|
if (DirtyShutdownEventHandler(TRUE) != S_OK)
|
|
{
|
|
LogEvent(EVENT_UNABLE_TO_REPORT_DIRTY_SHUTDOWN, 0, NULL);
|
|
}
|
|
|
|
if (HaveCrashData && g_CcSendAlert)
|
|
{
|
|
SendCrashDumpAlert();
|
|
}
|
|
}
|