This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Imagehlp Replacement
  Submitted by



There's been a few submissions to cotd and totd that use the imagehlp library to generate symbol information; to output the call stack when an assert fires, or to store the call stack for memory leak tracking. This code does away entirely with imagehlp by directly accessing CodeView information in a PE file. It doesn't require you to link a huge bunch of obscure functions from a dll, and it's fairly fast too.

There's a few shortcomings in this code - it doesn't cope with pdb files (pdb files apparently use a modified CodeView format that Microsoft haven't publicly released - uncheck the 'Use Progam Database' checkbox in Project Settings->Link->Customize if you are using Visual Studio), and it's not been rigorously tested (i.e. it seems to be ok on a few projects here at work but in the real world... who knows).

Here's all you need to do to output the current call stack to the debug output window (in a double-click-to-take-me-there format) :

{
	GPEMap			tmpMap;
	const int		TestStackSize = 7;
	void *			TestStack[TestStackSize];
	GPEMap::CodeSourceData	TestStackSource[TestStackSize];

// Get the first 7 address on the stack (parameter 2 is the number of addresses to skip (note that // _WalkTheStack automatically skips itself)) if (GPEMap::_WalkTheStack(TestStackSize,0,TestStack)) { // Get the name, source file and line number of each of the addresses tmpMap.GetStackWalkCodeSource(TestStackSize,(const void**)TestStack,TestStackSource); } }



One or two caveats : The GPEMap constructor makes a memory map of an entire executable file so it's not a wise idea to use it in an inner loop or anything. Making it a global or class static in debug builds seems like a good idea. At the moment, only CodeView debug symbols are used, but there are hooks in for COFF stuff too. Finally, _WalkTheStack assumes that frame pointers are stored in the EBP register, so don't call this the Frame Pointer Omission optimization on.


Currently browsing [GPEMap.zip] (11,478 bytes) - [GPEMap.cpp] - (27,900 bytes)


#include "GPEMap.h"

#include <stdio.h> #include <malloc.h>

// // Internal Image Data structures // #pragma pack(push,1) struct ImageDebugDirectory { unsigned int m_Characteristics; unsigned int m_TimeDateStamp; unsigned short m_MajorVersion; unsigned short m_MinorVersion; unsigned int m_Type; unsigned int m_SizeOfData; unsigned int m_AddressOfRawData; unsigned int m_PointerToRawData; // The important bit - pointer to the debug directory's data // in the PE file }; //ImageDebugDirectory - Analagous to IMAGE_DEBUG_DIRECTORY with all the unnecessary stuff //chopped out struct ImageDosStubHeader { unsigned short m_Magic; // DOS signature ('MZ') unsigned short m_Filler[29]; // Don't need to know this stuff (see MSDN for details) long m_LFA; // File address of new exe header }; //ImageDosStubHeader - Analagous to IMAGE_DOS_HEADER, with all the useless crap chopped out struct ImageDataDirectory {

unsigned long m_VAddr; // Data virtual offset from m_pMapAddress unsigned long m_Size; // Size of the data }; //ImageDataDirectory - Analogous to IMAGE_DATA_DIRECTORY (this is an internal structure in <winnt.h>) struct ImageNTHeaders {

unsigned long m_Signature; // PE file signature ('PE\0\0') unsigned short m_FileHdrFiller[10]; // Don't need to know this shit (see MSDN for details) unsigned short m_OpFileHdrFiller[48]; // Don't need to know this shit (see MSDN for details) ImageDataDirectory m_DataDirectory[16]; // PE data directories (resources, debug data, etc.) // (debug is at offset 144 from m_OpFileHdrFiller - i.e. // position 6 in this array - see MSDN help for // IMAGE_DATA_DIRECTORY) }; //ImageNTHeaders - Analagous to IMAGE_NT_HEADERS in <dbghelp.h>, except with all the unnecessary //stuff chopped out

// // Internal CV Data structures //

struct CVSignature { char m_Signature[4]; // CV signature ('NB11' or 'NB09') unsigned int m_FilePos; // File position (CV base file address) }; //CVSignature - CodeView type and file position reference struct CVSubSection { unsigned short m_SubSection; // Subsection type (sst...) unsigned short m_ModuleIndex; // Module index int m_LFO; // Large file offset of subsection unsigned int m_SubSectionSize; // Number of bytes in subsection }; //CVSubSection - CodeView subsection

struct CVDirHeader { unsigned short m_DirHeader; // Length of this structure unsigned short m_DirEntry; // Number of bytes in each directory entry unsigned int m_NumDirEntries; // Number of directory entries int m_NextDir; // Offset from the base of next directory (unused) unsigned int m_Flags; // Status flags }; //CVDirHeader - Section directory header struct CVSrcModuleHeader { unsigned short m_NumFiles; // Number of source files contained in this module unsigned short m_NumSegs; // Number of base segments in this module }; //CVSrcModuleHeader - Source module (.obj) header struct CVSymTableHeader { unsigned short m_SymHashFnc; // Symbol hash function (must be 12) unsigned short m_AddrHashFnc; // Address hash function (must be 10) unsigned int m_NumSymBytes; // Number of bytes in the upcoming $$SYMBOL table unsigned int m_NumSymHashBytes; // Number of bytes in the symbol hash table unsigned int m_NumAddrHashBytes; // Number of bytes in the address hash table }; //CVSymTableHeader struct CVSymData { unsigned int m_TypeID; // Type identifier for the symbol unsigned int m_Offset; // Offset of the symbol unsigned short m_Segment; // Symbol segment unsigned char m_NameLen; // Length of the symbol name char m_Name[512]; // Symbol name (not really 512 characters, but I only // ever use this structure to reinterpret the data // pointer, never as a stack or heap object) }; //CVSymData - variable names in the $$SYMBOL table (S_LDATA32 and S_GDATA32) struct CVSymProcStart { unsigned int m_Parent; // unsigned int m_End; unsigned int m_Next; unsigned int m_ProcLength; unsigned int m_DbgStart; unsigned int m_DbgEnd; unsigned int m_ProcType; unsigned int m_Offset; unsigned short m_Segment; unsigned char m_Flags; unsigned char m_NameLen; char m_Name[512]; }; //CVSymProcStart - proc start in a $$SYMBOL table (S_LPROC32 and S_GPROC32) struct CVSymProcRef { unsigned int m_Checksum; unsigned int m_SymOffset; unsigned short m_Module; }; //CVSymProcRef - proc reference in global $$SYMBOL tables (S_PROCREF) struct CVAddrHashOffset { unsigned int m_CurSymOffset; unsigned int m_CurMemOffset; }; //CVAddrHashOffset - used in the address sort table in global symbol tables #pragma pack(pop)



// // CV Enums // enum ModuleType { // Module identifiers eMT_sstModule = 0x120, eMT_sstTypes = 0x121, eMT_sstPublic = 0x122, eMT_sstPublicSym = 0x123, eMT_sstSymbols = 0x124, eMT_sstAlignSym = 0x125, eMT_sstSrcLnSeg = 0x126, eMT_sstSrcModule = 0x127, eMT_sstLibraries = 0x128, eMT_sstGlobalSym = 0x129, eMT_sstGlobalPub = 0x12a, eMT_sstGlobalTypes = 0x12b, eMT_sstMPC = 0x12c, eMT_sstSegMap = 0x12d, eMT_sstSegName = 0x12e, eMT_sstPreComp = 0x12f, eMT_unused = 0x130, eMT_sstOffsetMap16 = 0x131, eMT_sstOffsetMap32 = 0x132, eMT_sstFileIndex = 0x133, eMT_sstStaticSym = 0x134,

// Module type range eMT_lastModule, eMT_firstModule = eMT_sstModule,

// Maximum module types eModuleTypes = eMT_lastModule - eMT_firstModule

}; //ModuleType

enum SType { // Symbol type identifiers (ripped from MSDN) S_COMPILE = 0x0001, S_SSEARCH = 0x0005, S_END = 0x0006, S_SKIP = 0x0007, S_CVRESERVE = 0x0008, S_OBJNAME = 0x0009, S_ENDARG = 0x000a, S_COBOLUDT = 0x000b, S_MANYREG = 0x000c, S_RETURN = 0x000d, S_ENTRYTHIS = 0x000e, S_REGISTER = 0x1001, S_CONSTANT = 0x1002, S_UDT = 0x1003, S_COBOLUDT2 = 0x1004, S_MANYREG2 = 0x1005, S_BPREL32 = 0x1006, S_LDATA32 = 0x1007, S_GDATA32 = 0x1008, S_PUB32 = 0x1009, S_LPROC32 = 0x100a, S_GPROC32 = 0x100b, S_THUNK32 = 0x0206, S_BLOCK32 = 0x0207, S_WITH32 = 0x0208, S_LABEL32 = 0x0209, S_CEXMODEL32 = 0x020a, S_VFTTABLE32 = 0x100c, S_REGREL32 = 0x100d, S_LTHREAD32 = 0x100e, S_GTHREAD32 = 0x100f, S_LPROCMIPS = 0x1010, S_GPROCMIPS = 0x1011, S_PROCREF = 0x0400, S_DATAREF = 0x0401, S_ALIGN = 0x0402,

S_FIRST = S_COMPILE, S_LAST = S_ALIGN };







// // Global Utility Functions // TCHAR * _StringifyOSError(const int BufferSize,TCHAR * pBuffer,const unsigned long ErrCode = -1) { // _StringifyOSError fills up to BufferSize character in pBuffer with either the stringified // version either GetLastError(), or ErrCode if it's not -1 const unsigned long ActErrCode = ErrCode == -1 ? GetLastError() : ErrCode;

FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, ActErrCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language LPTSTR(pBuffer), BufferSize, NULL);

// FormatMessage seems to add newlines to the end of the string buffer. This is unacceptable! { const int NewBufLen = strlen(pBuffer);

if (pBuffer[NewBufLen - 1] == _T('\n')) { pBuffer[NewBufLen - 1] = _T('\0'); }

if (pBuffer[NewBufLen - 2] == _T('\r')) { pBuffer[NewBufLen - 2] = _T('\0'); } }

return pBuffer; } //_StringifyOSError const unsigned char * _GetDataPtr(const unsigned char * & pDataPtr,const unsigned int NumBytes) { // _GetDataPtr - Stores pDataPtr, increments pDataPtr by NumByte, then returns the stored pDataPtr // (useful for parsing the memory mapped file). const unsigned char * pOldDataPtr = pDataPtr; pDataPtr += NumBytes; return pOldDataPtr; } //_GetDataPtr template <class T> const T * _GetCastDataPtr(const unsigned char * & pDataPtr,const int Count = 1,T * pDummy = 0) { // _GetCastDataPtr - Templated version of _GetDataPtr - Count specifies the number of T's expected at pDataPtr return (const T*)_GetDataPtr(pDataPtr,sizeof(T) * Count); } //_GetCastDataPtr unsigned int _ConvertAddrToMap(const void * pAddr,const unsigned int SystemPageSize) { // _ConvertAddrToMap - Convert pAddr to the map file using VirtualQuery MEMORY_BASIC_INFORMATION tmpMem; VirtualQuery(pAddr,&tmpMem,sizeof(tmpMem));

