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