eathread_callstack_win32.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include <EABase/eabase.h>
  5. #include <eathread/eathread_callstack.h>
  6. #include <eathread/eathread_callstack_context.h>
  7. #include <eathread/eathread_storage.h>
  8. #if defined(EA_PLATFORM_WIN32) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) // The following only works on Win32 and not Win64.
  9. EA_DISABLE_ALL_VC_WARNINGS()
  10. #include <Windows.h>
  11. #include <DbgHelp.h>
  12. #include <stdio.h>
  13. EA_RESTORE_ALL_VC_WARNINGS()
  14. EA_DISABLE_VC_WARNING(4740) // flow in or out of inline asm code suppresses global optimization
  15. #ifdef EA_COMPILER_MSVC
  16. #pragma comment(lib, "dbghelp.lib")
  17. #pragma comment(lib, "psapi.lib")
  18. #endif
  19. typedef BOOL (__stdcall *SYMINITIALIZE)(HANDLE, LPSTR, BOOL);
  20. typedef BOOL (__stdcall *SYMCLEANUP)(HANDLE);
  21. typedef BOOL (__stdcall *STACKWALK)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID,PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE,PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE);
  22. typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESS)(HANDLE, DWORD);
  23. typedef DWORD (__stdcall *SYMGETMODULEBASE)(HANDLE, DWORD);
  24. typedef BOOL (__stdcall *SYMGETSYMFROMADDR)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL);
  25. typedef BOOL (__stdcall *SYMGETLINEFROMADDR)(HANDLE, DWORD, PDWORD, PIMAGEHLP_LINE);
  26. namespace // We construct an anonymous namespace because doing so keeps the definitions within it local to this module.
  27. {
  28. struct Win32DbgHelp
  29. {
  30. HMODULE mhDbgHelp;
  31. bool mbSymInitialized;
  32. SYMINITIALIZE mpSymInitialize;
  33. SYMCLEANUP mpSymCleanup;
  34. STACKWALK mpStackWalk;
  35. SYMFUNCTIONTABLEACCESS mpSymFunctionTableAccess;
  36. SYMGETMODULEBASE mpSymGetModuleBase;
  37. SYMGETSYMFROMADDR mpSymGetSymFromAddr;
  38. SYMGETLINEFROMADDR mpSymGetLineFromAddr;
  39. Win32DbgHelp() : mhDbgHelp(0), mbSymInitialized(false), mpSymInitialize(0),
  40. mpSymCleanup(0), mpStackWalk(0), mpSymFunctionTableAccess(0),
  41. mpSymGetModuleBase(0), mpSymGetSymFromAddr(0), mpSymGetLineFromAddr(0)
  42. {
  43. // Empty. The initialization is done externally, due to tricky startup/shutdown ordering issues.
  44. }
  45. ~Win32DbgHelp()
  46. {
  47. // Empty. The shutdown is done externally, due to tricky startup/shutdown ordering issues.
  48. }
  49. void Init()
  50. {
  51. if(!mhDbgHelp)
  52. {
  53. mhDbgHelp = ::LoadLibraryA("DbgHelp.dll");
  54. if(mhDbgHelp)
  55. {
  56. mpSymInitialize = (SYMINITIALIZE)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymInitialize");
  57. mpSymCleanup = (SYMCLEANUP)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymCleanup");
  58. mpStackWalk = (STACKWALK)(uintptr_t) ::GetProcAddress(mhDbgHelp, "StackWalk");
  59. mpSymFunctionTableAccess = (SYMFUNCTIONTABLEACCESS)(uintptr_t)::GetProcAddress(mhDbgHelp, "SymFunctionTableAccess");
  60. mpSymGetModuleBase = (SYMGETMODULEBASE)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetModuleBase");
  61. mpSymGetSymFromAddr = (SYMGETSYMFROMADDR)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetSymFromAddr");
  62. mpSymGetLineFromAddr = (SYMGETLINEFROMADDR)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetLineFromAddr");
  63. }
  64. }
  65. }
  66. void Shutdown()
  67. {
  68. if(mhDbgHelp)
  69. {
  70. if(mbSymInitialized && mpSymCleanup)
  71. mpSymCleanup(::GetCurrentProcess());
  72. ::FreeLibrary(mhDbgHelp);
  73. }
  74. }
  75. };
  76. static int sInitCount = 0;
  77. static Win32DbgHelp sWin32DbgHelp;
  78. }
  79. namespace EA
  80. {
  81. namespace Thread
  82. {
  83. /* To consider: Enable usage of this below.
  84. ///////////////////////////////////////////////////////////////////////////////
  85. // IsAddressReadable
  86. //
  87. static bool IsAddressReadable(const void* pAddress)
  88. {
  89. bool bPageReadable;
  90. MEMORY_BASIC_INFORMATION mbi;
  91. if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
  92. {
  93. const DWORD flags = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READONLY | PAGE_READWRITE);
  94. bPageReadable = (mbi.State == MEM_COMMIT) && ((mbi.Protect & flags) != 0);
  95. }
  96. else
  97. bPageReadable = false;
  98. return bPageReadable;
  99. }
  100. */
  101. ///////////////////////////////////////////////////////////////////////////////
  102. // InitCallstack
  103. //
  104. EATHREADLIB_API void InitCallstack()
  105. {
  106. if(++sInitCount == 1)
  107. sWin32DbgHelp.Init();
  108. }
  109. ///////////////////////////////////////////////////////////////////////////////
  110. // ShutdownCallstack
  111. //
  112. EATHREADLIB_API void ShutdownCallstack()
  113. {
  114. if(--sInitCount == 0)
  115. sWin32DbgHelp.Shutdown();
  116. }
  117. ///////////////////////////////////////////////////////////////////////////////
  118. // GetCallstack
  119. //
  120. EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
  121. {
  122. size_t nEntryIndex(0);
  123. if(!sWin32DbgHelp.mhDbgHelp)
  124. sWin32DbgHelp.Init();
  125. if(sWin32DbgHelp.mpStackWalk)
  126. {
  127. CONTEXT context;
  128. memset(&context, 0, sizeof(context));
  129. context.ContextFlags = CONTEXT_CONTROL;
  130. if(pContext)
  131. {
  132. context.Eip = pContext->mEIP;
  133. context.Esp = pContext->mESP;
  134. context.Ebp = pContext->mEBP;
  135. }
  136. else
  137. {
  138. // RtlCaptureStackBackTrace can only generate stack traces on Win32 when the stack frame contains frame
  139. // pointers. This only a limitation on 32-bit Windows and is controlled by the following compilers switches.
  140. //
  141. // /Oy : removes frame-pointers
  142. // /Oy- : emits frame-pointers
  143. //
  144. // The language is wierd here because Microsoft refers it as enabling/disabling an performance optimization.
  145. // https://docs.microsoft.com/en-us/cpp/build/reference/oy-frame-pointer-omission?view=vs-2017
  146. //
  147. // EATHREAD_WIN32_FRAME_POINTER_OPTIMIZATION_DISABLED is enabled/disabled based on if the user has requested eaconfig to disable
  148. // frame-pointer optimizations (enable frame-pointers). See property: 'eaconfig.disable_framepointer_optimization'.
  149. //
  150. #ifdef EATHREAD_WIN32_FRAME_POINTER_OPTIMIZATION_DISABLED
  151. return RtlCaptureStackBackTrace(1, (ULONG)nReturnAddressArrayCapacity, pReturnAddressArray, NULL);
  152. #else
  153. // With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
  154. // VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
  155. __asm{
  156. mov context.Ebp, EBP
  157. mov context.Esp, ESP
  158. call GetEIP
  159. GetEIP:
  160. pop context.Eip
  161. }
  162. #endif
  163. }
  164. // Initialize the STACKFRAME structure for the first call. This is only
  165. // necessary for Intel CPUs, and isn't mentioned in the documentation.
  166. STACKFRAME sf;
  167. memset(&sf, 0, sizeof(sf));
  168. sf.AddrPC.Offset = context.Eip;
  169. sf.AddrPC.Mode = AddrModeFlat;
  170. sf.AddrStack.Offset = context.Esp;
  171. sf.AddrStack.Mode = AddrModeFlat;
  172. sf.AddrFrame.Offset = context.Ebp;
  173. sf.AddrFrame.Mode = AddrModeFlat;
  174. const HANDLE hCurrentProcess = ::GetCurrentProcess();
  175. const HANDLE hCurrentThread = ::GetCurrentThread();
  176. // To consider: We have had some other code which can read the stack with better success
  177. // than the DbgHelp stack walk function that we use here. In particular, the DbgHelp
  178. // stack walking function doesn't do well unless x86 stack frames are used.
  179. for(int nStackIndex = 0; nEntryIndex < (nReturnAddressArrayCapacity - 1); ++nStackIndex)
  180. {
  181. if(!sWin32DbgHelp.mpStackWalk(IMAGE_FILE_MACHINE_I386, hCurrentProcess, hCurrentThread,
  182. &sf, &context, NULL, sWin32DbgHelp.mpSymFunctionTableAccess,
  183. sWin32DbgHelp.mpSymGetModuleBase, NULL))
  184. {
  185. break;
  186. }
  187. if(sf.AddrFrame.Offset == 0) // Basic sanity check to make sure the frame is OK. Bail if not.
  188. break;
  189. // If using the current execution context, then we ignore the first
  190. // one because it is the one that is our stack walk function itself.
  191. if(pContext || (nStackIndex > 0))
  192. pReturnAddressArray[nEntryIndex++] = ((void*)(uintptr_t)sf.AddrPC.Offset);
  193. }
  194. }
  195. pReturnAddressArray[nEntryIndex] = 0;
  196. return nEntryIndex;
  197. }
  198. ///////////////////////////////////////////////////////////////////////////////
  199. // GetCallstackContext
  200. //
  201. EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* pContext)
  202. {
  203. #if defined(EA_PLATFORM_WIN32)
  204. EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::Context, Eip) == offsetof(CONTEXT, Eip));
  205. EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::Context, SegSs) == offsetof(CONTEXT, SegSs));
  206. #endif
  207. context.mEIP = pContext->Eip;
  208. context.mESP = pContext->Esp;
  209. context.mEBP = pContext->Ebp;
  210. }
  211. ///////////////////////////////////////////////////////////////////////////////
  212. // GetModuleFromAddress
  213. //
  214. EATHREADLIB_API size_t GetModuleFromAddress(const void* address, char* pModuleName, size_t moduleNameCapacity)
  215. {
  216. MEMORY_BASIC_INFORMATION mbi;
  217. if(VirtualQuery(address, &mbi, sizeof(mbi)))
  218. {
  219. HMODULE hModule = (HMODULE)mbi.AllocationBase;
  220. if(hModule)
  221. return GetModuleFileNameA(hModule, pModuleName, (DWORD)moduleNameCapacity);
  222. }
  223. pModuleName[0] = 0;
  224. return 0;
  225. }
  226. ///////////////////////////////////////////////////////////////////////////////
  227. // GetModuleHandleFromAddress
  228. //
  229. EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* pAddress)
  230. {
  231. MEMORY_BASIC_INFORMATION mbi;
  232. if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
  233. return (ModuleHandle)mbi.AllocationBase;
  234. return 0;
  235. }
  236. ///////////////////////////////////////////////////////////////////////////////
  237. // GetThreadIdFromThreadHandle
  238. //
  239. // This implementation is the same as the one in EAThread.
  240. //
  241. EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId)
  242. {
  243. struct THREAD_BASIC_INFORMATION_WIN32
  244. {
  245. BOOL ExitStatus;
  246. PVOID TebBaseAddress;
  247. DWORD UniqueProcessId;
  248. DWORD UniqueThreadId;
  249. DWORD AffinityMask;
  250. DWORD Priority;
  251. DWORD BasePriority;
  252. };
  253. static HMODULE hKernel32 = NULL;
  254. if(!hKernel32)
  255. hKernel32 = LoadLibraryA("kernel32.dll");
  256. if(hKernel32)
  257. {
  258. typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE);
  259. static GetThreadIdFunc pGetThreadIdFunc = NULL;
  260. if(!pGetThreadIdFunc)
  261. pGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId");
  262. if(pGetThreadIdFunc)
  263. return pGetThreadIdFunc((HANDLE)threadId);
  264. }
  265. static HMODULE hNTDLL = NULL;
  266. if(!hNTDLL)
  267. hNTDLL = LoadLibraryA("ntdll.dll");
  268. if(hNTDLL)
  269. {
  270. typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG);
  271. static NtQueryInformationThreadFunc pNtQueryInformationThread = NULL;
  272. if(!pNtQueryInformationThread)
  273. pNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
  274. if(pNtQueryInformationThread)
  275. {
  276. THREAD_BASIC_INFORMATION_WIN32 tbi;
  277. if(pNtQueryInformationThread((HANDLE)threadId, 0, &tbi, sizeof(tbi), NULL) == 0)
  278. return tbi.UniqueThreadId;
  279. }
  280. }
  281. return 0;
  282. }
  283. ///////////////////////////////////////////////////////////////////////////////
  284. // GetCallstackContext
  285. //
  286. // The threadId is the same thing as the Windows' HANDLE GetCurrentThread() function
  287. // and not the same thing as Windows' GetCurrentThreadId function. See the
  288. // GetCallstackContextSysThreadId for the latter.
  289. //
  290. EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId)
  291. {
  292. if((threadId == (intptr_t)kThreadIdInvalid) || (threadId == (intptr_t)kThreadIdCurrent))
  293. threadId = (intptr_t)::GetCurrentThread(); // GetCurrentThread returns a thread 'pseudohandle' and not a real thread handle.
  294. const DWORD sysThreadId = EA::Thread::GetThreadIdFromThreadHandle(threadId);
  295. const DWORD sysThreadIdCurrent = ::GetCurrentThreadId();
  296. CONTEXT win32CONTEXT;
  297. NT_TIB* pTib;
  298. if(sysThreadIdCurrent == sysThreadId)
  299. {
  300. // With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
  301. // VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
  302. __asm{
  303. mov win32CONTEXT.Ebp, EBP
  304. mov win32CONTEXT.Esp, ESP
  305. call GetEIP
  306. GetEIP:
  307. pop win32CONTEXT.Eip
  308. }
  309. // Offset 0x18 from the FS segment register gives a pointer to
  310. // the thread information block for the current thread
  311. __asm {
  312. mov eax, fs:[18h]
  313. mov pTib, eax
  314. }
  315. }
  316. else
  317. {
  318. // In this case we are working with a separate thread, so we suspend it
  319. // and read information about it and then resume it.
  320. ::SuspendThread((HANDLE)threadId);
  321. win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS;
  322. ::GetThreadContext((HANDLE)threadId, &win32CONTEXT);
  323. // TODO: This has not been tested!
  324. pTib = *((NT_TIB**)(win32CONTEXT.SegFs * 16 + 18));
  325. ::ResumeThread((HANDLE)threadId);
  326. }
  327. context.mEBP = (uint32_t)win32CONTEXT.Ebp;
  328. context.mESP = (uint32_t)win32CONTEXT.Esp;
  329. context.mEIP = (uint32_t)win32CONTEXT.Eip;
  330. context.mStackBase = (uintptr_t)pTib->StackBase;
  331. context.mStackLimit = (uintptr_t)pTib->StackLimit;
  332. context.mStackPointer = (uintptr_t)win32CONTEXT.Esp;
  333. return true;
  334. }
  335. ///////////////////////////////////////////////////////////////////////////////
  336. // GetCallstackContextSysThreadId
  337. //
  338. // A sysThreadId is a Microsoft DWORD thread id, which can be obtained from
  339. // the currently running thread via GetCurrentThreadId. It can be obtained from
  340. // a Microsoft thread HANDLE via EA::Thread::GetThreadIdFromThreadHandle();
  341. // A DWORD thread id can be converted to a thread HANDLE via the Microsoft OpenThread
  342. // system function.
  343. //
  344. EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
  345. {
  346. bool bReturnValue = true;
  347. const DWORD sysThreadIdCurrent = ::GetCurrentThreadId();
  348. CONTEXT win32CONTEXT;
  349. if(sysThreadIdCurrent == (DWORD)sysThreadId)
  350. {
  351. // With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
  352. // VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
  353. __asm{
  354. mov win32CONTEXT.Ebp, EBP
  355. mov win32CONTEXT.Esp, ESP
  356. call GetEIP
  357. GetEIP:
  358. pop win32CONTEXT.Eip
  359. }
  360. }
  361. else
  362. {
  363. // In this case we are working with a separate thread, so we suspend it
  364. // and read information about it and then resume it.
  365. HANDLE threadId = ::OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, TRUE, (DWORD)sysThreadId);
  366. if(threadId)
  367. {
  368. ::SuspendThread(threadId);
  369. win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
  370. ::GetThreadContext(threadId, &win32CONTEXT);
  371. ::ResumeThread(threadId);
  372. ::CloseHandle(threadId);
  373. }
  374. else
  375. {
  376. memset(&win32CONTEXT, 0, sizeof(win32CONTEXT));
  377. bReturnValue = false;
  378. }
  379. }
  380. context.mEBP = (uint32_t)win32CONTEXT.Ebp;
  381. context.mESP = (uint32_t)win32CONTEXT.Esp;
  382. context.mEIP = (uint32_t)win32CONTEXT.Eip;
  383. //context.mStackBase = (uintptr_t)pTib->StackBase; // To do. (Whoever added mStackBase to CallstackContext forgot to add this code)
  384. //context.mStackLimit = (uintptr_t)pTib->StackLimit;
  385. //context.mStackPointer = (uintptr_t)win32CONTEXT.Esp;
  386. return bReturnValue;
  387. }
  388. ///////////////////////////////////////////////////////////////////////////////
  389. // SetStackBase
  390. //
  391. EATHREADLIB_API void SetStackBase(void* /*pStackBase*/)
  392. {
  393. // Nothing to do, as GetStackBase always works on its own.
  394. }
  395. ///////////////////////////////////////////////////////////////////////////////
  396. // GetStackBase
  397. //
  398. EATHREADLIB_API void* GetStackBase()
  399. {
  400. CallstackContext context;
  401. GetCallstackContext(context, 0);
  402. return (void*)context.mStackBase;
  403. }
  404. ///////////////////////////////////////////////////////////////////////////////
  405. // GetStackLimit
  406. //
  407. EATHREADLIB_API void* GetStackLimit()
  408. {
  409. CallstackContext context;
  410. GetCallstackContext(context, 0);
  411. return (void*)context.mStackLimit;
  412. // Alternative which returns a slightly different answer:
  413. // We return our stack pointer, which is a good approximation of the stack limit of the caller.
  414. // void* pStack = NULL;
  415. // __asm { mov pStack, ESP};
  416. // return pStack;
  417. }
  418. } // namespace Thread
  419. } // namespace EA
  420. #else // Stub out function for WinRT / Windows Phone 8
  421. namespace EA
  422. {
  423. namespace Thread
  424. {
  425. EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
  426. {
  427. EA_UNUSED(pContext);
  428. EA_UNUSED(pReturnAddressArray);
  429. EA_UNUSED(nReturnAddressArrayCapacity);
  430. return 0;
  431. }
  432. } // namespace Thread
  433. } // namespace EA
  434. EA_RESTORE_VC_WARNING()
  435. #endif // defined(EA_PLATFORM_WIN32)