// The difference in the allocation base and the base address tells us by how much pAddr was rounded down unsigned int Mod = (((unsigned int)tmpMem.BaseAddress - (unsigned int)tmpMem.AllocationBase) / SystemPageSize) - 1;

return ((unsigned int)(pAddr) - (unsigned int)tmpMem.BaseAddress) + (Mod * SystemPageSize); } //_ConvertAddrToMap



// // GPEMap Methods //

GPEMap::GPEMap() {

Init();

const int MaxModuleFilenameLen = 512; TCHAR ModulePEFile[MaxModuleFilenameLen];

if (GetModuleFileName(0,ModulePEFile,MaxModuleFilenameLen) > 0) { // Try to open the pe file and process it if (OpenPEFile(ModulePEFile) && ProcessPEFile()) { m_bOK = true; } } else { const int OSErrLen = 512; TCHAR OSErr[OSErrLen]; OutputErrorF(_T("GPEMap::GPEMap - GetModuleFilename failed (OS Error = '%s')"), _StringifyOSError(OSErrLen,OSErr)); }

} //GPEMap::GPEMap

GPEMap::GPEMap(const TCHAR * pPEFileToMap) {

Init();

// Try to open the pe file and process it if (OpenPEFile(pPEFileToMap) && ProcessPEFile()) { m_bOK = true; }

} //GPEMap::GPEMap void GPEMap::Init() { // Initialize file mapping members m_bOK = false; m_hFile = 0; m_hMapFile = 0; m_pMapAddress = 0;

// COFF m_pCOFFDbgDir = 0;

// CodeView m_pCVDbgDir = 0; m_pCVGlobalSym = 0; m_pCVSrcMods = 0; m_pCVAlignSym = 0; m_NumCVSrcMods = 0; m_NumCVAlignSyms = 0;

// Initialize the system page size { SYSTEM_INFO tmpSysInfo; GetSystemInfo(&tmpSysInfo); m_SystemPageSize = tmpSysInfo.dwPageSize; } } //GPEMap::Init bool GPEMap::OpenPEFile(const TCHAR * pPEFileToMap) {

// OpePEFile - Opens pPEFileToMap in a standard file, then creates a memory map of it. The base address // of the map is stored in m_pMapAddress // OS error storage const int OSErrLen = 256; TCHAR OSErr[OSErrLen];

// Open up the PE file OFSTRUCT FileInfo; m_hFile = (HANDLE)OpenFile(pPEFileToMap,&FileInfo,OF_READ); if (m_hFile != HANDLE(HFILE_ERROR)) {

// Create a file mapping m_hMapFile = CreateFileMapping(HANDLE(m_hFile), // Current file handle. NULL, // Default security PAGE_READONLY, // Read/write permission. 0, // Max. object size 0, // Size of hFile _T("EXEMap")); // Name of mapping object. if (m_hMapFile == NULL) { OutputErrorF(_T("GPEMap::GPEMap - Failed to open map file (OS Error = '%s')"), _StringifyOSError(OSErrLen,OSErr)); } else { // Create a view of the file m_pMapAddress = (unsigned char*)MapViewOfFile(m_hMapFile, // Handle to mapping object FILE_MAP_READ, // Read/write permission 0, // Max. object size 0, // Size of hFile 0); // Map entire file if (m_pMapAddress == NULL) { OutputErrorF(_T("GPEMap::GPEMap - Could not map view of file (OS Error = '%s')"), _StringifyOSError(OSErrLen,OSErr)); } else { return true; }

}

} else { OutputErrorF(_T("GPEMap::GPEMap - Failed to open PE file (OS Error = '%s'"), _StringifyOSError(OSErrLen,OSErr)); }

