eathread_callstack_win32.cpp 16 KB

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