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.