return false; } //GPEMap::OpenPEFile bool GPEMap::ProcessPEFile() { // ProcessPEFile - Sets up precalculated file pointers for quicker symbol lookup. At the moment, // only the CodeView debug format is supported // Access the debug directories unsigned long DataSize; const unsigned char * pDirEntry; // Get a pointer to the first debug directory. { // Initial structure = a dos stub header, then image nt headers, which contains the pointers // to the image directories (resources, debug data, import and export tables, and other stuff). // The debug directories should be at position 6 (offset 144) in this array const unsigned char * pDataPtr = m_pMapAddress; const ImageDosStubHeader * pDosHdr = _GetCastDataPtr<ImageDosStubHeader>(pDataPtr); const ImageNTHeaders * pNT = (ImageNTHeaders*)(m_pMapAddress + pDosHdr->m_LFA); pDirEntry = m_pMapAddress + pNT->m_DataDirectory[6].m_VAddr; DataSize = pNT->m_DataDirectory[6].m_Size; }

// If we got some debug directory data... if ((pDirEntry != m_pMapAddress) && (DataSize > 0)) { // ... process it const ImageDebugDirectory * pDebugDir = (ImageDebugDirectory*)pDirEntry; const int NumDebugDirs = int(DataSize) / sizeof(ImageDebugDirectory);

