/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// #include #include #include #include #if defined(EA_PLATFORM_WIN32) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) // The following only works on Win32 and not Win64. EA_DISABLE_ALL_VC_WARNINGS() #include #include #include EA_RESTORE_ALL_VC_WARNINGS() EA_DISABLE_VC_WARNING(4740) // flow in or out of inline asm code suppresses global optimization #ifdef EA_COMPILER_MSVC #pragma comment(lib, "dbghelp.lib") #pragma comment(lib, "psapi.lib") #endif typedef BOOL (__stdcall *SYMINITIALIZE)(HANDLE, LPSTR, BOOL); typedef BOOL (__stdcall *SYMCLEANUP)(HANDLE); typedef BOOL (__stdcall *STACKWALK)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID,PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE,PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE); typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESS)(HANDLE, DWORD); typedef DWORD (__stdcall *SYMGETMODULEBASE)(HANDLE, DWORD); typedef BOOL (__stdcall *SYMGETSYMFROMADDR)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL); typedef BOOL (__stdcall *SYMGETLINEFROMADDR)(HANDLE, DWORD, PDWORD, PIMAGEHLP_LINE); namespace // We construct an anonymous namespace because doing so keeps the definitions within it local to this module. { struct Win32DbgHelp { HMODULE mhDbgHelp; bool mbSymInitialized; SYMINITIALIZE mpSymInitialize; SYMCLEANUP mpSymCleanup; STACKWALK mpStackWalk; SYMFUNCTIONTABLEACCESS mpSymFunctionTableAccess; SYMGETMODULEBASE mpSymGetModuleBase; SYMGETSYMFROMADDR mpSymGetSymFromAddr; SYMGETLINEFROMADDR mpSymGetLineFromAddr; Win32DbgHelp() : mhDbgHelp(0), mbSymInitialized(false), mpSymInitialize(0), mpSymCleanup(0), mpStackWalk(0), mpSymFunctionTableAccess(0), mpSymGetModuleBase(0), mpSymGetSymFromAddr(0), mpSymGetLineFromAddr(0) { // Empty. The initialization is done externally, due to tricky startup/shutdown ordering issues. } ~Win32DbgHelp() { // Empty. The shutdown is done externally, due to tricky startup/shutdown ordering issues. } void Init() { if(!mhDbgHelp) { mhDbgHelp = ::LoadLibraryA("DbgHelp.dll"); if(mhDbgHelp) { mpSymInitialize = (SYMINITIALIZE)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymInitialize"); mpSymCleanup = (SYMCLEANUP)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymCleanup"); mpStackWalk = (STACKWALK)(uintptr_t) ::GetProcAddress(mhDbgHelp, "StackWalk"); mpSymFunctionTableAccess = (SYMFUNCTIONTABLEACCESS)(uintptr_t)::GetProcAddress(mhDbgHelp, "SymFunctionTableAccess"); mpSymGetModuleBase = (SYMGETMODULEBASE)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetModuleBase"); mpSymGetSymFromAddr = (SYMGETSYMFROMADDR)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetSymFromAddr"); mpSymGetLineFromAddr = (SYMGETLINEFROMADDR)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetLineFromAddr"); } } } void Shutdown() { if(mhDbgHelp) { if(mbSymInitialized && mpSymCleanup) mpSymCleanup(::GetCurrentProcess()); ::FreeLibrary(mhDbgHelp); } } }; static int sInitCount = 0; static Win32DbgHelp sWin32DbgHelp; } namespace EA { namespace Thread { /* To consider: Enable usage of this below. /////////////////////////////////////////////////////////////////////////////// // IsAddressReadable // static bool IsAddressReadable(const void* pAddress) { bool bPageReadable; MEMORY_BASIC_INFORMATION mbi; if(VirtualQuery(pAddress, &mbi, sizeof(mbi))) { const DWORD flags = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READONLY | PAGE_READWRITE); bPageReadable = (mbi.State == MEM_COMMIT) && ((mbi.Protect & flags) != 0); } else bPageReadable = false; return bPageReadable; } */ /////////////////////////////////////////////////////////////////////////////// // InitCallstack // EATHREADLIB_API void InitCallstack() { if(++sInitCount == 1) sWin32DbgHelp.Init(); } /////////////////////////////////////////////////////////////////////////////// // ShutdownCallstack // EATHREADLIB_API void ShutdownCallstack() { if(--sInitCount == 0) sWin32DbgHelp.Shutdown(); } /////////////////////////////////////////////////////////////////////////////// // GetCallstack // EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext) { size_t nEntryIndex(0); if(!sWin32DbgHelp.mhDbgHelp) sWin32DbgHelp.Init(); if(sWin32DbgHelp.mpStackWalk) { CONTEXT context; memset(&context, 0, sizeof(context)); context.ContextFlags = CONTEXT_CONTROL; if(pContext) { context.Eip = pContext->mEIP; context.Esp = pContext->mESP; context.Ebp = pContext->mEBP; } else { // RtlCaptureStackBackTrace can only generate stack traces on Win32 when the stack frame contains frame // pointers. This only a limitation on 32-bit Windows and is controlled by the following compilers switches. // // /Oy : removes frame-pointers // /Oy- : emits frame-pointers // // The language is wierd here because Microsoft refers it as enabling/disabling an performance optimization. // https://docs.microsoft.com/en-us/cpp/build/reference/oy-frame-pointer-omission?view=vs-2017 // // EATHREAD_WIN32_FRAME_POINTER_OPTIMIZATION_DISABLED is enabled/disabled based on if the user has requested eaconfig to disable // frame-pointer optimizations (enable frame-pointers). See property: 'eaconfig.disable_framepointer_optimization'. // #ifdef EATHREAD_WIN32_FRAME_POINTER_OPTIMIZATION_DISABLED return RtlCaptureStackBackTrace(1, (ULONG)nReturnAddressArrayCapacity, pReturnAddressArray, NULL); #else // With VC++, EIP is not accessible directly, but we can use an assembly trick to get it. // VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails. __asm{ mov context.Ebp, EBP mov context.Esp, ESP call GetEIP GetEIP: pop context.Eip } #endif } // Initialize the STACKFRAME structure for the first call. This is only // necessary for Intel CPUs, and isn't mentioned in the documentation. STACKFRAME sf; memset(&sf, 0, sizeof(sf)); sf.AddrPC.Offset = context.Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = context.Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = context.Ebp; sf.AddrFrame.Mode = AddrModeFlat; const HANDLE hCurrentProcess = ::GetCurrentProcess(); const HANDLE hCurrentThread = ::GetCurrentThread(); // To consider: We have had some other code which can read the stack with better success // than the DbgHelp stack walk function that we use here. In particular, the DbgHelp // stack walking function doesn't do well unless x86 stack frames are used. for(int nStackIndex = 0; nEntryIndex < (nReturnAddressArrayCapacity - 1); ++nStackIndex) { if(!sWin32DbgHelp.mpStackWalk(IMAGE_FILE_MACHINE_I386, hCurrentProcess, hCurrentThread, &sf, &context, NULL, sWin32DbgHelp.mpSymFunctionTableAccess, sWin32DbgHelp.mpSymGetModuleBase, NULL)) { break; } if(sf.AddrFrame.Offset == 0) // Basic sanity check to make sure the frame is OK. Bail if not. break; // If using the current execution context, then we ignore the first // one because it is the one that is our stack walk function itself. if(pContext || (nStackIndex > 0)) pReturnAddressArray[nEntryIndex++] = ((void*)(uintptr_t)sf.AddrPC.Offset); } } pReturnAddressArray[nEntryIndex] = 0; return nEntryIndex; } /////////////////////////////////////////////////////////////////////////////// // GetCallstackContext // EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* pContext) { #if defined(EA_PLATFORM_WIN32) EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::Context, Eip) == offsetof(CONTEXT, Eip)); EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::Context, SegSs) == offsetof(CONTEXT, SegSs)); #endif context.mEIP = pContext->Eip; context.mESP = pContext->Esp; context.mEBP = pContext->Ebp; } /////////////////////////////////////////////////////////////////////////////// // GetModuleFromAddress // EATHREADLIB_API size_t GetModuleFromAddress(const void* address, char* pModuleName, size_t moduleNameCapacity) { MEMORY_BASIC_INFORMATION mbi; if(VirtualQuery(address, &mbi, sizeof(mbi))) { HMODULE hModule = (HMODULE)mbi.AllocationBase; if(hModule) return GetModuleFileNameA(hModule, pModuleName, (DWORD)moduleNameCapacity); } pModuleName[0] = 0; return 0; } /////////////////////////////////////////////////////////////////////////////// // GetModuleHandleFromAddress // EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* pAddress) { MEMORY_BASIC_INFORMATION mbi; if(VirtualQuery(pAddress, &mbi, sizeof(mbi))) return (ModuleHandle)mbi.AllocationBase; return 0; } /////////////////////////////////////////////////////////////////////////////// // GetThreadIdFromThreadHandle // // This implementation is the same as the one in EAThread. // EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId) { struct THREAD_BASIC_INFORMATION_WIN32 { BOOL ExitStatus; PVOID TebBaseAddress; DWORD UniqueProcessId; DWORD UniqueThreadId; DWORD AffinityMask; DWORD Priority; DWORD BasePriority; }; static HMODULE hKernel32 = NULL; if(!hKernel32) hKernel32 = LoadLibraryA("kernel32.dll"); if(hKernel32) { typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE); static GetThreadIdFunc pGetThreadIdFunc = NULL; if(!pGetThreadIdFunc) pGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId"); if(pGetThreadIdFunc) return pGetThreadIdFunc((HANDLE)threadId); } static HMODULE hNTDLL = NULL; if(!hNTDLL) hNTDLL = LoadLibraryA("ntdll.dll"); if(hNTDLL) { typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG); static NtQueryInformationThreadFunc pNtQueryInformationThread = NULL; if(!pNtQueryInformationThread) pNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread"); if(pNtQueryInformationThread) { THREAD_BASIC_INFORMATION_WIN32 tbi; if(pNtQueryInformationThread((HANDLE)threadId, 0, &tbi, sizeof(tbi), NULL) == 0) return tbi.UniqueThreadId; } } return 0; } /////////////////////////////////////////////////////////////////////////////// // GetCallstackContext // // The threadId is the same thing as the Windows' HANDLE GetCurrentThread() function // and not the same thing as Windows' GetCurrentThreadId function. See the // GetCallstackContextSysThreadId for the latter. // EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId) { if((threadId == (intptr_t)kThreadIdInvalid) || (threadId == (intptr_t)kThreadIdCurrent)) threadId = (intptr_t)::GetCurrentThread(); // GetCurrentThread returns a thread 'pseudohandle' and not a real thread handle. const DWORD sysThreadId = EA::Thread::GetThreadIdFromThreadHandle(threadId); const DWORD sysThreadIdCurrent = ::GetCurrentThreadId(); CONTEXT win32CONTEXT; NT_TIB* pTib; if(sysThreadIdCurrent == sysThreadId) { // With VC++, EIP is not accessible directly, but we can use an assembly trick to get it. // VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails. __asm{ mov win32CONTEXT.Ebp, EBP mov win32CONTEXT.Esp, ESP call GetEIP GetEIP: pop win32CONTEXT.Eip } // Offset 0x18 from the FS segment register gives a pointer to // the thread information block for the current thread __asm { mov eax, fs:[18h] mov pTib, eax } } else { // In this case we are working with a separate thread, so we suspend it // and read information about it and then resume it. ::SuspendThread((HANDLE)threadId); win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS; ::GetThreadContext((HANDLE)threadId, &win32CONTEXT); // TODO: This has not been tested! pTib = *((NT_TIB**)(win32CONTEXT.SegFs * 16 + 18)); ::ResumeThread((HANDLE)threadId); } context.mEBP = (uint32_t)win32CONTEXT.Ebp; context.mESP = (uint32_t)win32CONTEXT.Esp; context.mEIP = (uint32_t)win32CONTEXT.Eip; context.mStackBase = (uintptr_t)pTib->StackBase; context.mStackLimit = (uintptr_t)pTib->StackLimit; context.mStackPointer = (uintptr_t)win32CONTEXT.Esp; return true; } /////////////////////////////////////////////////////////////////////////////// // GetCallstackContextSysThreadId // // A sysThreadId is a Microsoft DWORD thread id, which can be obtained from // the currently running thread via GetCurrentThreadId. It can be obtained from // a Microsoft thread HANDLE via EA::Thread::GetThreadIdFromThreadHandle(); // A DWORD thread id can be converted to a thread HANDLE via the Microsoft OpenThread // system function. // EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId) { bool bReturnValue = true; const DWORD sysThreadIdCurrent = ::GetCurrentThreadId(); CONTEXT win32CONTEXT; if(sysThreadIdCurrent == (DWORD)sysThreadId) { // With VC++, EIP is not accessible directly, but we can use an assembly trick to get it. // VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails. __asm{ mov win32CONTEXT.Ebp, EBP mov win32CONTEXT.Esp, ESP call GetEIP GetEIP: pop win32CONTEXT.Eip } } else { // In this case we are working with a separate thread, so we suspend it // and read information about it and then resume it. HANDLE threadId = ::OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, TRUE, (DWORD)sysThreadId); if(threadId) { ::SuspendThread(threadId); win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; ::GetThreadContext(threadId, &win32CONTEXT); ::ResumeThread(threadId); ::CloseHandle(threadId); } else { memset(&win32CONTEXT, 0, sizeof(win32CONTEXT)); bReturnValue = false; } } context.mEBP = (uint32_t)win32CONTEXT.Ebp; context.mESP = (uint32_t)win32CONTEXT.Esp; context.mEIP = (uint32_t)win32CONTEXT.Eip; //context.mStackBase = (uintptr_t)pTib->StackBase; // To do. (Whoever added mStackBase to CallstackContext forgot to add this code) //context.mStackLimit = (uintptr_t)pTib->StackLimit; //context.mStackPointer = (uintptr_t)win32CONTEXT.Esp; return bReturnValue; } /////////////////////////////////////////////////////////////////////////////// // SetStackBase // EATHREADLIB_API void SetStackBase(void* /*pStackBase*/) { // Nothing to do, as GetStackBase always works on its own. } /////////////////////////////////////////////////////////////////////////////// // GetStackBase // EATHREADLIB_API void* GetStackBase() { CallstackContext context; GetCallstackContext(context, 0); return (void*)context.mStackBase; } /////////////////////////////////////////////////////////////////////////////// // GetStackLimit // EATHREADLIB_API void* GetStackLimit() { CallstackContext context; GetCallstackContext(context, 0); return (void*)context.mStackLimit; // Alternative which returns a slightly different answer: // We return our stack pointer, which is a good approximation of the stack limit of the caller. // void* pStack = NULL; // __asm { mov pStack, ESP}; // return pStack; } } // namespace Thread } // namespace EA #else // Stub out function for WinRT / Windows Phone 8 namespace EA { namespace Thread { EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext) { EA_UNUSED(pContext); EA_UNUSED(pReturnAddressArray); EA_UNUSED(nReturnAddressArrayCapacity); return 0; } } // namespace Thread } // namespace EA EA_RESTORE_VC_WARNING() #endif // defined(EA_PLATFORM_WIN32)