First
This commit is contained in:
@@ -0,0 +1,536 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <EABase/eabase.h>
|
||||
#include <eathread/eathread_callstack.h>
|
||||
#include <eathread/eathread_callstack_context.h>
|
||||
#include <eathread/eathread_storage.h>
|
||||
|
||||
#if defined(EA_PLATFORM_WIN32) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) // The following only works on Win32 and not Win64.
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push, 0)
|
||||
#endif
|
||||
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable: 4740) // flow in or out of inline asm code suppresses global optimization
|
||||
#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
|
||||
|
||||
#endif // defined(EA_PLATFORM_WIN32)
|
||||
|
||||
Reference in New Issue
Block a user