/* Copyright 1999 Microsoft Corporation Simplified PCHealth stack trace output Walter Smith (wsmith) Rajesh Soy (nsoy) - modified 4/27/2000 Rajesh Soy (nsoy) - reorganized code and cleaned it up, added comments 06/06/2000 */ #ifdef THIS_FILE #undef THIS_FILE #endif static char __szTraceSourceFile[] = __FILE__; #define THIS_FILE __szTraceSourceFile #include "stdafx.h" #define NOTRACE #include "logging.h" #include #include using namespace std; // Forward declarations struct MODINFO; // // This code is largely stolen from PC Health's fault handler. // At some point we should probably refactor that and share the code. // However, this code has been redone to handle exceptions properly and // eliminate useless stuff. // struct MODINFO { TCHAR szFilename[MAX_PATH]; TCHAR szFileDesc[MAX_PATH]; TCHAR szVersion[MAX_PATH]; TCHAR szCreationDate[MAX_PATH]; TCHAR szMfr[MAX_PATH]; DWORD dwFilesize; DWORD dwCheckSum; DWORD dwPDBSignature; DWORD dwPDBAge; TCHAR szPDBFile[MAX_PATH]; UINT_PTR BaseAddress; }; typedef MODINFO* PMODINFO; struct MPC_STACKFRAME { const MODINFO* pModule; DWORD dwSection; UINT_PTR Offset; }; typedef MPC_STACKFRAME* PMPC_STACKFRAME; // // PDB debug directory structures // typedef struct NB10I // NB10 debug info { DWORD nb10; // NB10 DWORD off; // offset, always 0 DWORD sig; DWORD age; } NB10I; typedef struct cvinfo { NB10I nb10; char rgb[0x200 - sizeof(NB10I)]; } CVINFO; typedef map MODINFOMap; // // Routines defined here // void GetFileInfo( LPTSTR szFile, LPTSTR szWhat, LPTSTR szValue); void GetFileDateAndSize(MODINFO* pModule); BOOL DebugDirectoryIsUseful(LPVOID Pointer, ULONG Size); void GetPDBDebugInfo(MODINFO* pModule); bool GetLogicalAddress(PVOID addr, DWORD* pdwSection, UINT_PTR* pOffset, UINT_PTR* pbaseAddr); void AddDLLSubnode(SimpleXMLNode* pParentElt, LPCWSTR pTag, const MODINFO* pInfo); void AddFunctionSubnode(SimpleXMLNode* pParentElt, MPC_STACKFRAME* pFrame); void GenerateXMLStackTrace(PSTACKTRACEDATA pstd, SimpleXMLNode* pTopElt); // // ModuleCache class: Class to extrace information from binaries // class ModuleCache { public: ModuleCache() { } ~ModuleCache() { } const MODINFO* GetModuleInfo(UINT_PTR baseAddr); const MODINFO* GetEXEInfo(); void AddDLLInfoSubnode(SimpleXMLNode* pParentElt); private: MODINFOMap mimap; }; // // ModuleCache::GetModuleInfo: Extracts the following information given a base address // ModuleName, CompanyName, FileVersion, FileDescription, FileDateAndSize and PDBDebugInfo // const MODINFO* ModuleCache::GetModuleInfo( UINT_PTR baseAddr // [in] - baseAddress of binary ) { TraceFunctEnter("ModuleCache::GetModuleInfo"); // // Locate the base address in modinfo map if present // MODINFOMap::const_iterator it = mimap.find(baseAddr); DWORD dwRetVal = 0; if (it == mimap.end()) { MODINFO mi; TCHAR szModName[ MAX_PATH ]; mi.BaseAddress = baseAddr; mi.dwCheckSum = 0; // // Obtain the ModuleFileName // DebugTrace(0, "Calling GetModuleFileName"); dwRetVal = GetModuleFileName((HMODULE) baseAddr, szModName, MAX_PATH ) ; if(0 == dwRetVal) { FatalTrace(0, "GetModuleFileName failed. Error: %ld", GetLastError()); ThrowIfZero( dwRetVal ); } ZeroMemory( mi.szFilename, MAX_PATH ); // // Parse the Module name. GetModuleFileName returns filepaths of the form \??\filepath // if the user is not logged on. // if(( szModName[0] == '\\')&&( szModName[1] == '?') && ( szModName[2] == '?') && ( szModName[3] == '\\')) { DebugTrace(0, "Stripping the funny characters from infront of the module name"); _tcscpy( mi.szFilename, &szModName[4]); } else { DebugTrace(0, "Normal module name"); _tcscpy( mi.szFilename, szModName); } DebugTrace(0, "ModuleName: %ls", mi.szFilename); // // Obtain the CompanyName // DebugTrace(0, "Obtaining CompanyName"); GetFileInfo(mi.szFilename, TEXT("CompanyName"), mi.szMfr); // // Obtain FileVersion // DebugTrace(0, "Obtaining FileVersion"); GetFileInfo(mi.szFilename, TEXT("FileVersion"), mi.szVersion); // // Obtain FileDescription // DebugTrace(0, "Obtaining FileDescription"); GetFileInfo(mi.szFilename, TEXT("FileDescription"), mi.szFileDesc); // // Obtain FileDateAndSize // DebugTrace(0, "Calling GetFileDateAndSize"); GetFileDateAndSize(&mi); // // Obtain PDBDebugInfo // DebugTrace(0, "Calling GetPDBDebugInfo"); GetPDBDebugInfo(&mi); // // Insert MODINFO into the ModInfo map // DebugTrace(0, "Calling mimap.insert"); it = mimap.insert(MODINFOMap::value_type(baseAddr, mi)).first; } TraceFunctLeave(); return &(*it).second; } // // ModuleCache::GetEXEInfo: Obtain information on binaries that are Exes // const MODINFO* ModuleCache::GetEXEInfo() { return GetModuleInfo((UINT_PTR) GetModuleHandle(NULL)); } // // ModuleCache::AddDLLInfoSubnode: adds the information contained in the MODInfo Map // into a DLLINFO XML subnode void ModuleCache::AddDLLInfoSubnode( SimpleXMLNode* pParentElt // [in] - parent XML node ) { _ASSERT(pParentElt != NULL); // // Create a DLLINFO subnode // SimpleXMLNode* pTopElt = pParentElt->AppendChild(wstring(L"DLLINFO")); // // Add DLL subnodes under DLLINFO node for each item contained in the MODInfo Map // for (MODINFOMap::const_iterator it = mimap.begin(); it != mimap.end(); it++) { AddDLLSubnode(pTopElt, L"DLL", &(*it).second); } } // // // GetFileInfo: This routine uses Version.Dll API to get requested file info // info is returned as a string in szValue // void GetFileInfo( LPTSTR szFile, // [in] file to get info for LPTSTR szWhat, // [in] may specify "FileVersion", // "CompanyName" or "FileDescription" LPTSTR szValue // [out] file info obtained ) { DWORD dwSize; DWORD dwScratch; DWORD* pdwLang = NULL; DWORD dwLang; TCHAR szLang[MAX_PATH] = TEXT(""); TCHAR szLocalValue[MAX_PATH] = TEXT(""); LPTSTR szLocal; lstrcpy(szValue, TEXT("")); _ASSERT(szFile != NULL); _ASSERT(szWhat != NULL); _ASSERT(szValue != NULL); // // get fileinfo data size // dwSize = GetFileVersionInfoSize(szFile, &dwScratch); if (dwSize == 0) return; auto_ptr pFileInfo(new BYTE[dwSize]); // // get fileinfo data // ThrowIfZero(GetFileVersionInfo(szFile, 0, dwSize, (PVOID) pFileInfo.get())); // // set default language to english // dwLang = 0x040904E4; pdwLang = &dwLang; // // read language identifier and code page of file // if (VerQueryValue(pFileInfo.get(), TEXT("\\VarFileInfo\\Translation"), (PVOID *) &pdwLang, (UINT *) &dwScratch)) { // // prepare query string - specify what we need ("FileVersion", // "CompanyName" or "FileDescription") // _stprintf(szLang, TEXT("\\StringFileInfo\\%04X%04X\\%s"), LOWORD(*pdwLang), HIWORD(*pdwLang), szWhat); szLocal = szLocalValue; // // query for the value using codepage from file // if (VerQueryValue(pFileInfo.get(), szLang, (PVOID *) &szLocal, (UINT *) &dwScratch)) { lstrcpy(szValue,szLocal); return; } } // // if that fails, try Unicode // _stprintf(szLang, TEXT("\\StringFileInfo\\%04X04B0\\%s"), GetUserDefaultLangID(), szWhat); if (!VerQueryValue(pFileInfo.get(), szLang, (PVOID *) &szLocal, (UINT *) &dwScratch)) { // // if that fails too, try Multilingual // _stprintf(szLang, TEXT("\\StringFileInfo\\%04X04E4\\%s"), GetUserDefaultLangID(), szWhat); if (!VerQueryValue(pFileInfo.get(), szLang, (PVOID *) &szLocal, (UINT *) &dwScratch)) { // // and if that fails as well, try nullPage // _stprintf(szLang, TEXT("\\StringFileInfo\\%04X0000\\%s"), GetUserDefaultLangID(), szWhat); if (!VerQueryValue(pFileInfo.get(), szLang, (PVOID *) &szLocal, (UINT *) &dwScratch)) { // giving up szValue[0] = 0; return; } } } // // successful; copy to return string // lstrcpy(szValue,szLocal); } // // GetFileDateAndSize: get file creation date and size // void GetFileDateAndSize( MODINFO* pModule // [in] [out] pointer to module node // fills file date and size fields ) { TraceFunctEnter("GetFileDateAndSize"); _ASSERT(pModule != NULL); SYSTEMTIME STCreationTime; WIN32_FIND_DATA FindData; lstrcpy(pModule->szCreationDate,TEXT("")); pModule->dwFilesize = 0; HANDLE hFind = FindFirstFile(pModule->szFilename,&FindData); if(INVALID_HANDLE_VALUE == hFind) { FatalTrace(0, "FindFirstFile on %ls failed. Error: %ld", pModule->szFilename, GetLastError()); ThrowIfTrue(hFind == INVALID_HANDLE_VALUE); } // NO THROWS -- hFind will leak _ASSERT(FindData.ftCreationTime.dwLowDateTime || FindData.ftCreationTime.dwHighDateTime); FileTimeToSystemTime(&(FindData.ftCreationTime),&STCreationTime); _stprintf(pModule->szCreationDate, _T("%d-%02d-%02dT%02d:%02d:%02d.%03d"), STCreationTime.wYear, STCreationTime.wMonth, STCreationTime.wDay, STCreationTime.wHour, STCreationTime.wMinute, STCreationTime.wSecond, STCreationTime.wMilliseconds); pModule->dwFilesize = (FindData.nFileSizeHigh * MAXDWORD) + FindData.nFileSizeLow; FindClose(hFind); TraceFunctLeave(); } // // DebugDirectoryIsUseful: Check if this is an userful debug directory // BOOL DebugDirectoryIsUseful(LPVOID Pointer, ULONG Size) { return (Pointer != NULL) && (Size >= sizeof(IMAGE_DEBUG_DIRECTORY)) && ((Size % sizeof(IMAGE_DEBUG_DIRECTORY)) == 0); } // // GetPDBDebugInfo: Looks up the Debug Directory to get PDB Debug Info // void GetPDBDebugInfo( MODINFO* pModule // [in] [out] pointer to module node // fills PDB Signature, Age and Filename ) { TraceFunctEnter("GetPDBDebugInfo"); _ASSERT(pModule != NULL); HANDLE hMapping = NULL; PIMAGE_NT_HEADERS pntHeaders = NULL; void* pvImageBase = NULL; // // Open the Binary for reading // HANDLE hFile = CreateFile(pModule->szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE != hFile ) { // // Create a FileMapping // hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (NULL != hMapping) { // // Map view to Memory // pvImageBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); if (NULL != pvImageBase) { __try { // // Obtain the NtImage Headers // pntHeaders = ImageNtHeader(pvImageBase); if(NULL == pntHeaders) { goto done; } if (pntHeaders->OptionalHeader.MajorLinkerVersion >= 3 || pntHeaders->OptionalHeader.MinorLinkerVersion >= 5) { // // Find the debug directory entries // ULONG cbDebugDirectories; PIMAGE_DEBUG_DIRECTORY pDebugDirectories = (PIMAGE_DEBUG_DIRECTORY) ImageDirectoryEntryToData(pvImageBase, FALSE, IMAGE_DIRECTORY_ENTRY_DEBUG, &cbDebugDirectories); if (DebugDirectoryIsUseful(pDebugDirectories, cbDebugDirectories)) { // // Find the codeview information // ULONG cDebugDirectories = cbDebugDirectories / sizeof(IMAGE_DEBUG_DIRECTORY); ULONG iDirectory; for (iDirectory=0; iDirectoryType == IMAGE_DEBUG_TYPE_CODEVIEW) { LPVOID pv = (PCHAR)pvImageBase + pDir->PointerToRawData; ULONG cb = pDir->SizeOfData; // // Is it the NAME of a .PDB file rather than CV info itself? // NB10I* pnb10 = (NB10I*)pv; pModule->dwPDBSignature = pnb10->sig; pModule->dwPDBAge = pnb10->age; if (pnb10->nb10 == '01BN') { // // Got a PDB name, which immediately follows the NB10I; we take everything. // mbstowcs(pModule->szPDBFile, (char *)(pnb10+1), MAX_PATH); } else { // // Got the PDB Signature and Age // This information is used by the backend to // locate the PDB symbol file for this binary // to resolve symbols // pModule->dwPDBSignature = pnb10->sig; pModule->dwPDBAge = pnb10->age; } } } } } } __except(EXCEPTION_EXECUTE_HANDLER) { FatalTrace(0, "Unable to extract PDB information from binary"); } } } } done: if(NULL != pvImageBase) { UnmapViewOfFile(pvImageBase); pvImageBase = NULL; } if(NULL != hMapping) { CloseHandle(hMapping); hMapping = NULL; } if(NULL != hFile) { CloseHandle(hFile); hFile = NULL; } TraceFunctLeave(); } // // GetLogicalAddress: converts virtual address to section and offset by searching module's section table // bool GetLogicalAddress( PVOID addr, // [in] address to be resolved DWORD* pdwSection, // [out] section number UINT_PTR* pOffset, // [out] offset in section UINT_PTR* pbaseAddr // [out] base address of module ) { MEMORY_BASIC_INFORMATION mbi; UINT_PTR baseAddr; // local base address of module IMAGE_DOS_HEADER* pDosHdr; // PE File Headers IMAGE_NT_HEADERS* pNTHdr; IMAGE_SECTION_HEADER* pSectionHdr; UINT_PTR rva; // relative virtual address of "addr" UINT_PTR sectionStart; UINT_PTR sectionEnd; UINT i; DWORD dwDump; DWORD dwRW; DWORD dwRetVQ; bool fFound = false; static DWORD s_dwSystemPageSize = 0; TraceFunctEnter("GetLogicalAddress"); if (s_dwSystemPageSize == 0) { SYSTEM_INFO si; GetSystemInfo(&si); s_dwSystemPageSize = si.dwPageSize; } *pdwSection = 0; *pOffset = 0; *pbaseAddr = 0; // // addr should not be NULL // if(NULL == addr) { FatalTrace(0, "addr is NULL"); goto done; } // // get page info for page containing "addr" // DebugTrace(0, "Calling VirtualQuery"); dwRetVQ = VirtualQuery(addr, &mbi, sizeof(mbi)); if(0 == dwRetVQ) { FatalTrace(0, "dwRetVQ is 0. Error: %ld", GetLastError()); ThrowIfZero(dwRetVQ); } // // Just in case this goes wild on us... // __try { baseAddr = (UINT_PTR) mbi.AllocationBase; // // get relative virtual address corresponding to addr // rva = (UINT_PTR) addr - baseAddr; // // read Dos header of PE file // pDosHdr = (IMAGE_DOS_HEADER*) baseAddr; // // read NT header of PE file // pNTHdr = (IMAGE_NT_HEADERS*) (baseAddr + pDosHdr->e_lfanew); // // get section header address // pSectionHdr = (IMAGE_SECTION_HEADER*) ((UINT_PTR) IMAGE_FIRST_SECTION(pNTHdr) - (UINT_PTR) pNTHdr + baseAddr + pDosHdr->e_lfanew); // // step through section table to get to the section containing rva // DebugTrace(0, "stepping through section table..."); for (i=0; i< pNTHdr->FileHeader.NumberOfSections; i++) { // // get section boundaries // sectionStart = pSectionHdr->VirtualAddress; sectionEnd = sectionStart + max(pSectionHdr->SizeOfRawData, pSectionHdr->Misc.VirtualSize); // // check if section envelopes rva // if ((rva >= sectionStart) && (rva <= sectionEnd)) { *pdwSection = i+1; *pOffset = rva-sectionStart; *pbaseAddr = baseAddr; fFound = true; break; } // // move pointer to next section // pSectionHdr = pSectionHdr + sizeof(IMAGE_SECTION_HEADER); } } __except (EXCEPTION_EXECUTE_HANDLER) { _ASSERT(0); *pbaseAddr = NULL; } if (!fFound) _ASSERT(0); done: TraceFunctLeave(); return fFound; } // // AddDLLSubnode: Inserts a DLL subnode to the given parent XML node // void AddDLLSubnode( SimpleXMLNode* pParentElt, // [in] - parent XML node LPCWSTR pTag, // [in] - name of subnode tag const MODINFO* pInfo // [in] - Module Information ) { USES_CONVERSION; TraceFunctEnter("AddDLLSubnode"); _ASSERT(pParentElt != NULL); _ASSERT(pTag != NULL); _ASSERT(pInfo != NULL); if(NULL == pInfo) { FatalTrace(0, "pInfo is NULL"); throw E_FAIL; } // // Create the XML subnode // SimpleXMLNode* pNode = pParentElt->AppendChild(wstring(pTag)); // // Set the various attributes of the DLL subnode // pNode->SetAttribute(wstring(L"FILENAME"), wstring(T2CW(pInfo->szFilename))); pNode->SetAttribute(wstring(L"VERSION"), wstring(T2CW(pInfo->szVersion))); pNode->SetAttribute(wstring(L"CREATIONDATE"), wstring(T2CW(pInfo->szCreationDate))); pNode->SetAttribute(wstring(L"CHECKSUM"), Hexify(pInfo->dwCheckSum)); pNode->SetAttribute(wstring(L"PDBSIGNATURE"), Hexify(pInfo->dwPDBSignature)); pNode->SetAttribute(wstring(L"PDBAGE"), Hexify(pInfo->dwPDBAge)); pNode->SetAttribute(wstring(L"PDBFILE"), wstring(T2CW(pInfo->szPDBFile))); pNode->SetAttribute(wstring(L"FILESIZE"), Hexify(pInfo->dwFilesize)); pNode->SetAttribute(wstring(L"BASEADDRESS"), Hexify(pInfo->BaseAddress)); pNode->SetAttribute(wstring(L"MANUFACTURER"), wstring(T2CW(pInfo->szMfr))); pNode->SetAttribute(wstring(L"DESCRIPTION"), wstring(T2CW(pInfo->szFileDesc))); TraceFunctLeave(); } // // AddFunctionSubnode: Adds a FUNCTION subnode to the given XML node // void AddFunctionSubnode( SimpleXMLNode* pParentElt, // [in] - parent XML node MPC_STACKFRAME* pFrame // [in] - Stack Frame ) { USES_CONVERSION; _ASSERT(pParentElt != NULL); _ASSERT(pFrame != NULL); // // Create the FUNCTION subnode // SimpleXMLNode* pNode = pParentElt->AppendChild(wstring(L"FUNCTION")); // // Add the Attributes of the FUNCTION subnode // pNode->SetAttribute(wstring(L"FILENAME"), wstring(T2CW(pFrame->pModule->szFilename))); pNode->SetAttribute(wstring(L"SECTION"), Decimalify(pFrame->dwSection)); pNode->SetAttribute(wstring(L"OFFSET"), Decimalify(pFrame->Offset)); } // // GenerateXMLStackTrace: Generate a stack trace in PCHealth-standard XML format // void GenerateXMLStackTrace( PSTACKTRACEDATA pstd, // [in] - pointer to call stack SimpleXMLNode* pTopElt // [in] - pointer to STACKTRACE XML node ) { TraceFunctEnter("GenerateXMLStackTrace"); _ASSERT(pTopElt != NULL); ModuleCache modCache; // // Set the Tag Name // pTopElt->tag = wstring(L"STACKTRACE"); // // Obtain the Info on the binary being commented // DebugTrace(0, "Calling modCache.GetEXEInfo"); const MODINFO* pExeInfo = modCache.GetEXEInfo(); // // Add the info collected as a EXEINFO subnode under STACKTRACE // DebugTrace(0, "Calling AddDLLSubnode"); AddDLLSubnode(pTopElt, L"EXEINFO", pExeInfo); // // Create a CALLSTACK subnode under STACKTRACE, but only if pstd is not NULL // if (pstd != NULL) { DebugTrace(0, "Adding CALLSTACK element"); SimpleXMLNode* pCallStackElt = pTopElt->AppendChild(wstring(L"CALLSTACK")); ULONG_PTR stackBase; stackBase = (ULONG_PTR)pstd; DWORD dwValue; ULONG ulFrames; ulFrames = pstd->nCallers; bool fProceed = 1; PVOID caller; int iFrame = 0; // // Step through the call stack // caller = (int *)pstd->callers; while(caller != 0) { MPC_STACKFRAME frame; UINT_PTR modBase; // // Obtain the Logical Address for each caller // DebugTrace(0, "GetLogicalAddress"); if (GetLogicalAddress(caller, &frame.dwSection, &frame.Offset, &modBase)) { // // Get Module Information for this caller // frame.pModule = modCache.GetModuleInfo(modBase); // // Add the ModuleInformation obtained as a FUNCTION subnode under the CALLSTACK node // DebugTrace(0, "Calling AddFunctionSubnode"); AddFunctionSubnode(pCallStackElt, &frame); } DebugTrace(0, "iFrame: %ld", iFrame); caller = pstd->callers[iFrame]; iFrame++; } // // Now, add the DLLINFO subnode. This must happen after all the GetModuleInfo calls so the cache // is populated with all the necessary info. // DebugTrace(0, "Calling AddDLLInfoSubnode"); modCache.AddDLLInfoSubnode(pTopElt); } TraceFunctLeave(); }