GCallStack::GCallStack(HANDLE hThread, CONTEXT& c) { if (!_bLockInit) // only init the single instance of the CRITICAL_SECTION 1 time for the many instances of GCallStack { InitializeCriticalSection(&_DbgHelpLock); _bLockInit = true; } DWORD imageType = IMAGE_FILE_MACHINE_I386; HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId()); int frameNum = 0; // counts walked frames int MAX_STACK_FRAMES = 7777; // in C# the maximum stack frames imposed by the language is 1000. Arbitrary limit to guarantee no condition of infinate walking in corrupted memory. DWORD offsetFromLine; // tells us line number in the source file #if defined(_LINUX64) || defined(_WIN64) || defined(_IOS) unsigned __int64 offsetFromSymbol; // tells us how far from the symbol we were #else DWORD offsetFromSymbol; // tells us how far from the symbol we were #endif DWORD symOptions; // symbol handler settings IMAGEHLP_SYMBOL *pSym = (IMAGEHLP_SYMBOL *) malloc( IMGSYMLEN + MAXNAMELEN ); GString strStackName(MAXNAMELEN + 512); // undecorated method/function name + Source file and line number IMAGEHLP_MODULE Module; IMAGEHLP_LINE Line; STACKFRAME64 s; // in/out stackframe memset( &s, '\0', sizeof s ); // note: converted code from [std::string symSearchPath] to [GString symSearchPath] so it will compile with the _UNICODE build flag - 8/18/2014 GString symSearchPath; #ifdef _UNICODE wchar_t *tt = 0, *p; tt = new wchar_t[TTBUFLEN]; #else char *tt = 0, *p; tt = new char[TTBUFLEN]; #endif // build symbol search path from: symSearchPath = ""; // current directory if (GetCurrentDirectory(TTBUFLEN, tt)) symSearchPath << tt << "; "; // dir with executable if ( GetModuleFileName( 0, tt, TTBUFLEN ) ) { #ifdef _UNICODE for (p = tt + wcslen(tt) - 1; p >= tt; --p) #else for (p = tt + strlen(tt) - 1; p >= tt; --p) // VC6 does not have a _tcsclen() and we still support VC6 #endif { // locate the rightmost path separator if ( *p == '\\' || *p == '/' || *p == ':' ) break; } // if we found one, p is pointing at it; if not, tt only contains an exe name (no path), and p points before its first byte if ( p != tt ) // path sep found? { if ( *p == ':' ) // we leave colons in place ++ p; *p = '\0'; // eliminate the exe name and last path sep symSearchPath << tt << "; "; } } // environment variable _NT_SYMBOL_PATH GString g("_NT_SYMBOL_PATH"); if (GetEnvironmentVariable(g, tt, TTBUFLEN)) symSearchPath << tt << "; "; // environment variable _NT_ALTERNATE_SYMBOL_PATH g = "_NT_ALTERNATE_SYMBOL_PATH"; if (GetEnvironmentVariable(g, tt, TTBUFLEN)) symSearchPath << tt << "; "; // environment variable SYSTEMROOT g = "SYSTEMROOT"; if (GetEnvironmentVariable(g, tt, TTBUFLEN)) symSearchPath << tt << "; "; if ( symSearchPath.size() > 0 ) // if we added anything, we have a trailing semicolon symSearchPath = symSearchPath.substr( 0, symSearchPath.size() - 1 ); // 8/20/2014 note: In older Windows API's SymInitialize()'s 2nd argument was not defined as "const char *", it was only "char *" // Although "const" was not defined, the API call is "const" in behavior. In newer versions of the Windows API this has been fixed. // In newer versions - SymInitialize's 2nd argument may resolve to either "const char *" OR "const wchar_t *", and in those builds the // GString has a default conversion to the correct string type, however in the older build configurations, GString does not (and should not) // know how to resolve to a "char *" by default, so in that case the preprocessor directive isolates the code needed to convert to "char *" #if defined(_MSC_VER) && _MSC_VER <= 1200 if (!SymInitialize(hProcess, symSearchPath.Buf(), false)) // symSearchPath == (char *) #else if (!SymInitialize(hProcess, symSearchPath, true)) // symSearchPath == (const char *) --OR-- (const wchar_t *) depending on the _UNICODE preprocessor definition #endif { goto tagCleanUp; } symOptions = SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES; symOptions &= ~SYMOPT_UNDNAME; SymSetOptions( symOptions ); enumAndLoadModuleSymbols( hProcess, GetCurrentProcessId() ); // init STACKFRAME for first call, definitions found in ImageHlp.h #ifdef _M_IX86 imageType = IMAGE_FILE_MACHINE_I386; s.AddrPC.Offset = c.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Ebp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Esp; s.AddrStack.Mode = AddrModeFlat; #elif _M_X64 imageType = IMAGE_FILE_MACHINE_AMD64; s.AddrPC.Offset = c.Rip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Rsp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Rsp; s.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 imageType = IMAGE_FILE_MACHINE_IA64; s.AddrPC.Offset = c.StIIP; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.IntSp; s.AddrFrame.Mode = AddrModeFlat; s.AddrBStore.Offset = c.RsBSP; s.AddrBStore.Mode = AddrModeFlat; s.AddrStack.Offset = c.IntSp; s.AddrStack.Mode = AddrModeFlat; #endif 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; // DbgHelp is single threaded, so acquire a lock. EnterCriticalSection(&_DbgHelpLock); while ( frameNum < MAX_STACK_FRAMES ) { // 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. #ifdef _WIN64 if (!StackWalk64(imageType, hProcess, hThread, &s, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) #else if (!StackWalk(imageType, hProcess, hThread, &s, &c, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL)) #endif break; // Maybe it failed, maybe we have finished walking the stack if ( s.AddrPC.Offset != 0 ) { // Most likely a valid stack rame // show procedure info if ( ! SymGetSymFromAddr64( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) ) { break; } else { // UnDecorateSymbolName() to get the Class::Method or function() name in tyhe callstack strStackName.Empty(); UnDecorateSymbolName(pSym->Name, strStackName._str, MAXNAMELEN, UNDNAME_COMPLETE); strStackName.SetLength(strlen(strStackName._str)); // SymGetLineFromAddr() to get the source.cpp and the line number IMAGEHLP_LINE64 Line; if (SymGetLineFromAddr64(hProcess, s.AddrPC.Offset, &offsetFromLine, &Line) != FALSE) { GString g(Line.FileName); // Line.FileName conains the "c:\Full\Path\TO\Source.cpp" // Builds string "Foo::Bar[Source.cpp]@777" strStackName << "[" << g.StartingAt(g.ReverseFind("\\") + 1) << "]@" << Line.LineNumber; } // add the GString to the GStringList, do not add frame 0 because it will always be GException::GSeception where we divided by 0 if (frameNum > 0) _stk += strStackName; } } else { // base reached SetLastError(0); break; } ++frameNum; } LeaveCriticalSection(&_DbgHelpLock); // de-init symbol handler etc. (SymCleanup()) SymCleanup( hProcess ); free( pSym ); tagCleanUp:; delete [] tt; CloseHandle(hProcess); }
//************************************************************************* // Method: GetStackInfo // Description: Gets the call stack for the specified thread // // Parameters: // hThread - the handle to the target thread // threadContext - the context of the target thread // // Return Value: (FrameInfo *) An array containing stack trace data //************************************************************************* SiUtils::SiArray <FrameInfo *> StackWalker::GetStackInfo(HANDLE hThread, CONTEXT &threadContext) { //Clear the frame array of any previous data frameArray.Clear(); DWORD imageType = IMAGE_FILE_MACHINE_I386; HANDLE hProcess = GetCurrentProcess(); 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; std::string symSearchPath; char *tt = 0, *p; STACKFRAME s; // in/out stackframe memset( &s, '\0', sizeof s ); tt = new char[TTBUFLEN]; // build symbol search path from: symSearchPath = ""; // current directory if (GetCurrentDirectory(TTBUFLEN, tt )) symSearchPath += tt + std::string( ";" ); // dir with executable if (GetModuleFileName(0, tt, TTBUFLEN)) { for ( p = tt + strlen( tt ) - 1; p >= tt; -- p ) { // locate the rightmost path separator if ( *p == '\\' || *p == '/' || *p == ':' ) break; } if ( p != tt ) { if ( *p == ':' ) // we leave colons in place ++ p; *p = '\0'; // eliminate the exe name and last path sep symSearchPath += tt + std::string( ";" ); } } // environment variable _NT_SYMBOL_PATH if ( GetEnvironmentVariable( "_NT_SYMBOL_PATH", tt, TTBUFLEN ) ) symSearchPath += tt + std::string( ";" ); // environment variable _NT_ALTERNATE_SYMBOL_PATH if ( GetEnvironmentVariable( "_NT_ALTERNATE_SYMBOL_PATH", tt, TTBUFLEN ) ) symSearchPath += tt + std::string( ";" ); // environment variable SYSTEMROOT if ( GetEnvironmentVariable( "SYSTEMROOT", tt, TTBUFLEN ) ) symSearchPath += tt + std::string( ";" ); 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 (!SymInitialize(hProcess, tt, false )) goto cleanup; // SymGetOptions() symOptions = SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES; symOptions &= ~SYMOPT_UNDNAME; SymSetOptions( symOptions ); // Enumerate modules and tell imagehlp.dll about them. 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. s.AddrPC.Offset = threadContext.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = threadContext.Ebp; s.AddrFrame.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; for ( frameNum = 0; ; ++ frameNum ) { FrameInfo * frameInfo = new FrameInfo(); if (!StackWalk( imageType, hProcess, hThread, &s, &threadContext, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL ) ) break; frameInfo->frameNumber = frameNum; frameInfo->IsWOWFarCall = s.Far; frameInfo->IsVirtualFrame = s.Virtual; frameInfo->Eip = s.AddrPC.Offset; //if 0, the no symbols frameInfo->ReturnAddr = s.AddrReturn.Offset; frameInfo->FramePtr = s.AddrFrame.Offset; frameInfo->StackPtr = 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 ( ! SymGetSymFromAddr( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) ) { /*if ( GetLastError() != 487 ) printf( "SymGetSymFromAddr(): GetLastError() = %lu\n", GetLastError() );*/ } else { // UnDecorateSymbolName() UnDecorateSymbolName( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY ); UnDecorateSymbolName( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE ); strcpy (frameInfo->undecoratedName, undName); strcpy (frameInfo->undecoratedFullName, undFullName); strcpy (frameInfo->signature, pSym->Name ); frameInfo->offsetFromSymbol = offsetFromSymbol; } // show module info (SymGetModuleInfo()) if (SymGetModuleInfo( hProcess, s.AddrPC.Offset, &Module ) ) { strcpy (frameInfo->ModuleName, Module.ModuleName); strcpy (frameInfo->ImageName, Module.ImageName); frameInfo->BaseOfImage = Module.BaseOfImage; } } // we seem to have a valid PC // no return address means no deeper stackframe if ( s.AddrReturn.Offset == 0 ) { // avoid misunderstandings in the printf() following the loop SetLastError( 0 ); break; } this->frameArray.Add (frameInfo); } // for ( frameNum ) cleanup: // de-init symbol handler etc. (SymCleanup()) SymCleanup( hProcess ); free( pSym ); delete [] tt; return frameArray; }
GCallStack::GCallStack(HANDLE hThread, CONTEXT& c) { DWORD imageType = IMAGE_FILE_MACHINE_I386; HANDLE hProcess = GetCurrentProcess(); int frameNum; // counts walked frames #if defined(_LINUX64) || defined(_WIN64) || defined(_IOS) unsigned __int64 offsetFromSymbol; // tells us how far from the symbol we were #else DWORD offsetFromSymbol; // tells us how far from the symbol we were #endif DWORD symOptions; // symbol handler settings IMAGEHLP_SYMBOL *pSym = (IMAGEHLP_SYMBOL *) malloc( IMGSYMLEN + MAXNAMELEN ); char undFullName[MAXNAMELEN]; // undecorated name with all shenanigans IMAGEHLP_MODULE Module; IMAGEHLP_LINE Line; std::string symSearchPath; char *tt = 0, *p; STACKFRAME s; // in/out stackframe memset( &s, '\0', sizeof s ); tt = new char[TTBUFLEN]; // build symbol search path from: symSearchPath = ""; // current directory if ( GetCurrentDirectory( TTBUFLEN, tt ) ) symSearchPath += tt + std::string( ";" ); // dir with executable if ( GetModuleFileName( 0, tt, TTBUFLEN ) ) { for ( p = tt + strlen( tt ) - 1; p >= tt; -- p ) { // locate the rightmost path separator if ( *p == '\\' || *p == '/' || *p == ':' ) break; } // if we found one, p is pointing at it; if not, tt only contains // an exe name (no path), and p points before its first byte if ( p != tt ) // path sep found? { if ( *p == ':' ) // we leave colons in place ++ p; *p = '\0'; // eliminate the exe name and last path sep symSearchPath += tt + std::string( ";" ); } } // environment variable _NT_SYMBOL_PATH if ( GetEnvironmentVariable( "_NT_SYMBOL_PATH", tt, TTBUFLEN ) ) symSearchPath += tt + std::string( ";" ); // environment variable _NT_ALTERNATE_SYMBOL_PATH if ( GetEnvironmentVariable( "_NT_ALTERNATE_SYMBOL_PATH", tt, TTBUFLEN ) ) symSearchPath += tt + std::string( ";" ); // environment variable SYSTEMROOT if ( GetEnvironmentVariable( "SYSTEMROOT", tt, TTBUFLEN ) ) symSearchPath += tt + std::string( ";" ); if ( symSearchPath.size() > 0 ) // if we added anything, we have a trailing semicolon symSearchPath = symSearchPath.substr( 0, symSearchPath.size() - 1 ); // why oh why does SymInitialize() want a writeable string? 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 ( ! SymInitialize( hProcess, tt, false ) ) { goto tagCleanUp; } symOptions = SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES; symOptions &= ~SYMOPT_UNDNAME; SymSetOptions( symOptions ); // SymSetOptions() enumAndLoadModuleSymbols( hProcess, GetCurrentProcessId() ); // init STACKFRAME for first call // Notes: will have to be #ifdef-ed for Alphas; MIPSes are dead anyway, #ifndef _WIN64 s.AddrPC.Offset = c.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Ebp; s.AddrFrame.Mode = AddrModeFlat; #endif 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; for ( frameNum = 0; ;) { // 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 ( ! StackWalk( imageType, hProcess, hThread, &s, &c, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL ) ) break; if ( s.AddrPC.Offset != 0 ) { // we seem to have a valid PC // show procedure info (SymGetSymFromAddr()) if ( ! SymGetSymFromAddr( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) ) { break; } else { // UnDecorateSymbolName() UnDecorateSymbolName( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE ); if (!frameNum && strstr(undFullName, "Exception")) continue; _stk += undFullName; } } // we seem to have a valid PC // no return address means no deeper stackframe if ( s.AddrReturn.Offset == 0 ) { // avoid misunderstandings in the printf() following the loop SetLastError( 0 ); break; } ++frameNum; } // de-init symbol handler etc. (SymCleanup()) SymCleanup( hProcess ); free( pSym ); tagCleanUp:; delete [] tt; }