for (int i = 0; i < NumDebugDirs; i++, pDebugDir++) { switch (pDebugDir->m_Type) { // Unprocessed debug directories case IMAGE_DEBUG_TYPE_UNKNOWN : case IMAGE_DEBUG_TYPE_FPO : case IMAGE_DEBUG_TYPE_MISC : case IMAGE_DEBUG_TYPE_EXCEPTION : case IMAGE_DEBUG_TYPE_FIXUP : case IMAGE_DEBUG_TYPE_BORLAND : break;

// Processed debug directories case IMAGE_DEBUG_TYPE_COFF : ProcessCOFFDbgDir(pDebugDir); // TODO... break;

case IMAGE_DEBUG_TYPE_CODEVIEW : ProcessCVDbgDir(pDebugDir); break; }; } }

return UsingCOFF() || UsingCV(); } //GPEMap::ProcessPEFile void GPEMap::ClosePEFile() { // Close down the file handles and stuff if (m_pMapAddress) { UnmapViewOfFile(m_pMapAddress); CloseHandle(m_hMapFile); CloseHandle(m_hFile);

m_pMapAddress = 0; m_hMapFile = 0; m_hFile = 0; }

} //GPEMap::ClosePEFile void GPEMap::ProcessCVDbgDir(const ImageDebugDirectory * pDebugDir) { //assert(UsingCV()); // Verify the CV signature before we do anything else m_pLFO = m_pMapAddress + pDebugDir->m_PointerToRawData; m_pCVSig = (const CVSignature *)m_pLFO; if (!CheckCVSig(*((unsigned int*)&m_pCVSig->m_Signature))) { return; }

const CVDirHeader * pCVDirHeader; const CVSubSection * pCVSubSections;

// Read in the directory header and subsections const unsigned char * pDataPtr = m_pLFO + m_pCVSig->m_FilePos; pCVDirHeader = _GetCastDataPtr<CVDirHeader>(pDataPtr); pCVSubSections = _GetCastDataPtr<CVSubSection>(pDataPtr);

// Run through and process each of the debug directories { const int MaxSrcMods = 1024; const CVSubSection * SrcModList[MaxSrcMods]; const CVSubSection * AlignSymList[MaxSrcMods];

