aboutsummaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/Kernel/OVR_DebugHelp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVR/Src/Kernel/OVR_DebugHelp.cpp')
-rw-r--r--LibOVR/Src/Kernel/OVR_DebugHelp.cpp3926
1 files changed, 3926 insertions, 0 deletions
diff --git a/LibOVR/Src/Kernel/OVR_DebugHelp.cpp b/LibOVR/Src/Kernel/OVR_DebugHelp.cpp
new file mode 100644
index 0000000..fe7b54f
--- /dev/null
+++ b/LibOVR/Src/Kernel/OVR_DebugHelp.cpp
@@ -0,0 +1,3926 @@
+/************************************************************************************
+
+Filename : ExceptionHandler.cpp
+Content : Platform-independent exception handling interface
+Created : October 6, 2014
+
+Copyright : Copyright 2014 Oculus VR, LLC. All Rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+************************************************************************************/
+
+
+#include "OVR_DebugHelp.h"
+#include "OVR_Types.h"
+#include "OVR_UTF8Util.h"
+#include "../OVR_CAPI.h"
+#include "../OVR_CAPI_Keys.h"
+#include "../CAPI/CAPI_HMDState.h"
+#include <stdlib.h>
+
+#if defined(OVR_OS_WIN32) || defined(OVR_OS_WIN64)
+ #pragma warning(push, 0)
+ #include <Windows.h>
+ #include <WinNT.h>
+ #include <DbgHelp.h>
+ #include <WinVer.h>
+ #include <share.h>
+ #include <Psapi.h>
+ #include <TlHelp32.h>
+ #include <comutil.h>
+ #include <Wbemcli.h>
+ #include <Wbemidl.h>
+ #include <ShlObj.h>
+ #include <ObjBase.h>
+ #pragma warning(pop)
+
+ #pragma comment(lib, "Psapi.lib") // To consider: It may be a problem to statically link to these libraries if the application at runtime intends to dynamically
+ #pragma comment(lib, "ole32.lib") // link to a different version of the same library, and we are statically linked into that application instead of being a DLL.
+ #pragma comment(lib, "shell32.lib")
+
+ // NtQueryInformation and THREAD_BASIC_INFORMATION are undocumented but frequently needed for digging into thread information.
+ typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG);
+
+ struct THREAD_BASIC_INFORMATION
+ {
+ LONG ExitStatus;
+ PVOID TebBaseAddress;
+ PVOID UniqueProcessId;
+ PVOID UniqueThreadId;
+ PVOID Priority;
+ PVOID BasePriority;
+ };
+
+ #ifndef UNW_FLAG_NHANDLER // Older Windows SDKs don't support this.
+ #define UNW_FLAG_NHANDLER 0
+ #endif
+
+#elif defined(OVR_OS_MAC)
+ #include <unistd.h>
+ #include <sys/sysctl.h>
+ #include <sys/utsname.h>
+ #include <sys/types.h>
+ #include <sys/mman.h>
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <pthread.h>
+ #include <mach/mach.h>
+ #include <mach/mach_error.h>
+ #include <mach/thread_status.h>
+ #include <mach/exception.h>
+ #include <mach/task.h>
+ #include <mach/thread_act.h>
+ #include <mach-o/dyld.h>
+ #include <mach-o/dyld_images.h>
+ #include <libproc.h>
+ #include <libgen.h>
+ #include <execinfo.h>
+ #include <cxxabi.h>
+ #include "OVR_mach_exc_OSX.h"
+
+ #if defined(__LP64__)
+ typedef struct mach_header_64 MachHeader;
+ typedef struct segment_command_64 SegmentCommand;
+ typedef struct section_64 Section;
+ #define kLCSegment LC_SEGMENT_64
+ #else
+ typedef struct mach_header MachHeader;
+ typedef struct segment_command SegmentCommand;
+ typedef struct section Section;
+ #define kLCSegment LC_SEGMENT
+ #endif
+
+ extern "C" const struct dyld_all_image_infos* _dyld_get_all_image_infos(); // From libdyld.dylib
+
+#elif defined(OVR_OS_UNIX)
+ #include <unistd.h>
+ #include <sys/sysctl.h>
+ #include <sys/utsname.h>
+ #include <sys/types.h>
+ #include <sys/ptrace.h>
+ #include <sys/wait.h>
+ #include <sys/mman.h>
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <pthread.h>
+ #include <libgen.h>
+ #include <execinfo.h>
+ #include <cxxabi.h>
+ //#include <libunwind.h> // Can't use this until we can ensure that we have an installed version of it.
+#endif
+
+#if !defined(MIN)
+ #define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
+ #define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
+#endif
+
+
+OVR_DISABLE_MSVC_WARNING(4351) // new behavior: elements of array will be default initialized
+OVR_DISABLE_MSVC_WARNING(4996) // This function or variable may be unsafe
+
+
+
+
+#if defined(OVR_OS_APPLE)
+ static OVR::ExceptionHandler* sExceptionHandler = nullptr;
+ const uint32_t sMachCancelMessageType = 0x0ca9ce11; // This is a made-up value of our own choice.
+
+ extern "C"
+ {
+ kern_return_t catch_mach_exception_raise_OVR(mach_port_t /*exceptionPort*/, mach_port_t /*threadSysId*/,
+ mach_port_t /*machTask*/, exception_type_t /*machExceptionType*/,
+ mach_exception_data_t /*machExceptionData*/, mach_msg_type_number_t /*machExceptionDataCount*/)
+ {
+ return KERN_FAILURE;
+ }
+
+ kern_return_t catch_mach_exception_raise_state_OVR(mach_port_t /*exceptionPort*/, exception_type_t /*exceptionType*/,
+ const mach_exception_data_t /*machExceptionData*/, mach_msg_type_number_t /*machExceptionDataCount*/,
+ int* /*pMachExceptionFlavor*/, const thread_state_t /*threadStatePrev*/, mach_msg_type_number_t /*threaStatePrevCount*/,
+ thread_state_t /*threadStateNew*/, mach_msg_type_number_t* /*pThreadStateNewCount*/)
+ {
+ return KERN_FAILURE;
+ }
+
+ kern_return_t catch_mach_exception_raise_state_identity_OVR(mach_port_t exceptionPort, mach_port_t threadSysId, mach_port_t machTask,
+ exception_type_t exceptionType, mach_exception_data_type_t* machExceptionData,
+ mach_msg_type_number_t machExceptionDataCount, int* pMachExceptionFlavor,
+ thread_state_t threadStatePrev, mach_msg_type_number_t threadStatePrevCount,
+ thread_state_t threadStateNew, mach_msg_type_number_t* pThreadStateNewCount)
+ {
+ return sExceptionHandler->HandleMachException(exceptionPort, threadSysId, machTask, exceptionType, machExceptionData,
+ machExceptionDataCount, pMachExceptionFlavor, threadStatePrev, threadStatePrevCount,
+ threadStateNew, pThreadStateNewCount);
+ }
+
+ void* MachHandlerThreadFunctionStatic(void* pExceptionHandlerVoid)
+ {
+ return static_cast<OVR::ExceptionHandler*>(pExceptionHandlerVoid)->MachHandlerThreadFunction();
+ }
+
+ } // extern "C"
+#endif
+
+
+
+
+namespace OVR {
+
+
+void GetInstructionPointer(void*& pInstruction)
+{
+ #if defined(OVR_CC_MSVC)
+ pInstruction = _ReturnAddress();
+ #else // GCC, clang
+ pInstruction = __builtin_return_address(0);
+ #endif
+}
+
+
+static size_t SprintfAddress(char* threadHandleStr, size_t threadHandleStrCapacity, const void* pAddress)
+{
+ #if defined(OVR_CC_MSVC)
+ #if (OVR_PTR_SIZE >= 8)
+ return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "0x%016I64x", pAddress); // e.g. 0x0123456789abcdef
+ #else
+ return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "0x%08x", pAddress); // e.g. 0x89abcdef
+ #endif
+ #else
+ #if (OVR_PTR_SIZE >= 8)
+ return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "%016llx", pAddress); // e.g. 0x0123456789abcdef
+ #else
+ return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "%08x", pAddress); // e.g. 0x89abcdef
+ #endif
+ #endif
+}
+
+
+static size_t SprintfThreadHandle(char* threadHandleStr, size_t threadHandleStrCapacity, const ThreadHandle& threadHandle)
+{
+ return SprintfAddress(threadHandleStr, threadHandleStrCapacity, threadHandle);
+}
+
+
+static size_t SprintfThreadSysId(char* threadSysIdStr, size_t threadSysIdStrCapacity, const ThreadSysId& threadSysId)
+{
+ #if defined(OVR_CC_MSVC) // Somebody could conceivably use VC++ with a different standard library that supports %ll. And VS2012+ also support %ll.
+ return OVR_snprintf(threadSysIdStr, threadSysIdStrCapacity, "%I64u", (uint64_t)threadSysId);
+ #else
+ return OVR_snprintf(threadSysIdStr, threadSysIdStrCapacity, "%llu", (uint64_t)threadSysId);
+ #endif
+}
+
+
+
+
+
+void GetThreadStackBounds(void*& pStackBase, void*& pStackLimit, ThreadHandle threadHandle)
+{
+ #if defined(OVR_OS_WIN64) || defined(OVR_OS_WIN32) || (defined(OVR_OS_MS) && defined(OVR_OS_CONSOLE))
+ ThreadSysId threadSysIdCurrent = (ThreadSysId)GetCurrentThreadId();
+ ThreadSysId threadSysId;
+ NT_TIB* pTIB = nullptr;
+
+ if(threadHandle == OVR_THREADHANDLE_INVALID)
+ threadSysId = threadSysIdCurrent;
+ else
+ threadSysId = ConvertThreadHandleToThreadSysId(threadHandle);
+
+ if(threadSysId == threadSysIdCurrent)
+ {
+ #if (OVR_PTR_SIZE == 4)
+ // Need to use __asm__("movl %%fs:0x18, %0" : "=r" (pTIB) : : ); under gcc/clang.
+ __asm {
+ mov eax, fs:[18h]
+ mov pTIB, eax
+ }
+ #else
+ pTIB = (NT_TIB*)NtCurrentTeb();
+ #endif
+ }
+ else
+ {
+ #if (OVR_PTR_SIZE == 4)
+ // It turns out we don't need to suspend the thread when getting SegFs/SegGS, as that's
+ // constant per thread and doesn't require the thread to be suspended.
+ //SuspendThread((HANDLE)threadHandle);
+ CONTEXT context;
+ memset(&context, 0, sizeof(context));
+ context.ContextFlags = CONTEXT_SEGMENTS;
+ GetThreadContext((HANDLE)threadHandle, &context); // Requires THREAD_QUERY_INFORMATION privileges.
+
+ LDT_ENTRY ldtEntry;
+ if(GetThreadSelectorEntry(threadHandle, context.SegFs, &ldtEntry)) // Requires THREAD_QUERY_INFORMATION
+ pTIB = (NT_TIB*)((ldtEntry.HighWord.Bits.BaseHi << 24 ) | (ldtEntry.HighWord.Bits.BaseMid << 16) | ldtEntry.BaseLow);
+
+ //ResumeThread((HANDLE)threadHandle);
+ #else
+ // We cannot use GetThreadSelectorEntry or Wow64GetThreadSelectorEntry on Win64.
+ // We need to read the SegGs qword at offset 0x30. We can't use pTIB = (NT_TIB*)__readgsqword(0x30) because that reads only the current setGs offset.
+ // mov rax, qword ptr gs:[30h]
+ // mov qword ptr [pTIB],rax
+ // In the meantime we rely on the NtQueryInformationThread function.
+
+ static NtQueryInformationThreadFunc spNtQueryInformationThread = nullptr;
+
+ if(!spNtQueryInformationThread)
+ {
+ HMODULE hNTDLL = GetModuleHandleA("ntdll.dll");
+ spNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
+ }
+
+ if(spNtQueryInformationThread)
+ {
+ THREAD_BASIC_INFORMATION tbi;
+
+ memset(&tbi, 0, sizeof(tbi));
+ LONG result = spNtQueryInformationThread(threadHandle, 0, &tbi, sizeof(tbi), nullptr); // Requires THREAD_QUERY_INFORMATION privileges
+ if(result == 0)
+ pTIB = (NT_TIB*)tbi.TebBaseAddress;
+ }
+ #endif
+ }
+
+ if(pTIB)
+ {
+ pStackBase = (void*)pTIB->StackBase;
+ pStackLimit = (void*)pTIB->StackLimit;
+ }
+ else
+ {
+ pStackBase = nullptr;
+ pStackLimit = nullptr;
+ }
+
+ #elif defined(OVR_OS_APPLE)
+ if(!threadHandle)
+ threadHandle = pthread_self();
+
+ pStackBase = pthread_get_stackaddr_np((pthread_t)threadHandle);
+ size_t stackSize = pthread_get_stacksize_np((pthread_t)threadHandle);
+ pStackLimit = (void*)((size_t)pStackBase - stackSize);
+
+ #elif defined(OVR_OS_UNIX)
+ pStackBase = nullptr;
+ pStackLimit = nullptr;
+
+ pthread_attr_t threadAttr;
+ pthread_attr_init(&threadAttr);
+
+ #if defined(OVR_OS_LINUX)
+ int result = pthread_getattr_np((pthread_t)threadHandle, &threadAttr);
+ #else
+ int result = pthread_attr_get_np((pthread_t)threadHandle, &threadAttr);
+ #endif
+
+ if(result == 0)
+ {
+ size_t stackSize = 0;
+ result = pthread_attr_getstack(&threadAttr, &pStackLimit, &stackSize);
+
+ if(result == 0)
+ pStackBase = (void*)((uintptr_t)pStackLimit + stackSize); // We assume the stack grows downward.
+ }
+
+ #endif
+}
+
+
+bool OVRIsDebuggerPresent()
+{
+ #if defined(OVR_OS_MS)
+ return ::IsDebuggerPresent() != 0;
+
+ #elif defined(OVR_OS_APPLE)
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
+ struct kinfo_proc info;
+ size_t size = sizeof(info);
+
+ info.kp_proc.p_flag = 0;
+ sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0);
+
+ return ((info.kp_proc.p_flag & P_TRACED) != 0);
+
+ #elif (defined(OVR_OS_LINUX) || defined(OVR_OS_BSD)) && !defined(OVR_OS_ANDROID)
+ // This works better than the PT_TRACE_ME approach.
+ // However, it presents a problem:
+ // http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html
+ // When the application calls fork() from a signal handler and any of the
+ // fork handlers registered by pthread_atfork() calls a function that is
+ // not asynch-signal-safe, the behavior is undefined.
+ // We may need to provide two pathways within this function, one of which
+ // doesn't fork and instead uses PT_TRACE_ME.
+ int pid = fork();
+ int status;
+ bool present = false;
+
+ if (pid == -1) // If fork failed...
+ {
+ // perror("fork");
+ }
+ else if (pid == 0) // If we are the child process...
+ {
+ int ppid = getppid();
+
+ #if defined(OVR_OS_LINUX)
+ if (ptrace(PTRACE_ATTACH, ppid, nullptr, nullptr) == 0)
+ #else
+ if (ptrace(PT_ATTACH, ppid, nullptr, nullptr) == 0)
+ #endif
+ {
+ waitpid(ppid, nullptr, 0);
+
+ #if defined(OVR_OS_LINUX)
+ ptrace(PTRACE_CONT, getppid(), nullptr, nullptr);
+ ptrace(PTRACE_DETACH, getppid(), nullptr, nullptr);
+ #else
+ ptrace(PT_CONTINUE, getppid(), nullptr, nullptr);
+ ptrace(PT_DETACH, getppid(), nullptr, nullptr);
+ #endif
+ }
+ else
+ {
+ // ptrace failed so the debugger is present.
+ present = true;
+ }
+
+ exit(present ? 1 : 0); // The WEXITSTATUS call below will read this exit value.
+ }
+ else // Else we are the original process.
+ {
+ waitpid(pid, &status, 0);
+ present = WEXITSTATUS(status) ? true : false; // Read the exit value from the child's call to exit.
+ }
+
+ return present;
+
+ #elif defined(PT_TRACE_ME) && !defined(OVR_OS_ANDROID)
+ return (ptrace(PT_TRACE_ME, 0, 1, 0) < 0);
+
+ #else
+ return false;
+ #endif
+}
+
+
+// Exits the process with the given exit code.
+void ExitProcess(intptr_t processReturnValue)
+{
+ exit((int)processReturnValue);
+}
+
+
+void* SafeMMapAlloc(size_t size)
+{
+ #if defined(OVR_OS_MS)
+ return VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // size is rounded up to a page. // Returned memory is 0-filled.
+
+ #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
+ #if !defined(MAP_FAILED)
+ #define MAP_FAILED ((void*)-1)
+ #endif
+
+ void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); // Returned memory is 0-filled.
+ if(result == MAP_FAILED) // mmap returns MAP_FAILED (-1) upon failure.
+ result = nullptr;
+ return result;
+ #endif
+}
+
+
+void SafeMMapFree(const void* memory, size_t size)
+{
+ #if defined(OVR_OS_MS)
+ OVR_UNUSED(size);
+ VirtualFree(const_cast<void*>(memory), 0, MEM_RELEASE);
+
+ #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
+ size_t pageSize = getpagesize();
+ size = (((size + (pageSize - 1)) / pageSize) * pageSize);
+ munmap(const_cast<void*>(memory), size); // Must supply the size to munmap.
+ #endif
+}
+
+
+// Note that we can't just return sizeof(void*) == 8, as we may have the case of a
+// 32 bit app running on a 64 bit operating system.
+static bool Is64BitOS()
+{
+ #if (OVR_PTR_SIZE >= 8)
+ return true;
+
+ #elif defined(OVR_OS_WIN32) || defined(OVR_OS_WIN64)
+ BOOL is64BitOS = FALSE;
+ bool IsWow64ProcessPresent = (GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "IsWow64Process") != nullptr);
+ return (IsWow64ProcessPresent && IsWow64Process(GetCurrentProcess(), &is64BitOS) && is64BitOS);
+
+ #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
+ utsname utsName;
+ memset(&utsName, 0, sizeof(utsName));
+ return (uname(&utsName) == 0) && (strcmp(utsName.machine, "x86_64") == 0);
+
+ #else
+ return false;
+ #endif
+}
+
+
+// The output will always be 0-terminated.
+// Returns the required strlen of the output.
+// Returns (size_t)-1 on failure.
+size_t SpawnShellCommand(const char* shellCommand, char* output, size_t outputCapacity)
+{
+ #if defined(OVR_OS_UNIX) || defined(OVR_OS_APPLE)
+ FILE* pFile = popen(shellCommand, "r");
+
+ if(pFile)
+ {
+ size_t requiredLength = 0;
+ char buffer[256];
+
+ while(fgets(buffer, sizeof(buffer), pFile)) // fgets 0-terminates the buffer.
+ {
+ size_t length = OVR_strlen(buffer);
+ requiredLength += length;
+
+ if(outputCapacity)
+ {
+ OVR_strlcpy(output, buffer, outputCapacity);
+ length = MIN(outputCapacity, length);
+ }
+
+ output += length;
+ outputCapacity -= length;
+ }
+
+ pclose(pFile);
+ return requiredLength;
+ }
+ #else
+ // To do. Properly solving this on Windows requires a bit of code.
+ OVR_UNUSED(shellCommand);
+ OVR_UNUSED(output);
+ OVR_UNUSED(outputCapacity);
+ #endif
+
+ return (size_t)-1;
+}
+
+
+// Retrieves a directory path which ends with a path separator.
+// Returns the required strlen of the path.
+// Guarantees the presence of the directory upon returning true.
+static size_t GetUserDocumentsDirectory(char* directoryPath, size_t directoryPathCapacity)
+{
+ #if defined(OVR_OS_MS)
+ wchar_t pathW[MAX_PATH + 1]; // +1 because we append a path separator.
+ HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, nullptr, SHGFP_TYPE_CURRENT, pathW);
+
+ if(SUCCEEDED(hr))
+ {
+ OVR_UNUSED(directoryPathCapacity);
+
+ intptr_t requiredUTF8Length = OVR::UTF8Util::GetEncodeStringSize(pathW); // Returns required strlen.
+ if(requiredUTF8Length < MAX_PATH) // We need space for a trailing path separator.
+ {
+ OVR::UTF8Util::EncodeString(directoryPath, pathW, -1);
+ OVR::OVR_strlcat(directoryPath, "\\", directoryPathCapacity);
+ }
+
+ return (requiredUTF8Length + 1);
+ }
+
+ #elif defined(OVR_OS_MAC)
+ // This is the same location that Apple puts its OS-generated .crash files.
+ const char* home = getenv("HOME");
+ size_t requiredStrlen = OVR::OVR_snprintf(directoryPath, directoryPathCapacity, "%s/Library/Logs/DiagnosticReports/", home ? home : "/Users/Shared/Logs/DiagnosticReports/");
+ // To do: create the directory if it doesn't already exist.
+ return requiredStrlen;
+
+ #elif defined(OVR_OS_UNIX) || defined(OVR_OS_MAC)
+ const char* home = getenv("HOME");
+ size_t requiredStrlen = OVR::OVR_snprintf(directoryPath, directoryPathCapacity, "%s/Library/", home ? home : "/Users/Shared/");
+ // To do: create the directory if it doesn't already exist.
+ return requiredStrlen;
+ #endif
+
+ return 0;
+}
+
+
+// Retrieves the name of the given thread.
+// To do: Move this to OVR_Threads.h
+bool GetThreadName(OVR::ThreadHandle threadHandle, char* threadName, size_t threadNameCapacity)
+{
+ #if defined(OVR_OS_APPLE) || defined(OVR_OS_LINUX)
+ int result = pthread_getname_np((pthread_t)threadHandle, threadName, threadNameCapacity);
+ if(result == 0)
+ return true;
+ #else
+ // This is not currently possible on Windows, as only the debugger stores the thread name. We could potentially use a vectored
+ // exception handler to catch all thread name exceptions (0x406d1388) and record them in a static list at runtime. To detect
+ // thread exit we could use WMI Win32_ThreadStopTrace. Maintain a list of thread names between these two events.
+ OVR_UNUSED(threadHandle);
+ OVR_UNUSED(threadNameCapacity);
+ #endif
+
+ if(threadNameCapacity)
+ threadName[0] = 0;
+
+ return false;
+}
+
+
+OVR::ThreadSysId ConvertThreadHandleToThreadSysId(OVR::ThreadHandle threadHandle)
+{
+ #if defined(OVR_OS_WIN64)
+ return (OVR::ThreadSysId)::GetThreadId(threadHandle); // Requires THREAD_QUERY_INFORMATION privileges.
+
+ #elif defined(OVR_OS_WIN32)
+ typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE);
+
+ static volatile bool sInitialized = false;
+ static GetThreadIdFunc spGetThreadIdFunc = nullptr;
+ static NtQueryInformationThreadFunc spNtQueryInformationThread = nullptr;
+
+ if(!sInitialized)
+ {
+ HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
+ if(hKernel32)
+ spGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId");
+
+ if(!spGetThreadIdFunc)
+ {
+ HMODULE hNTDLL = GetModuleHandleA("ntdll.dll");
+
+ if(hNTDLL)
+ spNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
+ }
+
+ sInitialized = true;
+ }
+
+ if(spGetThreadIdFunc)
+ return (OVR::ThreadSysId)spGetThreadIdFunc(threadHandle);
+
+ if(spNtQueryInformationThread)
+ {
+ THREAD_BASIC_INFORMATION tbi;
+
+ if(spNtQueryInformationThread(threadHandle, 0, &tbi, sizeof(tbi), nullptr) == 0)
+ return (OVR::ThreadSysId)tbi.UniqueThreadId;
+ }
+
+ return OVR_THREADSYSID_INVALID;
+
+ #elif defined(OVR_OS_APPLE)
+ mach_port_t threadSysId = pthread_mach_thread_np((pthread_t)threadHandle); // OS 10.4 and later.
+ return (ThreadSysId)threadSysId;
+
+ #elif defined(OVR_OS_LINUX)
+
+ // I believe we can usually (though not portably) intepret the pthread_t as a pointer to a struct whose first member is a lwp id.
+ OVR_UNUSED(threadHandle);
+ return OVR_THREADSYSID_INVALID;
+
+ #else
+ OVR_UNUSED(threadHandle);
+ return OVR_THREADSYSID_INVALID;
+ #endif
+}
+
+
+OVR::ThreadHandle ConvertThreadSysIdToThreadHandle(OVR::ThreadSysId threadSysId)
+{
+ #if defined(OVR_OS_MS)
+ // We currently request the given rights because that's what users of this function typically need it for. Ideally there would
+ // be a way to specify the requested rights in order to avoid the problem if we need only a subset of them but can't get it.
+ // The solution we use below to try opening with successively reduced rights will work for our uses here but isn't a good general solution to this.
+ OVR::ThreadHandle threadHandle = ::OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, TRUE, (DWORD)threadSysId);
+
+ if(threadHandle == OVR_THREADHANDLE_INVALID)
+ {
+ threadHandle = ::OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, TRUE, (DWORD)threadSysId);
+
+ if(threadHandle == OVR_THREADHANDLE_INVALID)
+ threadHandle = ::OpenThread(THREAD_QUERY_INFORMATION, TRUE, (DWORD)threadSysId);
+ }
+
+ return threadHandle;
+ #elif defined(OVR_OS_MAC)
+ return (ThreadHandle)pthread_from_mach_thread_np((mach_port_t)threadSysId);
+ #else
+ return (ThreadHandle)threadSysId;
+ #endif
+}
+
+
+void FreeThreadHandle(OVR::ThreadHandle threadHandle)
+{
+ #if defined(OVR_OS_MS)
+ if(threadHandle != OVR_THREADHANDLE_INVALID)
+ ::CloseHandle(threadHandle);
+ #else
+ OVR_UNUSED(threadHandle);
+ #endif
+}
+
+
+OVR::ThreadSysId GetCurrentThreadSysId()
+{
+ #if defined(OVR_OS_MS)
+ return ::GetCurrentThreadId();
+ #elif defined(OVR_OS_APPLE)
+ return (ThreadSysId)mach_thread_self();
+ #else
+ return (ThreadSysId)pthread_self();
+ #endif
+}
+
+
+
+static void GetCurrentProcessFilePath(char* appPath, size_t appPathCapacity)
+{
+ appPath[0] = 0;
+
+ #if defined(OVR_OS_MS)
+ wchar_t pathW[MAX_PATH];
+ GetModuleFileNameW(0, pathW, (DWORD)OVR_ARRAY_COUNT(pathW));
+
+ size_t requiredUTF8Length = (size_t)OVR::UTF8Util::GetEncodeStringSize(pathW); // Returns required strlen.
+
+ if(requiredUTF8Length < appPathCapacity)
+ {
+ OVR::UTF8Util::EncodeString(appPath, pathW, -1);
+ }
+ else
+ {
+ appPath[0] = 0;
+ }
+
+ #elif defined(OVR_OS_APPLE)
+ struct BunderFolder
+ {
+ // Returns true if pStr ends with pFind, case insensitively.
+ // To do: Move OVR_striend to OVRKernel/Std.h
+ static bool OVR_striend(const char* pStr, const char* pFind, size_t strLength = (size_t)-1, size_t findLength = (size_t)-1)
+ {
+ if(strLength == (size_t)-1)
+ strLength = OVR_strlen(pStr);
+ if(findLength == (size_t)-1)
+ findLength = OVR_strlen(pFind);
+ if(strLength >= findLength)
+ return (OVR_stricmp(pStr + strLength - findLength, pFind) == 0);
+ return false;
+ }
+
+ static bool IsBundleFolder(const char* filePath)
+ {
+ // https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/AboutBundles/AboutBundles.html#//apple_ref/doc/uid/10000123i-CH100-SW1
+ static const char* extensionArray[] = { ".app", ".bundle", ".framework", ".plugin", ".kext" };
+
+ for(size_t i = 0; i < OVR_ARRAY_COUNT(extensionArray); i++)
+ {
+ if(OVR_striend(filePath, extensionArray[i]))
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ char appPathTemp[PATH_MAX];
+ uint32_t appPathTempCapacity32 = PATH_MAX;
+ size_t requiredStrlen = appPathCapacity;
+
+ if(_NSGetExecutablePath(appPathTemp, &appPathTempCapacity32) == 0)
+ {
+ char appPathTempReal[PATH_MAX];
+
+ if(realpath(appPathTemp, appPathTempReal)) // If the path is a symbolic link, this converts it to the real path.
+ {
+ // To consider: Enable reading the internal bundle executable path. An application on Mac may in
+ // fact be within a file bundle, which is an private file system within a file. With Objective C
+ // we could use: [[NSWorkspace sharedWorkspace] isFilePackageAtPath:fullPath];
+ bool shouldReadTheBunderPath = false;
+
+ if(shouldReadTheBunderPath)
+ {
+ // We recursively call dirname() until we find .app/.bundle/.plugin as a directory name.
+ OVR_strlcpy(appPathTemp, appPathTempReal, OVR_ARRAY_COUNT(appPathTemp));
+ bool found = BunderFolder::IsBundleFolder(appPathTemp);
+
+ while(!found && OVR_strcmp(appPathTemp, ".") && OVR_strcmp(appPathTemp, "/"))
+ {
+ OVR_strlcpy(appPathTemp, dirname(appPathTemp), OVR_ARRAY_COUNT(appPathTemp));
+ found = BunderFolder::IsBundleFolder(appPathTemp);
+ }
+
+ if(found) // If somewhere above we found a parent bundle container...
+ requiredStrlen = OVR_strlcpy(appPath, appPathTemp, appPathCapacity);
+ else
+ requiredStrlen = OVR_strlcpy(appPath, appPathTempReal, appPathCapacity);
+ }
+ else
+ {
+ requiredStrlen = OVR_strlcpy(appPath, appPathTempReal, appPathCapacity);
+ }
+ }
+ }
+
+ if(requiredStrlen >= appPathCapacity)
+ appPath[0] = '\0';
+
+ #elif defined(OVR_OS_LINUX)
+ ssize_t length = readlink("/proc/self/exe", appPath, appPathCapacity);
+
+ if((length != -1) && ((size_t)length < (appPathCapacity - 1)))
+ {
+ appPath[length] = '\0';
+ }
+ #endif
+}
+
+
+static const char* GetFileNameFromPath(const char* filePath)
+{
+ #if defined(OVR_OS_MS)
+ const char* lastPathSeparator = max(strrchr(filePath, '\\'), strrchr(filePath, '/')); // Microsoft APIs are inconsistent with respect to allowing / as a path separator.
+ #else
+ const char* lastPathSeparator = strrchr(filePath, '/');
+ #endif
+
+ if(lastPathSeparator)
+ return lastPathSeparator + 1;
+
+ return filePath;
+}
+
+
+
+static void FormatDateTime(char* buffer, size_t bufferCapacity, time_t timeValue, bool getDate, bool getTime, bool localDateTime, bool fileNameSafeCharacters = false)
+{
+ char temp[128];
+ const tm* pTime = localDateTime ? localtime(&timeValue) : gmtime(&timeValue);
+
+ if(bufferCapacity)
+ buffer[0] = 0;
+
+ if(getDate)
+ {
+ const char* format = fileNameSafeCharacters ? "%Y-%m-%d" : "%Y/%m/%d";
+ strftime(temp, OVR_ARRAY_COUNT(temp), format, pTime);
+ OVR::OVR_strlcpy(buffer, temp, bufferCapacity);
+ }
+
+ if(getTime)
+ {
+ const char* format = fileNameSafeCharacters ? " %H.%M.%S" : " %H:%M:%S";
+ strftime(temp, OVR_ARRAY_COUNT(temp), (getDate ? format : format + 1), pTime);
+ OVR::OVR_strlcat(buffer, temp, bufferCapacity);
+ }
+}
+
+
+static void GetOSVersionName(char* versionName, size_t versionNameCapacity)
+{
+ #if defined(OVR_OS_MS)
+ const char* name = "unknown";
+
+ OSVERSIONINFOEXW vi;
+ memset(&vi, 0, sizeof(vi));
+ vi.dwOSVersionInfoSize = sizeof(vi);
+
+ if(GetVersionExW((LPOSVERSIONINFOW)&vi))
+ {
+ if(vi.dwMajorVersion >= 7)
+ {
+ // Unknown recent version.
+ }
+ if(vi.dwMajorVersion >= 6)
+ {
+ if(vi.dwMinorVersion >= 4)
+ name = "Windows 10";
+ else if(vi.dwMinorVersion >= 3)
+ {
+ if(vi.wProductType == VER_NT_WORKSTATION)
+ name = "Windows 8.1";
+ else
+ name = "Windows Server 2012 R2";
+ }
+ else if(vi.dwMinorVersion >= 2)
+ {
+ if(vi.wProductType == VER_NT_WORKSTATION)
+ name = "Windows 8";
+ else
+ name = "Windows Server 2012";
+ }
+ else if(vi.dwMinorVersion >= 1)
+ {
+ if(vi.wProductType == VER_NT_WORKSTATION)
+ name = "Windows 7";
+ else
+ name = "Windows Server 2008 R2";
+ }
+ else
+ {
+ if(vi.wProductType == VER_NT_WORKSTATION)
+ name = "Windows Vista";
+ else
+ name = "Windows Server 2008";
+ }
+ }
+ else if(vi.dwMajorVersion >= 5)
+ {
+ if(vi.dwMinorVersion == 0)
+ name = "Windows 2000";
+ else if(vi.dwMinorVersion == 1)
+ name = "Windows XP";
+ else // vi.dwMinorVersion == 2
+ {
+ if(GetSystemMetrics(SM_SERVERR2) != 0)
+ name = "Windows Server 2003 R2";
+ else if(vi.wSuiteMask & VER_SUITE_WH_SERVER)
+ name = "Windows Home Server";
+ if(GetSystemMetrics(SM_SERVERR2) == 0)
+ name = "Windows Server 2003";
+ else
+ name = "Windows XP Professional x64 Edition";
+ }
+ }
+ else
+ name = "Windows 98 or earlier";
+ }
+
+ OVR_strlcpy(versionName, name, versionNameCapacity);
+
+ #elif defined(OVR_OS_UNIX) || defined(OVR_OS_APPLE)
+ utsname utsName;
+ memset(&utsName, 0, sizeof(utsName));
+
+ if(uname(&utsName) == 0)
+ OVR_snprintf(versionName, versionNameCapacity, "%s %s %s %s", utsName.sysname, utsName.release, utsName.version, utsName.machine);
+ else
+ OVR_snprintf(versionName, versionNameCapacity, "Unix");
+ #endif
+}
+
+
+
+
+void CreateException(CreateExceptionType exceptionType)
+{
+ char buffer[1024] = {};
+
+ switch(exceptionType)
+ {
+ case kCETAccessViolation:
+ {
+ int* pNullPtr = reinterpret_cast<int*>((rand() / 2) / RAND_MAX);
+ pNullPtr[0] = 0; // This line should generate an exception.
+ sprintf(buffer, "%p", pNullPtr);
+ break;
+ }
+
+ case kCETDivideByZero:
+ {
+ int smallValue = 1;
+ int largeValue = (1000 * exceptionType);
+ int divByZero = (smallValue / largeValue); // This line should generate a div/0 exception.
+ sprintf(buffer, "%d", divByZero);
+ break;
+ }
+
+ case kCETIllegalInstruction:
+ {
+ #if defined(OVR_CPU_X86) || (defined(OVR_CPU_X86_64) && !defined(OVR_CC_MSVC)) // (if x86) or (if x64 and any computer but VC++)...
+ #if defined(OVR_CC_MSVC)
+ __asm ud2
+ #else // e.g. GCC
+ asm volatile("ud2");
+ #endif
+
+ #elif defined(OVR_CPU_X86_64) && (defined(OVR_OS_MS) && defined(PAGE_EXECUTE_READWRITE))
+ // VC++ for x64 doesn't support inline asm.
+ void* pVoid = _AddressOfReturnAddress();
+ void** ppVoid = reinterpret_cast<void**>(pVoid);
+ void* pReturnAddress = *ppVoid;
+ DWORD dwProtectPrev = 0;
+
+ if(VirtualProtect(pReturnAddress, 2, PAGE_EXECUTE_READWRITE, &dwProtectPrev)) // If we can set the memory to be executable...
+ {
+ // Modify the code we return to.
+ uint8_t asm_ud2[] = { 0x0f, 0x0b };
+ memcpy(pReturnAddress, asm_ud2, sizeof(asm_ud2));
+ VirtualProtect(pReturnAddress, 2, dwProtectPrev, &dwProtectPrev);
+ }
+ else
+ {
+ // To do: Fix this.
+ }
+
+ #else
+ // To do: Fix this.
+ #endif
+
+ break;
+ }
+
+ case kCETStackCorruption:
+ {
+ size_t size = (sizeof(buffer) * 16) - (rand() % 16);
+ char* pOutsizeStack = buffer - ((sizeof(buffer) * 16) + (rand() % 16));
+
+ memset(buffer, 0, size);
+ memset(pOutsizeStack, 0, size); // This line should generate an exception, or an exception will be generated upon return from this function.
+ break;
+ }
+
+ case kCETStackOverflow:
+ {
+ CreateException(exceptionType); // Call ourselves recursively. This line should generate a div/0 exception.
+ sprintf(buffer, "%d", exceptionType);
+ break;
+ }
+
+ case kCETAlignment:
+ {
+ // Not all platforms generate alignment exceptions. Some internally handle it.
+ void* pAligned = malloc(16);
+ char* pMisaligned = (char*)pAligned + 1;
+ uint64_t* pMisaligned64 = reinterpret_cast<uint64_t*>(pMisaligned);
+
+ *pMisaligned64 = 0; // This line should generate an exception.
+ free(pAligned);
+ break;
+ }
+
+ case kCETFPU:
+ // Platforms usually have FPU exceptions disabled. In order to test FPU exceptions we will need to at least
+ // temporarily disable them before executing code here to generate such exceptions.
+ // To do.
+ break;
+
+ case kCETTrap:
+ // To do. This is hardware-specific.
+ break;
+ }
+}
+
+
+
+
+#if defined(OVR_OS_MS)
+ typedef BOOL (WINAPI * StackWalk64Type)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
+ typedef PVOID (WINAPI * SymFunctionTableAccess64Type)(HANDLE hProcess, DWORD64 dwAddr);
+ typedef DWORD64 (WINAPI * SymGetModuleBase64Type)(HANDLE hProcess, DWORD64 dwAddr);
+ typedef DWORD (WINAPI * SymSetOptionsType)(DWORD SymOptions);
+ typedef BOOL (WINAPI * SymInitializeWType)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess);
+ typedef BOOL (WINAPI * SymCleanupType)(HANDLE hProcess);
+ typedef DWORD64 (WINAPI * SymLoadModule64Type)(HANDLE hProcess, HANDLE hFile, PCSTR ImageName, PCSTR ModuleName, DWORD64 BaseOfDll, DWORD SizeOfDll);
+ typedef BOOL (WINAPI * SymFromAddrType)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFO Symbol);
+ typedef BOOL (WINAPI * SymGetLineFromAddr64Type)(HANDLE hProcess, DWORD64 qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64);
+
+ StackWalk64Type pStackWalk64;
+ SymFunctionTableAccess64Type pSymFunctionTableAccess64;
+ SymGetModuleBase64Type pSymGetModuleBase64;
+ SymSetOptionsType pSymSetOptions;
+ SymInitializeWType pSymInitializeW;
+ SymCleanupType pSymCleanup;
+ SymLoadModule64Type pSymLoadModule64;
+ SymFromAddrType pSymFromAddr;
+ SymGetLineFromAddr64Type pSymGetLineFromAddr64;
+#endif
+
+
+
+SymbolLookup::SymbolLookup()
+ : initialized(false),
+ allowMemoryAllocation(true),
+ moduleListUpdated(false),
+ moduleInfoArray(),
+ moduleInfoArraySize(0)
+{
+}
+
+SymbolLookup::~SymbolLookup()
+{
+ Shutdown();
+}
+
+void SymbolLookup::AddSourceCodeDirectory(const char* pDirectory)
+{
+ OVR_UNUSED(pDirectory);
+}
+
+bool SymbolLookup::Initialize()
+{
+ if(!initialized)
+ {
+ #if defined(OVR_OS_MS)
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms679294%28v=vs.85%29.aspx
+ HANDLE hProcess = GetCurrentProcess();
+ HMODULE hDbgHelp = LoadLibraryW(L"DbgHelp.dll"); // It's best if the application supplies a recent version of this.
+
+ if(hDbgHelp)
+ {
+ pStackWalk64 = (StackWalk64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "StackWalk64");
+ pSymFunctionTableAccess64 = (SymFunctionTableAccess64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymFunctionTableAccess64");
+ pSymGetModuleBase64 = (SymGetModuleBase64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymGetModuleBase64");
+ pSymSetOptions = (SymSetOptionsType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymSetOptions");
+ pSymInitializeW = (SymInitializeWType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymInitializeW");
+ pSymCleanup = (SymCleanupType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymCleanup");
+ pSymLoadModule64 = (SymLoadModule64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymLoadModule64");
+ pSymFromAddr = (SymFromAddrType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymFromAddr");
+ pSymGetLineFromAddr64 = (SymGetLineFromAddr64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymGetLineFromAddr64");
+ }
+
+ pSymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
+
+ // To consider: Use a manually created search path:
+ // wchar_t searchPathW[4096]; // Semicolon-separated strings.
+ // The current working directory of the application.
+ // The directory of the application itself (GetModuleFileName).
+ // The _NT_SYMBOL_PATH environment variable.
+ // The _NT_ALTERNATE_SYMBOL_PATH environment variable.
+
+ if(pSymInitializeW(hProcess, nullptr /*searchPathW*/, FALSE))
+ {
+ initialized = true;
+ }
+ #endif
+ }
+
+ return true;
+}
+
+void SymbolLookup::Shutdown()
+{
+ if(initialized)
+ {
+ initialized = false;
+
+ #if defined(OVR_OS_MS)
+ HANDLE hProcess = GetCurrentProcess();
+
+ // SymCleanup should handle this for us.
+ //if(moduleListUpdated)
+ //{
+ // for(size_t i = 0; i < moduleInfoArraySize; i++)
+ // pSymUnloadModule64(hProcess, moduleInfoArray[i].baseAddress);
+ //}
+
+ moduleInfoArraySize = 0;
+
+ pSymCleanup(hProcess);
+ #endif
+ }
+}
+
+
+void SymbolLookup::EnableMemoryAllocation(bool enabled)
+{
+ allowMemoryAllocation = enabled;
+}
+
+
+OVR_DISABLE_MSVC_WARNING(4740) // flow in or out of inline asm code suppresses global optimization
+OVR_DISABLE_MSVC_WARNING(4748) // /GS can not protect parameters and local variables from local buffer overrun because optimizations are disabled in function
+
+
+size_t SymbolLookup::GetBacktrace(void* addressArray[], size_t addressArrayCapacity, size_t skipCount, void* platformThreadContext, OVR::ThreadSysId threadSysIdHelp)
+{
+ #if defined(OVR_OS_WIN64) || (defined(OVR_OS_MS) && defined(OVR_OS_CONSOLE))
+ OVR_UNUSED(threadSysIdHelp);
+
+ if(platformThreadContext == nullptr)
+ return RtlCaptureStackBackTrace(1, (ULONG)addressArrayCapacity, addressArray, nullptr);
+
+ // We need to get the call stack of another thread.
+ size_t frameIndex = 0;
+ CONTEXT context;
+ PRUNTIME_FUNCTION pRuntimeFunction;
+ ULONG64 imageBase = 0;
+ ULONG64 imageBasePrev = 0;
+
+ memcpy(&context, (CONTEXT*)platformThreadContext, sizeof(CONTEXT));
+ context.ContextFlags = CONTEXT_CONTROL;
+
+ if(context.Rip && (frameIndex < addressArrayCapacity))
+ addressArray[frameIndex++] = (void*)(uintptr_t)context.Rip;
+
+ while(context.Rip && (frameIndex < addressArrayCapacity))
+ {
+ imageBasePrev = imageBase;
+ pRuntimeFunction = (PRUNTIME_FUNCTION)RtlLookupFunctionEntry(context.Rip, &imageBase, nullptr);
+
+ if(pRuntimeFunction)
+ {
+ VOID* handlerData = nullptr;
+ ULONG64 establisherFramePointers[2] = { 0, 0 };
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, context.Rip, pRuntimeFunction, &context, &handlerData, establisherFramePointers, nullptr);
+ }
+ else
+ {
+ context.Rip = (ULONG64)(*(PULONG64)context.Rsp);
+ context.Rsp += 8;
+ }
+
+ if(context.Rip && (frameIndex < addressArrayCapacity))
+ {
+ if(skipCount)
+ --skipCount;
+ else
+ addressArray[frameIndex++] = (void*)(uintptr_t)context.Rip;
+ }
+ }
+
+ return frameIndex;
+
+ #elif defined(OVR_OS_WIN32)
+ OVR_UNUSED(threadSysIdHelp);
+
+ size_t frameIndex = 0;
+
+ if(pStackWalk64)
+ {
+ CONTEXT context;
+
+ if(platformThreadContext)
+ {
+ memcpy(&context, platformThreadContext, sizeof(context));
+ context.ContextFlags = CONTEXT_CONTROL;
+ }
+ else
+ {
+ memset(&context, 0, sizeof(context));
+ context.ContextFlags = CONTEXT_CONTROL;
+
+ __asm {
+ mov context.Ebp, EBP
+ mov context.Esp, ESP
+ call GetEIP
+ GetEIP:
+ pop context.Eip
+ }
+ }
+
+ STACKFRAME64 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();
+
+ if(!platformThreadContext) // If we are reading the current thread's call stack then we ignore this current function.
+ skipCount++;
+
+ while(frameIndex < addressArrayCapacity)
+ {
+ if(!pStackWalk64(IMAGE_FILE_MACHINE_I386, hCurrentProcess, hCurrentThread, &sf, &context, nullptr, pSymFunctionTableAccess64, pSymGetModuleBase64, nullptr))
+ break;
+
+ if(sf.AddrFrame.Offset == 0)
+ break;
+
+ if(skipCount)
+ --skipCount;
+ else
+ addressArray[frameIndex++] = ((void*)(uintptr_t)sf.AddrPC.Offset);
+ }
+ }
+
+ return frameIndex;
+
+ #elif defined(OVR_OS_APPLE)
+ struct StackFrame
+ {
+ StackFrame* pParentStackFrame;
+ void* pReturnPC;
+ };
+
+ void* pInstruction;
+ StackFrame* pStackFrame;
+ size_t frameIndex = 0;
+
+ if(platformThreadContext)
+ {
+ #if defined(OVR_CPU_ARM)
+ arm_thread_state_t* pThreadState = (arm_thread_state_t*)platformThreadContext;
+ pStackFrame = (StackFrame*)pThreadState->__fp;
+ pInstruction = (void*) pThreadState->__pc;
+ #define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0x1) == 0)
+ #elif defined(OVR_CPU_X86_64)
+ x86_thread_state_t* pThreadState = (x86_thread_state_t*)platformThreadContext;
+ pInstruction = (void*) pThreadState->uts.ts64.__rip;
+ pStackFrame = (StackFrame*)pThreadState->uts.ts64.__rbp;
+ #define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 0)
+ #elif defined(OVR_CPU_X86)
+ x86_thread_state_t* pThreadState = (x86_thread_state_t*)platformThreadContext;
+ pInstruction = (void*) pThreadState->uts.ts32.__eip;
+ pStackFrame = (StackFrame*)pThreadState->uts.ts32.__ebp;
+ #define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 8)
+ #endif
+
+ if(frameIndex < addressArrayCapacity)
+ addressArray[frameIndex++] = pInstruction;
+ }
+ else // Else get the current values...
+ {
+ pStackFrame = (StackFrame*)__builtin_frame_address(0);
+ GetInstructionPointer(pInstruction);
+ }
+
+ pthread_t threadSelf = pthread_self();
+ void* pCurrentStackBase = pthread_get_stackaddr_np(threadSelf);
+ void* pCurrentStackLimit = (void*)((uintptr_t)pCurrentStackBase - pthread_get_stacksize_np(threadSelf));
+ bool threadIsCurrent = (platformThreadContext == nullptr) || (((void*)pStackFrame > pCurrentStackLimit) && ((void*)pStackFrame <= pCurrentStackBase));
+ StackFrame* pStackBase;
+ StackFrame* pStackLimit;
+
+ if(threadIsCurrent)
+ {
+ pStackBase = (StackFrame*)pCurrentStackBase;
+ pStackLimit = (StackFrame*)pCurrentStackLimit;
+ }
+ else if(threadSysIdHelp)
+ {
+ pthread_t threadHandle = pthread_from_mach_thread_np((mach_port_t)threadSysIdHelp);
+ pStackBase = (StackFrame*)pthread_get_stackaddr_np(threadHandle);
+ pStackLimit = (StackFrame*)((uintptr_t)pStackBase - pthread_get_stacksize_np(threadHandle));
+ }
+ else
+ { // We guess what the limits are.
+ pStackBase = pStackFrame + ((384 * 1024) / sizeof(StackFrame));
+ pStackLimit = pStackFrame - ((384 * 1024) / sizeof(StackFrame));
+ }
+
+ if((frameIndex < addressArrayCapacity) && pStackFrame && FrameIsAligned(pStackFrame))
+ {
+ addressArray[frameIndex++] = pStackFrame->pReturnPC;
+
+ while(pStackFrame && pStackFrame->pReturnPC && (frameIndex < addressArrayCapacity))
+ {
+ pStackFrame = pStackFrame->pParentStackFrame;
+
+ if(pStackFrame && FrameIsAligned(pStackFrame) && pStackFrame->pReturnPC && (pStackFrame > pStackLimit) && (pStackFrame < pStackBase))
+ {
+ if(skipCount)
+ --skipCount;
+ else
+ addressArray[frameIndex++] = pStackFrame->pReturnPC;
+ }
+ else
+ break;
+ }
+ }
+
+ return frameIndex;
+
+ #elif defined(OVR_OS_LINUX) && (defined( __LIBUNWIND__) || defined(LIBUNWIND_AVAIL))
+ // Libunwind-based solution. Requires installation of libunwind package.
+ // Libunwind isn't always safe for threads that are in signal handlers.
+ // An approach to get the callstack of another thread is to use signal injection into the target thread.
+
+ OVR_UNUSED(platformThreadContext);
+ OVR_UNUSED(threadSysIdHelp);
+
+ size_t frameIndex = 0;
+ unw_cursor_t cursor;
+ unw_context_t uc;
+ unw_word_t ip, sp;
+
+ unw_getcontext(&uc); // This gets the current thread's context. We could alternatively initialize another thread's context with it.
+ unw_init_local(&cursor, &uc);
+
+ while((unw_step(&cursor) > 0) && (frameIndex < addressArrayCapacity))
+ {
+ // We can get the function name here too on some platforms with unw_get_proc_info() and unw_get_proc_name().
+
+ if(skipCount)
+ --skipCount;
+ else
+ {
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ addressArray[frameIndex++] = (void*)ip;
+ }
+ }
+
+ return frameIndex;
+ #else
+ OVR_UNUSED(addressArray);
+ OVR_UNUSED(addressArrayCapacity);
+ OVR_UNUSED(skipCount);
+ OVR_UNUSED(platformThreadContext);
+ OVR_UNUSED(threadSysIdHelp);
+
+ return 0;
+ #endif
+}
+
+
+size_t SymbolLookup::GetBacktraceFromThreadHandle(void* addressArray[], size_t addressArrayCapacity, size_t skipCount, OVR::ThreadHandle threadHandle)
+{
+ #if defined(OVR_OS_MS)
+ size_t count = 0;
+ DWORD threadSysId = (DWORD)ConvertThreadHandleToThreadSysId(threadHandle);
+
+ // Compare to 0, compare to the self 'pseudohandle' and compare to the self id.
+ if((threadHandle == OVR_THREADHANDLE_INVALID) || (threadHandle == ::GetCurrentThread()) || (threadSysId == ::GetCurrentThreadId())) // If threadSysId refers to the current thread...
+ return GetBacktrace(addressArray, addressArrayCapacity, skipCount, nullptr);
+
+ // We are working with another thread. We need to suspend it and get its CONTEXT.
+ // Suspending other threads is risky, as they may be in some state that cannot be safely blocked.
+ BOOL result = false;
+ DWORD suspendResult = ::SuspendThread(threadHandle); // Requires that the handle have THREAD_SUSPEND_RESUME rights.
+
+ if(suspendResult != (DWORD)-1) // Returns previous suspend count, or -1 if failed.
+ {
+ CONTEXT context;
+ context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; // Requires that the handle have THREAD_GET_CONTEXT rights.
+ result = ::GetThreadContext(threadHandle, &context);
+ count = GetBacktrace(addressArray, addressArrayCapacity, skipCount, &context);
+ suspendResult = ::ResumeThread(threadHandle);
+ OVR_ASSERT_AND_UNUSED(suspendResult != (DWORD)-1, suspendResult);
+ }
+
+ return count;
+
+ #elif defined(OVR_OS_APPLE)
+ mach_port_t threadSysID = pthread_mach_thread_np((pthread_t)threadHandle); // Convert pthread_t to mach thread id.
+ return GetBacktraceFromThreadSysId(addressArray, addressArrayCapacity, skipCount, (OVR::ThreadSysId)threadSysID);
+
+ #elif defined(OVR_OS_LINUX)
+ // To do.
+ OVR_UNUSED(addressArray);
+ OVR_UNUSED(addressArrayCapacity);
+ OVR_UNUSED(skipCount);
+ OVR_UNUSED(threadHandle);
+ return 0;
+ #endif
+}
+
+
+size_t SymbolLookup::GetBacktraceFromThreadSysId(void* addressArray[], size_t addressArrayCapacity, size_t skipCount, OVR::ThreadSysId threadSysId)
+{
+ #if defined(OVR_OS_MS)
+ OVR::ThreadHandle threadHandle = ConvertThreadSysIdToThreadHandle(threadSysId);
+ if(threadHandle)
+ {
+ size_t count = GetBacktraceFromThreadHandle(addressArray, addressArrayCapacity, skipCount, threadHandle);
+ FreeThreadHandle(threadHandle);
+ return count;
+ }
+ return 0;
+
+ #elif defined(OVR_OS_APPLE)
+ mach_port_t threadCurrent = pthread_mach_thread_np(pthread_self());
+ mach_port_t thread = (mach_port_t)threadSysId;
+
+ if(thread == threadCurrent)
+ {
+ return GetBacktrace(addressArray, addressArrayCapacity, skipCount, nullptr);
+ }
+ else
+ {
+ kern_return_t result = thread_suspend(thread); // Do we need to do this if it's an thread who exception is being handled?
+ size_t count = 0;
+
+ if(result == KERN_SUCCESS)
+ {
+ #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
+ x86_thread_state_t threadState;
+ #elif defined(OVR_CPU_ARM)
+ arm_thread_state_t threadState;
+ #endif
+ mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;
+
+ result = thread_get_state(thread, MACHINE_THREAD_STATE, (natural_t*)(uintptr_t)&threadState, &stateCount);
+
+ if(result == KERN_SUCCESS)
+ count = GetBacktrace(addressArray, addressArrayCapacity, skipCount, &threadState, threadSysId);
+
+ thread_resume(thread);
+
+ return count;
+ }
+ }
+
+ return 0;
+
+ #elif defined(OVR_OS_LINUX)
+ // To do.
+ OVR_UNUSED(addressArray);
+ OVR_UNUSED(addressArrayCapacity);
+ OVR_UNUSED(skipCount);
+ OVR_UNUSED(threadSysId);
+ return 0;
+ #endif
+}
+
+
+// We need to return the required moduleInfoArrayCapacity.
+size_t SymbolLookup::GetModuleInfoArray(ModuleInfo* pModuleInfoArray, size_t moduleInfoArrayCapacity)
+{
+ #if defined(OVR_OS_MS)
+ size_t moduleCountRequired = 0; // The count we would copy to pModuleInfoArray if moduleInfoArrayCapacity was enough.
+ size_t moduleCount = 0; // The count we actually copy to pModuleInfoArray. Will be <= moduleInfoArrayCapacity.
+ HANDLE hProcess = GetCurrentProcess();
+ HMODULE hModuleArray[200];
+ DWORD cbNeeded = 0;
+ MODULEINFO mi;
+
+ if(EnumProcessModules(hProcess, hModuleArray, sizeof(hModuleArray), &cbNeeded))
+ {
+ moduleCountRequired = ((cbNeeded / sizeof(HMODULE)) < OVR_ARRAY_COUNT(hModuleArray)) ? (cbNeeded / sizeof(HMODULE)) : OVR_ARRAY_COUNT(hModuleArray);
+ moduleCount = MIN(moduleCountRequired, OVR_ARRAY_COUNT(hModuleArray));
+ moduleCount = MIN(moduleCount, moduleInfoArrayCapacity);
+
+ for(size_t i = 0; i < moduleCount; i++)
+ {
+ ModuleInfo& moduleInfo = pModuleInfoArray[i];
+
+ memset(&mi, 0, sizeof(mi));
+ BOOL result = GetModuleInformation(hProcess, hModuleArray[i], &mi, sizeof(mi));
+
+ if(result)
+ {
+ wchar_t pathW[MAX_PATH];
+ char pathA[MAX_PATH * 3]; // *3 to handle UTF8 multibyte encoding.
+
+ moduleInfo.handle = hModuleArray[i];
+ moduleInfo.baseAddress = (uintptr_t)mi.lpBaseOfDll;
+ moduleInfo.size = mi.SizeOfImage;
+
+ GetModuleFileNameW(hModuleArray[i], pathW, OVR_ARRAY_COUNT(pathW));
+ OVR::UTF8Util::EncodeString(pathA, pathW, -1); // Problem: DecodeString provides no way to specify the destination capacity.
+ OVR::OVR_strlcpy(moduleInfo.filePath, pathA, OVR_ARRAY_COUNT(moduleInfo.filePath));
+
+ const char* fileName = GetFileNameFromPath(pathA);
+ OVR::OVR_strlcpy(moduleInfo.name, fileName, OVR_ARRAY_COUNT(moduleInfo.name));
+ }
+ else
+ {
+ moduleInfo.handle = 0;
+ moduleInfo.baseAddress = 0;
+ moduleInfo.size = 0;
+ moduleInfo.filePath[0] = 0;
+ moduleInfo.name[0] = 0;
+ }
+ }
+ }
+
+ return moduleCountRequired;
+
+ #elif defined(OVR_OS_MAC)
+ size_t moduleCountRequired = 0;
+ size_t moduleCount = 0;
+
+ struct MacModuleInfo // This struct exists solely so we can have a local function within this function..
+ {
+ static void AddMacModuleInfo(ModuleInfo* pModuleInfoArrayL, size_t& moduleCountRequiredL, size_t& moduleCountL, size_t moduleInfoArrayCapacityL,
+ const char* pTypeFilterL, const char* pModulePath, uintptr_t currentSegmentPos, const MachHeader* pMachHeader, uint64_t offset)
+ {
+ for(size_t i = 0; i < pMachHeader->ncmds; i++)
+ {
+ const SegmentCommand* pSegmentCommand = reinterpret_cast<const SegmentCommand*>(currentSegmentPos);
+
+ if(pSegmentCommand->cmd == kLCSegment)
+ {
+ const size_t segnameSize = (sizeof(pSegmentCommand->segname) + 1); // +1 so we can have a trailing '\0'.
+ char segname[segnameSize];
+
+ memcpy(segname, pSegmentCommand->segname, sizeof(pSegmentCommand->segname));
+ segname[segnameSize - 1] = '\0';
+
+ if(!pTypeFilterL || OVR_strncmp(segname, pTypeFilterL, sizeof(segname)))
+ {
+ moduleCountRequiredL++;
+
+ if(moduleCountL < moduleInfoArrayCapacityL)
+ {
+ ModuleInfo& info = pModuleInfoArrayL[moduleCountL++];
+
+ info.baseAddress = (uint64_t)(pSegmentCommand->vmaddr + offset);
+ info.handle = reinterpret_cast<ModuleHandle>((uintptr_t)info.baseAddress);
+ info.size = (uint64_t)pSegmentCommand->vmsize;
+ OVR_strlcpy(info.filePath, pModulePath, OVR_ARRAY_COUNT(info.filePath));
+ OVR_strlcpy(info.name, GetFileNameFromPath(pModulePath), OVR_ARRAY_COUNT(info.name));
+
+ info.permissions[0] = (pSegmentCommand->initprot & VM_PROT_READ) ? 'r' : '-';
+ info.permissions[1] = (pSegmentCommand->initprot & VM_PROT_WRITE) ? 'w' : '-';
+ info.permissions[2] = (pSegmentCommand->initprot & VM_PROT_EXECUTE) ? 'x' : '-';
+ info.permissions[3] = '/';
+ info.permissions[4] = (pSegmentCommand->maxprot & VM_PROT_READ) ? 'r' : '-';
+ info.permissions[5] = (pSegmentCommand->maxprot & VM_PROT_WRITE) ? 'w' : '-';
+ info.permissions[6] = (pSegmentCommand->maxprot & VM_PROT_EXECUTE) ? 'x' : '-';
+ info.permissions[7] = '\0';
+
+ OVR_strlcpy(info.type, pSegmentCommand->segname, OVR_ARRAY_COUNT(info.type));
+ }
+ }
+ }
+
+ currentSegmentPos += pSegmentCommand->cmdsize;
+ }
+ }
+ };
+
+ // Iterate dyld_all_image_infos->infoArray
+ const struct dyld_all_image_infos* pAllImageInfos = _dyld_get_all_image_infos();
+
+ for(uint32_t i = 0; i < pAllImageInfos->infoArrayCount; i++)
+ {
+ const char* pModulePath = pAllImageInfos->infoArray[i].imageFilePath;
+
+ if(pModulePath && *pModulePath)
+ {
+ uintptr_t currentSegmentPos = (uintptr_t)pAllImageInfos->infoArray[i].imageLoadAddress;
+ const MachHeader* pMachHeader = reinterpret_cast<const MachHeader*>(currentSegmentPos);
+ uint64_t offset = (uint64_t)_dyld_get_image_vmaddr_slide(i);
+
+ currentSegmentPos += sizeof(*pMachHeader);
+
+ MacModuleInfo::AddMacModuleInfo(pModuleInfoArray, moduleCountRequired, moduleCount, moduleInfoArrayCapacity,
+ nullptr /*"__TEXT"*/, pModulePath, currentSegmentPos, pMachHeader, offset);
+ }
+ }
+
+ // In addition to iterating dyld_all_image_infos->infoArray we need to also iterate /usr/lib/dyld entries.
+ const MachHeader* pMachHeader = (const MachHeader*)pAllImageInfos->dyldImageLoadAddress;
+ uintptr_t currentSegmentPos = (uintptr_t)pMachHeader + sizeof(*pMachHeader);
+ char modulePath[OVR_MAX_PATH] = "";
+ pid_t pid = getpid();
+ int filenameLen = proc_regionfilename((int)pid, currentSegmentPos, modulePath, (uint32_t)sizeof(modulePath));
+
+ if(filenameLen > 0)
+ MacModuleInfo::AddMacModuleInfo(pModuleInfoArray, moduleCountRequired, moduleCount, moduleInfoArrayCapacity,
+ "__TEXT", modulePath, currentSegmentPos, pMachHeader, 0);
+
+ return moduleCountRequired;
+
+ #elif defined(EA_PLATFORM_LINUX)
+ // One approach is to read /proc/self/maps, which is supported by Linux (though not BSD).
+ // Linux glibc dladdr() can tell us what module an arbitrary function address comes from, but can't tell us the list of modules.
+ OVR_UNUSED(pModuleInfoArray);
+ OVR_UNUSED(moduleInfoArrayCapacity);
+ return 0;
+
+ #else
+ OVR_UNUSED(pModuleInfoArray);
+ OVR_UNUSED(moduleInfoArrayCapacity);
+ return 0;
+ #endif
+}
+
+
+size_t SymbolLookup::GetThreadList(ThreadHandle* threadHandleArray, ThreadSysId* threadSysIdArray, size_t threadArrayCapacity)
+{
+ size_t countRequired = 0;
+ size_t count = 0;
+
+ #if defined(OVR_OS_MS)
+ // Print a list of threads.
+ DWORD currentProcessId = GetCurrentProcessId();
+ HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, currentProcessId); // ICreateToolhelp32Snapshot actually ignores currentProcessId.
+
+ if(hThreadSnap != INVALID_HANDLE_VALUE)
+ {
+ THREADENTRY32 te32;
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ if(Thread32First(hThreadSnap, &te32))
+ {
+ do
+ {
+ if(te32.th32OwnerProcessID == currentProcessId)
+ {
+ HANDLE hThread = ConvertThreadSysIdToThreadHandle(te32.th32ThreadID);
+
+ if(hThread)
+ {
+ ++countRequired;
+
+ if((threadHandleArray || threadSysIdArray) && (count < threadArrayCapacity))
+ {
+ if(threadHandleArray)
+ threadHandleArray[count] = hThread; // The caller must call CloseHandle on this thread, or call DoneThreadList on the returned array.
+ if(threadSysIdArray)
+ threadSysIdArray[count] = ConvertThreadHandleToThreadSysId(hThread);
+ ++count;
+ }
+
+ if(!threadHandleArray) // If we aren't giving this back to the user...
+ FreeThreadHandle(hThread);
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+ }
+
+ CloseHandle(hThreadSnap);
+ }
+
+ #elif defined(OVR_OS_APPLE)
+ mach_port_t taskSelf = mach_task_self();
+ thread_act_port_array_t threadArray;
+ mach_msg_type_number_t threadCount;
+
+ kern_return_t result = task_threads(taskSelf, &threadArray, &threadCount);
+
+ if(result == KERN_SUCCESS)
+ {
+ for(mach_msg_type_number_t i = 0; i < threadCount; i++)
+ {
+ ++countRequired;
+
+ if((threadHandleArray || threadSysIdArray) && (count < threadArrayCapacity))
+ {
+ if(threadHandleArray)
+ threadHandleArray[count] = pthread_from_mach_thread_np(threadArray[i]);
+ if(threadSysIdArray)
+ threadSysIdArray[count] = threadArray[i];
+ ++count;
+ }
+ }
+
+ vm_deallocate(taskSelf, (vm_address_t)threadArray, threadCount * sizeof(thread_act_t));
+ }
+
+ #elif defined(OVR_OS_LINUX)
+ // To do.
+ OVR_UNUSED(count);
+ OVR_UNUSED(threadHandleArray);
+ OVR_UNUSED(threadSysIdArray);
+ OVR_UNUSED(threadArrayCapacity);
+ #endif
+
+ return countRequired;
+}
+
+
+void SymbolLookup::DoneThreadList(ThreadHandle* threadHandleArray, ThreadSysId* threadSysIdArray, size_t threadArrayCount)
+{
+ #if defined(OVR_OS_MS)
+ for(size_t i = 0; i != threadArrayCount; ++i)
+ {
+ if(threadHandleArray[i])
+ {
+ CloseHandle(threadHandleArray[i]);
+ threadHandleArray[i] = OVR_THREADHANDLE_INVALID;
+ }
+ }
+
+ OVR_UNUSED(threadSysIdArray);
+ #else
+ OVR_UNUSED(threadHandleArray);
+ OVR_UNUSED(threadSysIdArray);
+ OVR_UNUSED(threadArrayCount);
+ #endif
+}
+
+
+// Writes a given thread's callstack wity symbols to the given output.
+// It may not be safe to call this from an exception handler, as sOutput allocates memory.
+bool SymbolLookup::ReportThreadCallstack(OVR::String& sOutput, size_t skipCount, ThreadSysId threadSysId)
+{
+ if(!threadSysId)
+ threadSysId = GetCurrentThreadSysId();
+
+ void* addressArray[64];
+ size_t addressCount = GetBacktraceFromThreadSysId(addressArray, OVR_ARRAY_COUNT(addressArray), skipCount, threadSysId);
+
+ // Print the header
+ char headerBuffer[256];
+ char threadName[32];
+ char threadHandleStr[24];
+ char threadSysIdStr[24];
+ char stackBaseStr[24];
+ char stackLimitStr[24];
+ void* pStackBase;
+ void* pStackLimit;
+ //void* pStackCurrent; // Current stack pointer. To do: support reporting this.
+ ThreadHandle threadHandle = ConvertThreadSysIdToThreadHandle(threadSysId);
+ OVR::GetThreadStackBounds(pStackBase, pStackLimit, threadHandle);
+
+ Thread::GetThreadName(threadName, OVR_ARRAY_COUNT(threadName), threadName);
+ SprintfThreadHandle(threadHandleStr, OVR_ARRAY_COUNT(threadHandleStr), threadHandle);
+ SprintfThreadSysId(threadSysIdStr, OVR_ARRAY_COUNT(threadSysIdStr), threadSysId);
+ SprintfAddress(stackBaseStr, OVR_ARRAY_COUNT(stackBaseStr), pStackBase);
+ SprintfAddress(stackLimitStr, OVR_ARRAY_COUNT(stackLimitStr), pStackLimit);
+
+ if(threadName[0])
+ OVR_snprintf(headerBuffer, OVR_ARRAY_COUNT(headerBuffer), "Thread \"%s\" handle: %s, id: %s, stack base: %s, stack limit: %s\r\n", threadName, threadHandleStr, threadSysIdStr, stackBaseStr, stackLimitStr);
+ else
+ OVR_snprintf(headerBuffer, OVR_ARRAY_COUNT(headerBuffer), "Thread handle: %s, id: %s, stack base: %s, stack limit: %s\r\n", threadHandleStr, threadSysIdStr, stackBaseStr, stackLimitStr);
+
+ sOutput += headerBuffer;
+
+ // Print the backtrace info
+ char backtraceBuffer[1024]; // Sometimes function symbol names are very long.
+ SymbolInfo symbolInfo;
+ const char* pModuleName;
+
+ if(addressCount == 0)
+ {
+ sOutput += "<Unable to read backtrace>\r\n";
+ }
+ else
+ {
+ for(size_t i = 0; i < addressCount; ++i)
+ {
+ LookupSymbol((uint64_t)addressArray[i], symbolInfo);
+
+ if(symbolInfo.pModuleInfo && symbolInfo.pModuleInfo->name[0])
+ pModuleName = symbolInfo.pModuleInfo->name;
+ else
+ pModuleName = "(unknown module)";
+
+ char addressStr[24];
+ SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), addressArray[i]);
+
+ if(symbolInfo.filePath[0])
+ OVR_snprintf(backtraceBuffer, OVR_ARRAY_COUNT(backtraceBuffer), "%-2u %-24s %s %s+%d %s:%d\r\n", (unsigned)i, pModuleName, addressStr, symbolInfo.function, symbolInfo.functionOffset, symbolInfo.filePath, symbolInfo.fileLineNumber);
+ else
+ OVR_snprintf(backtraceBuffer, OVR_ARRAY_COUNT(backtraceBuffer), "%-2u %-24s %s %s+%d\r\n", (unsigned)i, pModuleName, addressStr, symbolInfo.function, symbolInfo.functionOffset);
+
+ sOutput += backtraceBuffer;
+ }
+ }
+
+ FreeThreadHandle(threadHandle);
+
+ return (addressCount > 0);
+}
+
+
+// Writes all thread's callstacks with symbols to the given output.
+// It may not be safe to call this from an exception handler, as sOutput allocates memory.
+bool SymbolLookup::ReportThreadCallstacks(OVR::String& sOutput, size_t skipCount)
+{
+ ThreadSysId threadSysIdArray[64];
+ size_t threadSysIdCount = GetThreadList(nullptr, threadSysIdArray, OVR_ARRAY_COUNT(threadSysIdArray));
+
+ if(threadSysIdCount > OVR_ARRAY_COUNT(threadSysIdArray))
+ threadSysIdCount = OVR_ARRAY_COUNT(threadSysIdArray);
+
+ for(size_t i = 0; i < threadSysIdCount; i++)
+ {
+ String sTemp;
+ ReportThreadCallstack(sTemp, skipCount, threadSysIdArray[i]);
+ if(i > 0)
+ sOutput += "\r\n";
+ sOutput += sTemp;
+ }
+
+ return (threadSysIdCount > 0);
+}
+
+
+bool SymbolLookup::RefreshModuleList()
+{
+ if(!moduleListUpdated)
+ {
+ #if defined(OVR_OS_MS)
+ // We can't rely on SymRefreshModuleList because it's present in DbgHelp 6.5,
+ // which doesn't distribute with Windows 7.
+
+ // Currently we support only refreshing the list once ever. With a little effort we could revise this code to
+ // support re-refreshing the list at runtime to account for the possibility that modules have recently been
+ // added or removed.
+ if(pSymLoadModule64)
+ {
+ const size_t requiredCount = GetModuleInfoArray(moduleInfoArray, OVR_ARRAY_COUNT(moduleInfoArray));
+ moduleInfoArraySize = MIN(requiredCount, OVR_ARRAY_COUNT(moduleInfoArray));
+
+ HANDLE hProcess = GetCurrentProcess();
+
+ for(size_t i = 0; i < moduleInfoArraySize; i++)
+ pSymLoadModule64(hProcess, nullptr, moduleInfoArray[i].filePath, nullptr, moduleInfoArray[i].baseAddress, (DWORD)moduleInfoArray[i].size);
+
+ moduleListUpdated = true;
+ }
+ #else
+ const size_t requiredCount = GetModuleInfoArray(moduleInfoArray, OVR_ARRAY_COUNT(moduleInfoArray));
+ moduleInfoArraySize = MIN(requiredCount, OVR_ARRAY_COUNT(moduleInfoArray));
+ moduleListUpdated = true;
+ #endif
+ }
+
+ return true;
+}
+
+
+bool SymbolLookup::LookupSymbol(uint64_t address, SymbolInfo& symbolInfo)
+{
+ return LookupSymbols(&address, &symbolInfo, 1);
+}
+
+
+bool SymbolLookup::LookupSymbols(uint64_t* addressArray, SymbolInfo* pSymbolInfoArray, size_t arraySize)
+{
+ if(!moduleListUpdated)
+ {
+ RefreshModuleList();
+ }
+
+ #if defined(OVR_OS_MS)
+ union SYMBOL_INFO_UNION
+ {
+ SYMBOL_INFO msSymbolInfo;
+ char suffixPadding[sizeof(SYMBOL_INFO) + 1024];
+ };
+
+ for(size_t i = 0; i < arraySize; i++)
+ {
+ uint64_t& address = addressArray[i];
+ SymbolInfo& symbolInfo = pSymbolInfoArray[i];
+
+ // Copy the address and ModuleInfo
+ symbolInfo.address = addressArray[i];
+ symbolInfo.pModuleInfo = GetModuleInfoForAddress(address); // We could also use siu.msSymbolInfo.ModBase to get the module slightly faster.
+
+ // Get the function/offset.
+ SYMBOL_INFO_UNION siu;
+ memset(&siu, 0, sizeof(siu));
+ siu.msSymbolInfo.SizeOfStruct = sizeof(siu.msSymbolInfo);
+ siu.msSymbolInfo.MaxNameLen = sizeof(siu.suffixPadding) - sizeof(SYMBOL_INFO) + 1; // +1 because SYMBOL_INFO itself has Name[1].
+
+ HANDLE hProcess = GetCurrentProcess();
+ DWORD64 displacement64 = 0;
+ bool bResult = (pSymFromAddr != nullptr) && (pSymFromAddr(hProcess, address, &displacement64, &siu.msSymbolInfo) != FALSE);
+
+ if(bResult)
+ {
+ symbolInfo.size = siu.msSymbolInfo.Size;
+ OVR_strlcpy(symbolInfo.function, siu.msSymbolInfo.Name, OVR_ARRAY_COUNT(symbolInfo.function));
+ symbolInfo.functionOffset = (int32_t)displacement64;
+ }
+ else
+ {
+ symbolInfo.size = kMISizeInvalid;
+ symbolInfo.function[0] = 0;
+ symbolInfo.functionOffset = kMIFunctionOffsetInvalid;
+ }
+
+ // Get the file/line
+ IMAGEHLP_LINE64 iLine64;
+ DWORD displacement = 0;
+ memset(&iLine64, 0, sizeof(iLine64));
+ iLine64.SizeOfStruct = sizeof(iLine64);
+
+ bResult = (pSymGetLineFromAddr64 != nullptr) && (pSymGetLineFromAddr64(hProcess, address, &displacement, &iLine64) != FALSE);
+
+ if(bResult)
+ {
+ OVR_strlcpy(symbolInfo.filePath, iLine64.FileName, OVR_ARRAY_COUNT(symbolInfo.filePath));
+ symbolInfo.fileLineNumber = (int32_t)iLine64.LineNumber;
+ }
+ else
+ {
+ symbolInfo.filePath[0] = 0;
+ symbolInfo.fileLineNumber = kMILineNumberInvalid;
+ }
+
+ // To do: get the source code when possible. We need to use the user-registered directory paths and the symbolInfo.filePath
+ // and find the given file in the tree(s), then open the file and find the symbolInfo.fileLineNumber line (and surrounding lines).
+ // symbolInfo.sourceCode[1024]
+ symbolInfo.sourceCode[0] = '\0';
+ }
+
+ #elif defined(OVR_OS_APPLE)
+ // Apple has an internal CoreSymbolication library which could help with this.
+ // Third party implementations of the CoreSymbolication header are available and could be used
+ // to get file/line info better than other means. It used Objective C, so we'll need a .m or .mm file.
+
+ memset(pSymbolInfoArray, 0, arraySize * sizeof(SymbolInfo));
+
+ for(size_t i = 0; i < arraySize; i++)
+ {
+ pSymbolInfoArray[i].address = addressArray[i];
+ pSymbolInfoArray[i].pModuleInfo = GetModuleInfoForAddress(addressArray[i]);
+ }
+
+ // Problem: backtrace_symbols allocates memory from malloc. If you got into a SIGSEGV due to
+ // malloc arena corruption (quite common) you will likely fault in backtrace_symbols.
+ // To do: Use allowMemoryAllocation here.
+
+ #if (OVR_PTR_SIZE == 4)
+ // backtrace_symbols takes a void* array, but we have a uint64_t array. So for 32 bit we
+ // need to convert the 64 bit array to 32 bit temporarily for the backtrace_symbols call.
+ void* ptr32Array[256]; // To do: Remove this limit.
+ for(size_t i = 0, iEnd = MIN(arraySize, OVR_ARRAY_COUNT(ptr32Array)); i < iEnd; i++)
+ ptr32Array[i] = reinterpret_cast<void*>(addressArray[i]);
+ char** symbolArray = backtrace_symbols(reinterpret_cast<void**>(ptr32Array), (int)arraySize);
+ #else
+ char** symbolArray = backtrace_symbols(reinterpret_cast<void**>(addressArray), (int)arraySize);
+ #endif
+
+ if(symbolArray)
+ {
+ for(size_t i = 0; i < arraySize; i++)
+ {
+
+ // Generates a string like this: "0 OculusWorldDemo 0x000000010000cfd5 _ZN18OculusWorldDemoApp9OnStartupEiPPKc + 213"
+ static_assert(OVR_ARRAY_COUNT(pSymbolInfoArray[i].function) == 128, "Need to change the string format size below");
+
+ sscanf(symbolArray[i], "%*d %*s %*x %128s + %d", pSymbolInfoArray[i].function, &pSymbolInfoArray[i].functionOffset);
+
+ if(allowMemoryAllocation)
+ {
+ int status = 0;
+ char* strDemangled = abi::__cxa_demangle(pSymbolInfoArray[i].function, nullptr, nullptr, &status);
+
+ if(strDemangled)
+ {
+ OVR_strlcpy(pSymbolInfoArray[i].function, strDemangled, OVR_ARRAY_COUNT(pSymbolInfoArray[i].function));
+ free(strDemangled);
+ }
+ }
+ }
+
+ free(symbolArray);
+ }
+
+ // To consider: use CoreSybolication to get file/line info instead. atos is a bit slow and cumbersome.
+ // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/atos.1.html
+ // atos -p <pid> <addr> <addr> ...
+ // atos -o <binary image path> -l <load-address> <addr> <addr> ...
+ // Generates output like this: "OVR::CreateException(OVR::CreateExceptionType) (in OculusWorldDemo) (ExceptionHandler.cpp:598)"
+ for(size_t i = 0; i < arraySize; i++)
+ {
+ struct stat statStruct;
+
+ if(pSymbolInfoArray[i].pModuleInfo && pSymbolInfoArray[i].pModuleInfo->filePath[0] && (stat(pSymbolInfoArray[i].pModuleInfo->filePath, &statStruct) == 0))
+ {
+ char command[PATH_MAX * 2]; // Problem: We can't unilaterally use pSymbolInfoArray[0] for all addresses. We need to match addresses to the corresponding modules.
+ OVR_snprintf(command, OVR_ARRAY_COUNT(command), "atos -o %s -l 0x%llx 0x%llx",
+ pSymbolInfoArray[i].pModuleInfo->filePath, (int64_t)pSymbolInfoArray[i].pModuleInfo->baseAddress, (int64_t)pSymbolInfoArray[i].address);
+
+ char output[512];
+ if(SpawnShellCommand(command, output, OVR_ARRAY_COUNT(output)) != (size_t)-1)
+ {
+ char* pLastOpenParen = strrchr(output, '(');
+ char* pColon = strrchr(output, ':');
+
+ if(pLastOpenParen && (pColon > pLastOpenParen))
+ {
+ *pColon = '\0';
+ OVR_strlcpy(pSymbolInfoArray[i].filePath, pLastOpenParen + 1, OVR_ARRAY_COUNT(pSymbolInfoArray[i].filePath));
+ }
+ }
+ }
+ }
+
+ #elif defined(OVR_OS_LINUX)
+ // We can use libunwind's unw_get_proc_name to try to get function name info. It can work regardless of relocation.
+ // Use backtrace_symbols and addr2line. Need to watch out for module load-time relocation.
+ // Ned to pass the -rdynamic flag to the linker. It will cause the linker to out in the link
+ // tables the name of all the none static functions in your code, not just the exported ones.
+ OVR_UNUSED(addressArray);
+ OVR_UNUSED(pSymbolInfoArray);
+ OVR_UNUSED(arraySize);
+ #endif
+
+ return true; // To do: Return true only if something was found.
+}
+
+
+const ModuleInfo* SymbolLookup::GetModuleInfoForAddress(uint64_t address)
+{
+ // This is a linear seach. To consider: it would be significantly faster to search by
+ // address if we ordered it by base address and did a binary search.
+ for(size_t i = 0; i < moduleInfoArraySize; ++i)
+ {
+ const ModuleInfo& mi = moduleInfoArray[i];
+
+ if((mi.baseAddress <= address) && (address < (mi.baseAddress + mi.size)))
+ return &mi;
+ }
+
+ return nullptr;
+}
+
+
+
+
+ExceptionInfo::ExceptionInfo()
+ : time()
+ , timeVal(0)
+ , backtrace()
+ , backtraceCount(0)
+ , threadHandle(OVR_THREADHANDLE_INVALID)
+ , threadSysId(OVR_THREADSYSID_INVALID)
+ , threadName()
+ , pExceptionInstructionAddress(nullptr)
+ , pExceptionMemoryAddress(nullptr)
+ , cpuContext()
+ , exceptionDescription()
+ , symbolInfo()
+ #if defined(OVR_OS_MS)
+ , exceptionRecord()
+ #elif defined(OVR_OS_APPLE)
+ , exceptionType(0)
+ , cpuExceptionId(0)
+ , cpuExceptionIdError(0)
+ , machExceptionDetail()
+ , machExceptionDetailCount(0)
+ #endif
+{
+}
+
+
+
+ExceptionHandler::ExceptionHandler()
+ : enabled(false)
+ , reportPrivacyEnabled(true)
+ , exceptionResponse(kERHandle)
+ , exceptionListener(nullptr)
+ , exceptionListenerUserValue(0)
+ , appDescription()
+ , codeBasePathArray()
+ , reportFilePath()
+ , miniDumpFlags(0)
+ , miniDumpFilePath()
+ , file(nullptr)
+ , scratchBuffer()
+ , exceptionOccurred(false)
+ , handlingBusy(0)
+ , reportFilePathActual()
+ , minidumpFilePathActual()
+ , terminateReturnValue(0)
+ , exceptionInfo()
+ #if defined(OVR_OS_MS)
+ , vectoredHandle(nullptr)
+ , previousFilter(nullptr)
+ , pExceptionPointers(nullptr)
+ #elif defined(OVR_OS_MAC)
+ , machHandlerInitialized(false)
+ , machExceptionPort(0)
+ , machExceptionPortsSaved()
+ , machThreadShouldContinue(false)
+ , machThreadExecuting(false)
+ , machThread((pthread_t)OVR_THREADHANDLE_INVALID)
+ #endif
+{
+ SetExceptionPaths("default", "default");
+}
+
+
+ExceptionHandler::~ExceptionHandler()
+{
+ if(enabled)
+ {
+ Enable(false);
+ }
+}
+
+
+#if defined(OVR_OS_MS)
+ static ExceptionHandler* sExceptionHandler = nullptr;
+
+ LONG WINAPI Win32ExceptionFilter(LPEXCEPTION_POINTERS pExceptionPointers)
+ {
+ if(sExceptionHandler)
+ return (LONG)sExceptionHandler->ExceptionFilter(pExceptionPointers);
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ LONG ExceptionHandler::ExceptionFilter(LPEXCEPTION_POINTERS pExceptionPointers)
+ {
+ // Exception codes < 0x80000000 are not true exceptions but rather are debugger notifications. They include DBG_TERMINATE_THREAD,
+ // DBG_TERMINATE_PROCESS, DBG_CONTROL_BREAK, DBG_COMMAND_EXCEPTION, DBG_CONTROL_C, DBG_PRINTEXCEPTION_C, DBG_RIPEXCEPTION,
+ // and 0x406d1388 (thread named, http://blogs.msdn.com/b/stevejs/archive/2005/12/19/505815.aspx).
+
+ if(pExceptionPointers->ExceptionRecord->ExceptionCode < 0x80000000)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ // VC++ C++ exceptions use code 0xe06d7363 ('Emsc')
+ // http://support.microsoft.com/kb/185294
+ // http://blogs.msdn.com/b/oldnewthing/archive/2010/07/30/10044061.aspx
+ if(pExceptionPointers->ExceptionRecord->ExceptionCode == 0xe06d7363)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ if(handlingBusy.CompareAndSet_Acquire(0, 1)) // If we can successfully change it from 0 to 1.
+ {
+ exceptionOccurred = true;
+
+ this->pExceptionPointers = pExceptionPointers;
+
+ // Disable the handler while we do this processing.
+ ULONG result = RemoveVectoredExceptionHandler(vectoredHandle);
+ OVR_ASSERT_AND_UNUSED(result != 0, result);
+
+ // Time
+ exceptionInfo.timeVal = time(nullptr);
+ exceptionInfo.time = *gmtime(&exceptionInfo.timeVal);
+
+ // Thread id
+ // This is the thread id of the current thread and not the exception thread.
+ if(!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &exceptionInfo.threadHandle, 0, true, DUPLICATE_SAME_ACCESS))
+ exceptionInfo.threadHandle = 0;
+ exceptionInfo.threadSysId = ConvertThreadHandleToThreadSysId(exceptionInfo.threadHandle);
+
+ OVR::GetThreadName(exceptionInfo.threadHandle, exceptionInfo.threadName, OVR_ARRAY_COUNT(exceptionInfo.threadName));
+
+ // Backtraces
+ exceptionInfo.backtraceCount = symbolLookup.GetBacktrace(exceptionInfo.backtrace, OVR_ARRAY_COUNT(exceptionInfo.backtrace));
+
+ // Context
+ exceptionInfo.cpuContext = *pExceptionPointers->ContextRecord;
+ exceptionInfo.exceptionRecord = *pExceptionPointers->ExceptionRecord;
+ exceptionInfo.pExceptionInstructionAddress = exceptionInfo.exceptionRecord.ExceptionAddress;
+ if((exceptionInfo.exceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) || (exceptionInfo.exceptionRecord.ExceptionCode == EXCEPTION_IN_PAGE_ERROR))
+ exceptionInfo.pExceptionMemoryAddress = (void*)exceptionInfo.exceptionRecord.ExceptionInformation[1]; // ExceptionInformation[0] indicates if it was a read (0), write (1), or data execution attempt (8).
+ else
+ exceptionInfo.pExceptionMemoryAddress = pExceptionPointers->ExceptionRecord->ExceptionAddress;
+
+ WriteExceptionDescription();
+
+ if(miniDumpFilePath[0])
+ WriteMiniDump();
+
+ if(reportFilePath[0])
+ WriteReport();
+
+ if(exceptionListener)
+ exceptionListener->HandleException(exceptionListenerUserValue, this, &exceptionInfo, reportFilePathActual);
+
+ if(exceptionInfo.threadHandle)
+ {
+ CloseHandle(exceptionInfo.threadHandle);
+ exceptionInfo.threadHandle = 0;
+ }
+
+ // Restore the handler that we temporarily disabled above.
+ vectoredHandle = AddVectoredExceptionHandler(1, Win32ExceptionFilter);
+
+ handlingBusy.Store_Release(0);
+ }
+
+ if(exceptionResponse == ExceptionHandler::kERTerminate)
+ {
+ TerminateProcess(GetCurrentProcess(), (UINT)terminateReturnValue);
+ return terminateReturnValue;
+ }
+ else if(exceptionResponse == ExceptionHandler::kERThrow)
+ return EXCEPTION_CONTINUE_SEARCH;
+ else if(exceptionResponse == ExceptionHandler::kERContinue)
+ return EXCEPTION_CONTINUE_EXECUTION;
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+
+#endif // defined(OVR_OS_MS)
+
+
+#if defined(OVR_OS_APPLE)
+ // http://www.opensource.apple.com/source/xnu/xnu-2050.22.13/
+ // http://www.opensource.apple.com/source/xnu/xnu-2050.22.13/osfmk/man/
+ // http://www.opensource.apple.com/source/Libc/Libc-825.26/
+ // https://mikeash.com/pyblog/friday-qa-2013-01-11-mach-exception-handlers.html
+
+ void* ExceptionHandler::MachHandlerThreadFunction()
+ {
+ __Request__mach_exception_raise_state_identity_t msg;
+ __Reply__mach_exception_raise_state_identity_t reply;
+ mach_msg_return_t result;
+
+ machThreadExecuting = true;
+ pthread_setname_np("ExceptionHandler");
+
+ while(machThreadShouldContinue)
+ {
+ mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_LARGE;
+ natural_t timeout = 0; // Would be better to support a non-zero time.
+
+ if(timeout)
+ options |= MACH_RCV_TIMEOUT;
+
+ result = mach_msg(&msg.Head, options, 0, sizeof(msg), machExceptionPort, timeout, MACH_PORT_NULL);
+
+ if(msg.Head.msgh_id != sMachCancelMessageType)
+ {
+ if(result == MACH_MSG_SUCCESS)
+ {
+ if(mach_exc_server_OVR(&msg.Head, &reply.Head) == 0) //This will call our HandleMachException function.
+ result = ~MACH_MSG_SUCCESS;
+ }
+
+ // Send the reply
+ if(result == MACH_MSG_SUCCESS)
+ {
+ result = mach_msg(&reply.Head, MACH_SEND_MSG, reply.Head.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ if(result != MACH_MSG_SUCCESS)
+ {
+ // Failure.
+ }
+ }
+ }
+ }
+
+ machThreadExecuting = false;
+
+ return nullptr;
+ }
+
+
+ kern_return_t ExceptionHandler::HandleMachException(mach_port_t /*machPort*/, mach_port_t threadSysId, mach_port_t machTask,
+ exception_type_t machExceptionType, mach_exception_data_type_t* pExceptionDetail,
+ mach_msg_type_number_t exceptionDetailCount, int* /*pMachExceptionFlavor*/, thread_state_t threadStatePrev,
+ mach_msg_type_number_t /*threadStatePrevCount*/, thread_state_t /*threadStateNew*/,
+ mach_msg_type_number_t* /*pThreadStateNewCount*/)
+ {
+ // We don't want to handle exceptions for other processes.
+ if(machTask != mach_task_self())
+ return ForwardMachException(threadSysId, machTask, machExceptionType, pExceptionDetail, exceptionDetailCount);
+
+ if(handlingBusy.CompareAndSet_Acquire(0, 1)) // If we can successfully change it from 0 to 1.
+ {
+ exceptionOccurred = true;
+
+ // Disable the handler while we do this processing.
+ // To do.
+
+ // Time
+ exceptionInfo.timeVal = time(nullptr);
+ exceptionInfo.time = *gmtime(&exceptionInfo.timeVal);
+
+ // Thread id
+ exceptionInfo.threadHandle = pthread_from_mach_thread_np(threadSysId);
+ exceptionInfo.threadSysId = threadSysId;
+ pthread_getname_np((pthread_t)exceptionInfo.threadHandle, exceptionInfo.threadName, sizeof(exceptionInfo.threadName));
+
+ // Backtraces
+ exceptionInfo.backtraceCount = symbolLookup.GetBacktraceFromThreadSysId(exceptionInfo.backtrace, OVR_ARRAY_COUNT(exceptionInfo.backtrace), 0, threadSysId);
+
+ // Context
+ #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
+ // We can read x86_THREAD_STATE directly fromk threadStatePrev.
+ exceptionInfo.cpuContext.threadState = *reinterpret_cast<x86_thread_state_t*>(threadStatePrev);
+
+ mach_msg_type_number_t stateCount = x86_FLOAT_STATE_COUNT;
+ thread_get_state(threadSysId, x86_FLOAT_STATE, (natural_t*)&exceptionInfo.cpuContext.floatState, &stateCount);
+
+ stateCount = x86_DEBUG_STATE_COUNT;
+ thread_get_state(threadSysId, x86_DEBUG_STATE, (natural_t*)&exceptionInfo.cpuContext.debugState, &stateCount);
+
+ stateCount = x86_AVX_STATE_COUNT;
+ thread_get_state(threadSysId, x86_AVX_STATE, (natural_t*)&exceptionInfo.cpuContext.avxState, &stateCount);
+
+ stateCount = x86_EXCEPTION_STATE_COUNT;
+ thread_get_state(threadSysId, x86_EXCEPTION_STATE, (natural_t*)&exceptionInfo.cpuContext.exceptionState, &stateCount);
+
+ #if defined(OVR_CPU_X86)
+ exceptionInfo.pExceptionInstructionAddress = (void*)exceptionInfo.cpuContext.threadState.uts.ts32.__eip;
+ exceptionInfo.pExceptionMemoryAddress = (void*)exceptionInfo.cpuContext.exceptionState.ues.es32.__faultvaddr;
+ exceptionInfo.cpuExceptionId = exceptionInfo.cpuContext.exceptionState.ues.es32.__trapno;
+ exceptionInfo.cpuExceptionIdError = exceptionInfo.cpuContext.exceptionState.ues.es32.__err;
+ #else
+ exceptionInfo.pExceptionInstructionAddress = (void*)exceptionInfo.cpuContext.threadState.uts.ts64.__rip;
+ exceptionInfo.pExceptionMemoryAddress = (void*)exceptionInfo.cpuContext.exceptionState.ues.es64.__faultvaddr;
+ exceptionInfo.cpuExceptionId = exceptionInfo.cpuContext.exceptionState.ues.es64.__trapno;
+ exceptionInfo.cpuExceptionIdError = exceptionInfo.cpuContext.exceptionState.ues.es64.__err;
+ #endif
+ #endif
+
+ exceptionInfo.exceptionType = machExceptionType;
+
+ exceptionInfo.machExceptionDetailCount = MIN(exceptionDetailCount, OVR_ARRAY_COUNT(exceptionInfo.machExceptionDetail));
+ for(int i = 0; i < exceptionInfo.machExceptionDetailCount; i++)
+ exceptionInfo.machExceptionDetail[i] = pExceptionDetail[i];
+
+ WriteExceptionDescription();
+
+ if(reportFilePath[0])
+ WriteReport();
+
+ if(miniDumpFilePath[0])
+ WriteMiniDump();
+
+ if(exceptionListener)
+ exceptionListener->HandleException(exceptionListenerUserValue, this, &exceptionInfo, reportFilePathActual);
+
+ // Re-restore the handler.
+ // To do.
+
+ handlingBusy.Store_Release(0);
+ }
+
+ kern_return_t result = KERN_FAILURE; // By default pass on the exception to another handler after we are done here.
+
+ if(exceptionResponse == ExceptionHandler::kERTerminate)
+ ::exit(terminateReturnValue);
+ else if(exceptionResponse == ExceptionHandler::kERThrow)
+ ForwardMachException(threadSysId, machTask, machExceptionType, pExceptionDetail, exceptionDetailCount);
+ else if(exceptionResponse == ExceptionHandler::kERDefault)
+ ::exit(terminateReturnValue);
+ else if(exceptionResponse == ExceptionHandler::kERContinue)
+ result = KERN_SUCCESS; // This will trigger a re-execution of the function.
+
+ return result;
+ }
+
+
+ bool ExceptionHandler::InitMachExceptionHandler()
+ {
+ if(!machHandlerInitialized)
+ {
+ mach_port_t machTaskSelf = mach_task_self();
+ kern_return_t result = MACH_MSG_SUCCESS;
+ exception_mask_t mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_CRASH;
+
+ if(machExceptionPort == MACH_PORT_NULL)
+ {
+ result = mach_port_allocate(machTaskSelf, MACH_PORT_RIGHT_RECEIVE, &machExceptionPort);
+
+ if(result == MACH_MSG_SUCCESS)
+ {
+ result = mach_port_insert_right(machTaskSelf, machExceptionPort, machExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
+
+ if(result == MACH_MSG_SUCCESS)
+ result = task_get_exception_ports(machTaskSelf, mask, machExceptionPortsSaved.masks, &machExceptionPortsSaved.count,
+ machExceptionPortsSaved.ports, machExceptionPortsSaved.behaviors, machExceptionPortsSaved.flavors);
+ }
+ }
+
+ if(result == MACH_MSG_SUCCESS)
+ {
+ result = task_set_exception_ports(machTaskSelf, mask, machExceptionPort, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE);
+
+ if(result == MACH_MSG_SUCCESS)
+ {
+ machThreadShouldContinue = true;
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+
+ result = pthread_create(&machThread, &attr, MachHandlerThreadFunctionStatic, (void*)this);
+ pthread_attr_destroy(&attr);
+
+ machHandlerInitialized = (result == 0);
+ }
+ }
+
+ if(!machHandlerInitialized)
+ ShutdownMachExceptionHandler();
+ }
+
+ return machHandlerInitialized;
+ }
+
+
+ void ExceptionHandler::ShutdownMachExceptionHandler()
+ {
+ if(machThreadExecuting)
+ {
+ machThreadShouldContinue = false; // Tell it to stop.
+
+ // Cancel the current exception handler thread (which is probably blocking in a call to mach_msg) by sending it a cencel message.
+ struct CancelMessage
+ {
+ mach_msg_header_t msgHeader;
+ };
+
+ CancelMessage msg;
+ memset(&msg.msgHeader, 0, sizeof(CancelMessage));
+ msg.msgHeader.msgh_id = sMachCancelMessageType;
+ msg.msgHeader.msgh_size = sizeof(CancelMessage);
+ msg.msgHeader.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MAKE_SEND);
+ msg.msgHeader.msgh_remote_port = machExceptionPort;
+ msg.msgHeader.msgh_local_port = MACH_PORT_NULL;
+
+ mach_msg_return_t result = mach_msg(&msg.msgHeader, MACH_SEND_MSG, msg.msgHeader.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ if(result == MACH_MSG_SUCCESS)
+ {
+ const double threeSecondsLater = ovr_GetTimeInSeconds() + 3.f;
+
+ while(machThreadExecuting && (ovr_GetTimeInSeconds() < threeSecondsLater))
+ {
+ timespec ts = { 0, 1000000000 };
+ nanosleep(&ts, nullptr);
+ }
+ }
+
+ void* joinResult = nullptr;
+ pthread_join(machThread, &joinResult);
+ machThread = 0;
+ }
+
+ if(machExceptionPort != MACH_PORT_NULL)
+ {
+ // Restore the previous ports
+ kern_return_t result = KERN_SUCCESS;
+ mach_port_t machTaskSelf = mach_task_self();
+
+ for(unsigned i = 0; (i < machExceptionPortsSaved.count) && (result == KERN_SUCCESS); i++)
+ {
+ result = task_set_exception_ports(machTaskSelf, machExceptionPortsSaved.masks[i], machExceptionPortsSaved.ports[i],
+ machExceptionPortsSaved.behaviors[i], machExceptionPortsSaved.flavors[i]);
+ }
+
+ mach_port_deallocate(machTaskSelf, machExceptionPort);
+ machExceptionPort = MACH_PORT_NULL;
+ }
+
+ machHandlerInitialized = false;
+ }
+
+
+ kern_return_t ExceptionHandler::ForwardMachException(mach_port_t thread, mach_port_t task, exception_type_t exceptionType,
+ mach_exception_data_t pExceptionDetail, mach_msg_type_number_t exceptionDetailCount)
+ {
+ kern_return_t result = KERN_FAILURE;
+ mach_msg_type_number_t i;
+
+ for(i = 0; i < machExceptionPortsSaved.count; i++)
+ {
+ if(machExceptionPortsSaved.masks[i] & (1 << exceptionType))
+ break;
+ }
+
+ if(i < machExceptionPortsSaved.count)
+ {
+ mach_port_t port = machExceptionPortsSaved.ports[i];
+ exception_behavior_t behavior = machExceptionPortsSaved.behaviors[i];
+ thread_state_flavor_t flavor = machExceptionPortsSaved.flavors[i];
+ mach_msg_type_number_t threadStateCount = THREAD_STATE_MAX;
+ thread_state_data_t threadState;
+
+ if(behavior != EXCEPTION_DEFAULT)
+ thread_get_state(thread, flavor, threadState, &threadStateCount);
+
+ switch(behavior)
+ {
+ case EXCEPTION_DEFAULT:
+ result = mach_exception_raise_OVR(port, thread, task, exceptionType, pExceptionDetail, exceptionDetailCount);
+ break;
+
+ case EXCEPTION_STATE:
+ result = mach_exception_raise_state_OVR(port, exceptionType, pExceptionDetail, exceptionDetailCount,
+ &flavor, threadState, threadStateCount, threadState, &threadStateCount);
+ break;
+
+ case EXCEPTION_STATE_IDENTITY:
+ result = mach_exception_raise_state_identity_OVR(port, thread, task, exceptionType, pExceptionDetail,
+ exceptionDetailCount, &flavor, threadState, threadStateCount, threadState, &threadStateCount);
+ break;
+
+ default:
+ result = KERN_FAILURE;
+ break;
+ }
+
+ if(behavior != EXCEPTION_DEFAULT)
+ result = thread_set_state(thread, flavor, threadState, threadStateCount);
+ }
+
+ return result;
+ }
+
+
+#endif // OVR_OS_APPLE
+
+
+bool ExceptionHandler::Enable(bool enable)
+{
+ #if defined(OVR_OS_MS)
+ if(enable && !enabled)
+ {
+ OVR_ASSERT(vectoredHandle == nullptr);
+ vectoredHandle = AddVectoredExceptionHandler(1, Win32ExceptionFilter); // Windows call.
+ enabled = (vectoredHandle != nullptr);
+ OVR_ASSERT(enabled);
+ sExceptionHandler = this;
+ return enabled;
+ }
+ else if(!enable && enabled)
+ {
+ if(sExceptionHandler == this)
+ sExceptionHandler = nullptr;
+ OVR_ASSERT(vectoredHandle != nullptr);
+ ULONG result = RemoveVectoredExceptionHandler(vectoredHandle); // Windows call.
+ OVR_ASSERT_AND_UNUSED(result != 0, result);
+ vectoredHandle = nullptr;
+ enabled = false;
+ return true;
+ }
+
+ #elif defined(OVR_OS_APPLE)
+
+ if(enable && !enabled)
+ {
+ enabled = InitMachExceptionHandler();
+ OVR_ASSERT(enabled);
+ sExceptionHandler = this;
+ return enabled;
+ }
+ else if(!enable && enabled)
+ {
+ if(sExceptionHandler == this)
+ sExceptionHandler = nullptr;
+ ShutdownMachExceptionHandler();
+ enabled = false;
+ return true;
+ }
+ #else
+ OVR_UNUSED(enable);
+ #endif
+
+ return true;
+}
+
+
+void ExceptionHandler::EnableReportPrivacy(bool enable)
+{
+ reportPrivacyEnabled = enable;
+}
+
+void ExceptionHandler::WriteExceptionDescription()
+{
+ #if defined(OVR_OS_MS)
+ // There is some extra information available for AV exception.
+ if(exceptionInfo.exceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
+ {
+ const char* error = (exceptionInfo.exceptionRecord.ExceptionInformation[0] == 0) ? "reading" :
+ ((exceptionInfo.exceptionRecord.ExceptionInformation[0] == 1) ? "writing" : "executing");
+
+ char addressStr[24];
+ SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), exceptionInfo.pExceptionMemoryAddress);
+ OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription), "ACCESS_VIOLATION %s address %s", error, addressStr);
+ }
+ else
+ {
+ exceptionInfo.exceptionDescription[0] = 0;
+
+ // Process "standard" exceptions, other than 'access violation'
+ #define FORMAT_EXCEPTION(x) \
+ case EXCEPTION_##x: \
+ OVR::OVR_strlcpy(exceptionInfo.exceptionDescription, #x, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription)); \
+ break;
+
+ switch(exceptionInfo.exceptionRecord.ExceptionCode)
+ {
+ //FORMAT_EXCEPTION(ACCESS_VIOLATION) Already handled above.
+ FORMAT_EXCEPTION(DATATYPE_MISALIGNMENT)
+ FORMAT_EXCEPTION(BREAKPOINT)
+ FORMAT_EXCEPTION(SINGLE_STEP)
+ FORMAT_EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
+ FORMAT_EXCEPTION(FLT_DENORMAL_OPERAND)
+ FORMAT_EXCEPTION(FLT_DIVIDE_BY_ZERO)
+ FORMAT_EXCEPTION(FLT_INEXACT_RESULT)
+ FORMAT_EXCEPTION(FLT_INVALID_OPERATION)
+ FORMAT_EXCEPTION(FLT_OVERFLOW)
+ FORMAT_EXCEPTION(FLT_STACK_CHECK)
+ FORMAT_EXCEPTION(FLT_UNDERFLOW)
+ FORMAT_EXCEPTION(INT_DIVIDE_BY_ZERO)
+ FORMAT_EXCEPTION(INT_OVERFLOW)
+ FORMAT_EXCEPTION(PRIV_INSTRUCTION)
+ FORMAT_EXCEPTION(IN_PAGE_ERROR)
+ FORMAT_EXCEPTION(ILLEGAL_INSTRUCTION)
+ FORMAT_EXCEPTION(NONCONTINUABLE_EXCEPTION)
+ FORMAT_EXCEPTION(STACK_OVERFLOW)
+ FORMAT_EXCEPTION(INVALID_DISPOSITION)
+ FORMAT_EXCEPTION(GUARD_PAGE)
+ FORMAT_EXCEPTION(INVALID_HANDLE)
+ #if defined(EXCEPTION_POSSIBLE_DEADLOCK) && defined(STATUS_POSSIBLE_DEADLOCK) // This type seems to be non-existant in practice.
+ FORMAT_EXCEPTION(POSSIBLE_DEADLOCK)
+ #endif
+ }
+
+ // If not one of the "known" exceptions, try to get the string from NTDLL.DLL's message table.
+ if(exceptionInfo.exceptionDescription[0] == 0)
+ {
+ char addressStr[24];
+ SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), exceptionInfo.pExceptionMemoryAddress);
+
+ #if !defined(OVR_OS_CONSOLE) // If FormatMessage is supported...
+ char buffer[384];
+ DWORD capacity = OVR_ARRAY_COUNT(buffer);
+
+ const size_t length = (size_t)FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
+ GetModuleHandleW(L"NTDLL.DLL"), exceptionInfo.exceptionRecord.ExceptionCode, 0, buffer, capacity, nullptr);
+ if(length)
+ OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription),
+ "%s at instruction %s", buffer, addressStr);
+ #endif
+
+ // If everything else failed just show the hex code.
+ if(exceptionInfo.exceptionDescription[0] == 0)
+ OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription),
+ "Unknown exception 0x%08x at instruction %s", exceptionInfo.exceptionRecord.ExceptionCode, addressStr);
+ }
+ }
+
+ #elif defined(OVR_OS_APPLE)
+ struct MachExceptionInfo
+ {
+ static const char* GetCPUExceptionIdString(uint32_t cpuExceptionId)
+ {
+ const char* id;
+
+ #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
+ switch (cpuExceptionId)
+ {
+ case 0: id = "integer div/0"; break;
+ case 1: id = "breakpoint fault"; break;
+ case 2: id = "non-maskable interrupt"; break;
+ case 3: id = "int 3"; break;
+ case 4: id = "overflow"; break;
+ case 5: id = "bounds check failure"; break;
+ case 6: id = "invalid instruction"; break;
+ case 7: id = "coprocessor unavailable"; break;
+ case 8: id = "exception within exception"; break;
+ case 9: id = "coprocessor segment overrun"; break;
+ case 10: id = "invalid task switch"; break;
+ case 11: id = "segment not present"; break;
+ case 12: id = "stack exception"; break;
+ case 13: id = "general protection fault"; break;
+ case 14: id = "page fault"; break;
+ case 16: id = "coprocessor error"; break;
+ default: id = "<unknown>"; break;
+ }
+ #else
+ // To do: Support ARM or others.
+ #endif
+
+ return id;
+ }
+
+ static const char* GetMachExceptionTypeString(uint64_t exceptionCause)
+ {
+ switch (exceptionCause)
+ {
+ case EXC_ARITHMETIC: return "EXC_ARITHMETIC";
+ case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS";
+ case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION";
+ case EXC_BREAKPOINT: return "EXC_BREAKPOINT";
+ case EXC_CRASH: return "EXC_CRASH";
+ case EXC_EMULATION: return "EXC_EMULATION";
+ case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL";
+ case EXC_RPC_ALERT: return "EXC_RPC_ALERT";
+ case EXC_SOFTWARE: return "EXC_SOFTWARE";
+ case EXC_SYSCALL: return "EXC_SYSCALL";
+ };
+
+ return "EXC_<unknown>";
+ }
+
+ static const char* GetMachExceptionIdString(uint64_t machExceptionId, uint64_t code0)
+ {
+ const char* id = "<unknown>";
+
+ #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
+ switch (machExceptionId)
+ {
+ case EXC_ARITHMETIC:
+ switch (code0)
+ {
+ case EXC_I386_BOUND: id = "EXC_I386_BOUND"; break;
+ case EXC_I386_DIV: id = "EXC_I386_DIV"; break;
+ case EXC_I386_EMERR: id = "EXC_I386_EMERR"; break;
+ case EXC_I386_EXTERR: id = "EXC_I386_EXTERR"; break;
+ case EXC_I386_EXTOVR: id = "EXC_I386_EXTOVR"; break;
+ case EXC_I386_INTO: id = "EXC_I386_INTO"; break;
+ case EXC_I386_NOEXT: id = "EXC_I386_NOEXT"; break;
+ case EXC_I386_SSEEXTERR: id = "EXC_I386_SSEEXTERR"; break;
+ }
+ break;
+
+ case EXC_BAD_INSTRUCTION:
+ if(code0 == EXC_I386_INVOP)
+ id = "EXC_I386_INVOP";
+ break;
+
+ case EXC_BREAKPOINT:
+ if(code0 == EXC_I386_BPT)
+ id = "EXC_I386_BPT";
+ else if(code0 == EXC_I386_SGL)
+ id = "EXC_I386_SGL";
+ break;
+ };
+ #else
+ // To do.
+ #endif
+
+ return id;
+ }
+ };
+
+ OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription),
+ "Mach exception type: %llu (%s)\r\n", exceptionInfo.exceptionType, MachExceptionInfo::GetMachExceptionTypeString(exceptionInfo.exceptionType));
+
+ OVR::OVR_snprintf(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), "CPU exception info: exception id: %u (%s), exception id error: %u, fault memory address: %p\r\n",
+ exceptionInfo.cpuExceptionId, MachExceptionInfo::GetCPUExceptionIdString(exceptionInfo.cpuExceptionId), exceptionInfo.cpuExceptionIdError, exceptionInfo.pExceptionMemoryAddress);
+ OVR::OVR_strlcat(exceptionInfo.exceptionDescription, scratchBuffer, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription));
+
+
+ OVR::OVR_snprintf(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), "Mach exception info: exception id: %llu (%s), 0x%llx (%llu)\r\n", (uint64_t)exceptionInfo.machExceptionDetail[0],
+ MachExceptionInfo::GetMachExceptionIdString(exceptionInfo.exceptionType, exceptionInfo.machExceptionDetail[0]),
+ (uint64_t)exceptionInfo.machExceptionDetail[1], (uint64_t)exceptionInfo.machExceptionDetail[1]);
+ OVR::OVR_strlcat(exceptionInfo.exceptionDescription, scratchBuffer, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription));
+ #else
+ // To do.
+ exceptionInfo.exceptionDescription[0] = 0;
+ #endif
+}
+
+
+void ExceptionHandler::WriteReportLine(const char* pLine)
+{
+ fwrite(pLine, strlen(pLine), 1, file);
+}
+
+
+void ExceptionHandler::WriteReportLineF(const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ int length = OVR_vsnprintf(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), format, args);
+ if(length >= (int)OVR_ARRAY_COUNT(scratchBuffer)) // If we didn't have enough space...
+ length = (OVR_ARRAY_COUNT(scratchBuffer) - 1); // ... use what we have.
+ va_end(args);
+
+ fwrite(scratchBuffer, length, 1, file);
+}
+
+
+// Thread <name> <handle> <id>
+// 0 <module> <address> <function> <file>:<line>
+// 1 <module> <address> <function> <file>:<line>
+// . . .
+//
+void ExceptionHandler::WriteThreadCallstack(ThreadHandle threadHandle, ThreadSysId threadSysId, const char* additionalInfo)
+{
+ // We intentionally do not directly use the SymbolInfo::ReportThreadCallstack function because that function allocates memory,
+ // which we cannot do due to possibly being within an exception handler.
+
+ // Print the header
+ char threadName[32];
+ char threadHandleStr[32];
+ char threadSysIdStr[32];
+ char stackBaseStr[24];
+ char stackLimitStr[24];
+ char stackCurrentStr[24];
+ void* pStackBase;
+ void* pStackLimit;
+ bool isExceptionThread = (threadSysId == exceptionInfo.threadSysId);
+
+ #if defined(OVR_OS_MS) && (OVR_PTR_SIZE == 8)
+ void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.Rsp : nullptr; // We would need to suspend the thread, get its context, resume it, then read the rsp register. It turns out we are already doing that suspend/resume below in the backtrace call.
+ #elif defined(OVR_OS_MS)
+ void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.Esp : nullptr;
+ #elif defined(OVR_OS_MAC) && (OVR_PTR_SIZE == 8)
+ void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.threadState.uts.ts64.__rsp : nullptr;
+ #elif defined(OVR_OS_MAC)
+ void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.threadState.uts.ts32.__esp : nullptr;
+ #elif defined(OVR_OS_LINUX)
+ void* pStackCurrent = nullptr; // To do.
+ #endif
+
+ OVR::GetThreadStackBounds(pStackBase, pStackLimit, threadHandle);
+
+ OVR::Thread::GetThreadName(threadName, OVR_ARRAY_COUNT(threadName), threadName);
+ SprintfThreadHandle(threadHandleStr, OVR_ARRAY_COUNT(threadHandleStr), threadHandle);
+ SprintfThreadSysId(threadSysIdStr, OVR_ARRAY_COUNT(threadSysIdStr), threadSysId);
+ SprintfAddress(stackBaseStr, OVR_ARRAY_COUNT(stackBaseStr), pStackBase);
+ SprintfAddress(stackLimitStr, OVR_ARRAY_COUNT(stackLimitStr), pStackLimit);
+ SprintfAddress(stackCurrentStr, OVR_ARRAY_COUNT(stackCurrentStr), pStackCurrent);
+
+ if(threadName[0])
+ WriteReportLineF("Thread \"%s\" handle: %s, id: %s, stack base: %s, stack limit: %s, stack current: %s, %s\r\n", threadName, threadHandleStr, threadSysIdStr, stackBaseStr, stackLimitStr, stackCurrentStr, additionalInfo ? additionalInfo : "");
+ else
+ WriteReportLineF("Thread handle: %s, id: %s, stack base: %s, stack limit: %s, stack current: %s, %s\r\n", threadHandleStr, threadSysIdStr, stackBaseStr, stackLimitStr, stackCurrentStr, additionalInfo ? additionalInfo : "");
+
+ // Print the backtrace info
+ void* addressArray[64];
+ size_t addressCount = symbolLookup.GetBacktraceFromThreadSysId(addressArray, OVR_ARRAY_COUNT(addressArray), 0, threadSysId);
+ SymbolInfo symbolInfo;
+ const char* pModuleName;
+ size_t backtraceSkipCount = 0;
+
+ if(isExceptionThread)
+ {
+ // If this thread is the exception thread, skip some frames.
+ #if defined(OVR_OS_MS)
+ size_t i, iEnd = MIN(16, addressCount);
+
+ for(i = 0; i < iEnd; i++)
+ {
+ symbolLookup.LookupSymbol((uint64_t)addressArray[i], symbolInfo);
+ if(strstr(symbolInfo.function, "UserExceptionDispatcher") != nullptr)
+ break;
+ }
+
+ if(i < iEnd) // If found...
+ backtraceSkipCount = i;
+ else if(addressCount >= 9) // Else default to 9, which is coincidentally what works.
+ backtraceSkipCount = 9;
+ else
+ backtraceSkipCount = 0;
+
+ addressArray[backtraceSkipCount] = exceptionInfo.pExceptionInstructionAddress;
+ #endif
+ }
+
+ if(addressCount == 0)
+ {
+ WriteReportLine("<Unable to read backtrace>\r\n\r\n");
+ }
+ else
+ {
+ for(size_t i = backtraceSkipCount; i < addressCount; ++i)
+ {
+ symbolLookup.LookupSymbol((uint64_t)addressArray[i], symbolInfo);
+
+ if(symbolInfo.pModuleInfo && symbolInfo.pModuleInfo->name[0])
+ pModuleName = symbolInfo.pModuleInfo->name;
+ else
+ pModuleName = "(unknown module)";
+
+ char addressStr[24];
+ SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), addressArray[i]);
+
+ if(symbolInfo.filePath[0])
+ WriteReportLineF("%-2u %-24s %s %s+%d %s:%d\r\n%s", (unsigned)i, pModuleName, addressStr,
+ symbolInfo.function, symbolInfo.functionOffset, symbolInfo.filePath,
+ symbolInfo.fileLineNumber, (i + 1) == addressCount ? "\r\n" : "");
+ else
+ WriteReportLineF("%-2u %-24s %s %s+%d\r\n%s", (unsigned)i, pModuleName, addressStr,
+ symbolInfo.function, symbolInfo.functionOffset, (i + 1) == addressCount ? "\r\n" : ""); // If this is the last line, append another \r\n.
+ }
+ }
+}
+
+
+void ExceptionHandler::WriteReport()
+{
+ // It's important that we don't allocate any memory here if we can help it.
+ using namespace OVR;
+
+ if(strstr(reportFilePath, "%s")) // If the user-specified file path includes a date/time component...
+ {
+ char dateTimeBuffer[64];
+ FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(dateTimeBuffer), exceptionInfo.timeVal, true, true, false, true);
+ OVR_snprintf(reportFilePathActual, OVR_ARRAY_COUNT(reportFilePathActual), reportFilePath, dateTimeBuffer);
+ }
+ else
+ {
+ OVR_strlcpy(reportFilePathActual, reportFilePath, OVR_ARRAY_COUNT(reportFilePathActual));
+ }
+
+ file = fopen(reportFilePathActual, "w");
+ OVR_ASSERT(file != nullptr);
+ if(!file)
+ return;
+
+ symbolLookup.Initialize();
+
+ {
+ // Exception information
+ WriteReportLine("Exception Info\r\n");
+
+ WriteReportLineF("Exception report file: %s\r\n", reportFilePathActual);
+
+ #if defined(OVR_OS_MS)
+ if(miniDumpFilePath[0])
+ WriteReportLineF("Exception minidump file: %s\r\n", minidumpFilePathActual);
+ #endif
+
+ char dateTimeBuffer[64];
+ FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(dateTimeBuffer), exceptionInfo.timeVal, true, true, false, false);
+ WriteReportLineF("Time (GMT): %s\r\n", dateTimeBuffer);
+
+ FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(scratchBuffer), exceptionInfo.timeVal, true, true, true, false);
+ WriteReportLineF("Time (local): %s\r\n", dateTimeBuffer);
+ WriteReportLineF("Thread name: %s\r\n", exceptionInfo.threadName[0] ? exceptionInfo.threadName : "(not available)"); // It's never possible on Windows to get thread names, as they are stored in the debugger at runtime.
+
+ SprintfThreadHandle(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), exceptionInfo.threadHandle);
+ OVR_strlcat(scratchBuffer, "\r\n", OVR_ARRAY_COUNT(scratchBuffer));
+ WriteReportLine("Thread handle: ");
+ WriteReportLine(scratchBuffer);
+
+ SprintfThreadSysId(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), exceptionInfo.threadSysId);
+ OVR_strlcat(scratchBuffer, "\r\n", OVR_ARRAY_COUNT(scratchBuffer));
+ WriteReportLine("Thread sys id: ");
+ WriteReportLine(scratchBuffer);
+
+ char addressStr[24];
+ SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), exceptionInfo.pExceptionInstructionAddress);
+ WriteReportLineF("Exception instruction address: %s (see callstack below)\r\n", addressStr);
+ WriteReportLineF("Exception description: %s\r\n", exceptionInfo.exceptionDescription);
+
+ if(symbolLookup.LookupSymbol((uint64_t)exceptionInfo.pExceptionInstructionAddress, exceptionInfo.symbolInfo))
+ {
+ if(exceptionInfo.symbolInfo.filePath[0])
+ WriteReportLineF("Exception location: %s (%d)\r\n", exceptionInfo.symbolInfo.filePath, exceptionInfo.symbolInfo.fileLineNumber);
+ else
+ WriteReportLineF("Exception location: %s (%d)\r\n", exceptionInfo.symbolInfo.function, exceptionInfo.symbolInfo.functionOffset);
+ }
+
+ // To consider: print exceptionInfo.cpuContext registers
+ }
+
+ // OVR information
+ WriteReportLine("\r\nOVR Info\r\n");
+ WriteReportLineF("OVR time: %f\r\n", ovr_GetTimeInSeconds());
+ WriteReportLineF("OVR version: %s\r\n", ovr_GetVersionString());
+
+ // OVR util information
+ // The following would be useful to use if they didn't allocate memory, which we can't do.
+ // To do: see if we can have versions of the functions below which don't allocate memory
+ // or allocate it safely (e.g. use an alternative heap).
+ // String OVR::GetDisplayDriverVersion();
+ // String OVR::GetCameraDriverVersion();
+
+ // OVR HMD information
+ WriteReportLine("\r\nOVR HMD Info\r\n");
+
+ const OVR::List<OVR::CAPI::HMDState>& hmdStateList = OVR::CAPI::HMDState::GetHMDStateList();
+ const OVR::CAPI::HMDState* pHMDState = hmdStateList.GetFirst();
+
+ if(hmdStateList.IsNull(pHMDState))
+ {
+ WriteReportLine("No HMDs found.\r\n");
+ }
+
+ while(!hmdStateList.IsNull(pHMDState))
+ {
+ if(pHMDState->pProfile)
+ {
+ const char* user = pHMDState->pProfile->GetValue(OVR_KEY_USER);
+
+ if(user)
+ WriteReportLineF("Profile user: %s\r\n", reportPrivacyEnabled ? "<disabled by report privacy settings>" : user);
+ else
+ WriteReportLine("Null profile user\r\n");
+
+ float NeckEyeDistance[2];
+ float EyeToNoseDistance[2];
+ float MaxEyeToPlateDist[2];
+ pHMDState->pProfile->GetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, NeckEyeDistance, 2);
+ pHMDState->pProfile->GetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, EyeToNoseDistance, 2);
+ pHMDState->pProfile->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, MaxEyeToPlateDist, 2);
+
+ WriteReportLineF("Player height: %f, eye height: %f, IPD: %f, Neck eye distance: %f,%f, eye relief dial: %d, eye to nose distance: %f,%f, max eye to plate distance: %f,%f, custom eye render: %s\r\n",
+ pHMDState->pProfile->GetFloatValue(OVR_KEY_PLAYER_HEIGHT, 0.f),
+ pHMDState->pProfile->GetFloatValue(OVR_KEY_EYE_HEIGHT, 0.f),
+ pHMDState->pProfile->GetFloatValue(OVR_KEY_IPD, 0.f),
+ NeckEyeDistance[0], NeckEyeDistance[1],
+ pHMDState->pProfile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, 0),
+ EyeToNoseDistance[0], EyeToNoseDistance[1],
+ MaxEyeToPlateDist[0], MaxEyeToPlateDist[1],
+ pHMDState->pProfile->GetBoolValue(OVR_KEY_CUSTOM_EYE_RENDER, false) ? "yes" : "no");
+
+ // Not currently used:
+ // OVR_KEY_NAME
+ // OVR_KEY_GENDER
+ // OVR_KEY_EYE_CUP
+ // OVR_KEY_CAMERA_POSITION
+ }
+ else
+ {
+ WriteReportLine("Null HMD profile\r\n");
+ }
+
+ if(pHMDState->pHmdDesc) // This should usually be true.
+ {
+ WriteReportLineF("HMD %d: Type: %u ProductName: %s, Manufacturer: %s VendorId: %d, ProductId: %d, SerialNumber: %s, FirmwareMajor: %d, FirmwareMinor: %d, Resolution: %dx%d, DisplayDeviceName: %s, DisplayId: %d\r\n",
+ 0, (unsigned)pHMDState->pHmdDesc->Type, pHMDState->pHmdDesc->ProductName, pHMDState->pHmdDesc->Manufacturer, pHMDState->pHmdDesc->VendorId,
+ pHMDState->pHmdDesc->ProductId, pHMDState->pHmdDesc->SerialNumber, pHMDState->pHmdDesc->FirmwareMajor, pHMDState->pHmdDesc->FirmwareMinor,
+ pHMDState->pHmdDesc->Resolution.w, pHMDState->pHmdDesc->Resolution.h, pHMDState->pHmdDesc->DisplayDeviceName, pHMDState->pHmdDesc->DisplayId);
+
+ // HSW display state
+ ovrHSWDisplayState hswDS;
+ ovrHmd_GetHSWDisplayState(pHMDState->pHmdDesc, &hswDS);
+ WriteReportLineF("HSW displayed for hmd: %s\r\n", hswDS.Displayed ? "yes" : "no");
+ }
+
+ char threadIdStr[24];
+ SprintfAddress(threadIdStr, OVR_ARRAY_COUNT(threadIdStr), pHMDState->BeginFrameThreadId);
+
+ WriteReportLineF("Hmd Caps: %x, Hmd Service Caps: %x, Latency test active: %s, Last frame time: %f, Last get frame time: %f, Rendering configred: %s, Begin frame called: %s, Begin frame thread id: %s\r\n",
+ pHMDState->EnabledHmdCaps, pHMDState->EnabledServiceHmdCaps, pHMDState->LatencyTestActive ? "yes" : "no", pHMDState->LastFrameTimeSeconds, pHMDState->LastGetFrameTimeSeconds, pHMDState->RenderingConfigured ? "yes" : "no",
+ pHMDState->BeginFrameCalled ? "yes" : "no", threadIdStr);
+
+ if(pHMDState->pLastError)
+ {
+ WriteReportLineF("OVR last error for hmd: %s\r\n", pHMDState->pLastError);
+ }
+
+ pHMDState = hmdStateList.GetNext(pHMDState);
+ }
+
+ #if defined(OVR_OS_WIN32)
+ {
+ WriteReportLine("\r\nApp Info\r\n");
+
+ // Print the app path.
+ char appPath[MAX_PATH];
+ GetCurrentProcessFilePath(appPath, OVR_ARRAY_COUNT(appPath));
+ WriteReportLineF("Process path: %s\r\n", appPath);
+
+ #if (OVR_PTR_SIZE == 4)
+ WriteReportLine("App format: 32 bit\r\n");
+ #else
+ WriteReportLine("App format: 64 bit\r\n");
+ #endif
+
+ // Print the app version
+ wchar_t pathW[MAX_PATH] = {};
+ GetModuleFileNameW(0, pathW, (DWORD)OVR_ARRAY_COUNT(pathW));
+ DWORD dwUnused;
+ DWORD dwSize = GetFileVersionInfoSizeW(pathW, &dwUnused);
+ scratchBuffer[0] = 0;
+
+ if(dwSize > 0)
+ {
+ void* const pVersionData = SafeMMapAlloc(dwSize);
+
+ if(pVersionData)
+ {
+ if(GetFileVersionInfoW(pathW, 0, dwSize, pVersionData))
+ {
+ VS_FIXEDFILEINFO* pFFI;
+ UINT size;
+
+ if(VerQueryValueA(pVersionData, "\\", (void**)&pFFI, &size))
+ {
+ WriteReportLineF("App version: %u.%u.%u.%u\r\n",
+ HIWORD(pFFI->dwFileVersionMS), LOWORD(pFFI->dwFileVersionMS),
+ HIWORD(pFFI->dwFileVersionLS), LOWORD(pFFI->dwFileVersionLS));
+ }
+ }
+
+ SafeMMapFree(pVersionData, dwSize);
+ }
+ }
+
+ if(!scratchBuffer[0]) // If version info couldn't be found or read...
+ WriteReportLine("App version info not present\r\n");
+ }
+
+ {
+ WriteReportLine("\r\nSystem Info\r\n");
+
+ OSVERSIONINFOEXW vi;
+ memset(&vi, 0, sizeof(vi));
+ vi.dwOSVersionInfoSize = sizeof(vi);
+ GetVersionExW((LPOSVERSIONINFOW)&vi); // Cast to the older type.
+
+ char osVersionName[256];
+ GetOSVersionName(osVersionName, OVR_ARRAY_COUNT(osVersionName));
+ WriteReportLineF("OS name: %s, version: %u.%u build %u, %s, platform id: %u, service pack: %ls\r\n",
+ osVersionName, vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber, Is64BitOS() ? "64 bit" : "32 bit",
+ vi.dwPlatformId, vi.szCSDVersion[0] ? vi.szCSDVersion : L"<none>");
+
+ WriteReportLineF("Debugger present: %s\r\n", OVRIsDebuggerPresent() ? "yes" : "no");
+
+ // System info
+ SYSTEM_INFO systemInfo;
+ GetNativeSystemInfo(&systemInfo);
+
+ WriteReportLineF("Processor count: %u\r\n", systemInfo.dwNumberOfProcessors);
+
+ // Windows Vista and later:
+ // BOOL WINAPI GetLogicalProcessorInformation(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer, PDWORD ReturnLength);
+
+ if(systemInfo.wProcessorArchitecture == 0)
+ WriteReportLineF("Processor type: x86\r\n");
+ else if(systemInfo.wProcessorArchitecture == 9)
+ WriteReportLineF("Processor type: x86-64\r\n");
+ else if(systemInfo.wProcessorArchitecture == 10)
+ WriteReportLineF("Processor type: x86 on x86-64\r\n");
+
+ WriteReportLineF("Processor level: %u\r\n", systemInfo.wProcessorLevel);
+ WriteReportLineF("Processor revision: %u\r\n", systemInfo.wProcessorRevision);
+
+ // Memory information
+ MEMORYSTATUSEX memoryStatusEx;
+ memset(&memoryStatusEx, 0, sizeof(memoryStatusEx));
+ memoryStatusEx.dwLength = sizeof(memoryStatusEx);
+ GlobalMemoryStatusEx(&memoryStatusEx);
+
+ WriteReportLineF("Memory load: %d%%\r\n", memoryStatusEx.dwMemoryLoad);
+ WriteReportLineF("Total physical memory: %I64d MiB\r\n", memoryStatusEx.ullTotalPhys / (1024 * 1024)); // Or are Mebibytes equal to (1024 * 1000)
+ WriteReportLineF("Available physical memory: %I64d MiB\r\n", memoryStatusEx.ullAvailPhys / (1024 * 1024));
+ WriteReportLineF("Total page file memory: %I64d MiB\r\n", memoryStatusEx.ullTotalPageFile / (1024 * 1024));
+ WriteReportLineF("Available page file memory: %I64d MiB\r\n", memoryStatusEx.ullAvailPageFile / (1024 * 1024));
+ WriteReportLineF("Total virtual memory: %I64d MiB\r\n", memoryStatusEx.ullTotalVirtual / (1024 * 1024));
+ WriteReportLineF("Free virtual memory: %I64d MiB\r\n", memoryStatusEx.ullAvailVirtual / (1024 * 1024));
+
+ DISPLAY_DEVICE dd;
+ memset(&dd, 0, sizeof(DISPLAY_DEVICE));
+ dd.cb = sizeof(DISPLAY_DEVICE);
+
+ for(int i = 0; EnumDisplayDevicesW(nullptr, (DWORD)i, &dd, EDD_GET_DEVICE_INTERFACE_NAME); ++i)
+ {
+ WriteReportLineF("Display Device %d name: %ls, context: %ls, primary: %s, mirroring: %s\r\n",
+ i, dd.DeviceName, dd.DeviceString, (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no", (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) ? "yes" : "no");
+ }
+ }
+
+ // Print video card information
+ // http://msdn.microsoft.com/en-us/library/aa394512%28v=vs.85%29.aspx
+ {
+ IWbemLocator* pIWbemLocator = nullptr;
+ BSTR bstrServer = nullptr;
+ IWbemServices* pIWbemServices = nullptr;
+ BSTR bstrWQL = nullptr;
+ BSTR bstrPath = nullptr;
+ IEnumWbemClassObject* pEnum = nullptr;
+
+ CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+
+ HRESULT hr = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator);
+ if(FAILED(hr))
+ goto End;
+
+ bstrServer = SysAllocString(L"\\\\.\\root\\cimv2");
+ hr = pIWbemLocator->ConnectServer(bstrServer, nullptr, nullptr, 0L, 0L, nullptr, nullptr, &pIWbemServices);
+ if(FAILED(hr))
+ goto End;
+
+ hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL,
+ RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DEFAULT);
+ if(FAILED(hr))
+ goto End;
+
+ bstrWQL = SysAllocString(L"WQL");
+ bstrPath = SysAllocString(L"select * from Win32_VideoController");
+ hr = pIWbemServices->ExecQuery(bstrWQL, bstrPath, WBEM_FLAG_FORWARD_ONLY, nullptr, &pEnum);
+ if(FAILED(hr))
+ goto End;
+
+ ULONG uReturned;
+ IWbemClassObject* pObj = nullptr;
+ hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);
+ if(FAILED(hr))
+ goto End;
+
+ WriteReportLine("\r\nDisplay adapter list\r\n");
+
+ for(unsigned i = 0; SUCCEEDED(hr) && uReturned; i++)
+ {
+ char sString[256];
+ VARIANT var;
+
+ if(i > 0)
+ WriteReportLine("\r\n");
+
+ WriteReportLineF("Info for display adapter %u\r\n", i);
+
+ hr = pObj->Get(L"Name", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
+ WriteReportLineF("Display Adapter Name: %s\r\n", sString);
+ }
+
+ hr = pObj->Get(L"AdapterRAM", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ WriteReportLineF("Display Adapter RAM: %u %s\r\n",
+ ((uint32_t)var.lVal > (1024*1024*1024) ? (uint32_t)var.lVal/(1024*1024*1024) : (uint32_t)var.lVal/(1024*1024)), ((uint32_t)var.lVal > (1024*1024*1024) ? "GiB" : "MiB"));
+ }
+
+ hr = pObj->Get(L"DeviceID", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
+ WriteReportLineF("Display Adapter DeviceID: %s\r\n", sString);
+ }
+
+ hr = pObj->Get(L"DriverVersion", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
+ WriteReportLineF("Display Adapter DriverVersion: %s\r\n", sString);
+ }
+
+ hr = pObj->Get(L"DriverDate", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ // http://technet.microsoft.com/en-us/library/ee156576.aspx
+ wchar_t year[5] = { var.bstrVal[0], var.bstrVal[1], var.bstrVal[2], var.bstrVal[3], 0 };
+ wchar_t month[3] = { var.bstrVal[4], var.bstrVal[5], 0 };
+ wchar_t monthDay[3] = { var.bstrVal[6], var.bstrVal[7], 0 };
+
+ WriteReportLineF("Display Adapter DriverDate (US format): %ls/%ls/%ls\r\n", month, monthDay, year);
+ }
+
+ // VideoProcessor
+ hr = pObj->Get(L"VideoProcessor", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
+ WriteReportLineF("Display Adapter VideoProcessor %s\r\n", sString);
+ }
+
+ hr = pObj->Get(L"VideoModeDescription", 0, &var, nullptr, nullptr);
+ if(SUCCEEDED(hr))
+ {
+ WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
+ WriteReportLineF("Display Adapter VideoModeDescription: %s\r\n", sString);
+ }
+
+ pObj->Release();
+
+ hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);
+ }
+
+ End:
+ if(pEnum)
+ pEnum->Release();
+ if(bstrPath)
+ SysFreeString(bstrPath);
+ if(bstrWQL)
+ SysFreeString(bstrWQL);
+ if(pIWbemServices)
+ pIWbemServices->Release();
+ if(bstrServer)
+ SysFreeString(bstrServer);
+ if(pIWbemLocator)
+ pIWbemLocator->Release();
+
+ CoUninitialize();
+ }
+
+ {
+ // Print a list of threads.
+ DWORD currentProcessId = GetCurrentProcessId();
+ HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, currentProcessId); // ICreateToolhelp32Snapshot actually ignores currentProcessId.
+
+ if(hThreadSnap != INVALID_HANDLE_VALUE)
+ {
+ THREADENTRY32 te32;
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ if(Thread32First(hThreadSnap, &te32))
+ {
+ WriteReportLine("\r\nThread list\r\n");
+
+ do {
+ if(te32.th32OwnerProcessID == currentProcessId)
+ {
+ HANDLE hThread = ConvertThreadSysIdToThreadHandle(te32.th32ThreadID);
+
+ if(hThread)
+ {
+ char buffer[96]; // Can't use scratchBuffer, because it's used by WriteThreadCallstack.
+ OVR_snprintf(buffer, OVR_ARRAY_COUNT(buffer), "base priority: %ld, delta priority: %ld", te32.tpBasePri, te32.tpDeltaPri);
+
+ bool threadIsExceptionThread = (te32.th32ThreadID == (DWORD)exceptionInfo.threadSysId);
+ if(threadIsExceptionThread)
+ OVR_strlcat(buffer, ", exception thread", OVR_ARRAY_COUNT(buffer));
+
+ WriteThreadCallstack(hThread, (OVR::ThreadSysId)te32.th32ThreadID, buffer);
+ FreeThreadHandle(hThread);
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+ }
+
+ CloseHandle(hThreadSnap);
+ }
+ }
+
+ {
+ // Print a list of the current modules within this process.
+ // DbgHelp.dll also provides a EnumerateLoadedModules64 function.
+ // To do: Convert the code below to use the GetModuleInfoArray function which we now have.
+ #if defined(OVR_OS_CONSOLE)
+ struct MODULEINFO {
+ LPVOID lpBaseOfDll;
+ DWORD SizeOfImage;
+ LPVOID EntryPoint;
+ };
+ HMODULE hModule = LoadLibraryW(L"toolhelpx.dll");
+ #else
+ HMODULE hModule = LoadLibraryW(L"psapi.dll");
+ #endif
+
+ if(hModule)
+ {
+ typedef BOOL (WINAPI * ENUMPROCESSMODULES) (HANDLE hProcess, HMODULE* phModule, DWORD cb, LPDWORD lpcbNeeded);
+ typedef DWORD (WINAPI * GETMODULEBASENAME) (HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
+ typedef DWORD (WINAPI * GETMODULEFILENAMEEX) (HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
+ typedef BOOL (WINAPI * GETMODULEINFORMATION)(HANDLE hProcess, HMODULE hModule, MODULEINFO* pmi, DWORD nSize);
+
+ #if defined(OVR_OS_CONSOLE)
+ ENUMPROCESSMODULES pEnumProcessModules = (ENUMPROCESSMODULES) (uintptr_t)GetProcAddress(hModule, "K32EnumProcessModules");
+ GETMODULEBASENAME pGetModuleBaseName = (GETMODULEBASENAME) (uintptr_t)GetProcAddress(hModule, "K32GetModuleBaseNameW");
+ GETMODULEFILENAMEEX pGetModuleFileNameEx = (GETMODULEFILENAMEEX) (uintptr_t)GetProcAddress(hModule, "K32GetModuleFileNameExW");
+ GETMODULEINFORMATION pGetModuleInformation = (GETMODULEINFORMATION)(uintptr_t)GetProcAddress(hModule, "K32GetModuleInformation");
+ #else
+ ENUMPROCESSMODULES pEnumProcessModules = (ENUMPROCESSMODULES) (uintptr_t)GetProcAddress(hModule, "EnumProcessModules");
+ GETMODULEBASENAME pGetModuleBaseName = (GETMODULEBASENAME) (uintptr_t)GetProcAddress(hModule, "GetModuleBaseNameW");
+ GETMODULEFILENAMEEX pGetModuleFileNameEx = (GETMODULEFILENAMEEX) (uintptr_t)GetProcAddress(hModule, "GetModuleFileNameExW");
+ GETMODULEINFORMATION pGetModuleInformation = (GETMODULEINFORMATION)(uintptr_t)GetProcAddress(hModule, "GetModuleInformation");
+ #endif
+
+ HANDLE hProcess = GetCurrentProcess();
+ HMODULE hModuleArray[200];
+ DWORD cbNeeded;
+
+ if(pEnumProcessModules(hProcess, hModuleArray, sizeof(hModuleArray), &cbNeeded))
+ {
+ size_t actualModuleCount = (cbNeeded / sizeof(HMODULE));
+
+ if(actualModuleCount > OVR_ARRAY_COUNT(hModuleArray)) //If hModuleArray's capacity was not enough...
+ actualModuleCount = OVR_ARRAY_COUNT(hModuleArray);
+
+ // Print a header
+ WriteReportLine("\r\nModule list\r\n");
+
+ #if (OVR_PTR_SIZE == 4)
+ WriteReportLine("Base Size Entrypoint Name Path\r\n");
+ #else
+ WriteReportLine("Base Size Entrypoint Name Path\r\n");
+ #endif
+
+ // And go through the list one by one
+ for(size_t i = 0; i < actualModuleCount; i++)
+ {
+ MODULEINFO mi;
+ size_t length;
+
+ if(!pGetModuleInformation(hProcess, hModuleArray[i], &mi, sizeof(mi)))
+ {
+ mi.EntryPoint = nullptr;
+ mi.lpBaseOfDll = nullptr;
+ mi.SizeOfImage = 0;
+ }
+
+ // Write the base name.
+ wchar_t name[MAX_PATH + 3];
+ name[0] = '"';
+ if(pGetModuleBaseName(hProcess, hModuleArray[i], name + 1, MAX_PATH))
+ length = wcslen(name);
+ else
+ {
+ wcscpy(name + 1, L"(unknown)");
+ length = 10;
+ }
+
+ name[length] = '"';
+ name[length + 1] = '\0';
+
+ // Write the path
+ wchar_t path[MAX_PATH + 3];
+ path[0] = '"';
+ if(pGetModuleFileNameEx(hProcess, hModuleArray[i], path + 1, MAX_PATH))
+ length = wcslen(path);
+ else
+ {
+ wcscpy(path + 1, L"(unknown)");
+ length = 10;
+ }
+ path[length] = '"';
+ path[length + 1] = '\0';
+
+ #if (OVR_PTR_SIZE == 4)
+ WriteReportLineF("0x%08x, 0x%08x 0x%08x %-24ls %ls\r\n", (uint32_t)mi.lpBaseOfDll, (uint32_t)mi.SizeOfImage, (uint32_t)mi.EntryPoint, name, path);
+ #else
+ WriteReportLineF("0x%016I64x 0x%016I64x 0x%016I64x %-24ls %ls\r\n", (uint64_t)mi.lpBaseOfDll, (uint64_t)mi.SizeOfImage, (uint64_t)mi.EntryPoint, name, path);
+ #endif
+ }
+ }
+ }
+ }
+
+ {
+ // Print a list of processes.
+ // DbgHelp.dll provides a SymEnumProcesses function, but it's available with DbgHelp.dll v6.2 which doesn't ship with Windows until Windows 8.
+ WriteReportLine("\r\nProcess list\r\n");
+
+ if(reportPrivacyEnabled)
+ WriteReportLine("Disabled by report privacy settings\r\n");
+ else
+ {
+ HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+
+ if(hProcessSnapshot != INVALID_HANDLE_VALUE)
+ {
+ PROCESSENTRY32W pe32;
+ memset(&pe32, 0, sizeof(pe32));
+ pe32.dwSize = sizeof(pe32);
+
+ if(Process32FirstW(hProcessSnapshot, &pe32))
+ {
+ WriteReportLine("Process Id File\r\n");
+
+ do {
+ // Try to get the full path to the process, as pe32.szExeFile holds only the process file name.
+ // This will typically fail with a privilege error unless this process has debug privileges: http://support.microsoft.com/kb/131065/en-us
+ wchar_t filePathW[MAX_PATH];
+ const wchar_t* pFilePathW = pe32.szExeFile;
+ HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID); // With Windows Vista+ we can use PROCESS_QUERY_LIMITED_INFORMATION.
+ if(hProcess)
+ {
+ if(GetProcessImageFileName(hProcess, filePathW, (DWORD)OVR_ARRAY_COUNT(filePathW)))
+ pFilePathW = filePathW;
+ }
+
+ WriteReportLineF("0x%08x %ls\r\n", pe32.th32ProcessID, pFilePathW);
+ } while(Process32NextW(hProcessSnapshot, &pe32));
+ }
+
+ CloseHandle(hProcessSnapshot);
+ }
+ else
+ {
+ WriteReportLine("Unable to read process list\r\n");
+ }
+ }
+ }
+
+ #elif defined(OVR_OS_APPLE)
+
+ WriteReportLine("\r\nApp Info\r\n");
+
+ // App path
+ const pid_t processId = getpid();
+ WriteReportLineF("Process id: ", "%lld (0x%llx)\r\n", (int64_t)processId, (int64_t)processId);
+
+ char appPath[PATH_MAX];
+ GetCurrentProcessFilePath(appPath, OVR_ARRAY_COUNT(appPath));
+ WriteReportLineF("Process path: %s\r\n", appPath);
+
+ #if (OVR_PTR_SIZE == 4)
+ WriteReportLine("App format: 32 bit\r\n");
+ #else
+ WriteReportLine("App format: 64 bit\r\n");
+ #endif
+
+ // App version
+ // To do.
+
+ // System Info
+ WriteReportLine("\r\nSystem Info\r\n");
+
+ char osVersionName[256];
+ GetOSVersionName(osVersionName, OVR_ARRAY_COUNT(osVersionName));
+ WriteReportLineF("OS name: %s, %s\r\n", osVersionName, Is64BitOS() ? "64 bit" : "32 bit");
+
+ int name[2];
+ int intValue;
+ size_t length;
+ char tempBuffer[256];
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_OSTYPE;
+ length = sizeof(tempBuffer);
+ tempBuffer[0] = 0;
+ if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
+ {
+ WriteReportLineF("KERN_OSTYPE: %s\r\n", tempBuffer);
+ }
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_OSREV;
+ length = sizeof(intValue);
+ intValue = 0;
+ if(sysctl(name, 2, &intValue, &length, nullptr, 0) == 0)
+ {
+ WriteReportLineF("KERN_OSREV: %d\r\n", intValue);
+ }
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_OSRELEASE;
+ length = sizeof(tempBuffer);
+ tempBuffer[0] = 0;
+ if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
+ WriteReportLineF("KERN_OSRELEASE: %s\r\n", tempBuffer);
+
+ name[0] = CTL_HW;
+ name[1] = HW_MACHINE;
+ length = sizeof(tempBuffer);
+ tempBuffer[0] = 0;
+ if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
+ WriteReportLineF("HW_MACHINE: %s\r\n", tempBuffer);
+
+ name[0] = CTL_HW;
+ name[1] = HW_MODEL;
+ length = sizeof(tempBuffer);
+ tempBuffer[0] = 0;
+ if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
+ WriteReportLineF("sHW_MODEL: %s\r\n", tempBuffer);
+
+ name[0] = CTL_HW;
+ name[1] = HW_NCPU;
+ length = sizeof(intValue);
+ intValue = 0;
+ if(sysctl(name, 2, &intValue, &length, nullptr, 0) == 0)
+ WriteReportLineF("HW_NCPU: %d\r\n", intValue);
+
+ length = sizeof(tempBuffer);
+ tempBuffer[0] = 0;
+ if(sysctlbyname("machdep.cpu.brand_string", &tempBuffer, &length, nullptr, 0) == 0)
+ WriteReportLineF("machdep.cpu.brand_string: %s\r\n", tempBuffer);
+
+ length = sizeof(tempBuffer);
+ tempBuffer[0] = 0;
+ if(sysctlbyname("hw.acpi.thermal.tz0.temperature", &tempBuffer, &length, nullptr, 0) == 0)
+ WriteReportLineF("hw.acpi.thermal.tz0.temperature: %s\r\n", tempBuffer);
+
+ host_basic_info_data_t hostinfo;
+ mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
+ kern_return_t kr = host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostinfo, &count);
+
+ if(kr == KERN_SUCCESS)
+ {
+ const uint64_t memoryMib = (uint64_t)hostinfo.max_mem / (1024 * 1024);
+ WriteReportLineF("System memory: %lld Mib (%.1f Gib)\r\n", memoryMib, (double)memoryMib / 1024);
+ }
+
+ // Video card info
+ // To do.
+
+ // Thread list
+ mach_port_t taskSelf = mach_task_self();
+ thread_act_port_array_t threadArray;
+ mach_msg_type_number_t threadCount;
+
+ kern_return_t result = task_threads(taskSelf, &threadArray, &threadCount);
+
+ if(result == KERN_SUCCESS)
+ {
+ WriteReportLine("\r\nThread list\r\n");
+
+ for(mach_msg_type_number_t i = 0; i < threadCount; i++)
+ {
+ union TBIUnion{
+ natural_t words[THREAD_INFO_MAX];
+ thread_basic_info tbi;
+ };
+
+ TBIUnion tbiUnion;
+ mach_port_t thread = threadArray[i];
+ pthread_t pthread = pthread_from_mach_thread_np(thread); // We assume the thread was created through pthreads.
+
+ char threadState[32] = "unknown";
+ mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
+ result = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&tbiUnion, &threadInfoCount);
+
+ if(result == KERN_SUCCESS)
+ {
+ const char* state;
+
+ switch (tbiUnion.tbi.run_state)
+ {
+ case TH_STATE_HALTED: state = "halted"; break;
+ case TH_STATE_RUNNING: state = "running"; break;
+ case TH_STATE_STOPPED: state = "stopped"; break;
+ case TH_STATE_UNINTERRUPTIBLE: state = "uninterruptible"; break;
+ case TH_STATE_WAITING: state = "waiting"; break;
+ default: state = "<unknown>"; break;
+ }
+
+ OVR_snprintf(threadState, OVR_ARRAY_COUNT(threadState), "%s", state);
+ if(tbiUnion.tbi.flags & TH_FLAGS_IDLE)
+ OVR_strlcat(threadState, ", idle", sizeof(threadState));
+ if(tbiUnion.tbi.flags & TH_FLAGS_SWAPPED)
+ OVR_strlcat(threadState, ", swapped", sizeof(threadState));
+ }
+
+ thread_identifier_info threadIdentifierInfo;
+ memset(&threadIdentifierInfo, 0, sizeof(threadIdentifierInfo));
+
+ mach_msg_type_number_t threadIdentifierInfoCount = THREAD_IDENTIFIER_INFO_COUNT;
+ thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&threadIdentifierInfo, &threadIdentifierInfoCount);
+
+ proc_threadinfo procThreadInfo;
+ memset(&procThreadInfo, 0, sizeof(procThreadInfo));
+ result = proc_pidinfo(processId, PROC_PIDTHREADINFO, threadIdentifierInfo.thread_handle, &procThreadInfo, sizeof(procThreadInfo));
+ OVR_UNUSED(result);
+
+ char buffer[256]; // Can't use scratchBuffer, because it's used by WriteThreadCallstack.
+ OVR_snprintf(buffer, OVR_ARRAY_COUNT(buffer), "state: %s, suspend count: %d, kernel priority: %d", threadState, (int)tbiUnion.tbi.suspend_count, (int)procThreadInfo.pth_curpri);
+
+ bool threadIsExceptionThread = (thread == exceptionInfo.threadSysId);
+ if(threadIsExceptionThread)
+ OVR_strlcat(buffer, ", exception thread", OVR_ARRAY_COUNT(buffer));
+
+ WriteThreadCallstack(pthread, thread, buffer);
+ }
+
+ vm_deallocate(taskSelf, (vm_address_t)threadArray, threadCount * sizeof(thread_act_t));
+ }
+
+
+ WriteReportLine("\r\nModule list\r\n");
+
+ const size_t mifCapacity = 256;
+ const size_t mifAllocSize = mifCapacity * sizeof(ModuleInfo);
+ ModuleInfo* moduleInfoArray = (ModuleInfo*)SafeMMapAlloc(mifAllocSize);
+
+ if(moduleInfoArray)
+ {
+ #if (OVR_PTR_SIZE == 4)
+ WriteReportLine("Base Size Name Path\r\n");
+ #else
+ WriteReportLine("Base Size Name Path\r\n");
+ #endif
+
+ size_t moduleCount = symbolLookup.GetModuleInfoArray(moduleInfoArray, mifCapacity);
+ if(moduleCount > mifCapacity)
+ moduleCount = mifCapacity;
+
+ for(size_t i = 0; i < moduleCount; i++)
+ {
+ const ModuleInfo& mi = moduleInfoArray[i];
+
+ #if (OVR_PTR_SIZE == 4)
+ WriteReportLineF("0x%08x, 0x%08x %-24s %s\r\n", (uint32_t)mi.baseAddress, (uint32_t)mi.size, mi.name, mi.filePath);
+ #else
+ WriteReportLineF("0x%016llx 0x%016llx %-24s %s\r\n", (uint64_t)mi.baseAddress, (uint64_t)mi.size, mi.name, mi.filePath);
+ #endif
+ }
+
+ SafeMMapFree(moduleInfoArray, mifAllocSize);
+ }
+
+
+ WriteReportLine("\r\nProcess list\r\n");
+
+ if(reportPrivacyEnabled)
+ WriteReportLine("Disabled by report privacy settings\r\n");
+ else
+ {
+ WriteReportLine("Process Id File\r\n");
+
+ pid_t pidArray[1024];
+ int processCount = proc_listpids(PROC_ALL_PIDS, 0, pidArray, sizeof(pidArray)); // Important that we use sizeof not OVR_ARRAY_COUNT.
+ char processFilePath[PATH_MAX];
+
+ for(int i = 0; i < processCount; i++)
+ {
+ if(proc_pidpath(pidArray[i], processFilePath, sizeof(processFilePath)) > 0)
+ WriteReportLineF("%-10d %s\r\n", pidArray[i], processFilePath);
+ }
+
+ if(!processCount)
+ WriteReportLine("Unable to read process list\r\n");
+ }
+
+ #elif defined(OVR_OS_UNIX)
+ Is64BitOS();
+ GetCurrentProcessFilePath(nullptr, 0);
+ GetFileNameFromPath(nullptr);
+ GetOSVersionName(nullptr, 0);
+
+ #endif // OVR_OS_MS
+
+ symbolLookup.Shutdown();
+
+ fclose(file);
+ file = nullptr;
+}
+
+
+void ExceptionHandler::WriteMiniDump()
+{
+ if(strstr(miniDumpFilePath, "%s")) // If the user-specified file path includes a date/time component...
+ {
+ char dateTimeBuffer[64];
+ FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(dateTimeBuffer), exceptionInfo.timeVal, true, true, false, true);
+ OVR_snprintf(minidumpFilePathActual, OVR_ARRAY_COUNT(minidumpFilePathActual), miniDumpFilePath, dateTimeBuffer);
+ }
+ else
+ {
+ OVR_strlcpy(minidumpFilePathActual, miniDumpFilePath, OVR_ARRAY_COUNT(minidumpFilePathActual));
+ }
+
+ #if defined(OVR_OS_WIN32) || defined(OVR_OS_WIN64) || (defined(OVR_OS_MS) && defined(OVR_OS_CONSOLE))
+ #if defined(OVR_OS_CONSOLE)
+ typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE dumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PVOID CallbackParam);
+ HMODULE hModuleDbgHelp = LoadLibraryW(L"toolhelpx.dll");
+ #else
+ typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE dumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
+ HMODULE hModuleDbgHelp = LoadLibraryW(L"DbgHelp.dll");
+ #endif
+
+ MINIDUMPWRITEDUMP pMiniDumpWriteDump = hModuleDbgHelp ? (MINIDUMPWRITEDUMP)(void*)GetProcAddress(hModuleDbgHelp, "MiniDumpWriteDump") : nullptr;
+
+ if(pMiniDumpWriteDump)
+ {
+ wchar_t miniDumpFilePathW[OVR_MAX_PATH];
+ OVR::UTF8Util::DecodeString(miniDumpFilePathW, minidumpFilePathActual, -1); // Problem: DecodeString provides no way to specify the destination capacity.
+
+ HANDLE hFile = CreateFileW(miniDumpFilePathW, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);
+
+ if(hFile != INVALID_HANDLE_VALUE)
+ {
+ MINIDUMP_EXCEPTION_INFORMATION minidumpExceptionInfo = { ::GetCurrentThreadId(), pExceptionPointers, TRUE };
+
+ #if defined(OVR_OS_CONSOLE)
+ BOOL result = pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
+ (MINIDUMP_TYPE)miniDumpType, &exceptionInfo,
+ (CONST PMINIDUMP_USER_STREAM_INFORMATION)nullptr, nullptr);
+ #else
+ BOOL result = pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
+ (MINIDUMP_TYPE)miniDumpFlags, &minidumpExceptionInfo,
+ (CONST PMINIDUMP_USER_STREAM_INFORMATION)nullptr, (CONST PMINIDUMP_CALLBACK_INFORMATION)nullptr);
+ #endif
+
+ OVR_ASSERT_AND_UNUSED(result, result);
+ CloseHandle(hFile);
+ hFile = 0;
+ }
+ else
+ {
+ OVR_ASSERT(pMiniDumpWriteDump); // OVR_FAIL_F(("ExceptionHandler::WriteMiniDump: Failed to create minidump file at %s", minidumpFilePathActual));
+ }
+ }
+
+ FreeLibrary(hModuleDbgHelp);
+ #else
+ // Some platforms support various forms or exception reports and core dumps which are automatically generated upon us
+ // returning from our own exception handling. We might want to put something here if we are using a custom version of
+ // this, such as Google Breakpad.
+ #endif
+}
+
+
+void ExceptionHandler::SetExceptionListener(ExceptionListener* pExceptionListener, uintptr_t userValue)
+{
+ exceptionListener = pExceptionListener;
+ exceptionListenerUserValue = userValue;
+}
+
+
+void ExceptionHandler::SetAppDescription(const char* pAppDescription)
+{
+ appDescription = pAppDescription;
+}
+
+
+void ExceptionHandler::SetExceptionPaths(const char* exceptionReportPath, const char* exceptionMiniDumpFilePath)
+{
+ char tempPath[OVR_MAX_PATH];
+
+ if(exceptionReportPath)
+ {
+ if(OVR_stricmp(exceptionReportPath, "default") == 0)
+ {
+ GetUserDocumentsDirectory(tempPath, OVR_ARRAY_COUNT(tempPath));
+ OVR::OVR_strlcat(tempPath, "Exception Report (%s).txt", OVR_ARRAY_COUNT(tempPath));
+ exceptionReportPath = tempPath;
+ }
+
+ OVR_strlcpy(reportFilePath, exceptionReportPath, OVR_ARRAY_COUNT(reportFilePath));
+ }
+ else
+ {
+ reportFilePath[0] = '\0';
+ }
+
+ if(exceptionMiniDumpFilePath)
+ {
+ if(OVR_stricmp(exceptionMiniDumpFilePath, "default") == 0)
+ {
+ GetUserDocumentsDirectory(tempPath, OVR_ARRAY_COUNT(tempPath));
+ OVR::OVR_strlcat(tempPath, "Exception Minidump (%s).mdmp", OVR_ARRAY_COUNT(tempPath));
+ exceptionMiniDumpFilePath = tempPath;
+ }
+
+ OVR_strlcpy(miniDumpFilePath, exceptionMiniDumpFilePath, OVR_ARRAY_COUNT(miniDumpFilePath));
+ }
+ else
+ {
+ miniDumpFilePath[0] = '\0';
+ }
+}
+
+
+void ExceptionHandler::SetCodeBaseDirectoryPaths(const char* codeBaseDirectoryPathArray[], size_t codeBaseDirectoryPathCount)
+{
+ for(size_t i = 0, iCount = OVR::Alg::Min<size_t>(codeBaseDirectoryPathCount, OVR_ARRAY_COUNT(codeBasePathArray)); i != iCount; ++i)
+ {
+ codeBasePathArray[i] = codeBaseDirectoryPathArray[i];
+ }
+}
+
+const char* ExceptionHandler::GetExceptionUIText(const char* exceptionReportPath)
+{
+ char* uiText = nullptr;
+ OVR::SysFile file(exceptionReportPath, SysFile::Open_Read, SysFile::Mode_ReadWrite);
+
+ if(file.IsValid())
+ {
+ size_t length = (size_t)file.GetLength();
+ uiText = (char*)OVR::SafeMMapAlloc(length + 1);
+
+ if(uiText)
+ {
+ file.Read((uint8_t*)uiText, (int)length);
+ uiText[length] = '\0';
+ file.Close();
+
+ // Currently on Mac our message box implementation is unable to display arbitrarily large amounts of text.
+ // So we reduce its size to a more summary version before presenting.
+ #if defined(OVR_OS_MAC)
+ struct Find { static char* PreviousChar(char* p, char c){ while(*p != c) p--; return p; } }; // Assumes the given c is present prior to p.
+
+ // Print that the full report is at <file path>
+ // Exception Info section
+ // Exception thread callstack.
+ char empty[] = "";
+ char* pExceptionInfoBegin = strstr(uiText, "Exception Info") ? strstr(uiText, "Exception Info") : empty;
+ char* pExceptionInfoEnd = (pExceptionInfoBegin == empty) ? (empty + 1) : strstr(uiText, "\r\n\r\n");
+ char* pExceptionThreadArea = strstr(uiText, ", exception thread");
+ char* pExceptionThreadBegin = pExceptionThreadArea ? Find::PreviousChar(pExceptionThreadArea, '\n') + 1 : empty;
+ char* pExceptionThreadEnd = (pExceptionThreadBegin == empty) ? (empty + 1) : strstr(pExceptionThreadArea, "\r\n\r\n");
+
+ if(!pExceptionInfoEnd)
+ pExceptionInfoEnd = pExceptionInfoBegin;
+ *pExceptionInfoEnd = '\0';
+
+ if(!pExceptionThreadEnd)
+ pExceptionThreadEnd = pExceptionThreadBegin;
+ *pExceptionThreadEnd = '\0';
+
+ size_t uiTextBriefLength = OVR_snprintf(nullptr, 0, "Full report:%s\n\nSummary report:\n%s\n\n%s", exceptionReportPath, pExceptionInfoBegin, pExceptionThreadBegin);
+ char* uiTextBrief = (char*)OVR::SafeMMapAlloc(uiTextBriefLength + 1);
+
+ if(uiTextBrief)
+ {
+ OVR_snprintf(uiTextBrief, uiTextBriefLength + 1, "Full report:%s\n\nSummary report:\n%s\n\n%s", exceptionReportPath, pExceptionInfoBegin, pExceptionThreadBegin);
+ OVR::SafeMMapFree(uiText, length);
+ uiText = uiTextBrief;
+ }
+ #endif
+ }
+ }
+
+ return uiText;
+}
+
+void ExceptionHandler::FreeExceptionUIText(const char* messageBoxText)
+{
+ OVR::SafeMMapFree(messageBoxText, OVR_strlen(messageBoxText));
+}
+
+
+} // namespace OVR
+
+
+OVR_RESTORE_MSVC_WARNING()
+