// Copyright (c) 1997-1999 Microsoft Corporation // // stack backtracing stuff // // 22-Nov-1999 sburns (refactored) #include "headers.hxx" #include // prevent us from calling ASSERT in this file: use RTLASSERT instead #ifdef ASSERT #undef ASSERT #endif // Since we call some of this code from Burnslib::FireAssertionFailure, // we use our own even more private ASSERT #if DBG #define RTLASSERT( exp ) \ if (!(exp)) \ RtlAssert( #exp, __FILE__, __LINE__, NULL ) #else #define RTLASSERT( exp ) #endif // DBG static HMODULE imageHelpDll = 0; // function pointers to be dynamically resolved by the Initialize function. typedef DWORD (*SymSetOptionsFunc)(DWORD); static SymSetOptionsFunc MySymSetOptions = 0; typedef BOOL (*SymInitializeFunc)(HANDLE, PSTR, BOOL); static SymInitializeFunc MySymInitialize = 0; typedef BOOL (*SymCleanupFunc)(HANDLE); static SymCleanupFunc MySymCleanup = 0; typedef BOOL (*SymGetModuleInfoFunc)(HANDLE, DWORD64, PIMAGEHLP_MODULE64); static SymGetModuleInfoFunc MySymGetModuleInfo = 0; typedef BOOL (*SymGetLineFromAddrFunc)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64); static SymGetLineFromAddrFunc MySymGetLineFromAddr = 0; typedef BOOL (*StackWalkFunc)( DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64); static StackWalkFunc MyStackWalk = 0; typedef BOOL (*SymGetSymFromAddrFunc)( HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64); static SymGetSymFromAddrFunc MySymGetSymFromAddr = 0; typedef PVOID (*SymFunctionTableAccess64Func)(HANDLE, DWORD64); static SymFunctionTableAccess64Func MySymFunctionTableAccess64 = 0; typedef DWORD64 (*SymGetModuleBase64Func)(HANDLE, DWORD64); static SymGetModuleBase64Func MySymGetModuleBase64 = 0; namespace Burnslib { namespace StackTrace { // This must be called before any of the other functions in this // namespace void Initialize(); bool IsInitialized() { return imageHelpDll != 0; } } } // namespace Burnslib // Determines the path to the parent folder of the binary from whence this // process was loaded. // // returns 0 on failure. // // Caller needs to free the result with delete[]. char* GetModuleFolderPath() { HRESULT hr = S_OK; char* result = 0; do { result = new char[MAX_PATH + 1]; ::ZeroMemory(result, MAX_PATH + 1); char tempBuf[MAX_PATH + 1] = {0}; DWORD res = ::GetModuleFileNameA(0, tempBuf, MAX_PATH); if (res != 0) { char driveBuf[_MAX_DRIVE] = {0}; char folderBuf[_MAX_DIR] = {0}; _splitpath(tempBuf, driveBuf, folderBuf, 0, 0); char* end1 = 0; hr = StringCchCatExA(result, MAX_PATH, driveBuf, &end1, 0, 0); BREAK_ON_FAILED_HRESULT(hr); RTLASSERT(end1 < result + MAX_PATH); char* end2 = 0; hr = StringCchCatExA(result, MAX_PATH, folderBuf, &end2, 0, 0); BREAK_ON_FAILED_HRESULT(hr); RTLASSERT(end2 < result + MAX_PATH); if (end2 - end1 > 1 && *(end2 - 1) == '\\') { // the folder is not the root folder, which means it also has a // trailing \ which we want to remove *(end2 - 1) = 0; } } else { hr = HRESULT_FROM_WIN32(::GetLastError()); } } while (0); if (FAILED(hr)) { delete[] result; result = 0; } return result; } // Expands an environment variable. Returns 0 on error. Caller must free // the result with delete[] char* ExpandEnvironmentVar(const char* var) { RTLASSERT(var); RTLASSERT(*var); // determine the length of the expanded string DWORD len = ::ExpandEnvironmentStringsA(var, 0, 0); RTLASSERT(len); if (!len) { return 0; } char* result = new char[len + 1]; // REVIEWED-2002/03/14-sburns correct byte count passed ::ZeroMemory(result, len + 1); DWORD len1 = ::ExpandEnvironmentStringsA( var, result, // REVIEWED-2002/03/14-sburns correct character count passed len); RTLASSERT(len1 + 1 == len); if (!len1) { delete[] result; return 0; } return result; } void InitHelper() { // we want to look for symbols first in the folder the app started from, // then on %_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%; // MAX_PATH * 3 for load + sym + alt sym, + 2 for semis, and +1 for null static const size_t PATH_BUF_SIZE = MAX_PATH * 3 + 2 + 1; char* symSearchPath = new char[PATH_BUF_SIZE]; // REVIEWED-2002/03/14-sburns correct byte count passed ::ZeroMemory(symSearchPath, PATH_BUF_SIZE); HRESULT hr = S_OK; do { char* moduleFolderPath = GetModuleFolderPath(); char* end = 0; hr = StringCchCatExA( symSearchPath, // -1 to make sure that there's space for a semi at the end PATH_BUF_SIZE - 1, moduleFolderPath, &end, 0, STRSAFE_IGNORE_NULLS); delete[] moduleFolderPath; BREAK_ON_FAILED_HRESULT(hr); // Since we know there will be space for it, just poke in a semi *end = ';'; char* env = ExpandEnvironmentVar("%_NT_SYMBOL_PATH%"); if (env) { end = 0; hr = StringCchCatExA( symSearchPath, // -1 to make sure that there's space for a semi at the end PATH_BUF_SIZE - 1, env, &end, 0, STRSAFE_IGNORE_NULLS); delete[] env; if (SUCCEEDED(hr)) { // Since we know there will be space for it, just poke in a semi *end = ';'; } else { // even if this part of the path is absent, use the others hr = S_OK; } } env = ExpandEnvironmentVar("%_NT_ALTERNATE_SYMBOL_PATH%"); if (env) { end = 0; // return code unchecked because even if this part of the path is // absent, we will use the others (void) StringCchCatExA( symSearchPath, PATH_BUF_SIZE, env, &end, 0, STRSAFE_IGNORE_NULLS); delete[] env; } } while (0); BOOL succeeded = MySymInitialize( ::GetCurrentProcess(), SUCCEEDED(hr) ? symSearchPath : 0, TRUE); RTLASSERT(succeeded); delete[] symSearchPath; } void Burnslib::StackTrace::Initialize() { RTLASSERT(!IsInitialized()); // load the dbghelp dll -- not the imagehlp dll. The latter is merely a // delayload-enabled wrapper of dbghelp, and in low-resource situations // loading imagehlp will succeed, but the its delayload of dbghelp will // fail, leading to calls to stubs that do nothing. // NTRAID#NTBUG9-572904-2002/03/12-sburns imageHelpDll = static_cast(::LoadLibrary(L"dbghelp.dll")); if (!imageHelpDll) { return; } // resolve the function pointers MySymSetOptions = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymSetOptions")); MySymInitialize = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymInitialize")); MySymCleanup = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymCleanup")); MySymGetModuleInfo = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymGetModuleInfo64")); MySymGetLineFromAddr = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymGetLineFromAddr64")); MyStackWalk = reinterpret_cast( ::GetProcAddress(imageHelpDll, "StackWalk64")); MySymGetSymFromAddr = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymGetSymFromAddr64")); MySymFunctionTableAccess64 = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymFunctionTableAccess64")); MySymGetModuleBase64 = reinterpret_cast( ::GetProcAddress(imageHelpDll, "SymGetModuleBase64")); if ( !MySymSetOptions || !MySymInitialize || !MySymCleanup || !MySymGetModuleInfo || !MySymGetLineFromAddr || !MyStackWalk || !MySymGetSymFromAddr || !MySymFunctionTableAccess64 || !MySymGetModuleBase64) { return; } // Init the stack trace facilities //lint -e(534) we're not interested in the return value. MySymSetOptions( SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES); InitHelper(); } void Burnslib::StackTrace::Cleanup() { if (IsInitialized()) { BOOL succeeded = MySymCleanup(::GetCurrentProcess()); RTLASSERT(succeeded); ::FreeLibrary(imageHelpDll); imageHelpDll = 0; } } // a SEH filter function that walks the stack, and stuffs the offset pointers // into the provided array. DWORD GetStackTraceFilter( DWORD64 stackTrace[], size_t traceMax, CONTEXT* context, size_t levelsToSkip) { RTLASSERT(Burnslib::StackTrace::IsInitialized()); RTLASSERT(MyStackWalk); RTLASSERT(context); // REVIEWED: correct byte count passed ::ZeroMemory(stackTrace, traceMax * sizeof DWORD64); if (!MyStackWalk) { // initialization failed in some way, so do nothing. return EXCEPTION_EXECUTE_HANDLER; } STACKFRAME64 frame; DWORD dwMachineType; // REVIEWED: correct byte count passed ::ZeroMemory(&frame, sizeof frame); #if defined(_M_IX86) dwMachineType = IMAGE_FILE_MACHINE_I386; frame.AddrPC.Offset = context->Eip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = context->Ebp; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Offset = context->Esp; frame.AddrStack.Mode = AddrModeFlat; #elif defined(_M_AMD64) dwMachineType = IMAGE_FILE_MACHINE_AMD64; frame.AddrPC.Offset = context->Rip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = context->Rsp; frame.AddrStack.Mode = AddrModeFlat; #elif defined(_M_IA64) dwMachineType = IMAGE_FILE_MACHINE_IA64; frame.AddrPC.Offset = context->StIIP; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = context->IntSp; frame.AddrStack.Mode = AddrModeFlat; #else #error( "unknown target machine" ); #endif HANDLE process = ::GetCurrentProcess(); HANDLE thread = ::GetCurrentThread(); // On ia64, the context struct can be whacked by StackWalk64 (thanks to // drewb for cluing me in to that subtle point). If the context record is a // pointer to the one gathered from GetExceptionInformation, whacking it is // a Very Bad Thing To Do. Stack corruption results. So in order to get // successive calls to work, we have to copy the struct, and let the copy // get whacked. CONTEXT dupContext; // REVIEWED-2002/03/06-sburns correct byte count passed. ::CopyMemory(&dupContext, context, sizeof dupContext); for (size_t i = 0, top = 0; top < traceMax; ++i) { BOOL result = MyStackWalk( dwMachineType, process, thread, &frame, &dupContext, 0, MySymFunctionTableAccess64, MySymGetModuleBase64, 0); if (!result) { break; } // skip the n most recent frames if (i >= levelsToSkip) { stackTrace[top++] = frame.AddrPC.Offset; } } return EXCEPTION_EXECUTE_HANDLER; } DWORD Burnslib::StackTrace::TraceFilter( DWORD64 stackTrace[], size_t traceMax, CONTEXT* context) { RTLASSERT(stackTrace); RTLASSERT(traceMax); RTLASSERT(context); if (!Burnslib::StackTrace::IsInitialized()) { Burnslib::StackTrace::Initialize(); } return GetStackTraceFilter(stackTrace, traceMax, context, 0); } void Burnslib::StackTrace::Trace(DWORD64 stackTrace[], size_t traceMax) { RTLASSERT(stackTrace); RTLASSERT(traceMax); if (!Burnslib::StackTrace::IsInitialized()) { Burnslib::StackTrace::Initialize(); } // the only way to get the context of a running thread is to raise an // exception.... __try { RaiseException(0, 0, 0, 0); } __except ( GetStackTraceFilter( stackTrace, traceMax, //lint --e(*) GetExceptionInformation is like a compiler intrinsic (GetExceptionInformation())->ContextRecord, // skip the 2 most recent function calls, as those correspond to // this function itself. 2)) { // do nothing in the handler } } // ISSUE-2002/03/06-sburns consider replacing with strsafe function // strncpy that will not overflow the buffer. inline void SafeStrncpy(char* dest, const char* src, size_t bufmax) { ::ZeroMemory(dest, bufmax); strncpy(dest, src, bufmax - 1); } void Burnslib::StackTrace::LookupAddress( DWORD64 traceAddress, char moduleName[], char fullImageName[], char symbolName[], // must be SYMBOL_NAME_MAX bytes DWORD64* displacement, DWORD* line, char fullpath[]) // must be MAX_PATH bytes { if (!Burnslib::StackTrace::IsInitialized()) { Burnslib::StackTrace::Initialize(); } RTLASSERT(traceAddress); HANDLE process = ::GetCurrentProcess(); if (moduleName || fullImageName) { IMAGEHLP_MODULE64 module; // REVIEWED-2002/03/06-sburns correct byte count passed ::ZeroMemory(&module, sizeof module); module.SizeOfStruct = sizeof(module); if (MySymGetModuleInfo(process, traceAddress, &module)) { if (moduleName) { SafeStrncpy(moduleName, module.ModuleName, MODULE_NAME_MAX); } if (fullImageName) { SafeStrncpy( fullImageName, module.LoadedImageName, MAX_PATH); } } } if (symbolName || displacement) { // CODEWORK: use SymFromAddr instead? // +1 for paranoid terminating null BYTE buf[SYMBOL_NAME_MAX + sizeof IMAGEHLP_SYMBOL64 + 1]; // REVIEWED-2002/03/06-sburns correct byte count passed ::ZeroMemory(buf, SYMBOL_NAME_MAX + sizeof IMAGEHLP_SYMBOL64 + 1); IMAGEHLP_SYMBOL64* symbol = reinterpret_cast(buf); symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); symbol->MaxNameLength = SYMBOL_NAME_MAX; if (MySymGetSymFromAddr(process, traceAddress, displacement, symbol)) { if (symbolName) { SafeStrncpy(symbolName, symbol->Name, SYMBOL_NAME_MAX); } } } if (line || fullpath) { DWORD disp2 = 0; IMAGEHLP_LINE64 lineinfo; // REVIEWED-2002/03/06-sburns correct byte count passed ::ZeroMemory(&lineinfo, sizeof lineinfo); lineinfo.SizeOfStruct = sizeof(lineinfo); if (MySymGetLineFromAddr(process, traceAddress, &disp2, &lineinfo)) { // disp2 ?= displacement if (line) { *line = lineinfo.LineNumber; } if (fullpath) { SafeStrncpy(fullpath, lineinfo.FileName, MAX_PATH); } } } } String Burnslib::StackTrace::LookupAddress( DWORD64 traceAddress, const wchar_t* format) { RTLASSERT(traceAddress); RTLASSERT(format); String result; if (!format || !traceAddress) { return result; } char ansiSymbol[Burnslib::StackTrace::SYMBOL_NAME_MAX]; char ansiModule[Burnslib::StackTrace::MODULE_NAME_MAX]; char ansiSource[MAX_PATH]; DWORD64 displacement = 0; DWORD line = 0; // REVIEWED-2002/03/06-sburns correct byte counts passed ::ZeroMemory(ansiSymbol, Burnslib::StackTrace::SYMBOL_NAME_MAX); ::ZeroMemory(ansiModule, Burnslib::StackTrace::MODULE_NAME_MAX); ::ZeroMemory(ansiSource, MAX_PATH); Burnslib::StackTrace::LookupAddress( traceAddress, ansiModule, 0, ansiSymbol, &displacement, &line, ansiSource); String module(ansiModule); String symbol(ansiSymbol); String source(ansiSource); result = String::format( format, module.c_str(), symbol.c_str(), source.c_str(), line); return result; }