// Parse each subsection for (unsigned int i = 0; i < pCVDirHeader->m_NumDirEntries; i++) { const CVSubSection * pSubSection = &(pCVSubSections[i]); switch (ModuleType(pSubSection->m_SubSection)) { case eMT_sstModule : case eMT_sstTypes : case eMT_sstPublic : case eMT_sstPublicSym : case eMT_sstSymbols : case eMT_sstSrcLnSeg : case eMT_sstLibraries : case eMT_sstGlobalPub : case eMT_sstGlobalTypes : case eMT_sstMPC : case eMT_sstSegMap : case eMT_sstSegName : case eMT_sstPreComp : case eMT_unused : case eMT_sstOffsetMap16 : case eMT_sstOffsetMap32 : case eMT_sstFileIndex : case eMT_sstStaticSym : /* Boring */ break; case eMT_sstAlignSym : { // Alignment symbols table - this is somewhat glossed over in the documentation, but // it seems that for each module, there's a matching one of these that contains all // the symbol information for it. AlignSymList[m_NumCVAlignSyms++] = pSubSection; break; }; //case eMT_sstAlignSym case eMT_sstSrcModule : { // Module source table - there's one of these per module in PE file SrcModList[m_NumCVSrcMods++] = pSubSection; break; }; //case eMT_sstSrcModule case eMT_sstGlobalSym : { // This is the global symbol lookup table - instead of searching each module for a // proc address, this table contains an address sort list that allows you to look // up the address directly in one of the alignment symbol tables. Note that there's // only one of these per PE file, although there's also a static symbol table and a // public symbol table (eMT_sstStaticSym and eMT_sstPublic) that are supposed to // do the same sort of thing. //assert(m_pCVGlobalSym); m_pCVGlobalSym = pSubSection; break; }; //case eMT_sstGlobalSym }; } // Transfer SrcModList and AlignSymList to m_pCVSrcMods and m_pCVAlignSym respectively { int i; m_pCVSrcMods = new const CVSubSection *[m_NumCVSrcMods]; m_pCVAlignSym = new const CVSubSection *[m_NumCVAlignSyms]; for (i = 0; i < m_NumCVSrcMods; m_pCVSrcMods[i] = SrcModList[i],i++); for (i = 0; i < m_NumCVAlignSyms; m_pCVAlignSym[i]= AlignSymList[i],i++); } }

m_pCVDbgDir = pDebugDir; } //GPEMap::ProcessCVDbgDir void GPEMap::ProcessCOFFDbgDir(const ImageDebugDirectory * pDebugDir) { // TODO... } //GPEMap::ProcessCOFFDbgDir

void GPEMap::OutputErrorF(const TCHAR * pError,...) const { TCHAR FullMsg[1024];

va_list VArgs; va_start(VArgs,pError); _vsnprintf(FullMsg,1024,pError,VArgs); va_end(VArgs);

MessageBox(0,FullMsg,_T("GPEMap Error"),MB_OK); } //GPEMap::OutputErrorF

