get callstack programmatically on windows using c++

to get call stack on windows load dbghelp.dll and get functions from it and use StackWalk() function. to get the stack pointer and base pointer I used _asm to access the registers. just call ShowCallStack() in your code and you will see the call_stack_.txt file in your working directory .
/*
write to smart.ram856@gmail.com if you want the code or any doubts on this
*/

#include "stdafx.h"
// use angular brackets later..for blog post iam not able to use angular braces
#include < windows.h >
#include"winbase.h"
#include"Dbghelp.h"
#include"iostream"
#include"vector"

using namespace std;

#define gle (GetLastError())
#define lenof(a) (sizeof(a) / sizeof((a)[0]))
#define MAXNAMELEN 1024 // max name length for found symbols
#define IMGSYMLEN ( sizeof IMAGEHLP_SYMBOL )
#define TTBUFLEN 65536 // for a temp buffer
#define MAX_MODULE_NAME32 255
#define TH32CS_SNAPMODULE 0x00000008
static FILE *fp=NULL;

struct ModuleEntry
{
std::string imageName;
std::string moduleName;
DWORD baseAddress;
DWORD size;
};

typedef struct _MODULEINFO {
LPVOID lpBaseOfDll;
DWORD SizeOfImage;
LPVOID EntryPoint;
} MODULEINFO, *LPMODULEINFO;

static HANDLE hIOMutex= CreateMutex (NULL, FALSE, NULL);

#pragma pack( push, 8 )
typedef struct tagMODULEENTRY32
{
DWORD dwSize;
DWORD th32ModuleID; // This module
DWORD th32ProcessID; // owning process
DWORD GlblcntUsage; // Global usage count on the module
DWORD ProccntUsage; // Module usage count in th32ProcessID's context
BYTE * modBaseAddr; // Base address of module in th32ProcessID's context
DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
HMODULE hModule; // The hModule of this module in th32ProcessID's context
char szModule[MAX_MODULE_NAME32 + 1];
char szExePath[MAX_PATH];
}MODULEENTRY32;
typedef MODULEENTRY32 * PMODULEENTRY32;
typedef MODULEENTRY32 * LPMODULEENTRY32;
#pragma pack( pop )

// CreateToolhelp32Snapshot()
typedef HANDLE (__stdcall *tCT32S)( DWORD dwFlags, DWORD th32ProcessID );
// Module32First()
typedef BOOL (__stdcall *tM32F)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );
// Module32Next()
typedef BOOL (__stdcall *tM32N)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );

typedef vector<> ModuleList ;
typedef ModuleList::iterator ModuleListIter ;

//these are for stacktrace functions

// SymCleanup()
typedef BOOL (__stdcall *tSC)( HANDLE hProcess );
tSC pSC = NULL;

// SymFunctionTableAccess()
typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD AddrBase );
tSFTA pSFTA = NULL;

// SymGetLineFromAddr()
typedef BOOL (__stdcall *tSGLFA)( HANDLE hProcess, DWORD dwAddr,
PDWORD pdwDisplacement, PIMAGEHLP_LINE Line );
tSGLFA pSGLFA = NULL;

// SymGetModuleBase()
typedef DWORD (__stdcall *tSGMB)( HANDLE hProcess, DWORD dwAddr );
tSGMB pSGMB = NULL;

// SymGetModuleInfo()
typedef BOOL (__stdcall *tSGMI)( HANDLE hProcess, DWORD dwAddr, PIMAGEHLP_MODULE ModuleInfo );
tSGMI pSGMI = NULL;

// SymGetOptions()
typedef DWORD (__stdcall *tSGO)( VOID );
tSGO pSGO = NULL;

// SymGetSymFromAddr()
typedef BOOL (__stdcall *tSGSFA)( HANDLE hProcess, DWORD dwAddr,
PDWORD pdwDisplacement, PIMAGEHLP_SYMBOL Symbol );
tSGSFA pSGSFA = NULL;

// SymInitialize()
typedef BOOL (__stdcall *tSI)( HANDLE hProcess, PSTR UserSearchPath, BOOL fInvadeProcess );
tSI pSI = NULL;

