Figure 3 SymbolEngine.h
/*
John Robbins - Microsoft Systems Journal Bugslayer Column
This is a paper thin layer over the IMAGEHLP.DLL symbol engine which helps hide some of the differences between the Original NT4.0 version and the November Platform SDK and later version. Note that this class only wraps those functions that take the unique HANDLE value. Other IMAGEHLP.DLL symbol engine functions are global in scope so I did not wrap the M with this class. While IMAGEHLP.DLL has a version API, ImagehlpApiVersion and ImagehlpApiVersionEx, it is very weird. No matter which of the many builds of IMAGEHLP.DLL I checked, they all returned major version 4, minor version 0, revision 5. This was even the case with the new NT5 IMAGEHLP.DLL. Whatever. What I did instead was to just look for the line specific exported functions with GetProcAddress. If they are there, then they can be used. To see if the line information functions are supported, call the class's CanDoSourceLines function. I was able to take care of almost all the differences between the different versions here. the One difference is that the IMAGEHLP_DEFERRED_SYMBOL_LOAD structure is a larger size in the November Platform SDK version.
Compilation Defines:
FLEXIBLE_SYMBOLENGINE - Define this to always have the CsymbolEngine class use the GetProcAddress method of determining if the IMAGEHLP.DLL in memory supports the new source and line functions.
*/
#ifndef _SYMBOLENGINE_H
#define _SYMBOLENGINE_H
#include "imagehlp.h"
// The API_VERSION_NUMBER define is 5 with the NT4 IMAGEHLP.H. It is
// 7 with the November Platform SDK version. This seems to be the Only
// reliable way to see which header is being used.
#if ( API_VERSION_NUMBER < 7 )
#define SYMENG_EXTRAWORK
#define SYMENG_NEEDDECLS
#else
#undef SYMENG_EXTRAWORK
#undef SYMENG_NEEDDECLS
#endif
// If FLEXIBLE_SYMBOLENGINE is defined, ALWAYS define SYMENG_EXTRAWORK
#ifdef FLEXIBLE_SYMBOLENGINE
#define SYMENG_EXTRAWORK
#endif FLEXIBLE_SYMBOLENGINE
// I will define the structures that need defining in case the included
// IMAGEHLP.H is from NT4.0.
#ifdef SYMENG_NEEDDECLS
// source file line data structure
typedef struct _IMAGEHLP_LINE
{
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE)
DWORD Key; // internal
DWORD LineNumber; // line number in file
PCHAR FileName; // full filename
DWORD Address; // first instruction of line
} IMAGEHLP_LINE, *PIMAGEHLP_LINE;
#endif // SYMENG_NEEDDECLS
#ifndef SYMOPT_LOAD_LINES
#define SYMOPT_LOAD_LINES 0x00000010
#endif // SYMOPT_LOAD_LINES
#ifndef SYMOPT_OMAP_FIND_NEAREST
#define SYMOPT_OMAP_FIND_NEAREST 0x00000020
#endif // SYMOPT_OMAP_FIND_NEAREST
/*//////////////////////////////////////////////////////////////////////
I just lifted the prototypes right out of the NT5 header.
Note: I had a whopper of a bug here. I had the declarations and
typedefs like the following:
BOOL
IMAGEAPI
SymGetLineFromAddr(
IN HANDLE hProcess,
IN DWORD dwAddr,
OUT PDWORD pdwDisplacement,
OUT PIMAGEHLP_LINE Line
);
typedef
BOOL (*PFNSYMGETLINEFROMADDR) ( IN HANDLE hProcess ,
IN DWORD dwAddr ,
OUT PDWORD pdwDisplacement ,
OUT PIMAGEHLP_LINE Line );
Whenever I ran the release build, it always crashed because the stack was not restoring the registers when certain functions in this class were called. Looking at the disassembly, I kept seeing instructions like ADD ESP,10h after certain calls. This was driving me nuts until it dawned on me that the extra ADD after the calls was cleaning up the stack for CDECL calls but that Windows API calls are all STDCALL. I had a calling convention missmatch!
I just thought I'd mention this so that it might save you some problems if you do something like this. Watch those calling conventions!
//////////////////////////////////////////////////////////////////////*/
#ifdef SYMENG_NEEDDECLS
BOOL
__stdcall
SymGetLineFromAddr(
IN HANDLE hProcess,
IN DWORD dwAddr,
OUT PDWORD pdwDisplacement,
OUT PIMAGEHLP_LINE Line
);
BOOL
__stdcall
SymGetLineFromName(
IN HANDLE hProcess,
IN LPSTR ModuleName,
IN LPSTR FileName,
IN DWORD dwLineNumber,
OUT PLONG plDisplacement,
IN OUT PIMAGEHLP_LINE Line
);
BOOL
__stdcall
SymGetLineNext(
IN HANDLE hProcess,
IN OUT PIMAGEHLP_LINE Line
);
BOOL
__stdcall
SymGetLinePrev(
IN HANDLE hProcess,
IN OUT PIMAGEHLP_LINE Line
);
BOOL
__stdcall
SymMatchFileName(
IN LPSTR FileName,
IN LPSTR Match,
OUT LPSTR *FileNameStop,
OUT LPSTR *MatchStop
);
#endif // SYMENG_NEEDDECLS
// The great Bugslayer idea of creating wrapper classes on structures
// that have size fields came from fellow MSJ columnist, Paul DiLascia.
// Thanks, Paul!
// I did not wrap IMAGEHLP_SYMBOL because that is a variable size
// structure.
// The IMAGEHLP_MODULE wrapper class.
struct CImageHlp_Module : public IMAGEHLP_MODULE
{
CImageHlp_Module ( )
{
memset ( this , NULL , sizeof ( IMAGEHLP_MODULE ) ) ;
SizeOfStruct = sizeof ( IMAGEHLP_MODULE ) ;
}
} ;
// The IMAGEHLP_LINE wrapper class.
struct CImageHlp_Line : public IMAGEHLP_LINE
{
CImageHlp_Line ( )
{
memset ( this , NULL , sizeof ( IMAGEHLP_LINE ) ) ;
SizeOfStruct = sizeof ( IMAGEHLP_LINE ) ;
}
} ;
// Typedefs for the new source and line functions.
typedef
BOOL (__stdcall *PFNSYMGETLINEFROMADDR)
( IN HANDLE hProcess ,
IN DWORD dwAddr ,
OUT PDWORD pdwDisplacement ,
OUT PIMAGEHLP_LINE Line ) ;
typedef
BOOL (__stdcall *PFNSYMGETLINEFROMNAME)
( IN HANDLE hProcess ,
IN LPSTR ModuleName ,
IN LPSTR FileName ,
IN DWORD dwLineNumber ,
OUT PLONG plDisplacement,
IN OUT PIMAGEHLP_LINE Line ) ;
typedef
BOOL (__stdcall *PFNSYMGETLINENEXT) ( IN HANDLE hProcess ,
IN OUT PIMAGEHLP_LINE Line );
typedef
BOOL (__stdcall *PFNSYMGETLINEPREV) ( IN HANDLE hProcess ,
IN OUT PIMAGEHLP_LINE Line );
typedef
BOOL (__stdcall *PFNSYMMATCHFILENAME) ( IN LPSTR FileName ,
IN LPSTR Match ,
OUT LPSTR * FileNameStop ,
OUT LPSTR * MatchStop ) ;
// The symbol engine class.
class CSymbolEngine
{
/*
Public Construction and Destruction
*/
public :
// The constructor just does the work of finding the new functions.
// Use this class just like the normal C functions and call the
// SymInitialize function to get everything started.
CSymbolEngine ( void )
{
#ifdef SYMENG_EXTRAWORK
HINSTANCE hInstImageHlp = GetModuleHandleA ( "IMAGEHLP.DLL" ) ;
m_pfnSymGetLineFromAddr =
(PFNSYMGETLINEFROMADDR)GetProcAddress(hInstImageHlp ,
"SymGetLineFromAddr");
if ( NULL == m_pfnSymGetLineFromAddr )
{
m_pfnSymGetLineFromName = NULL ;
m_pfnSymGetLineNext = NULL ;
m_pfnSymGetLinePrev = NULL ;
m_pfnSymMatchFileName = NULL ;
}
else
{
m_pfnSymGetLineFromName =
(PFNSYMGETLINEFROMNAME)GetProcAddress(hInstImageHlp ,
"SymGetLineFromName");
m_pfnSymGetLineNext =
(PFNSYMGETLINENEXT)GetProcAddress ( hInstImageHlp ,
"SymGetLineNext" ) ;
m_pfnSymGetLinePrev =
(PFNSYMGETLINEPREV)GetProcAddress ( hInstImageHlp ,
"SymGetLinePrev" ) ;
m_pfnSymMatchFileName =
(PFNSYMMATCHFILENAME)GetProcAddress (hInstImageHlp ,
"SymMatchFileName");
}
#endif // SYMENG_EXTRAWORK
}
virtual ~CSymbolEngine ( void )
{
}
/*
Public Helper Information Functions
*/
public :
BOOL CanDoSourceLines ( void )
{
#ifdef SYMENG_EXTRAWORK
return ( NULL != m_pfnSymGetLineFromAddr ) ;
#else
return ( TRUE ) ;
#endif // SYMENG_EXTRAWORK
}
/*
Public Initialization and Cleanup
*/
public :
BOOL SymInitialize ( IN HANDLE hProcess ,
IN LPSTR UserSearchPath ,
IN BOOL fInvadeProcess )
{
m_hProcess = hProcess ;
return ( ::SymInitialize ( hProcess ,
UserSearchPath ,
fInvadeProcess ) ) ;
}
BOOL SymCleanup ( void )
{
return ( ::SymCleanup ( m_hProcess ) ) ;
}
/*
Public Module Manipulation
*/
public :
BOOL SymEnumerateModules ( IN PSYM_ENUMMODULES_CALLBACK
EnumModulesCallback,
IN PVOID UserContext )
{
return ( ::SymEnumerateModules ( m_hProcess ,
EnumModulesCallback ,
UserContext ) ) ;
}
BOOL SymLoadModule ( IN HANDLE hFile ,
IN PSTR ImageName ,
IN PSTR ModuleName ,
IN DWORD BaseOfDll ,
IN DWORD SizeOfDll )
{
return ( ::SymLoadModule ( m_hProcess ,
hFile ,
ImageName ,
ModuleName ,
BaseOfDll ,
SizeOfDll ) ) ;
}
BOOL EnumerateLoadedModules ( IN PENUMLOADED_MODULES_CALLBACK
EnumLoadedModulesCallback,
IN PVOID UserContext )
{
return ( ::EnumerateLoadedModules ( m_hProcess ,
EnumLoadedModulesCallback ,
UserContext ));
}
BOOL SymUnloadModule ( IN DWORD BaseOfDll )
{
return ( ::SymUnloadModule ( m_hProcess , BaseOfDll ) ) ;
}
BOOL SymGetModuleInfo ( IN DWORD dwAddr ,
OUT PIMAGEHLP_MODULE ModuleInfo )
{
return ( ::SymGetModuleInfo ( m_hProcess ,
dwAddr ,
ModuleInfo ) ) ;
}
DWORD SymGetModuleBase ( IN DWORD dwAddr )
{
return ( ::SymGetModuleBase ( m_hProcess , dwAddr ) ) ;
}
/*
Public Symbol Manipulation
*/
public :
BOOL SymEnumerateSymbols (IN DWORD BaseOfDll,
IN PSYM_ENUMSYMBOLS_CALLBACK
EnumSymbolsCallback,
IN PVOID UserContext )
{
return ( ::SymEnumerateSymbols ( m_hProcess ,
BaseOfDll ,
EnumSymbolsCallback ,
UserContext ) ) ;
}
BOOL SymGetSymFromAddr ( IN DWORD dwAddr ,
OUT PDWORD pdwDisplacement ,
OUT PIMAGEHLP_SYMBOL Symbol )
{
return ( ::SymGetSymFromAddr ( m_hProcess ,
dwAddr ,
pdwDisplacement ,
Symbol ) ) ;
}
BOOL SymGetSymFromName ( IN LPSTR Name ,
OUT PIMAGEHLP_SYMBOL Symbol )
{
return ( ::SymGetSymFromName ( m_hProcess ,
Name ,
Symbol ) ) ;
}
BOOL SymGetSymNext ( IN OUT PIMAGEHLP_SYMBOL Symbol )
{
return ( ::SymGetSymNext ( m_hProcess , Symbol ) ) ;
}
BOOL SymGetSymPrev ( IN OUT PIMAGEHLP_SYMBOL Symbol )
{
return ( :: SymGetSymPrev ( m_hProcess , Symbol ) ) ;
}
/*
Public Source Line Manipulation
*/
public :
BOOL SymGetLineFromAddr ( IN DWORD dwAddr ,
OUT PDWORD pdwDisplacement ,
OUT PIMAGEHLP_LINE Line )
{
#ifdef SYMENG_EXTRAWORK
if ( NULL == m_pfnSymGetLineFromAddr )
{
return ( FALSE ) ;
}
return ( m_pfnSymGetLineFromAddr ( m_hProcess ,
dwAddr ,
pdwDisplacement ,
Line ) ) ;
#else
return ( ::SymGetLineFromAddr ( m_hProcess ,
dwAddr ,
pdwDisplacement ,
Line ) ) ;
#endif // SYMENG_EXTRAWORK
}
BOOL SymGetLineFromName ( IN LPSTR ModuleName ,
IN LPSTR FileName ,
IN DWORD dwLineNumber ,
OUT PLONG plDisplacement ,
IN OUT PIMAGEHLP_LINE Line )
{
#ifdef SYMENG_EXTRAWORK
if ( NULL == m_pfnSymGetLineFromName )
{
return ( FALSE ) ;
}
return ( m_pfnSymGetLineFromName ( m_hProcess ,
ModuleName ,
FileName ,
dwLineNumber ,
plDisplacement ,
Line ) ) ;
#else
return ( ::SymGetLineFromName ( m_hProcess ,
ModuleName ,
FileName ,
dwLineNumber ,
plDisplacement ,
Line ) ) ;
#endif // SYMENG_EXTRAWORK
}
BOOL SymGetLineNext ( IN OUT PIMAGEHLP_LINE Line )
{
#ifdef SYMENG_EXTRAWORK
if ( NULL == m_pfnSymGetLineNext )
{
return ( FALSE ) ;
}
return ( m_pfnSymGetLineNext ( m_hProcess , Line ) ) ;
#else
return ( ::SymGetLineNext ( m_hProcess , Line ) ) ;
#endif // SYMENG_EXTRAWORK
}
BOOL SymGetLinePrev ( IN OUT PIMAGEHLP_LINE Line )
{
#ifdef SYMENG_EXTRAWORK
if ( NULL == m_pfnSymGetLinePrev )
{
return ( FALSE ) ;
}
return ( m_pfnSymGetLinePrev ( m_hProcess , Line ) ) ;
#else
return ( ::SymGetLinePrev ( m_hProcess , Line ) ) ;
#endif // SYMENG_EXTRAWORK
}
// What is this?
BOOL SymMatchFileName ( IN LPSTR FileName ,
IN LPSTR Match ,
OUT LPSTR * FileNameStop ,
OUT LPSTR * MatchStop )
{
#ifdef SYMENG_EXTRAWORK
if ( NULL == m_pfnSymMatchFileName )
{
return ( FALSE ) ;
}
return ( m_pfnSymMatchFileName ( FileName ,
Match ,
FileNameStop ,
MatchStop ) ) ;
#else
return ( ::SymMatchFileName ( FileName ,
Match ,
FileNameStop ,
MatchStop ) ) ;
#endif // SYMENG_EXTRAWORK
}
/*
Public Misc Members
*/
public :
LPVOID SymFunctionTableAccess ( DWORD AddrBase )
{
return ( :: SymFunctionTableAccess ( m_hProcess , AddrBase ) ) ;
}
BOOL SymGetSearchPath ( OUT LPSTR SearchPath ,
IN DWORD SearchPathLength )
{
return ( :: SymGetSearchPath ( m_hProcess ,
SearchPath ,
SearchPathLength ) ) ;
}
BOOL SymSetSearchPath ( IN LPSTR SearchPath )
{
return ( :: SymSetSearchPath ( m_hProcess , SearchPath ) ) ;
}
BOOL SymRegisterCallback ( IN PSYMBOL_REGISTERED_CALLBACK
CallbackFunction,
IN PVOID UserContext )
{
return ( ::SymRegisterCallback ( m_hProcess ,
CallbackFunction ,
UserContext ) ) ;
}
/*
Protected Data Members
*/
protected :
// The unique value that will be used for this instance of the
// symbol engine. Note that this does not have to be an actual
// process value, just a unique value.
HANDLE m_hProcess ;
#ifdef SYMENG_EXTRAWORK
// The function pointers to the new symbol engine source and line
// functions.
PFNSYMGETLINEFROMADDR m_pfnSymGetLineFromAddr ;
PFNSYMGETLINEFROMNAME m_pfnSymGetLineFromName ;
PFNSYMGETLINENEXT m_pfnSymGetLineNext ;
PFNSYMGETLINEPREV m_pfnSymGetLinePrev ;
PFNSYMMATCHFILENAME m_pfnSymMatchFileName ;
#endif // SYMENG_EXTRAWORK
} ;
#endif // _SYMBOLENGINE_H
Figure 4 CCrashFinderDoc::LoadAndShowImage
BOOL CCrashFinderDoc :: LoadAndShowImage ( CBinaryImage * pImage ,
BOOL bModifiesDoc )
{
ASSERT ( this ) ;
ASSERT ( NULL != m_pcTreeControl ) ;
// A string that can be used for any user messages.
CString sMsg ;
// The state for the tree graphic.
int iState = STATE_NOTVALID ;
// A boolean return value holder.
BOOL bRet ;
// Make sure the parameter is good.
ASSERT ( NULL != pImage ) ;
// Check to see if this is a valid image. If it is, then make sure
// that it is not something already in the list and does not have
// a conflicting load address. If it is not a valid image, I add
// it anyway because it is not good form to just throw out user
// data. If the image is bad, then I just show it with the invalid
// bitmap and do not load it into the symbol engine.
if ( TRUE == pImage->IsValidImage ( ) )
{
// Here I walk through the items in the data array so I can look
// for three problem conditions.
// 1. The binary image is already in the list. This means that
// I can only abort and not add it at all.
// 2. The binary is going to be at a load address that already
// is in the list. If that's the case, I will pull up the
// property dialog for the binary image so it can be changed
// before finally sticking it into the list.
// 3. The project already has an EXE image in it and pImage is
// also an executable.
// I always start out thinking that the data in pImage is valid.
// Call me an optimist!
BOOL bValid = TRUE ;
int iCount = m_cDataArray.GetSize ( ) ;
for ( int i = 0 ; i < iCount ; i++ )
{
CBinaryImage * pTemp = (CBinaryImage *)m_cDataArray[ i ] ;
ASSERT ( NULL != pImage ) ;
// Is this an exact match?
if ( pImage->GetFullName ( ) == pTemp->GetFullName ( ) )
{
// Tell the user!!
sMsg.FormatMessage ( IDS_DUPLICATEFILE ,
pTemp->GetFullName ( ) ) ;
AfxMessageBox ( sMsg ) ;
return ( FALSE ) ;
}
// If the current image from the data structure is not
// valid, then I am kind of up a creek. While I can check
// duplicate names above, it is kind of hard to check load
// addresses and EXE characteristics. If pTemp is not
// valid, then I have to skip the Se checks. This can lead
// to problems but since pTemp is marked in the list as
// invalid, it is up to the user to reset the properties.
if ( TRUE == pTemp->IsValidImage ( FALSE ) )
{
// Check that I don't add two EXEs to the project.
if ( 0 == ( IMAGE_FILE_DLL &
pTemp->GetCharacteristics ( ) ) )
{
if ( 0 == ( IMAGE_FILE_DLL &
pImage->GetCharacteristics ( ) ) )
{
// Tell the user!!
sMsg.FormatMessage ( IDS_EXEALREADYINPROJECT ,
pImage->GetFullName ( ) ,
pTemp->GetFullName ( ) ) ;
AfxMessageBox ( sMsg ) ;
// Trying to load two things marked as EXEs will
// automatically have the data thrown out for
// pImage.
return ( FALSE ) ;
}
}
// Check for load address conflicts.
if ( pImage->GetLoadAddress ( ) ==
pTemp->GetLoadAddress( ) )
{
sMsg.FormatMessage ( IDS_DUPLICATELOADADDR ,
pImage->GetFullName ( ) ,
pTemp->GetFullName ( ) ) ;
if ( IDYES == AfxMessageBox ( sMsg , MB_YESNO ) )
{
// The user wants to change the properties by
// hand.
pImage->SetProperties ( ) ;
// Check that the load address really did
// change and does not now conflict with
// another binary.
int iIndex ;
if ( TRUE == IsConflictingLoadAddress (
pImage->GetLoadAddress(),
iIndex ))
{
sMsg.FormatMessage ( IDS_DUPLICATELOADADDRFINAL ,
pImage->GetFullName ( ) ,
((CBinaryImage*)m_cDataArray[iIndex])->GetFullName());
AfxMessageBox ( sMsg ) ;
// The data in pImage is not valid so go
// ahead and kick out now.
bValid = FALSE ;
break ;
}
}
else
{
// The data in pImage is not valid so go
// ahead and kick out now.
bValid = FALSE ;
break ;
}
}
}
}
if ( TRUE == bValid )
{
// This is a good image (at least up to the symbol load).
iState = STATE_VALIDATED ;
}
else
{
iState = STATE_NOTVALID ;
}
}
else
{
// Not a valid image.
iState = STATE_NOTVALID ;
}
if ( STATE_VALIDATED == iState )
{
// Try and load this image into the symbol engine.
bRet =
m_cSymEng.SymLoadModule(NULL ,
(PSTR)(LPCSTR)pImage->GetFullName() ,
NULL ,
pImage->GetLoadAddress ( ) ,
0 );
// Watch out. SymLoadModule returns the load address of the
// image, not TRUE.
ASSERT ( FALSE != bRet ) ;
if ( FALSE == bRet )
{
TRACE ( "m_cSymEng.SymLoadModule failed!!\n" ) ;
iState = STATE_NOTVALID ;
}
else
{
iState = STATE_VALIDATED ;
}
}
// Set the extra data value for pImage to the state of the symbol
// load.
if ( STATE_VALIDATED == iState )
{
pImage->SetExtraData ( TRUE ) ;
}
else
{
pImage->SetExtraData ( FALSE ) ;
}
// Put this item into the array.
m_cDataArray.Add ( pImage ) ;
// Does this add modify the document?
if ( TRUE == bModifiesDoc )
{
SetModifiedFlag ( ) ;
}
CCrashFinderApp * pApp = (CCrashFinderApp*)AfxGetApp ( ) ;
ASSERT ( NULL != pApp ) ;
// Put the string into the tree.
HTREEITEM hItem = m_pcTreeControl->InsertItem ( pApp->ShowFullPaths ( )
? pImage->GetFullName ( )
: pImage->GetName ( ) ,
iState ,
iState ) ;
ASSERT ( NULL != hItem ) ;
// Put a pointer to the item in the extra data. This way it is easy
// to figure out which item we are supposed to jump to when things
// in the view change.
bRet = m_pcTreeControl->SetItemData ( hItem , (DWORD)pImage ) ;
ASSERT ( bRet ) ;
// Force the item to be selected.
bRet = m_pcTreeControl->SelectItem ( hItem ) ;
return ( bRet ) ;
}