bool GPEMap::CheckCVSig(const unsigned int Sig) {

if ((Sig == (unsigned int)'11BN') || (Sig == (unsigned int)'90BN')) // I think NB09 is ok too... { return true; }

if (Sig == (unsigned int)'01BN') { // The PDB format is based on a modified CodeView structure that is not publicly available. Please // mail me at andrewp@redlemon.com if you know anything about it... OutputErrorF(_T("GPEMap::CheckCVSig - GPEMap does not support external program databases. If you are\n") _T("using Visual Studio 6.0 then remove the check from 'Use Program Database' in\n") _T("Project Settings->Link->Customize\n")); return false;

} else { // (should be ok for unicode) OutputErrorF(_T("GPEMap::CheckCVSig - Unsupported codeview format ('%c%c%c%c')\n"), ((char*)&Sig)[0],((char*)&Sig)[1],((char*)&Sig)[2],((char*)&Sig)[3]); }

return false; } //GPEMap::CheckCVSig bool GPEMap::GetCVCodeSource(const void * pAddr,GPEMap::CodeSourceData * pData) const { // Map pAddr const unsigned int AddrUInt = _ConvertAddrToMap(pAddr,m_SystemPageSize);

// Find the source information for AddrUInt if (!CVLookupAddrSrc(AddrUInt,pData)) { return false; }

// Find the symbol information for AddrUInt return CVLookupAddrSym(AddrUInt,pData); } //GPEMap::GetCVCodeSource bool GPEMap::CVLookupAddrSym(const unsigned int AddrUInt,CodeSourceData * pData) const { if (!m_pCVGlobalSym) { return false; }

const unsigned char * pGlobalSymBase = m_pLFO + m_pCVGlobalSym->m_LFO, * pDataPtr = pGlobalSymBase; const CVSymTableHeader * pSymTableHdr = _GetCastDataPtr<CVSymTableHeader>(pDataPtr); const unsigned char * pSymTable = pDataPtr;

//assert(SymHashFnc == 10); //assert(AddrHashFnc == 12); pDataPtr = pDataPtr + (pSymTableHdr->m_NumSymBytes + pSymTableHdr->m_NumSymHashBytes); const unsigned short NumSegs = *_GetCastDataPtr<unsigned short>(pDataPtr,2 /* (incl. padding) */); const unsigned int * pSegTable = _GetCastDataPtr<unsigned int>(pDataPtr,NumSegs); const unsigned int * pOffsetCounts = _GetCastDataPtr<unsigned int>(pDataPtr,NumSegs); const unsigned int * pOffsetTable = _GetCastDataPtr<unsigned int>(pDataPtr,NumSegs * 2);

pData->m_Fnc[0] = _T('\0');

// TODO : Binary search for (unsigned int CurSeg = 0; CurSeg < NumSegs; CurSeg++) { const CVAddrHashOffset * pCurOff = (CVAddrHashOffset*)pOffsetTable;

for (unsigned int i = 0; i < pOffsetCounts[CurSeg]; i++) {

if ((AddrUInt >= pCurOff[0].m_CurMemOffset) && (AddrUInt <= pCurOff[1].m_CurMemOffset)) { const unsigned char * pSymData = pSymTable + pCurOff[0].m_CurSymOffset; const unsigned short SymLen = *_GetCastDataPtr<unsigned short>(pSymData); const unsigned short SymType = *_GetCastDataPtr<unsigned short>(pSymData);

switch (SymType) { case S_PROCREF : { const CVSymProcRef * pProcRef = (CVSymProcRef*)pSymData; const unsigned char * pModAddr = m_pLFO + m_pCVAlignSym[pProcRef->m_Module-1]->m_LFO; const CVSymProcStart * pProcData = (CVSymProcStart *)&(pModAddr[pProcRef->m_SymOffset+4]);

_sntprintf(pData->m_Fnc,eMaxFncName,_T("%.*s"),pProcData->m_NameLen,pProcData->m_Name);

return true; };

// Can also do S_DATAREF for variables (see MSDN documentation and CVSymData for details) };

// Unhandled symbol type... return false; }

pCurOff++; } }

return false; } //GPEMap::CVLookupAddrSym bool GPEMap::CVLookupAddrSrc(const unsigned int AddrUInt,CodeSourceData * pData) const { // Run through the source module subsections for (int i = 0; i < m_NumCVSrcMods; i++) { // Goto the i'th module source section const unsigned char * pSrcModBase = m_pLFO + m_pCVSrcMods[i]->m_LFO, * pDataPtr = pSrcModBase;

// Get the module header information and the base source and segment data const CVSrcModuleHeader * pSrcModHdr = _GetCastDataPtr<CVSrcModuleHeader>(pDataPtr); const unsigned int * pBaseSrcPtrs = _GetCastDataPtr<unsigned int>(pDataPtr,pSrcModHdr->m_NumFiles); const unsigned int * pBaseSegPairs = _GetCastDataPtr<unsigned int>(pDataPtr,pSrcModHdr->m_NumSegs * 2); const unsigned short * pBaseSegs = _GetCastDataPtr<unsigned short>(pDataPtr,pSrcModHdr->m_NumSegs);

// TODO : Quick check of base seg pairs to reject this module fast bool bInModule = false; const unsigned int * pCurBasePair = pBaseSegPairs; for (unsigned int qs = 0; qs < pSrcModHdr->m_NumSegs; qs++) { if ((AddrUInt >= pCurBasePair[0]) && (AddrUInt <= pCurBasePair[1])) { bInModule = true; break; } pCurBasePair += 2; }

if (!bInModule) { // Address is not in this module... goto the next one continue; }

// RUn through the source files for (unsigned int f = 0; f < pSrcModHdr->m_NumFiles; f++) { // Goto the data for this source file pDataPtr = pSrcModBase + pBaseSrcPtrs[f];

// Get the name, line and segment information for this file const unsigned short NumSegs = *_GetCastDataPtr<unsigned short>(pDataPtr); const unsigned short Pad = *_GetCastDataPtr<unsigned short>(pDataPtr); const unsigned int * pSrcLines = _GetCastDataPtr<unsigned int>(pDataPtr,NumSegs); const unsigned int * pSegPairs = _GetCastDataPtr<unsigned int>(pDataPtr,NumSegs * 2);

// the documentation lies! FNameLen is only one byte... const unsigned char FNameLen = *_GetCastDataPtr<unsigned char>(pDataPtr); const char * pFName = _GetCastDataPtr<char>(pDataPtr,FNameLen);

// Run through the file segments const unsigned int * pCurPair = pSegPairs; for (unsigned int s = 0; s < NumSegs; s++) { if ((AddrUInt >= pCurPair[0]) && (AddrUInt<= pCurPair[1])) { // Found it! Find the line number it appears on const unsigned char * pTmpDataPtr = pSrcModBase + pSrcLines[s]; const unsigned short Seg = *_GetCastDataPtr<unsigned short>(pTmpDataPtr); const unsigned short Pair = *_GetCastDataPtr<unsigned short>(pTmpDataPtr); const unsigned int * pOffsets = _GetCastDataPtr<unsigned int>(pTmpDataPtr,Pair); const unsigned short * pLines = _GetCastDataPtr<unsigned short>(pTmpDataPtr,Pair);

// Find the matching offset for (int o = 0; o < Pair; o++) { if (pOffsets[o] >= AddrUInt) { _sntprintf(pData->m_File,eMaxSourceFileName,_T("%.*s"),FNameLen,pFName); pData->m_Line = pLines[o == 0 ? o : o-1];

return true; } }

