//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1999 - 2000 // // File: dmpfile.cpp // //-------------------------------------------------------------------------- // DmpFile.cpp: implementation of the CDmpFile class. // ////////////////////////////////////////////////////////////////////// #include #include #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); }