// SymLoadModule()
typedef DWORD (__stdcall *tSLM)( HANDLE hProcess, HANDLE hFile,
PSTR ImageName, PSTR ModuleName, DWORD BaseOfDll, DWORD SizeOfDll );
tSLM pSLM = NULL;

// SymSetOptions()
typedef DWORD (__stdcall *tSSO)( DWORD SymOptions );
tSSO pSSO = NULL;

// StackWalk()
typedef BOOL (__stdcall *tSW)( DWORD MachineType, HANDLE hProcess,
HANDLE hThread, LPSTACKFRAME StackFrame, PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE TranslateAddress );
tSW pSW = NULL;

// UnDecorateSymbolName()
typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName,
DWORD UndecoratedLength, DWORD Flags );
tUDSN pUDSN = NULL;

int threadAbortFlag = 0;
HANDLE hTapTapTap = NULL;


int init();


void ShowStack(HANDLE hThread, CONTEXT& c ); // dump a stack trace
void ShowCallStack(); //which calls the ShowCallStack()
DWORD Filter( EXCEPTION_POINTERS *ep );
void enumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid );
bool fillModuleList( ModuleList& modules, DWORD pid, HANDLE hProcess );
bool fillModuleListTH32( ModuleList& modules, DWORD pid );
bool fillModuleListPSAPI( ModuleList& modules, DWORD pid, HANDLE hProcess );




static int init()
{
// load the imagehlp.dll and its functions to get stacktraces.
hImagehlpDll = LoadLibrary(L"dbghelp.dll" );
if ( hImagehlpDll == NULL )
{
printf( "LoadLibrary( imagehlp.dll ) failed" );
return 1;
}
pSC = (tSC) GetProcAddress( hImagehlpDll, "SymCleanup" );
pSFTA = (tSFTA) GetProcAddress( hImagehlpDll, "SymFunctionTableAccess" );
pSGLFA = (tSGLFA) GetProcAddress( hImagehlpDll, "SymGetLineFromAddr" );
pSGMB = (tSGMB) GetProcAddress( hImagehlpDll, "SymGetModuleBase" );
pSGMI = (tSGMI) GetProcAddress( hImagehlpDll, "SymGetModuleInfo" );
pSGO = (tSGO) GetProcAddress( hImagehlpDll, "SymGetOptions" );
pSGSFA = (tSGSFA) GetProcAddress( hImagehlpDll, "SymGetSymFromAddr" );
pSI = (tSI) GetProcAddress( hImagehlpDll, "SymInitialize" );
pSSO = (tSSO) GetProcAddress( hImagehlpDll, "SymSetOptions" );
pSW = (tSW) GetProcAddress( hImagehlpDll, "StackWalk" );
pUDSN = (tUDSN) GetProcAddress( hImagehlpDll, "UnDecorateSymbolName" );
pSLM = (tSLM) GetProcAddress( hImagehlpDll, "SymLoadModule" );

if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||
pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||
pSW == NULL || pUDSN == NULL || pSLM == NULL )
{
puts( "GetProcAddress(): some required function not found." );
FreeLibrary( hImagehlpDll );
return 1;
}

}