return false; }

pCurPair += 2; } } }

return false; } //GPEMap::CVLookupAddrSrc bool GPEMap::GetCodeSource(const void * pAddr,CodeSourceData * pData) const { if (!IsOK()) { return false; }

//assert(pData); // Initialize pData pData->m_File[0] = _T('\0'); pData->m_Line = 0; pData->m_Fnc[0] = _T('\0');

// Process either using CodeView or COFF if (UsingCV()) { return GetCVCodeSource(pAddr,pData); } else if (UsingCOFF()) { // TODO... }

return false; } //GPEMap::GetCodeSource bool GPEMap::GetStackWalkCodeSource(const int NumCalls, const void * * pStackAddrs, CodeSourceData * pData) const { // Run through the stack address array for (int i = 0; i < NumCalls; i++) { if (pStackAddrs[i]) { // Find the source file,line and procedure / data name if (!GetCodeSource(pStackAddrs[i],&(pData[i]))) { return false; }

// Helpful... TCHAR tmpStr[256]; sprintf(tmpStr,_T("%s(%d) : Found in '%s'\n"),pData[i].m_File,pData[i].m_Line,pData[i].m_Fnc); OutputDebugString(tmpStr); } }

return true; } //GPEMap::GetStackWalkCodeSource

GPEMap::~GPEMap() { if (IsOK()) { ClosePEFile();

// Delete the source module and alignment symbols arrays if (m_pCVSrcMods) { delete []m_pCVSrcMods; }

if (m_pCVAlignSym) { delete []m_pCVAlignSym; } }

} //GPEMap::~GPEMap bool GPEMap::_WalkTheStack(const int NumCallsToCheck, const int NumCallsToSkip, void * * pStackAddrs) { // Check the parameters if (!NumCallsToCheck || !pStackAddrs) { return false; }

// Initialize the stack walk variables and clear out the stack address array int CurStackIn = 0; int CurSkip = 0; int ParentEBP;

for (int i = 0; i < NumCallsToCheck; pStackAddrs[i++] = 0);

// Store the parent EBP to begin with __asm MOV ParentEBP, EBP

do { // Get the caller pointer and move on to the caller parent void * pCaller = *((void**)ParentEBP + 1); ParentEBP = *((int*)ParentEBP);

// Either skip or store the caller in the stack address array if (CurSkip < NumCallsToSkip) { CurSkip++; } else { pStackAddrs[CurStackIn++] = pCaller; } // Until ParentEBP is null or we've reached the end of the stack address array } while ((ParentEBP != 0) && (CurStackIn < NumCallsToCheck));

return true; } //GPEMap::_WalkTheStack

#ifdef _TESTING

// // TESTING // class TestClass { public : void FinalDoStuff(GPEMap * pMap) { const int TestStackSize = 7; void * TestStack[TestStackSize]; GPEMap::CodeSourceData TestStackSource[TestStackSize];

if (GPEMap::_WalkTheStack(TestStackSize,0,TestStack)) { pMap->GetStackWalkCodeSource(TestStackSize,(const void**)TestStack,TestStackSource); } }

static void _PenultimateDoStuff(GPEMap * pMap) { TestClass t; t.FinalDoStuff(pMap); } };

void _DoMoreStuff(GPEMap * pMap) { TestClass::_PenultimateDoStuff(pMap); }

void _DoStuff(GPEMap * pMap) { _DoMoreStuff(pMap); }

void _TestGPEMap(const TCHAR * pPEFile) { if (pPEFile) { GPEMap tmpMap(pPEFile);

_DoStuff(&tmpMap); } else { GPEMap tmpMap; _DoStuff(&tmpMap); } }

#endif

