585 lines
16 KiB
C++
585 lines
16 KiB
C++
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1999 - 2000
|
|
//
|
|
// File: dmpfile.cpp
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
// DmpFile.cpp: implementation of the CDmpFile class.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "DmpFile.h"
|
|
#include "Globals.h"
|
|
#include "UtilityFunctions.h"
|
|
#include "ProgramOptions.h"
|
|
#include "ProcessInfo.h"
|
|
#include "Modules.h"
|
|
#include "FileData.h"
|
|
#include "ModuleInfoCache.h"
|
|
#include "ModuleInfo.h"
|
|
|
|
// Let's implement the DebugOutputCallback for the DBGENG... it'll be cool to have the debugger
|
|
// spit out info to us when it is running...
|
|
STDMETHODIMP
|
|
OutputCallbacks::QueryInterface(
|
|
THIS_
|
|
IN REFIID InterfaceId,
|
|
OUT PVOID* Interface
|
|
)
|
|
{
|
|
*Interface = NULL;
|
|
|
|
if (IsEqualIID(InterfaceId, IID_IUnknown) ||
|
|
IsEqualIID(InterfaceId, IID_IDebugOutputCallbacks))
|
|
{
|
|
*Interface = (IDebugOutputCallbacks *)this;
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
OutputCallbacks::AddRef(
|
|
THIS
|
|
)
|
|
{
|
|
// This class is designed to be static so
|
|
// there's no true refcount.
|
|
return 1;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
OutputCallbacks::Release(
|
|
THIS
|
|
)
|
|
{
|
|
// This class is designed to be static so
|
|
// there's no true refcount.
|
|
return 0;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
OutputCallbacks::Output(
|
|
THIS_
|
|
IN ULONG Mask,
|
|
IN PCSTR Text
|
|
)
|
|
{
|
|
HRESULT Status = S_OK;
|
|
|
|
if (!g_lpProgramOptions->GetMode(CProgramOptions::QuietMode) && (Mask & DEBUG_OUTPUT_NORMAL))
|
|
{
|
|
printf(Text);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
CDmpFile::CDmpFile()
|
|
{
|
|
m_szDmpFilePath = NULL;
|
|
m_szSymbolPath = NULL;
|
|
m_fDmpInitialized = false;
|
|
m_pIDebugClient = NULL;
|
|
m_pIDebugControl = NULL;
|
|
m_pIDebugSymbols = NULL;
|
|
m_pIDebugDataSpaces = NULL;
|
|
m_DumpClass = DEBUG_CLASS_UNINITIALIZED;
|
|
m_DumpClassQualifier = 0;
|
|
}
|
|
|
|
CDmpFile::~CDmpFile()
|
|
{
|
|
if (m_fDmpInitialized)
|
|
{
|
|
// Let's ensure that our debug output is set to normal (at least)
|
|
//m_pIDebugClient->GetOutputMask(&OutMask);
|
|
//OutMask = ~DEBUG_OUTPUT_NORMAL;
|
|
m_pIDebugClient->SetOutputMask(0);
|
|
|
|
// Let's be as least intrusive as possible...
|
|
m_pIDebugClient->EndSession(DEBUG_END_ACTIVE_DETACH);
|
|
}
|
|
|
|
if (m_szDmpFilePath)
|
|
delete [] m_szDmpFilePath;
|
|
|
|
if (m_szSymbolPath)
|
|
delete [] m_szSymbolPath;
|
|
}
|
|
|
|
OutputCallbacks g_OutputCb;
|
|
|
|
bool CDmpFile::Initialize(CFileData * lpOutputFile)
|
|
{
|
|
HRESULT Hr;
|
|
ULONG g_ExecStatus = DEBUG_STATUS_NO_DEBUGGEE;
|
|
LPTSTR tszExpandedString = NULL;
|
|
bool fReturn = false;
|
|
|
|
// Let's save off big objects so we don't have to keep passing this to
|
|
// our methods...
|
|
m_lpOutputFile = lpOutputFile;
|
|
|
|
// The DBGENG is somewhat ANSI oriented...
|
|
m_szDmpFilePath = CUtilityFunctions::CopyTSTRStringToAnsi(g_lpProgramOptions->GetDmpFilePath(), m_szDmpFilePath, 0);
|
|
|
|
// Create our interface pointer to do our Debug Work...
|
|
if (FAILED(Hr = DebugCreate(IID_IDebugClient, (void **)&m_pIDebugClient)))
|
|
goto cleanup;
|
|
|
|
// Let's query for IDebugControl interface (we need it to determine debug type easily)...
|
|
// Let's query for IDebugSymbols interface as we need it to receive module info...
|
|
// Let's query for IDebugDataSpaces interface as we need it to read DMP memory...
|
|
if (
|
|
FAILED(Hr = m_pIDebugClient->QueryInterface(IID_IDebugControl,(void **)&m_pIDebugControl)) ||
|
|
FAILED(Hr = m_pIDebugClient->QueryInterface(IID_IDebugSymbols,(void **)&m_pIDebugSymbols)) ||
|
|
FAILED(Hr = m_pIDebugClient->QueryInterface(IID_IDebugDataSpaces,(void **)&m_pIDebugDataSpaces))
|
|
)
|
|
{
|
|
_tprintf(TEXT("Error: DBGENG Interfaces required were not found!\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
// Set callbacks.
|
|
if (FAILED(Hr = m_pIDebugClient->SetOutputCallbacks(&g_OutputCb)) //||
|
|
//FAILED(Hr = m_pIDebugClient->SetEventCallbacks(&g_DebugEventCallbacks))
|
|
)
|
|
{
|
|
//
|
|
//
|
|
//
|
|
_tprintf(TEXT("Error: DBGENG - Unable to SetOutputCallbacks!\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
DWORD OutMask;
|
|
|
|
// Let's ensure that our debug output is set to normal (at least)
|
|
OutMask = m_pIDebugClient->GetOutputMask(&OutMask);
|
|
m_pIDebugClient->SetOutputMask(OutMask | DEBUG_OUTPUT_NORMAL);
|
|
|
|
// Set our symbol path... this is required prior to a "reload" of modules...
|
|
|
|
// The DBGENG is somewhat ASCII oriented... we need an environment-expanded string converted
|
|
// to an ASCII string...
|
|
tszExpandedString = CUtilityFunctions::ExpandPath(g_lpProgramOptions->GetSymbolPath());
|
|
|
|
if (!tszExpandedString)
|
|
goto cleanup;
|
|
|
|
m_szSymbolPath = CUtilityFunctions::CopyTSTRStringToAnsi( tszExpandedString, m_szSymbolPath, 0);
|
|
|
|
// It's a bit premature to set this now... but it's required by DBGENG.DLL before a reload...
|
|
if (FAILED(Hr = m_pIDebugSymbols->SetSymbolPath(m_szSymbolPath)))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
// Let's open the dump...
|
|
if (FAILED(Hr = m_pIDebugClient->OpenDumpFile(m_szDmpFilePath)))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
// Get Initial Execution state.
|
|
if (FAILED(Hr = m_pIDebugControl->GetExecutionStatus(&g_ExecStatus)))
|
|
{
|
|
_tprintf(TEXT("Unable to get execution status! Hr=0x%x\n"), Hr);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (g_ExecStatus != DEBUG_STATUS_NO_DEBUGGEE)
|
|
{
|
|
// I think we'll work just fine?
|
|
_tprintf(TEXT("Debug Session is already active!\n"));
|
|
// goto cleanup;
|
|
}
|
|
|
|
// What type of dump did we get?
|
|
if (FAILED(Hr = m_pIDebugControl->GetDebuggeeType(&m_DumpClass, &m_DumpClassQualifier)))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
m_pIDebugClient->SetOutputMask(0); // Temporarily suppress this stuff...
|
|
|
|
//
|
|
// All the good stuff happens here... modules load, etc.. we could suppress all the output
|
|
// but it's cool to watch...
|
|
//
|
|
if (FAILED(Hr = m_pIDebugControl->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
// Restore output!
|
|
m_pIDebugClient->SetOutputMask(OutMask | DEBUG_OUTPUT_NORMAL);
|
|
|
|
// Yee haa... we got something...
|
|
m_fDmpInitialized = true;
|
|
|
|
fReturn = true;
|
|
|
|
cleanup:
|
|
if (tszExpandedString)
|
|
delete [] tszExpandedString;
|
|
|
|
return fReturn;
|
|
}
|
|
|
|
bool CDmpFile::CollectData(CProcessInfo ** lplpProcessInfo, CModules ** lplpModules, CModuleInfoCache * lpModuleInfoCache)
|
|
{
|
|
bool fReturn = false;
|
|
// Okay... first order of business is to decide what we need to collect...
|
|
|
|
// Collect information from the file based on it's type...
|
|
if (IsUserDmpFile())
|
|
{
|
|
// Second, order of business is to prepare for collecting info about the
|
|
// process in the USER.DMP file...
|
|
(*lplpProcessInfo) = new CProcessInfo();
|
|
|
|
if ((*lplpProcessInfo) == NULL)
|
|
goto cleanup;
|
|
|
|
if (!(*lplpProcessInfo)->Initialize(lpModuleInfoCache, NULL, m_lpOutputFile, this))
|
|
goto cleanup;
|
|
} else
|
|
{
|
|
(*lplpModules) = new CModules();
|
|
|
|
if ((*lplpModules) == NULL)
|
|
goto cleanup;
|
|
|
|
if (!(*lplpModules)->Initialize(lpModuleInfoCache, NULL, m_lpOutputFile, this))
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!EumerateModulesFromDmp(lpModuleInfoCache, *lplpProcessInfo, *lplpModules))
|
|
goto cleanup;
|
|
|
|
fReturn = true;
|
|
|
|
cleanup:
|
|
|
|
return fReturn;
|
|
}
|
|
|
|
//
|
|
// Combined DMP Enumeration Code
|
|
//
|
|
bool CDmpFile::EumerateModulesFromDmp(CModuleInfoCache * lpModuleInfoCache, CProcessInfo * lpProcessInfo, CModules * lpModules)
|
|
{
|
|
//
|
|
// Consult DumpModuleTable in Ntsym.cpp for ideas...
|
|
//
|
|
CModuleInfo * lpModuleInfo;
|
|
HRESULT Hr;
|
|
ULONG ulNumberOfLoadedModules;
|
|
ULONG ulNumberOfUnloadedModules;
|
|
ULONG64 Base;
|
|
char szImageNameBuffer[_MAX_PATH];
|
|
TCHAR tszModulePath[_MAX_PATH];
|
|
// TCHAR tszModuleFilePath[_MAX_PATH];
|
|
TCHAR tszModuleFileName[_MAX_FNAME];
|
|
TCHAR tszModuleFileExtension[_MAX_EXT];
|
|
bool fNew, fProcessNameFound = false;
|
|
bool fUserDmp = IsUserDmpFile();
|
|
|
|
// How many modules were found?
|
|
if (FAILED(Hr = m_pIDebugSymbols->GetNumberModules(&ulNumberOfLoadedModules, &ulNumberOfUnloadedModules)))
|
|
{
|
|
_tprintf(TEXT("Unable to enumerate any modules in the DMP file!\n"));
|
|
return false;
|
|
}
|
|
|
|
if (!g_lpProgramOptions->GetMode(CProgramOptions::QuietMode))
|
|
{
|
|
_tprintf(TEXT("\n%-8s %-8s %-30s %s\n"), TEXT("Start"),
|
|
TEXT("End"),
|
|
TEXT("Module Name"),
|
|
TEXT("Time/Date"));
|
|
}
|
|
|
|
//
|
|
// Enumerate through the modules in the DMP file...
|
|
//
|
|
for (unsigned int i = 0; i < ulNumberOfLoadedModules; i++)
|
|
{
|
|
// First, we get the Base address by our index
|
|
if (FAILED(Hr = m_pIDebugSymbols->GetModuleByIndex(i, &Base)))
|
|
{
|
|
_tprintf(TEXT("Failed getting base address of module number %d\n"), i);
|
|
continue; // try the next?
|
|
}
|
|
|
|
// Second, we get the name from our base address
|
|
ULONG ulImageNameSize;
|
|
|
|
//
|
|
// This can return both the ImageNameBuffer and a ModuleNameBuffer...
|
|
// The ImageNameBuffer typically contains the entire module name like (MODULE.DLL),
|
|
// whereas the ModuleNameBuffer is typically just the module name like (MODULE).
|
|
//
|
|
if (FAILED(Hr = m_pIDebugSymbols->GetModuleNames( DEBUG_ANY_ID, // Use Base address
|
|
Base, // Base address from above
|
|
szImageNameBuffer,
|
|
_MAX_PATH,
|
|
&ulImageNameSize,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
NULL)))
|
|
{
|
|
_tprintf(TEXT("Failed getting name of module at base 0x%x\n"), Base);
|
|
continue; // try the next?
|
|
}
|
|
|
|
// Convert the string to something we can use...
|
|
CUtilityFunctions::CopyAnsiStringToTSTR(szImageNameBuffer, tszModulePath, _MAX_PATH);
|
|
|
|
// Third, we can now get whatever we want from memory...
|
|
|
|
if (!g_lpProgramOptions->fDoesModuleMatchOurSearch(tszModulePath))
|
|
continue;
|
|
|
|
// Okay, let's go ahead and get a ModuleInfo Object from our cache...
|
|
|
|
// If pfNew returns TRUE, then this object is new and we'll need
|
|
// to populate it with data...
|
|
lpModuleInfo = lpModuleInfoCache->AddNewModuleInfoObject(tszModulePath, &fNew);
|
|
|
|
if (false == fNew)
|
|
{
|
|
// We may have the object in the cache... now we need to
|
|
// save a pointer to this object in our Process Info list
|
|
if (fUserDmp )
|
|
{
|
|
lpProcessInfo->AddNewModuleInfoObject(lpModuleInfo); // Just do our best...
|
|
} else
|
|
{
|
|
lpModules->AddNewModuleInfoObject(lpModuleInfo); // Just do our best...
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Not in the cache... so we need to init it, and get the module info...
|
|
if (!lpModuleInfo->Initialize(NULL, m_lpOutputFile, this))
|
|
{
|
|
return false; // Hmmm... memory error?
|
|
}
|
|
|
|
//
|
|
// Okay, get the module info from the DMP file...
|
|
//
|
|
if (lpModuleInfo->GetModuleInfo(tszModulePath, true, Base) )
|
|
{
|
|
// We may have the object in the cache... now we need to
|
|
// save a pointer to this object in our Process Info list
|
|
if (fUserDmp)
|
|
{
|
|
lpProcessInfo->AddNewModuleInfoObject(lpModuleInfo); // Just do our best...
|
|
} else
|
|
{
|
|
lpModules->AddNewModuleInfoObject(lpModuleInfo); // Just do our best...
|
|
}
|
|
} else
|
|
{
|
|
// Continue back to try another module on error...
|
|
continue;
|
|
}
|
|
|
|
// Try and patch up the original name of the module...
|
|
|
|
// Save the current module path as the DBG stuff
|
|
|
|
// We'll tack on .DBG to roll through our own code correctly...
|
|
_tsplitpath(tszModulePath, NULL, NULL, tszModuleFileName, tszModuleFileExtension);
|
|
|
|
//_tcscpy(tszModulePath, tszModuleFileName);
|
|
|
|
//_tcscpy(tszModuleFileName, tszModulePath);
|
|
|
|
/* if (*tszModuleFileExtension == '\0')
|
|
{
|
|
_tcscat(tszModulePath, TEXT(".DBG"));
|
|
} else
|
|
{
|
|
_tcscat(tszModulePath, tszModuleFileExtension);
|
|
}
|
|
*/
|
|
if ( (lpModuleInfo->GetPESymbolInformation() == CModuleInfo::SYMBOLS_DBG) ||
|
|
(lpModuleInfo->GetPESymbolInformation() == CModuleInfo::SYMBOLS_DBG_AND_PDB) )
|
|
{
|
|
// Append .DBG to our module name
|
|
_tcscat(tszModuleFileName, TEXT(".DBG"));
|
|
|
|
lpModuleInfo->SetDebugDirectoryDBGPath(tszModuleFileName);
|
|
|
|
/*
|
|
|
|
// Ordinarily this seems very dangerous.. but the size of the new string
|
|
// will be less than the original, so this should be safe... we hope?!
|
|
if (_tcsicmp(&tszModulePath[_tcslen(tszModulePath)-4], TEXT(".DBG")) == 0)
|
|
{
|
|
_tsplitpath(tszModulePath, NULL, tszModuleFilePath, tszModuleFileName, NULL);
|
|
|
|
if (_tcslen(tszModuleFilePath)==4)
|
|
{
|
|
tszModuleFilePath[_tcslen(tszModuleFilePath)-1] = 0;
|
|
_tcscpy(tszModulePath, tszModuleFileName);
|
|
_tcscat(tszModulePath, TEXT("."));
|
|
_tcscat(tszModulePath, tszModuleFilePath);
|
|
|
|
} else if ( lpModuleInfo->IsDLL() )
|
|
{
|
|
_tcscpy(tszModulePath, tszModuleFileName);
|
|
_tcscat(tszModulePath, TEXT(".DLL"));
|
|
|
|
} else
|
|
{
|
|
_tcscpy(tszModulePath, tszModuleFileName);
|
|
_tcscat(tszModulePath, TEXT(".EXE"));
|
|
}
|
|
} else
|
|
{
|
|
// We didn't find a .DBG extension... let's tack on a guess...
|
|
if ( lpModuleInfo->IsDLL() )
|
|
{
|
|
_tcscat(tszModulePath, TEXT(".DLL"));
|
|
|
|
} else
|
|
{
|
|
_tcscat(tszModulePath, TEXT(".EXE"));
|
|
}
|
|
}
|
|
*/
|
|
} else if (lpModuleInfo->GetPESymbolInformation() == CModuleInfo::SYMBOLS_PDB)
|
|
{
|
|
if (lpModuleInfo->GetDebugDirectoryPDBPath())
|
|
{
|
|
/* // Try and translate the module name to something friendlier
|
|
_tsplitpath(lpModuleInfo->GetDebugDirectoryPDBPath(), NULL, NULL, tszModuleFileName, NULL);
|
|
|
|
// Compose the name by appending the extension (we hope if it is not a EXE it will
|
|
// be a DLL (with that extension)..
|
|
if ( lpModuleInfo->IsDLL() )
|
|
{
|
|
_tcscpy(tszModulePath, tszModuleFileName);
|
|
_tcscat(tszModulePath, TEXT(".DLL"));
|
|
|
|
} else
|
|
{
|
|
_tcscpy(tszModulePath, tszModuleFileName);
|
|
_tcscat(tszModulePath, TEXT(".EXE"));
|
|
}
|
|
*/
|
|
} else
|
|
{
|
|
//
|
|
// Unfortunately, we can't find the PDB Imagepath in the DMP file... so we'll
|
|
// just guess what it would be...
|
|
//
|
|
// Append .PDB to our module name
|
|
_tcscat(tszModuleFileName, TEXT(".PDB"));
|
|
|
|
lpModuleInfo->SetPEDebugDirectoryPDBPath(tszModuleFileName);
|
|
|
|
|
|
// Compose the name by appending the extension (we hope if it is not a EXE it will
|
|
// be a DLL (with that extension)... Also, by this point we MAY have found an image
|
|
// name like EXE\MODULE.DBG... we want to strip off the trailing .DBG before appending...
|
|
/* unsigned int cbModulePathLength = _tcslen(tszModulePath);
|
|
|
|
if ( cbModulePathLength > 4) // Look to see if this exceeds chars before doing this next operation..
|
|
{
|
|
if (_tcsicmp(&tszModulePath[cbModulePathLength-4], TEXT(".DBG")) == 0)
|
|
{
|
|
// We found a .DBG extension... let's nuke it...
|
|
tszModulePath[cbModulePathLength-4] = '\0';
|
|
}
|
|
}
|
|
|
|
// Append the appropriate extension...
|
|
if ( lpModuleInfo->IsDLL() )
|
|
{
|
|
_tcscat(tszModulePath, TEXT(".DLL"));
|
|
|
|
} else
|
|
{
|
|
_tcscat(tszModulePath, TEXT(".EXE"));
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
// Now, let's remove the extra path bits...
|
|
_tsplitpath(tszModulePath, NULL, NULL, tszModuleFileName, tszModuleFileExtension);
|
|
|
|
_tcscpy(tszModulePath, tszModuleFileName);
|
|
_tcscat(tszModulePath, tszModuleFileExtension);
|
|
|
|
// Save the current module path as the DBG stuff
|
|
lpModuleInfo->SetPEImageModulePath(tszModulePath);
|
|
|
|
// Save the current module name as well...
|
|
lpModuleInfo->SetPEImageModuleName(tszModulePath);
|
|
|
|
// Hey... if this is not a DLL, then it's probably the EXE!!!
|
|
if (fUserDmp && !fProcessNameFound)
|
|
{
|
|
if (!lpModuleInfo->IsDLL() )
|
|
{
|
|
lpProcessInfo->SetProcessName(tszModulePath);
|
|
fProcessNameFound = true;
|
|
}
|
|
}
|
|
|
|
// Filter out garbage.
|
|
if (!g_lpProgramOptions->GetMode(CProgramOptions::QuietMode))
|
|
{
|
|
time_t time = lpModuleInfo->GetPEImageTimeDateStamp();
|
|
|
|
if (time)
|
|
{
|
|
_tprintf(TEXT("%08x %08x %-30s %s"), (ULONG)Base,
|
|
(ULONG)Base+(ULONG)lpModuleInfo->GetPEImageSizeOfImage(),
|
|
tszModulePath,
|
|
_tctime(&time));
|
|
|
|
|
|
} else
|
|
{
|
|
_tprintf(TEXT("%08x %08x %-30s Unknown\n"), (ULONG)Base,
|
|
(ULONG)Base+(ULONG)lpModuleInfo->GetPEImageSizeOfImage(),
|
|
tszModulePath);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return (ulNumberOfLoadedModules != 0);
|
|
}
|