void ShowCallStack() {
init();
WaitForSingleObject( hIOMutex, INFINITE );
CONTEXT c;
static int file_open =0;
if(file_open == 0) {
char *s=(char *)malloc(30);;
sprintf(s,"call_stack_%d.txt",GetCurrentProcessId());
fp=fopen(s,"a+");
if(fp == NULL) fprintf(stdout,"cannot create file ");
file_open ++;
}

memset( &c, '\0', sizeof c );
c.ContextFlags = CONTEXT_FULL;
HANDLE hThread=GetCurrentThread();
if ( ! GetThreadContext( hThread, &c ) )
{
printf( "GetThreadContext(): gle = %lu\n", gle );
}
ShowStack(hThread, c );
ReleaseMutex( hIOMutex);
}
void ShowStack(HANDLE hThread, CONTEXT& c )
{

// normally, call ImageNtHeader() and use machine info from PE header
DWORD imageType = IMAGE_FILE_MACHINE_I386;
HANDLE hProcess = GetCurrentProcess(); // hProcess normally comes from outside
int frameNum; // counts walked frames
DWORD offsetFromSymbol; // tells us how far from the symbol we were
DWORD symOptions; // symbol handler settings
IMAGEHLP_SYMBOL *pSym = (IMAGEHLP_SYMBOL *) malloc( IMGSYMLEN + MAXNAMELEN );
char undName[MAXNAMELEN]; // undecorated name
char undFullName[MAXNAMELEN]; // undecorated name with all shenanigans
IMAGEHLP_MODULE Module;
IMAGEHLP_LINE Line;
string symSearchPath;
char *tt = 0;
DWORD regEip;
DWORD regEsp;
DWORD regEbp;

STACKFRAME s; // in/out stackframe
memset( &s, '\0', sizeof s );

// NOTE: normally, the exe directory and the current directory should be taken
// from the target process. The current dir would be gotten through injection
// of a remote thread; the exe fir through either ToolHelp32 or PSAPI.

tt = new char[TTBUFLEN]; // this is a _sample_. you can do the error checking yourself.

// build symbol search path from:
symSearchPath = "";

// environment variable _NT_SYMBOL_PATH
if ( GetEnvironmentVariable( L"_NT_SYMBOL_PATH", (LPWSTR)tt, TTBUFLEN ) )
strcat(tt,";");
//strcat(symSearchPath,tt);
symSearchPath += tt ;
// environment variable _NT_ALTERNATE_SYMBOL_PATH
if ( symSearchPath.size() > 0 ) // if we added anything, we have a trailing semicolon
symSearchPath = symSearchPath.substr( 0, symSearchPath.size() - 1 );
strncpy( tt, symSearchPath.c_str(), TTBUFLEN );
tt[TTBUFLEN - 1] = '\0'; // if strncpy() overruns, it doesn't add the null terminator

// init symbol handler stuff (SymInitialize())
if ( ! pSI( hProcess, tt, false ) )
{
printf( "SymInitialize(): gle = %lu\n", gle );
}

// SymGetOptions()
symOptions = pSGO();
symOptions |= SYMOPT_LOAD_LINES;
symOptions &= ~SYMOPT_UNDNAME;
pSSO( symOptions ); // SymSetOptions()

// Enumerate modules and tell imagehlp.dll about them.
// On NT, this is not necessary, but it won't hurt.
enumAndLoadModuleSymbols( hProcess, GetCurrentProcessId() );

// init STACKFRAME for first call
// Notes: AddrModeFlat is just an assumption. I hate VDM debugging.
// Notes: will have to be #ifdef-ed for Alphas; MIPSes are dead anyway,
// and good riddance.
// getting instruction, stack, base pointer from CONTEXT could result in weird result. its very hard to get CONTEXT of running thread. so using _asm.
_asm
{
label:
lea eax, label
mov regEip, eax
mov regEbp, ebp
mov regEsp, esp
}

s.AddrPC.Offset = regEip;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = regEbp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrStack.Offset = regEsp;
s.AddrStack.Mode = AddrModeFlat;


memset( pSym, '\0', IMGSYMLEN + MAXNAMELEN );
pSym->SizeOfStruct = IMGSYMLEN;
pSym->MaxNameLength = MAXNAMELEN;

memset( &Line, '\0', sizeof Line );
Line.SizeOfStruct = sizeof Line;

memset( &Module, '\0', sizeof Module );
Module.SizeOfStruct = sizeof Module;

offsetFromSymbol = 0;

fprintf(fp, "\n--# FV EIP----- RetAddr- FramePtr StackPtr Symbol\n" );

for ( frameNum = 0; ; ++ frameNum )
{
// get next stack frame (StackWalk(), SymFunctionTableAccess(), SymGetModuleBase())
// if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
// assume that either you are done, or that the stack is so hosed that the next
// deeper frame could not be found.
if ( ! pSW( imageType, hProcess, hThread, &s, &c, NULL,
pSFTA, pSGMB, NULL ) )
break;

// display its contents
fprintf(fp, "\n%3d %c%c %08lx %08lx %08lx %08lx ",
frameNum, s.Far? 'F': '.', s.Virtual? 'V': '.',
s.AddrPC.Offset, s.AddrReturn.Offset,
s.AddrFrame.Offset, s.AddrStack.Offset );

if ( s.AddrPC.Offset == 0 )
{
printf( "(-nosymbols- PC == 0)\n" );
}
else
{ // we seem to have a valid PC
// show procedure info (SymGetSymFromAddr())
if ( ! pSGSFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) )
{
if ( gle != 487 )
printf( "SymGetSymFromAddr(): gle = %lu\n", gle );
}
else
{
// UnDecorateSymbolName()
pUDSN( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY );
pUDSN( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE );
fprintf(fp, "%s", undName );
if ( offsetFromSymbol != 0 )
fprintf(fp, " %+ld bytes", (long) offsetFromSymbol );
// putchar( '\n' );
fprintf(fp,"\n");
fprintf(fp, " Sig: %s\n", pSym->Name );
//printf( " Decl: %s\n", undFullName );
}

// show line number info, NT5.0-method (SymGetLineFromAddr())
if ( pSGLFA != NULL )
{ // yes, we have SymGetLineFromAddr()
if ( ! pSGLFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, &Line ) )
{
if ( gle != 487 )
printf( "SymGetLineFromAddr(): gle = %lu\n", gle );
}
else
{
fprintf(fp, " Line: %s(%lu) %+ld bytes\n",
Line.FileName, Line.LineNumber, offsetFromSymbol );
}
} // yes, we have SymGetLineFromAddr()

// show module info (SymGetModuleInfo())
if ( ! pSGMI( hProcess, s.AddrPC.Offset, &Module ) )
{
printf( "SymGetModuleInfo): gle = %lu\n", gle );
}
else
{ // got module info OK
char ty[80];
switch ( Module.SymType )
{
case SymNone:
strcpy( ty, "-nosymbols-" );
break;
case SymCoff:
strcpy( ty, "COFF" );
break;
case SymCv:
strcpy( ty, "CV" );
break;
case SymPdb:
strcpy( ty, "PDB" );
break;
case SymExport:
strcpy( ty, "-exported-" );
break;
case SymDeferred:
strcpy( ty, "-deferred-" );
break;
case SymSym:
strcpy( ty, "SYM" );
break;
default:
_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
break;
}

fprintf(fp, " Mod: %s[%s], base: %08lxh\n",
Module.ModuleName, Module.ImageName, Module.BaseOfImage );
//fprintf(fp, " Sym: type: %s, file: %s\n",ty, Module.LoadedImageName );
} // got module info OK
} // we seem to have a valid PC
if ( s.AddrReturn.Offset == 0 )
{
// avoid misunderstandings in the printf() following the loop
SetLastError( 0 );
break;
}

} // for ( frameNum )
} // end CallStack


void enumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid )
{
ModuleList modules;
ModuleListIter it;
char *img, *mod;

// fill in module list
fillModuleList( modules, pid, hProcess );

for ( it = modules.begin(); it != modules.end(); ++ it )
{
// unfortunately, SymLoadModule() wants writeable strings
img = new char[(*it).imageName.size() + 1];
strcpy( img, (*it).imageName.c_str() );
mod = new char[(*it).moduleName.size() + 1];
strcpy( mod, (*it).moduleName.c_str() );

if ( pSLM( hProcess, 0, img, mod, (*it).baseAddress, (*it).size ) == 0 )
printf( "Error %lu loading symbols for \"%s\"\n",gle, (*it).moduleName.c_str() );
else
printf( "Symbols loaded: \"%s\"\n", (*it).moduleName.c_str() );

delete [] img;
delete [] mod;
}
}


bool fillModuleList( ModuleList& modules, DWORD pid, HANDLE hProcess )
{
// try toolhelp32 first
if ( fillModuleListTH32( modules, pid ) )
return true;
// nope? try psapi, then
return fillModuleListPSAPI( modules, pid, hProcess );
}


bool fillModuleListTH32( ModuleList& modules, DWORD pid )
{
// I think the DLL is called tlhelp32.dll on Win9X, so we try both
//const char *dllname = { L"tlhelp32.dll" };
HINSTANCE hToolhelp;
HINSTANCE kernldll;
tCT32S pCT32S;
tM32F pM32F;
tM32N pM32N;

HANDLE hSnap;
MODULEENTRY32 me = { sizeof me };
bool keepGoing;
ModuleEntry e;
hToolhelp = LoadLibrary(L"tlhelp32.dll");
if ( hToolhelp != NULL)
{
pCT32S = (tCT32S) GetProcAddress( hToolhelp, "CreateToolhelp32Snapshot" );
pM32F = (tM32F) GetProcAddress( hToolhelp, "Module32First" );
pM32N = (tM32N) GetProcAddress( hToolhelp, "Module32Next" );
}
else {
kernldll= LoadLibrary(L"kernel32.dll");
pCT32S = (tCT32S) GetProcAddress(kernldll , "CreateToolhelp32Snapshot" );
pM32F = (tM32F) GetProcAddress( kernldll, "Module32First" );
pM32N = (tM32N) GetProcAddress( kernldll, "Module32Next" );
}
if(pCT32S == NULL && pM32F== NULL && pM32N ==NULL )
return false;
hSnap = pCT32S( TH32CS_SNAPMODULE, pid );
if ( hSnap == (HANDLE) -1 )
return false;

keepGoing = !!pM32F( hSnap, &me );
while ( keepGoing )
{
// here, we have a filled-in MODULEENTRY32
fprintf(fp, "%08lXh %6lu %-15.15s %s\n", me.modBaseAddr, me.modBaseSize, me.szModule, me.szExePath );
e.imageName = me.szExePath;
e.moduleName = me.szModule;
e.baseAddress = (DWORD) me.modBaseAddr;
e.size = me.modBaseSize;
modules.push_back( e );
keepGoing = !!pM32N( hSnap, &me );
}

CloseHandle( hSnap );

FreeLibrary( hToolhelp );

return modules.size() != 0;
}