Currently browsing [GPEMap.zip] (11,478 bytes) - [GPEMap.h] - (4,954 bytes)


#ifndef _GPEMAP_HEADER
#define _GPEMAP_HEADER

#include <tchar.h> #include <windows.h>

/*

*** EXAMPLE USAGE ***

1) To get the name of a function : { GPEMap tmpMap; GPEMap::CodeSourceData tmpData; if (tmpMap.GetCodeSource(_SomeFunction,&tmpData)) { // tmpData now contains the filename, linenumber and name of _SomeFunction... } }

2) o

*/


// // Testing... // // Comment this out to remove the testing functions #define _TESTING

#ifdef _TESTING

extern void _TestGPEMap(const TCHAR * pPEFile);

#endif

// // Forward Struct Declarations //

struct ImageDebugDirectory; struct CVSignature; struct CVSubSection;

// // Classes // class GPEMap { public :

// // Enums // enum { eMaxSourceFileName = 512, // Max. size of the source filename eMaxFncName = 256 // Max. size of the enclosing function name };

// // Structs // struct CodeSourceData { TCHAR m_File[eMaxSourceFileName]; // Source file containing the code int m_Line; // Source line the code appears on TCHAR m_Fnc[eMaxFncName]; // Name of the function the code appears in }; //CodeSourceData private :

// // Members // bool m_bOK; // true if all initialization and processing went ok HANDLE m_hFile; // Actual PE file handle HANDLE m_hMapFile; // Memory mapped PE file const unsigned char * m_pMapAddress; // Base address of the mapped file unsigned int m_SystemPageSize; // Size of a page on this system (4096) // // COFF Debug Members // ImageDebugDirectory * m_pCOFFDbgDir; // Pointer to the COFF data directory in the mapped file // TODO... // // CV Debug Members // const ImageDebugDirectory * m_pCVDbgDir; // Pointer to the CV data directory in the mapped file const unsigned char * m_pLFO; // LFO CV pointer (base for all cv debug info) const CVSignature * m_pCVSig; // CV signature const CVSubSection * m_pCVGlobalSym; // Pointer to the CV global symbol table const CVSubSection * * m_pCVSrcMods; // Array of CV source module section pointers const CVSubSection * * m_pCVAlignSym; // Alignment symbol tables for all modules int m_NumCVSrcMods; // Number of pointers in m_pCVSrcMods int m_NumCVAlignSyms; // Number of alignment symbol subsections in m_pCVAlignSym



public :

// Constructor - sets up a file mapping of the current module GPEMap();

// Constructor - sets up a file mapping of the specified PE file GPEMap(const TCHAR * pPEFileToMap);

private :

// Shared constructor initialization function void Init();

// PE File management bool OpenPEFile(const TCHAR * pPEFileToMap); bool ProcessPEFile(); void ClosePEFile();

// CV and COFF debug directory processing void ProcessCVDbgDir(const ImageDebugDirectory * pDebugDir); void ProcessCOFFDbgDir(const ImageDebugDirectory * pDebugDir);

// Error output (to message box) void OutputErrorF(const TCHAR * pError,...) const;

// Success check - returns true if the GPEMap constructor successfully created the file mapping bool IsOK() const { return m_bOK; }

bool UsingCOFF() const { return m_pCOFFDbgDir != 0; } bool UsingCV() const { return m_pCVDbgDir != 0; }

// // CodeView Methods // // CodeView signature checker (may change map file from exe to pdb) bool CheckCVSig(const unsigned int Sig); // CodeView source lookup - note that the symbol lookup can be adapted to lookup the names // of variables too (see notes in source) bool GetCVCodeSource(const void * pAddr,CodeSourceData * pData) const; bool CVLookupAddrSym(const unsigned int AddrUInt,CodeSourceData * pData) const; bool CVLookupAddrSrc(const unsigned int AddrUInt,CodeSourceData * pData) const;

// // COFF Methods // // TODO...

public :

// Code source lookup bool GetCodeSource(const void * pAddr,CodeSourceData * pData) const;

// Stack walker code source lookup (use _WalkTheStack to build up a list of code pointers for // this method to interpret) bool GetStackWalkCodeSource(const int NumCalls, const void * * pStackAddrs, CodeSourceData * pData) const;

// Destructor - kills the file mapping ~GPEMap();

// // Static Methods // // Bonus fast stack walker - use to format pStackAddrs for a call to GetStackWalkCodeSource. Note // that this assumes a standard stack frame format that uses EBP as a frame pointer. I think there // are some optimizations which omit the frame pointer - check FPO_DATA in MSDN for details on // how to fixme static bool _WalkTheStack(const int NumCallsToCheck,const int NumCallsToSkip,void * * pStackAddrs);

}; //GPEMap

#endif

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.