/*++ Copyright (c) 2000 Microsoft Corporation Module Name: appparse.cpp Abstract: Core Engine for dumping importing information from DLL's and executables into an XML file Used by command line appparse and web-based appparse History: 06/07/2000 t-michkr Created --*/ //#define PJOB_SET_ARRAY int #include "stdafx.h" #include #include #include #include #include #include #include #include #include #include #include #include "acFileAttr.h" // These are needed for command line compiling #define stricmp _stricmp // Global heap for AppParse. If 0, Process Heap is used instead. HANDLE g_hHeap = 0; // Global search string char* g_szSearch = "*"; // Whether we are in "verbose" mode, or not. bool g_fVerbose = false; // To sort output by DLLs bool g_fAPILogging = false; // True if no XML tags are to be printed, false otherwise bool g_fRaw = false; // Whether to recurse into subdirectories. bool g_fRecurse = false; // Current path relative to start, used by CModule char g_szCurrentPath[MAX_PATH] = {'\0'}; // Returns true if szFileName is a system DLL (like gdi32, user32, etc.) bool IsSystemDLL(const char* szFileName); // Resolve a linker name to a "normal" name (unmangle C++ names, etc.) void LinkName2Name(char* szLinkName, char* szName); // Just do indentation, save repetitious code void Indent(int iLevel, FILE* pFile = stdout); // Check if function matches global search string bool MatchFunction(const char* szFunc); // Go through a directory and profile EXE's. void ProfileDirectory(char* szDirectory, HANDLE hEvent); void* __cdecl operator new(size_t size); void __cdecl operator delete(void* pVal); // Replace XML reserved characters like > with > void WriteXMLOKString(char* szString, FILE* pFile); // Parsing history for modules class CModuleParseStack { private: struct SNode { char* szName; SNode* pNext; SNode() { szName = 0; pNext = 0; } ~SNode() { if(szName) { delete szName; szName = 0; } } }; SNode* m_pList; public: // Constructor, setup empty list. CModuleParseStack() { m_pList = 0; } // Add a name to the top of the parse stack void PushName(char* szName) { assert(!IsBadReadPtr(szName, 1)); SNode* pNode = new SNode; pNode->szName = new char[strlen(szName)+1]; strcpy(pNode->szName, szName); pNode->pNext = m_pList; m_pList = pNode; } // Remove a name from the top of the parse stack void Pop() { assert(m_pList); SNode* pTemp = m_pList->pNext; delete m_pList; m_pList = pTemp; } // Return true if module has already been parsed bool CheckModuleParsed(char* szName) { assert(!IsBadReadPtr(szName, 1)); SNode* pNode = m_pList; while(pNode) { if(stricmp(pNode->szName, szName) == 0) return true; pNode = pNode->pNext; } return false; } bool IsEmpty() { return (m_pList == 0); } void ClearParseHistory() { SNode* pNode = m_pList; while(pNode) { SNode* pNext = pNode->pNext; delete pNode; pNode = pNext; } m_pList = 0; } }; // CFunction, an imported function and associated information. class CFunction { private: // Name of function (if imported by name) char* m_szName; // Name of function actually pointed to. char* m_szForwardName; // Ordinal, older style importing int m_iOrdinal; // Quick lookup info int m_iHint; // Address of function, if bound DWORD m_dwAddress; // Whether this function is a delayed import or not. bool m_fDelayed; // Next function in list CFunction* m_pNext; // No default construction or copying allowed CFunction(); CFunction operator=(const CFunction&); public: CFunction(char* szName, int iHint, int iOrdinal, DWORD dwAddress, bool fDelayed) { assert(!IsBadReadPtr(szName, 1)); m_szName = new char[strlen(szName)+1]; strcpy(m_szName, szName); m_iOrdinal = iOrdinal; m_iHint = iHint; m_dwAddress = dwAddress; m_pNext = 0; m_fDelayed = fDelayed; m_szForwardName = 0; } CFunction(const CFunction& fn) { m_szName = new char[strlen(fn.m_szName)+1]; strcpy(m_szName, fn.m_szName); m_iOrdinal = fn.m_iOrdinal; m_iHint = fn.m_iHint; m_dwAddress = fn.m_dwAddress; m_pNext = 0; m_fDelayed = fn.m_fDelayed; if(fn.m_szForwardName) { m_szForwardName = new char[strlen(fn.m_szForwardName)+1]; strcpy(m_szForwardName, fn.m_szForwardName); } else m_szForwardName = 0; } ~CFunction() { if(m_szName) { delete m_szName; m_szName = 0; } if(m_szForwardName) { delete m_szForwardName; m_szForwardName = 0; } } CFunction* Next() { return m_pNext; } char* Name() { return m_szName; } void SetForwardName(char* szForward) { assert(!IsBadReadPtr(szForward, 1)); m_szForwardName = new char[strlen(szForward)+1]; strcpy(m_szForwardName, szForward); } void SetNext(CFunction* pFunc) { assert(pFunc == 0 || !IsBadReadPtr(pFunc, 1)); m_pNext = pFunc; } // Display function info, either to console // or to XML file. static void WriteHeader(int iIndentLevel, FILE* pFile); void WriteFunction(int iIndentLevel, FILE* pFile); }; // COrdinalImport // A function imported by ordinal, to be resolved to a CFunction class COrdinalImport { private: int m_iOrdinal; COrdinalImport* m_pNext; bool m_fDelayed; COrdinalImport(); COrdinalImport(const COrdinalImport&); COrdinalImport& operator = (const COrdinalImport&); public: COrdinalImport(int iOrd, bool fDelayed = false) { m_iOrdinal = iOrd; m_fDelayed = fDelayed; } int GetOrdinal() { return m_iOrdinal;} bool GetDelayed() { return m_fDelayed; } COrdinalImport* Next() { return m_pNext; } void SetNext(COrdinalImport* pNext) { m_pNext = pNext; } }; // CModule, an executable image with imports class CModule { friend class CGlobalModuleList; private: // The name of this module (in the form path\foo.exe) char* m_szName; // The name of this module relative to the starting path char* m_szFullName; // Base pointer of the image in memory. void* m_pvImageBase; // DLL's imported by this module. CModule* m_pImportedDLLs; // Functions imported from this module by its parent. CFunction* m_pFunctions; // Functions imported by ordinal from this module COrdinalImport* m_pOrdinals; // Image headers PIMAGE_OPTIONAL_HEADER m_pioh; PIMAGE_SECTION_HEADER m_pish; PIMAGE_FILE_HEADER m_pifh; // Next module in a list CModule* m_pNext; // Text description of any errors that may have occurred char* m_szError; // Whether or not this module is an OS module bool m_fSystem; // Version info WORD m_wDosDate; WORD m_wDosTime; int m_nAttrCount; char** m_szAttrValues; char** m_szAttrNames; bool WalkImportTable(); bool WalkDelayImportTable(); static void InsertFunctionSorted(CFunction* pFunc, CFunction** ppList); bool ResolveForwardedFunctionsAndOrdinals(); bool ParseImportTables(); void InsertOrdinal(int iOrdinal, bool fDelayed = false); CModule* FindChild(char* szName); bool Empty(); void GetAllFunctions(CFunction** ppFunctionList); void* RVAToPtr(const void* pAddr) { return RVAToPtr(reinterpret_cast(pAddr)); } void* RVAToPtr(DWORD dwRVA); void GetFileVerInfo(HANDLE hFile, char* szFileName); public: CModule(char* szName); ~CModule(); bool ParseModule(HANDLE hEvent); void InsertChildModuleSorted(CModule* pcm); // Functions to write module info to either the console or an XML file void WriteModule(bool fTopLevel, int iIndentLevel, FILE* pFile); }; // List of all top-level modules being profiled class CGlobalModuleList { private: CModule* m_pModules; public: CGlobalModuleList() { m_pModules = 0; } ~CGlobalModuleList() { Clear(); } void Clear() { CModule* pMod = m_pModules; while(pMod) { CModule* pNext = pMod->m_pNext; delete pMod; pMod = pNext; } m_pModules = 0; } void InsertModuleSorted(CModule* pMod) { assert(!IsBadReadPtr(pMod, 1)); // Special case, insert at front if(m_pModules == 0 || stricmp(m_pModules->m_szFullName, pMod->m_szFullName) > 0) { pMod->m_pNext = m_pModules; m_pModules = pMod; return; } CModule* pPrev = m_pModules; CModule* pTemp = m_pModules->m_pNext; while(pTemp) { if(stricmp(pTemp->m_szFullName, pMod->m_szFullName) > 0) { pMod->m_pNext = pTemp; pPrev->m_pNext = pMod;; return; } pPrev = pTemp; pTemp = pTemp->m_pNext; } // Insert at end pMod->m_pNext = 0; pPrev->m_pNext = pMod;; } void Write(FILE* pFile, char* szProjectName, int iPtolemyID) { if(!g_fRaw) { fprintf(pFile, "\n"); fprintf(pFile, "\n", szProjectName, iPtolemyID); } CModule* pMod = m_pModules; while(pMod) { pMod->WriteModule(true, 0, pFile); pMod = pMod->m_pNext; } if(!g_fRaw) { fprintf(pFile, "\n"); fprintf(pFile, "\n"); } } }; // Global parsing history CModuleParseStack g_ParseStack; // Empty global module, containing all modules parsed CGlobalModuleList g_modules; CModule::CModule(char* szName) { assert(!IsBadReadPtr(szName, 1)); m_szName = new char[strlen(szName)+1]; strcpy(m_szName, szName); WIN32_FIND_DATA ffd; // Only give it the full relative path if it is in this directory // If elsewhere, give it just the filename. HANDLE hSearch = FindFirstFile(szName, &ffd); if(hSearch == INVALID_HANDLE_VALUE) { m_szFullName = new char[strlen(m_szName) + 1]; strcpy(m_szFullName, m_szName); } else { m_szFullName = new char[strlen(m_szName) + strlen(g_szCurrentPath)+1]; strcpy(m_szFullName, g_szCurrentPath); strcat(m_szFullName, m_szName); FindClose(hSearch); } m_pvImageBase = 0; m_pImportedDLLs = 0; m_pFunctions = 0; m_pOrdinals = 0; m_pioh = 0; m_pish = 0; m_pifh = 0; m_pNext = 0; m_szError = 0; m_fSystem = false; m_nAttrCount = 0; m_szAttrValues = 0; m_szAttrNames = 0; m_wDosDate = 0; m_wDosTime = 0; } CModule::~CModule() { if(m_szName) { delete m_szName; m_szName = 0; } if(m_szFullName) { delete m_szFullName; m_szFullName = 0; } CFunction* pFunc = m_pFunctions; while(pFunc) { CFunction* pNext = pFunc->Next(); delete pFunc; pFunc = pNext; } m_pFunctions = 0; COrdinalImport* pOrd = m_pOrdinals; while(pOrd) { COrdinalImport* pNext = pOrd->Next(); delete pOrd; pOrd = pNext; } m_pOrdinals = 0; for(int i = 0; i < m_nAttrCount; i++) { if(m_szAttrNames) { if(m_szAttrNames[i]) { delete m_szAttrNames[i]; m_szAttrNames[i] = 0; } } if(m_szAttrValues) { if(m_szAttrValues[i]) { delete m_szAttrValues[i]; m_szAttrValues[i] = 0; } } } if(m_szAttrNames) { delete m_szAttrNames; m_szAttrNames = 0; } if(m_szAttrValues) { delete m_szAttrValues; m_szAttrValues = 0; } } // Return true no functions are imported from this module, // or any of its children modules. bool CModule::Empty() { if(m_pFunctions != 0 || m_pOrdinals != 0) return false; CModule* pMod = m_pImportedDLLs; while(pMod) { if(!pMod->Empty()) return false; pMod = pMod->m_pNext; } return true; } // Convert a relative virtual address to an absolute address void* CModule::RVAToPtr(DWORD dwRVA) { assert(!IsBadReadPtr(m_pifh, sizeof(*m_pifh))); assert(!IsBadReadPtr(m_pish, sizeof(*m_pish))); assert(!IsBadReadPtr(m_pvImageBase, 1)); PIMAGE_SECTION_HEADER pish = m_pish; // Go through each section for (int i = 0; i < m_pifh->NumberOfSections; i++) { // If it's in this section, computer address and return it. if ((dwRVA >= pish->VirtualAddress) && (dwRVA < (pish->VirtualAddress + pish->SizeOfRawData))) { void* pAddr = reinterpret_cast(reinterpret_cast(m_pvImageBase) + pish->PointerToRawData + dwRVA - pish->VirtualAddress); return pAddr; } pish++; } // This indicates an invalid RVA, meaning an invalid image, so // throw an exception throw; return 0; } // Return a pointer to the first child matching szName, false otehrwise CModule* CModule::FindChild(char* szName) { assert(!IsBadReadPtr(szName, 1)); CModule* pMod = m_pImportedDLLs; while(pMod) { if(stricmp(pMod->m_szName, szName)==0) return pMod; pMod = pMod->m_pNext; } return 0; } // Add an ordinal import to the module. void CModule::InsertOrdinal(int iOrdinal, bool fDelayed) { COrdinalImport* pNew = new COrdinalImport(iOrdinal, fDelayed); pNew->SetNext(m_pOrdinals); m_pOrdinals = pNew; } // Add an imported function to a function list. void CModule::InsertFunctionSorted(CFunction* pFunc, CFunction** ppList) { // Special case, insert at front if((*ppList)== 0 || stricmp((*ppList)->Name(), pFunc->Name()) > 0) { pFunc->SetNext(*ppList); (*ppList) = pFunc; return; } CFunction* pPrev = *ppList; CFunction* pTemp = (*ppList)->Next(); while(pTemp) { // Don't insert duplicates. This is mainly for API logging only. if(strcmp(pTemp->Name(), pFunc->Name())==0) return; if(stricmp(pTemp->Name(), pFunc->Name()) > 0) { pFunc->SetNext(pTemp); pPrev->SetNext(pFunc); return; } pPrev = pTemp; pTemp = pTemp->Next(); } // Insert at end pFunc->SetNext(0); pPrev->SetNext(pFunc); } // Add a child module to this module. void CModule::InsertChildModuleSorted(CModule* pcm) { // Special case, insert at front if(m_pImportedDLLs == 0 || stricmp(m_pImportedDLLs->m_szName, pcm->m_szName) > 0) { pcm->m_pNext = m_pImportedDLLs; m_pImportedDLLs = pcm; return; } CModule* pPrev = m_pImportedDLLs; CModule* pTemp = m_pImportedDLLs->m_pNext; while(pTemp) { if(stricmp(pTemp->m_szName, pcm->m_szName) > 0) { pcm->m_pNext = pTemp; pPrev->m_pNext = pcm;; return; } pPrev = pTemp; pTemp = pTemp->m_pNext; } // Insert at end pcm->m_pNext = 0; pPrev->m_pNext = pcm;; } // Add all functions imported from this module to the function list // Used mainly for API logging. void CModule::GetAllFunctions(CFunction** ppFunctionList) { CFunction* pFunc = m_pFunctions; while(pFunc) { // Copy pFunc CFunction* pNew = new CFunction(*pFunc); InsertFunctionSorted(pNew, ppFunctionList); pFunc = pFunc->Next(); } CModule* pMod = m_pImportedDLLs; while(pMod) { pMod->GetAllFunctions(ppFunctionList); pMod = pMod->m_pNext; } } // Go through a modules export table and get forwarding information // and resolve ordinal imports to name. bool CModule::ResolveForwardedFunctionsAndOrdinals() { // Get virtual address of export table DWORD dwVAImageDir = m_pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; // Get export table info PIMAGE_EXPORT_DIRECTORY pied = reinterpret_cast(RVAToPtr(dwVAImageDir)); DWORD* pdwNames = reinterpret_cast(RVAToPtr(pied->AddressOfNames)); WORD* pwOrdinals = reinterpret_cast(RVAToPtr(pied->AddressOfNameOrdinals)); DWORD* pdwAddresses = reinterpret_cast(RVAToPtr(pied->AddressOfFunctions)); // Go through each entry in the export table for(unsigned uiHint = 0; uiHint < pied->NumberOfNames; uiHint++) { // Get function name, ordinal, and address info. char* szFunction = reinterpret_cast(RVAToPtr(pdwNames[uiHint])); int ordinal = pied->Base + static_cast(pwOrdinals[uiHint]); DWORD dwAddress = pdwAddresses[ordinal-pied->Base]; char* szForward = 0; // Check if this function has been forwarded to another DLL // Function has been forwarded if address is in this section. // NOTE: The DEPENDS 1.0 source says otherwise, but is incorrect. if( (dwAddress >= dwVAImageDir) && (dwAddress < (dwVAImageDir + m_pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size))) szForward = reinterpret_cast(RVAToPtr(dwAddress)); // Check if we have an ordinal import refering to this COrdinalImport* pOrd = m_pOrdinals; CFunction* pFunc = 0; // See if we have a matching ordinal import. while(pOrd) { if(pOrd->GetOrdinal() == ordinal) break; pOrd = pOrd->Next(); } if(pOrd != 0) { char szTemp[1024]; // Unmangle forwarded name. LinkName2Name(szFunction, szTemp); // Check against search string if(MatchFunction(szTemp)) { // Insert into module. pFunc = new CFunction(szTemp, -1, ordinal, dwAddress, pOrd->GetDelayed()); InsertFunctionSorted(pFunc, &m_pFunctions); } } // No matching ordinal import, check normal imports. else { // Duck out early if this function isn't used in the executable. pFunc = m_pFunctions; while(pFunc) { if(strcmp(pFunc->Name(), szFunction)==0) break; pFunc = pFunc->Next(); } if(pFunc == 0) continue; } // Set forwarding info if(szForward && pFunc) pFunc->SetForwardName(szForward); } return true; } // Get delayed import info from module. bool CModule::WalkDelayImportTable() { // Bail early if no delayed import table. if(m_pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].Size == 0) return true; // Locate the directory section DWORD dwVAImageDir = m_pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress; // Get the import descriptor array PImgDelayDescr pidd = reinterpret_cast(RVAToPtr(dwVAImageDir)); while(pidd->pINT) { char* szName; if(pidd->grAttrs & 1) szName = reinterpret_cast(RVAToPtr(pidd->szName)); else szName = reinterpret_cast(RVAToPtr(pidd->szName - m_pioh->ImageBase)); PIMAGE_THUNK_DATA pitdf; if(pidd->grAttrs & 1) pitdf = reinterpret_cast(RVAToPtr(pidd->pINT)); else pitdf = reinterpret_cast(RVAToPtr( reinterpret_cast(pidd->pINT) - static_cast(m_pioh->ImageBase))); // Locate child module, or create new if it does not exist. CModule* pcm = FindChild(szName); if(!pcm) { pcm = new CModule(szName); InsertChildModuleSorted(pcm); } // Loop through all imported functions while(pitdf->u1.Ordinal) { int iOrdinal; int iHint; // Check if imported by name or ordinal if(!IMAGE_SNAP_BY_ORDINAL(pitdf->u1.Ordinal)) { // Get name import info PIMAGE_IMPORT_BY_NAME piibn = reinterpret_cast( RVAToPtr(pitdf->u1.AddressOfData - m_pioh->ImageBase)); char* szTemp = reinterpret_cast(piibn->Name); char szBuffer[1024]; // Unmangle link name LinkName2Name(szTemp, szBuffer); // Ordinal info is invalid iOrdinal = -1; iHint = piibn->Hint; // Check against search string if(MatchFunction(szBuffer)) { // Insert into function list CFunction* psf = new CFunction(szBuffer, iHint, iOrdinal, static_cast(-1), true); pcm->InsertFunctionSorted(psf, &pcm->m_pFunctions); } } else { // Insert a new delayed ordinal import iOrdinal = static_cast(IMAGE_ORDINAL(pitdf->u1.Ordinal)); pcm->InsertOrdinal(iOrdinal, true); } // Move on to next function pitdf++; } // Move to next delay import descriptor pidd++; } return true; } // Determine all functions imported by this module bool CModule::WalkImportTable() { // Bail out early if no directory if(m_pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0) return true; // Locate the directory section DWORD dwVAImageDir = m_pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; // Get the import descriptor array PIMAGE_IMPORT_DESCRIPTOR piid = reinterpret_cast(RVAToPtr(dwVAImageDir)); // Loop through all imported modules while(piid->FirstThunk || piid->OriginalFirstThunk) { // Get module name char* szName = reinterpret_cast(RVAToPtr(piid->Name)); // Find child, or create new if it does not exist. CModule* pcm = FindChild(szName); if(!pcm) { pcm = new CModule(szName); InsertChildModuleSorted(pcm); } // Get all imports from this module PIMAGE_THUNK_DATA pitdf = 0; PIMAGE_THUNK_DATA pitda = 0; // Check for MS or Borland format if(piid->OriginalFirstThunk) { // MS format, function array is original first thunk pitdf = reinterpret_cast(RVAToPtr(piid->OriginalFirstThunk)); // If the time stamp is set, this module has // been bound and the first thunk is the bound address array if(piid->TimeDateStamp) pitda = reinterpret_cast(RVAToPtr(piid->FirstThunk)); } else { // Borland format uses first thunk for function array pitdf = reinterpret_cast(RVAToPtr(piid->FirstThunk)); } // Loop through all imported functions while(pitdf->u1.Ordinal) { int iOrdinal; int iHint; // Determine if imported by ordinal or name if(!IMAGE_SNAP_BY_ORDINAL(pitdf->u1.Ordinal)) { // Get name import info PIMAGE_IMPORT_BY_NAME piibn = reinterpret_cast( RVAToPtr(pitdf->u1.AddressOfData)); // Get function name char* szTemp = reinterpret_cast(piibn->Name); // Unmangle char szBuffer[1024]; LinkName2Name(szTemp, szBuffer); iOrdinal = -1; iHint = piibn->Hint; // Check against search string if(MatchFunction(szBuffer)) { // Insert into function list CFunction* psf = new CFunction(szBuffer, iHint, iOrdinal, pitda ? pitda->u1.Function : static_cast(-1), false); pcm->InsertFunctionSorted(psf, &pcm->m_pFunctions); } } else { // Insert an ordinal import into the module. iOrdinal = static_cast(IMAGE_ORDINAL(pitdf->u1.Ordinal)); pcm->InsertOrdinal(iOrdinal); } // Move to next function pitdf++; if(pitda) pitda++; } // Move to next module piid++; } return true; } // Parse all import tables bool CModule::ParseImportTables() { return (WalkImportTable() && WalkDelayImportTable()); } // Load a module into memory, and parse it. bool CModule::ParseModule(HANDLE hEvent) { // Cancel parsing if user canceled if(hEvent && WaitForSingleObject(hEvent, 0)==WAIT_OBJECT_0) return false; bool fSucceeded = false; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hMap = 0; bool fPushed = false; m_pvImageBase = 0; // Wrap in a __try block, because an invalid executable image // may have bad pointers in our memory mapped region. __try { // Open the file char szFileName[1024]; char* szJunk; if(!SearchPath(0, m_szName, 0, 1024, szFileName, &szJunk)) { m_szError = "Unable to find file"; __leave; } hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { m_szError = "Unable to open file"; __leave; } GetFileVerInfo(hFile, szFileName); // Map the file into memory hMap = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0); if(hMap == 0) { m_szError = "Unable to map file"; __leave; } m_pvImageBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if(m_pvImageBase == 0) { m_szError = "Unable to map file"; __leave; } // Get header information and verify this is a valid executable // Get the MS-DOS compatible header PIMAGE_DOS_HEADER pidh = reinterpret_cast(m_pvImageBase); if(pidh->e_magic != IMAGE_DOS_SIGNATURE) { m_szError = "Invalid image, no MS-DOS header"; __leave; } // Get the NT header and verify PIMAGE_NT_HEADERS pinth = reinterpret_cast( reinterpret_cast(m_pvImageBase) + pidh->e_lfanew); if(pinth->Signature != IMAGE_NT_SIGNATURE) { // Not a valid Win32 executable, may be a Win16 or OS/2 exe m_szError = "Invalid image, no PE signature"; __leave; } // Get the other headers m_pifh = &pinth->FileHeader; m_pioh = &pinth->OptionalHeader; m_pish = IMAGE_FIRST_SECTION(pinth); // Check if anyone is importing // functions from us, and if so resolve // function forwarding and ordinals if(m_pFunctions || m_pOrdinals) { if(!ResolveForwardedFunctionsAndOrdinals()) __leave; } // Parse import tables (only if not a system DLL or if parsing // this module may result in a dependency loop) m_fSystem = IsSystemDLL(m_szName); if(!m_fSystem && !g_ParseStack.CheckModuleParsed(m_szName)) { // Add to parse stack g_ParseStack.PushName(m_szName); fPushed = true; // Parse if(!ParseImportTables()) __leave; } // Loop through each DLL imported CModule* pModule = m_pImportedDLLs; while(pModule) { // Parse each child module pModule->ParseModule(hEvent); pModule = pModule->m_pNext; } fSucceeded = true; } __except(EXCEPTION_EXECUTE_HANDLER) { m_szError = "Unable to parse module"; fSucceeded = false; } // Cleanup . . . if(m_pvImageBase) UnmapViewOfFile(m_pvImageBase); if(hMap != 0) CloseHandle(hMap); if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); if(fPushed) g_ParseStack.Pop(); return fSucceeded; } void CModule::GetFileVerInfo(HANDLE hFile, char* szFileName) { if(g_fRaw || !g_fVerbose) return; // Get file version info HANDLE hVersionInfo = ReadFileAttributes(szFileName, &m_nAttrCount); // Get date info BY_HANDLE_FILE_INFORMATION fileInfo; GetFileInformationByHandle(hFile, &fileInfo); FILETIME ftDate; memcpy(&ftDate, &fileInfo.ftLastWriteTime, sizeof(FILETIME)); CoFileTimeToDosDateTime(&ftDate, &m_wDosDate, &m_wDosTime); if(m_nAttrCount) { m_szAttrValues = new char*[m_nAttrCount]; m_szAttrNames = new char*[m_nAttrCount]; ZeroMemory(m_szAttrValues, sizeof(char*)*m_nAttrCount); ZeroMemory(m_szAttrNames, sizeof(char*)*m_nAttrCount); if(hVersionInfo) for(int i = 0; i < m_nAttrCount; i++) { char* szVal = GetAttrValue(hVersionInfo, i); if(szVal) { m_szAttrValues[i] = new char[strlen(szVal)+1]; strcpy(m_szAttrValues[i], szVal); char* szAttrName = GetAttrNameXML(i); if(szAttrName) { m_szAttrNames[i] = new char[strlen(szAttrName)+1]; strcpy(m_szAttrNames[i], szAttrName); } } } } if(hVersionInfo) CleanupFileManager(hVersionInfo); } // Return true if module is a system DLL, false otherwise // We use the system file protection system, and assume all system // files are protected. bool IsSystemDLL(const char* szFileName) { char szBuffer[1024], *szJunk; if(!SearchPath(0, szFileName, 0, 1024, szBuffer, &szJunk)) return false; // Only check DLL's if(!StrStrI(szFileName, ".dll")) return false; wchar_t* wszFileName = new wchar_t[strlen(szBuffer) + 1]; MultiByteToWideChar(CP_ACP, 0, szBuffer, strlen(szBuffer)+1, wszFileName, strlen(szBuffer)+1); bool fRet = (SfcIsFileProtected(0, wszFileName) != FALSE); delete wszFileName; return fRet; } // LinkName2Name() // Resolve name mangling void LinkName2Name(char* szLinkName, char* szName) { /* * the link name is expected like ?Function@Class@@Params * to be converted to Class::Function */ static CHAR arrOperators[][8] = { "", "", "new", "delete", "=", ">>", "<<", "!", "==", "!=" }; DWORD dwCrr = 0; DWORD dwCrrFunction = 0; DWORD dwCrrClass = 0; DWORD dwSize; BOOL fIsCpp = FALSE; BOOL fHasClass = FALSE; BOOL fIsContructor = FALSE; BOOL fIsDestructor = FALSE; BOOL fIsOperator = FALSE; DWORD dwOperatorIndex = 0; bool fIsStdcall = false, fIsFastcall = false; char szFunction[1024]; char szClass[1024]; // Unmangle stdcall and fastcall names char* szAtSymbol = strrchr(szLinkName, '@'); fIsFastcall = (szLinkName[0] == '@') && szAtSymbol && isdigit(szAtSymbol[1]); fIsStdcall = (szLinkName[0] == '_') && szAtSymbol && isdigit(szAtSymbol[1]); if(fIsFastcall || fIsStdcall) { szLinkName++; // Modifying the link name, so make a copy. // The file is mapped as read-only, and if it // were read/write, changes would be made to the // executable. char* szTemp = new char[strlen(szLinkName)+1]; strcpy(szTemp, szLinkName); szLinkName = szTemp; *(strchr(szLinkName, '@'))= '\0'; // ????? // I think we need to keep going, because it is possible // to have C++ name mangling on a stdcall name. } if (*szLinkName == '@') szLinkName++; dwSize = lstrlen(szLinkName); /* * skip '?' */ while (dwCrr < dwSize) { if (szLinkName[dwCrr] == '?') { dwCrr++; fIsCpp = TRUE; } break; } /* * check to see if this is a special function (like ??0) */ if (fIsCpp) { if (szLinkName[dwCrr] == '?') { dwCrr++; /* * the next digit should tell as the function type */ if (isdigit(szLinkName[dwCrr])) { switch (szLinkName[dwCrr]) { case '0': fIsContructor = TRUE; break; case '1': fIsDestructor = TRUE; break; default: fIsOperator = TRUE; dwOperatorIndex = szLinkName[dwCrr] - '0'; break; } dwCrr++; } } } /* * get the function name */ while (dwCrr < dwSize) { if (szLinkName[dwCrr] != '@') { szFunction[dwCrrFunction] = szLinkName[dwCrr]; dwCrrFunction++; dwCrr++; } else { break; } } szFunction[dwCrrFunction] = '\0'; if (fIsCpp) { /* * skip '@' */ if (dwCrr < dwSize) { if (szLinkName[dwCrr] == '@') { dwCrr++; } } /* * get the class name (if any) */ while (dwCrr < dwSize) { if (szLinkName[dwCrr] != '@') { fHasClass = TRUE; szClass[dwCrrClass] = szLinkName[dwCrr]; dwCrrClass++; dwCrr++; } else { break; } } szClass[dwCrrClass] = '\0'; } /* * print the new name */ if (fIsContructor) { sprintf(szName, "%s::%s", szFunction, szFunction); } else if (fIsDestructor) { sprintf(szName, "%s::~%s", szFunction, szFunction); } else if (fIsOperator) { sprintf(szName, "%s::operator %s", szFunction, arrOperators[dwOperatorIndex]); } else if (fHasClass) { sprintf(szName, "%s::%s", szClass, szFunction); } else { sprintf(szName, "%s", szFunction); } // stdcall and fastcall unmangling do a slight modification to // the link name, we need to free it here. if(fIsStdcall || fIsFastcall) delete szLinkName; } // Parse a top level module void ParseHighLevelModule(char* szName, HANDLE hEvent) { // Create a new module CModule* pModule = new CModule(szName); assert(g_ParseStack.IsEmpty()); g_ParseStack.ClearParseHistory(); pModule->ParseModule(hEvent); // Add to global module list g_modules.InsertModuleSorted(pModule); } // Functions to print to console or XML file // Just do indentation, save repetitious code void Indent(int iLevel, FILE* pFile) { for(int i = 0; i < iLevel; i++) fprintf(pFile, "\t"); } // Write function header info for raw output void CFunction::WriteHeader(int iIndentLevel, FILE* pFile) { if(g_fVerbose && g_fRaw) { Indent(iIndentLevel, pFile); fprintf(pFile, "%-40s%-10s%-6s%-8s%-40s%-6s\n", "Name", "Address", "Hint", "Ordinal", "Forwarded to", "Delayed"); } } // Write a function, raw or XML void CFunction::WriteFunction(int iIndentLevel, FILE* pFile) { Indent(iIndentLevel, pFile); if(!g_fRaw) { if(g_fVerbose) { fprintf(pFile, "(-1)) fprintf(pFile, "ADDRESS=\"0x%x\" ", m_dwAddress); if(m_iHint != -1) fprintf(pFile, "HINT=\"%d\" ", m_iHint); if(m_iOrdinal != -1) fprintf(pFile, "ORDINAL=\"%d\" ", m_iOrdinal); if(m_szForwardName != 0) { fprintf(pFile, "FORWARD_TO=\""); WriteXMLOKString(m_szForwardName, pFile); fprintf(pFile, "\" "); } fprintf(pFile, "DELAYED=\"%s\"/>\n", m_fDelayed ? "true" : "false"); } else { fprintf(pFile, "\n"); } } else { if(g_fVerbose) { char szAddress[16] = "N/A"; if(m_dwAddress != static_cast(-1)) sprintf(szAddress, "0x%x", m_dwAddress); char szOrdinal[16] = "N/A"; if(m_iOrdinal != -1) sprintf(szOrdinal, "0x%x", m_iOrdinal); char szHint[16] = "N/A"; if(m_iHint != -1) sprintf(szHint, "%d", m_iHint); fprintf(pFile, "%-40s%-10s%-6s%-8s%-40s%-6s\n", m_szName, szAddress, szHint, szOrdinal, m_szForwardName ? m_szForwardName : "N/A", m_fDelayed ? "true" : "false"); } else { fprintf(pFile, "%s\n", m_szName); } } } // Write an XML-compliant string (no <'s and >'s, replace with >, <, etc.) void WriteXMLOKString(char* szString, FILE* pFile) { const int c_nChars = 5; char acIllegal[] = {'<','>', '&', '\'', '\"'}; char* szEntities[] = {"<", ">", "&", "'", """}; while(*szString) { int i; for(i = 0; i < c_nChars; i++) { if(*szString == acIllegal[i]) { fprintf(pFile, szEntities[i]); break; } } if(i == c_nChars) fputc(*szString, pFile); szString++; } } // Write an entire module as output, either raw or XML. void CModule::WriteModule(bool fTopLevel, int iIndentLevel, FILE* pFile) { if(Empty() && m_szError == 0) return; Indent(iIndentLevel, pFile); if(!g_fRaw) { if(fTopLevel) fprintf(pFile, "\n"); } else { fprintf(pFile, "%s:\n", m_szFullName); } if(!g_fRaw && g_fVerbose && (m_nAttrCount || m_wDosDate)) { Indent(iIndentLevel + 1, pFile); fprintf(pFile, "\n"); // Print out date information Indent(iIndentLevel + 1, pFile); fprintf(pFile, "%d/%d/%d\n", (m_wDosDate & 0x1E0) >> 5, m_wDosDate & 0x1F, ((m_wDosDate & 0xFE00) >> 9) + 1980); for(int i = 0; i < m_nAttrCount; i++) { if(m_szAttrValues[i]) { if(m_szAttrNames[i]) { if(strlen(m_szAttrNames[i]) != 0) { Indent(iIndentLevel+1, pFile); fprintf(pFile, "<"); WriteXMLOKString(m_szAttrNames[i], pFile); fprintf(pFile,">"); WriteXMLOKString(m_szAttrValues[i], pFile); fprintf(pFile,"\n"); } } } } Indent(iIndentLevel + 1, pFile); fprintf(pFile, "\n"); } // If an error occured in parsing if(m_szError) { Indent(iIndentLevel+1, pFile); if(!g_fRaw) { fprintf(pFile, "\n"); } else fprintf(pFile, "Parse Error: %s\n", m_szError); } if(g_fVerbose) { Indent(iIndentLevel+1, pFile); if(m_fSystem) { if(!g_fRaw) fprintf(pFile, "\n"); else fprintf(pFile, "(System Module)\n"); } else { if(!g_fRaw) fprintf(pFile, "\n"); else fprintf(pFile, "(Private Module)\n"); } } // Print all functions imported from this module if(g_fAPILogging && fTopLevel) { CFunction* pAllFunctions = 0; GetAllFunctions(&pAllFunctions); if(pAllFunctions) pAllFunctions->WriteHeader(iIndentLevel+1, pFile); while(pAllFunctions) { CFunction* pOld; pAllFunctions->WriteFunction(iIndentLevel+1, pFile); pOld = pAllFunctions; pAllFunctions = pAllFunctions->Next(); delete pOld; } } else { CFunction* pFunc = m_pFunctions; if(pFunc) pFunc->WriteHeader(iIndentLevel, pFile); while(pFunc) { pFunc->WriteFunction(iIndentLevel, pFile); pFunc = pFunc->Next(); } CModule* pMod = m_pImportedDLLs; while(pMod) { pMod->WriteModule(false, iIndentLevel + 1, pFile); pMod = pMod->m_pNext; } } Indent(iIndentLevel, pFile); if(!g_fRaw) { if(fTopLevel) fprintf(pFile, "\n"); else fprintf(pFile, "\n"); } fprintf(pFile, "\n"); // Child modules no longer needed, delete CModule* pMod = m_pImportedDLLs; while(pMod) { CModule* pNext = pMod->m_pNext; delete pMod; pMod = pNext; } m_pImportedDLLs = 0; } // Write out the XML header void WriteXMLHeader(FILE* pFile) { if(g_fRaw) return; static char* szMonths[] = {"", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; static char* szDays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; SYSTEMTIME st; GetLocalTime(&st); fprintf(pFile, "\n"); fprintf(pFile, "\n\n"); } // Return true if function name matches search string, false otherwise. bool MatchFunction(const char* szFunc) { if(strcmp(g_szSearch, "*") == 0) return true; char* szSearch = g_szSearch; while(*szSearch != '\0' && *szFunc != '\0') { // If we get a ?, we don't care and move on to the next // character. if(*szSearch == '?') { szSearch++; szFunc++; continue; } // If we have a wildcard, move to next search string and search for substring if(*szSearch == '*') { char* szCurrSearch; szSearch++; if(*szSearch == '\0') return true; // Don't change starting point. szCurrSearch = szSearch; for(;;) { // We're done if we hit another wildcard if(*szCurrSearch == '*' || *szCurrSearch == '?') { // Update the permanent search position. szSearch = szCurrSearch; break; } // At end of both strings, return true. if((*szCurrSearch == '\0') && (*szFunc == '\0')) return true; // We never found it if(*szFunc == '\0') return false; // If it doesn't match, start over if(toupper(*szFunc) != toupper(*szCurrSearch)) { // If mismatch on first character // of search string, move to next // character in function string. if(szCurrSearch == szSearch) szFunc++; else szCurrSearch = szSearch; } else { szFunc++; szCurrSearch++; } } } else { if(toupper(*szFunc) != toupper(*szSearch)) { return false; } szFunc++; szSearch++; } } if((*szFunc == 0) && ((*szSearch == '\0') || (strcmp(szSearch,"*")==0))) return true; else return false; } // Profile an entire directory void ProfileDirectory(char* szDirectory, HANDLE hEvent) { if(!SetCurrentDirectory(szDirectory)) return; WIN32_FIND_DATA ffd; // Find and parse all EXE's. HANDLE hSearch = FindFirstFile("*.exe", &ffd); if(hSearch != INVALID_HANDLE_VALUE) { do { ParseHighLevelModule(ffd.cFileName, hEvent); // Terminate parsing if user canceled if(hEvent && WaitForSingleObject(hEvent, 0)==WAIT_OBJECT_0) { FindClose(hSearch); SetCurrentDirectory(".."); return; } } while(FindNextFile(hSearch, &ffd)); FindClose(hSearch); } // See if we should go deeper into directories. if(g_fRecurse) { hSearch = FindFirstFile("*", &ffd); if(hSearch == INVALID_HANDLE_VALUE) { SetCurrentDirectory(".."); return; } do { if(GetFileAttributes(ffd.cFileName) & FILE_ATTRIBUTE_DIRECTORY) { // Don't do an infinite recursion. if(ffd.cFileName[0] != '.') { int nCurrLength = strlen(g_szCurrentPath); strcat(g_szCurrentPath, ffd.cFileName); strcat(g_szCurrentPath, "\\"); ProfileDirectory(ffd.cFileName, hEvent); g_szCurrentPath[nCurrLength] = '\0'; } // Terminate search if user signaled if(hEvent && WaitForSingleObject(hEvent, 0)==WAIT_OBJECT_0) { FindClose(hSearch); SetCurrentDirectory(".."); return; } } } while(FindNextFile(hSearch, &ffd)); } FindClose(hSearch); SetCurrentDirectory(".."); } void* __cdecl operator new(size_t size) { void* pv = 0; if(!g_hHeap) pv = HeapAlloc(GetProcessHeap(), 0, size); else pv = HeapAlloc(g_hHeap, 0, size); if(!pv) { MessageBox(0, TEXT("Out of memory, terminating."), TEXT("ERROR"), MB_OK | MB_ICONERROR); exit(-1); } return pv; } void __cdecl operator delete(void* pVal) { if(g_hHeap) HeapFree(g_hHeap, 0, pVal); else HeapFree(GetProcessHeap(), 0, pVal); } DWORD __stdcall AppParse(char* szAppName, FILE* pFile, bool fRaw, bool fAPILogging, bool fRecurse, bool fVerbose, char* szSearchKey, int iPtolemyID, HANDLE hEvent) { g_fRaw = fRaw; g_fAPILogging = fAPILogging; g_fVerbose = fVerbose; g_szSearch = szSearchKey; g_fRecurse = fRecurse; bool fProfileDirectory = false; // Check if it is a directory, or a regular file. DWORD dwAttributes = GetFileAttributes(szAppName); if(dwAttributes != static_cast(-1) && (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)) fProfileDirectory = true; // Check for directory profiling if(fProfileDirectory) { // Search for all EXE's in this Directory // Remove trailing \, if present if(szAppName[strlen(szAppName)-1]== '\\') szAppName[strlen(szAppName)-1] = '\0'; char szBuff[MAX_PATH]; strcpy(szBuff, szAppName); // If we're profiling a drive, don't include // the drive letter in the path if(szBuff[strlen(szBuff)-1]==':') { *g_szCurrentPath='\0'; } else { if(strrchr(szBuff, '\\')) strcpy(g_szCurrentPath, strrchr(szBuff, '\\')+1); else strcpy(g_szCurrentPath, szBuff); strcat(g_szCurrentPath, "\\"); } ProfileDirectory(szAppName, hEvent); } else { // Maybe they left off the .exe if(GetFileAttributes(szAppName) == static_cast(-1)) { char szBuffer[MAX_PATH+1]; strcpy(szBuffer, szAppName); strcat(szBuffer, ".exe"); dwAttributes = GetFileAttributes(szBuffer); if(dwAttributes == static_cast(-1)) { return ERROR_FILE_NOT_FOUND; } szAppName = szBuffer; } // Get the directory name char szBuffer[MAX_PATH+1]; strcpy(szBuffer, szAppName); char* p; for(p = &szBuffer[strlen(szBuffer)]; p != szBuffer; p--) { if(*p == '\\') { *p = '\0'; break; } } if(p != szBuffer) { SetCurrentDirectory(szBuffer); szAppName = p+1; } ParseHighLevelModule(szAppName, hEvent); } char* szProjectName = ""; if(fProfileDirectory) { // If a directory, get the volume name if(strrchr(szAppName, '\\')) szAppName = strrchr(szAppName, '\\') + 1; // If we're profiling a drive, get volume name if(szAppName[strlen(szAppName)-1]==':') { char szBuffer[MAX_PATH]; if(GetVolumeInformation(szAppName, szBuffer, MAX_PATH, 0, 0, 0, 0, 0)) szProjectName = szBuffer; else szProjectName = szAppName; } else szProjectName = szAppName; } else { szProjectName = szAppName; char* szExtension = strstr(szAppName, ".exe"); if(szExtension) *szExtension = '\0'; } // Only write if there wasn't an event object, or user canceled. if(!hEvent || WaitForSingleObject(hEvent, 0) != WAIT_OBJECT_0) { // Write all output WriteXMLHeader(pFile); g_modules.Write(pFile, szProjectName, iPtolemyID); } g_modules.Clear(); return ERROR_SUCCESS; }