bool fillModuleListPSAPI( ModuleList& modules, DWORD pid, HANDLE hProcess )
{
// EnumProcessModules()
typedef BOOL (__stdcall *tEPM)( HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded );
// GetModuleFileNameEx()
typedef DWORD (__stdcall *tGMFNE)( HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
// GetModuleBaseName() -- redundant, as GMFNE() has the same prototype, but who cares?
typedef DWORD (__stdcall *tGMBN)( HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
// GetModuleInformation()
typedef BOOL (__stdcall *tGMI)( HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize );

HINSTANCE hPsapi;
tEPM pEPM;
tGMFNE pGMFNE;
tGMBN pGMBN;
tGMI pGMI;

int i;
ModuleEntry e;
DWORD cbNeeded;
MODULEINFO mi;
HMODULE *hMods = 0;
char *tt = 0;

hPsapi = LoadLibrary( L"psapi.dll" );
if ( hPsapi == 0 )
return false;

modules.clear();

pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" );
pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" );
pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" );
pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
if ( pEPM == 0 || pGMFNE == 0 || pGMBN == 0 || pGMI == 0 )
{
// yuck. Some API is missing.
FreeLibrary( hPsapi );
return false;
}

hMods = new HMODULE[TTBUFLEN / sizeof HMODULE];
tt = new char[TTBUFLEN];
// not that this is a sample. Which means I can get away with
// not checking for errors, but you cannot. :)

if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) )
{
printf( "EPM failed, gle = %lu\n", gle );
goto cleanup;
}

if ( cbNeeded > TTBUFLEN )
{
printf( "More than %lu module handles. Huh?\n", lenof( hMods ) );
goto cleanup;
}

for ( i = 0; i <>
{
// for each module, get:
// base address, size
pGMI( hProcess, hMods[i], &mi, sizeof mi );
e.baseAddress = (DWORD) mi.lpBaseOfDll;
e.size = mi.SizeOfImage;
// image file name
tt[0] = '\0';
pGMFNE( hProcess, hMods[i], tt, TTBUFLEN );
e.imageName = tt;
// module name
tt[0] = '\0';
pGMBN( hProcess, hMods[i], tt, TTBUFLEN );
e.moduleName = tt;
fprintf(fp, "%08lXh %6lu %-15.15s %s\n", e.baseAddress,
e.size, e.moduleName.c_str(), e.imageName.c_str() );

modules.push_back( e );
}

cleanup:
if ( hPsapi )
FreeLibrary( hPsapi );
delete [] tt;
delete [] hMods;

return modules.size() != 0;
}

No comments: