ovr_sdk

view LibOVR/Src/Kernel/OVR_DebugHelp.cpp @ 0:1b39a1b46319

initial 0.4.4
author John Tsiombikas <nuclear@member.fsf.org>
date Wed, 14 Jan 2015 06:51:16 +0200
parents
children
line source
1 /************************************************************************************
3 Filename : ExceptionHandler.cpp
4 Content : Platform-independent exception handling interface
5 Created : October 6, 2014
7 Copyright : Copyright 2014 Oculus VR, LLC. All Rights reserved.
9 Licensed under the Apache License, Version 2.0 (the "License");
10 you may not use this file except in compliance with the License.
11 You may obtain a copy of the License at
13 http://www.apache.org/licenses/LICENSE-2.0
15 Unless required by applicable law or agreed to in writing, software
16 distributed under the License is distributed on an "AS IS" BASIS,
17 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 See the License for the specific language governing permissions and
19 limitations under the License.
21 ************************************************************************************/
24 #include "OVR_DebugHelp.h"
25 #include "OVR_Types.h"
26 #include "OVR_UTF8Util.h"
27 #include "../OVR_CAPI.h"
28 #include "../OVR_CAPI_Keys.h"
29 #include "../CAPI/CAPI_HMDState.h"
30 #include <stdlib.h>
32 #if defined(OVR_OS_WIN32) || defined(OVR_OS_WIN64)
33 #pragma warning(push, 0)
34 #include <Windows.h>
35 #include <WinNT.h>
36 #include <DbgHelp.h>
37 #include <WinVer.h>
38 #include <share.h>
39 #include <Psapi.h>
40 #include <TlHelp32.h>
41 #include <comutil.h>
42 #include <Wbemcli.h>
43 #include <Wbemidl.h>
44 #include <ShlObj.h>
45 #include <ObjBase.h>
46 #pragma warning(pop)
48 #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
49 #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.
50 #pragma comment(lib, "shell32.lib")
52 // NtQueryInformation and THREAD_BASIC_INFORMATION are undocumented but frequently needed for digging into thread information.
53 typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG);
55 struct THREAD_BASIC_INFORMATION
56 {
57 LONG ExitStatus;
58 PVOID TebBaseAddress;
59 PVOID UniqueProcessId;
60 PVOID UniqueThreadId;
61 PVOID Priority;
62 PVOID BasePriority;
63 };
65 #ifndef UNW_FLAG_NHANDLER // Older Windows SDKs don't support this.
66 #define UNW_FLAG_NHANDLER 0
67 #endif
69 #elif defined(OVR_OS_MAC)
70 #include <unistd.h>
71 #include <sys/sysctl.h>
72 #include <sys/utsname.h>
73 #include <sys/types.h>
74 #include <sys/mman.h>
75 #include <stdlib.h>
76 #include <stdio.h>
77 #include <pthread.h>
78 #include <mach/mach.h>
79 #include <mach/mach_error.h>
80 #include <mach/thread_status.h>
81 #include <mach/exception.h>
82 #include <mach/task.h>
83 #include <mach/thread_act.h>
84 #include <mach-o/dyld.h>
85 #include <mach-o/dyld_images.h>
86 #include <libproc.h>
87 #include <libgen.h>
88 #include <execinfo.h>
89 #include <cxxabi.h>
90 #include "OVR_mach_exc_OSX.h"
92 #if defined(__LP64__)
93 typedef struct mach_header_64 MachHeader;
94 typedef struct segment_command_64 SegmentCommand;
95 typedef struct section_64 Section;
96 #define kLCSegment LC_SEGMENT_64
97 #else
98 typedef struct mach_header MachHeader;
99 typedef struct segment_command SegmentCommand;
100 typedef struct section Section;
101 #define kLCSegment LC_SEGMENT
102 #endif
104 extern "C" const struct dyld_all_image_infos* _dyld_get_all_image_infos(); // From libdyld.dylib
106 #elif defined(OVR_OS_UNIX)
107 #include <unistd.h>
108 #include <sys/sysctl.h>
109 #include <sys/utsname.h>
110 #include <sys/types.h>
111 #include <sys/ptrace.h>
112 #include <sys/wait.h>
113 #include <sys/mman.h>
114 #include <stdlib.h>
115 #include <stdio.h>
116 #include <pthread.h>
117 #include <libgen.h>
118 #include <execinfo.h>
119 #include <cxxabi.h>
120 //#include <libunwind.h> // Can't use this until we can ensure that we have an installed version of it.
121 #endif
123 #if !defined(MIN)
124 #define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
125 #define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
126 #endif
129 OVR_DISABLE_MSVC_WARNING(4351) // new behavior: elements of array will be default initialized
130 OVR_DISABLE_MSVC_WARNING(4996) // This function or variable may be unsafe
135 #if defined(OVR_OS_APPLE)
136 static OVR::ExceptionHandler* sExceptionHandler = nullptr;
137 const uint32_t sMachCancelMessageType = 0x0ca9ce11; // This is a made-up value of our own choice.
139 extern "C"
140 {
141 kern_return_t catch_mach_exception_raise_OVR(mach_port_t /*exceptionPort*/, mach_port_t /*threadSysId*/,
142 mach_port_t /*machTask*/, exception_type_t /*machExceptionType*/,
143 mach_exception_data_t /*machExceptionData*/, mach_msg_type_number_t /*machExceptionDataCount*/)
144 {
145 return KERN_FAILURE;
146 }
148 kern_return_t catch_mach_exception_raise_state_OVR(mach_port_t /*exceptionPort*/, exception_type_t /*exceptionType*/,
149 const mach_exception_data_t /*machExceptionData*/, mach_msg_type_number_t /*machExceptionDataCount*/,
150 int* /*pMachExceptionFlavor*/, const thread_state_t /*threadStatePrev*/, mach_msg_type_number_t /*threaStatePrevCount*/,
151 thread_state_t /*threadStateNew*/, mach_msg_type_number_t* /*pThreadStateNewCount*/)
152 {
153 return KERN_FAILURE;
154 }
156 kern_return_t catch_mach_exception_raise_state_identity_OVR(mach_port_t exceptionPort, mach_port_t threadSysId, mach_port_t machTask,
157 exception_type_t exceptionType, mach_exception_data_type_t* machExceptionData,
158 mach_msg_type_number_t machExceptionDataCount, int* pMachExceptionFlavor,
159 thread_state_t threadStatePrev, mach_msg_type_number_t threadStatePrevCount,
160 thread_state_t threadStateNew, mach_msg_type_number_t* pThreadStateNewCount)
161 {
162 return sExceptionHandler->HandleMachException(exceptionPort, threadSysId, machTask, exceptionType, machExceptionData,
163 machExceptionDataCount, pMachExceptionFlavor, threadStatePrev, threadStatePrevCount,
164 threadStateNew, pThreadStateNewCount);
165 }
167 void* MachHandlerThreadFunctionStatic(void* pExceptionHandlerVoid)
168 {
169 return static_cast<OVR::ExceptionHandler*>(pExceptionHandlerVoid)->MachHandlerThreadFunction();
170 }
172 } // extern "C"
173 #endif
178 namespace OVR {
181 void GetInstructionPointer(void*& pInstruction)
182 {
183 #if defined(OVR_CC_MSVC)
184 pInstruction = _ReturnAddress();
185 #else // GCC, clang
186 pInstruction = __builtin_return_address(0);
187 #endif
188 }
191 static size_t SprintfAddress(char* threadHandleStr, size_t threadHandleStrCapacity, const void* pAddress)
192 {
193 #if defined(OVR_CC_MSVC)
194 #if (OVR_PTR_SIZE >= 8)
195 return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "0x%016I64x", pAddress); // e.g. 0x0123456789abcdef
196 #else
197 return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "0x%08x", pAddress); // e.g. 0x89abcdef
198 #endif
199 #else
200 #if (OVR_PTR_SIZE >= 8)
201 return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "%016llx", pAddress); // e.g. 0x0123456789abcdef
202 #else
203 return OVR_snprintf(threadHandleStr, threadHandleStrCapacity, "%08x", pAddress); // e.g. 0x89abcdef
204 #endif
205 #endif
206 }
209 static size_t SprintfThreadHandle(char* threadHandleStr, size_t threadHandleStrCapacity, const ThreadHandle& threadHandle)
210 {
211 return SprintfAddress(threadHandleStr, threadHandleStrCapacity, threadHandle);
212 }
215 static size_t SprintfThreadSysId(char* threadSysIdStr, size_t threadSysIdStrCapacity, const ThreadSysId& threadSysId)
216 {
217 #if defined(OVR_CC_MSVC) // Somebody could conceivably use VC++ with a different standard library that supports %ll. And VS2012+ also support %ll.
218 return OVR_snprintf(threadSysIdStr, threadSysIdStrCapacity, "%I64u", (uint64_t)threadSysId);
219 #else
220 return OVR_snprintf(threadSysIdStr, threadSysIdStrCapacity, "%llu", (uint64_t)threadSysId);
221 #endif
222 }
228 void GetThreadStackBounds(void*& pStackBase, void*& pStackLimit, ThreadHandle threadHandle)
229 {
230 #if defined(OVR_OS_WIN64) || defined(OVR_OS_WIN32) || (defined(OVR_OS_MS) && defined(OVR_OS_CONSOLE))
231 ThreadSysId threadSysIdCurrent = (ThreadSysId)GetCurrentThreadId();
232 ThreadSysId threadSysId;
233 NT_TIB* pTIB = nullptr;
235 if(threadHandle == OVR_THREADHANDLE_INVALID)
236 threadSysId = threadSysIdCurrent;
237 else
238 threadSysId = ConvertThreadHandleToThreadSysId(threadHandle);
240 if(threadSysId == threadSysIdCurrent)
241 {
242 #if (OVR_PTR_SIZE == 4)
243 // Need to use __asm__("movl %%fs:0x18, %0" : "=r" (pTIB) : : ); under gcc/clang.
244 __asm {
245 mov eax, fs:[18h]
246 mov pTIB, eax
247 }
248 #else
249 pTIB = (NT_TIB*)NtCurrentTeb();
250 #endif
251 }
252 else
253 {
254 #if (OVR_PTR_SIZE == 4)
255 // It turns out we don't need to suspend the thread when getting SegFs/SegGS, as that's
256 // constant per thread and doesn't require the thread to be suspended.
257 //SuspendThread((HANDLE)threadHandle);
258 CONTEXT context;
259 memset(&context, 0, sizeof(context));
260 context.ContextFlags = CONTEXT_SEGMENTS;
261 GetThreadContext((HANDLE)threadHandle, &context); // Requires THREAD_QUERY_INFORMATION privileges.
263 LDT_ENTRY ldtEntry;
264 if(GetThreadSelectorEntry(threadHandle, context.SegFs, &ldtEntry)) // Requires THREAD_QUERY_INFORMATION
265 pTIB = (NT_TIB*)((ldtEntry.HighWord.Bits.BaseHi << 24 ) | (ldtEntry.HighWord.Bits.BaseMid << 16) | ldtEntry.BaseLow);
267 //ResumeThread((HANDLE)threadHandle);
268 #else
269 // We cannot use GetThreadSelectorEntry or Wow64GetThreadSelectorEntry on Win64.
270 // 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.
271 // mov rax, qword ptr gs:[30h]
272 // mov qword ptr [pTIB],rax
273 // In the meantime we rely on the NtQueryInformationThread function.
275 static NtQueryInformationThreadFunc spNtQueryInformationThread = nullptr;
277 if(!spNtQueryInformationThread)
278 {
279 HMODULE hNTDLL = GetModuleHandleA("ntdll.dll");
280 spNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
281 }
283 if(spNtQueryInformationThread)
284 {
285 THREAD_BASIC_INFORMATION tbi;
287 memset(&tbi, 0, sizeof(tbi));
288 LONG result = spNtQueryInformationThread(threadHandle, 0, &tbi, sizeof(tbi), nullptr); // Requires THREAD_QUERY_INFORMATION privileges
289 if(result == 0)
290 pTIB = (NT_TIB*)tbi.TebBaseAddress;
291 }
292 #endif
293 }
295 if(pTIB)
296 {
297 pStackBase = (void*)pTIB->StackBase;
298 pStackLimit = (void*)pTIB->StackLimit;
299 }
300 else
301 {
302 pStackBase = nullptr;
303 pStackLimit = nullptr;
304 }
306 #elif defined(OVR_OS_APPLE)
307 if(!threadHandle)
308 threadHandle = pthread_self();
310 pStackBase = pthread_get_stackaddr_np((pthread_t)threadHandle);
311 size_t stackSize = pthread_get_stacksize_np((pthread_t)threadHandle);
312 pStackLimit = (void*)((size_t)pStackBase - stackSize);
314 #elif defined(OVR_OS_UNIX)
315 pStackBase = nullptr;
316 pStackLimit = nullptr;
318 pthread_attr_t threadAttr;
319 pthread_attr_init(&threadAttr);
321 #if defined(OVR_OS_LINUX)
322 int result = pthread_getattr_np((pthread_t)threadHandle, &threadAttr);
323 #else
324 int result = pthread_attr_get_np((pthread_t)threadHandle, &threadAttr);
325 #endif
327 if(result == 0)
328 {
329 size_t stackSize = 0;
330 result = pthread_attr_getstack(&threadAttr, &pStackLimit, &stackSize);
332 if(result == 0)
333 pStackBase = (void*)((uintptr_t)pStackLimit + stackSize); // We assume the stack grows downward.
334 }
336 #endif
337 }
340 bool OVRIsDebuggerPresent()
341 {
342 #if defined(OVR_OS_MS)
343 return ::IsDebuggerPresent() != 0;
345 #elif defined(OVR_OS_APPLE)
346 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
347 struct kinfo_proc info;
348 size_t size = sizeof(info);
350 info.kp_proc.p_flag = 0;
351 sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0);
353 return ((info.kp_proc.p_flag & P_TRACED) != 0);
355 #elif (defined(OVR_OS_LINUX) || defined(OVR_OS_BSD)) && !defined(OVR_OS_ANDROID)
356 // This works better than the PT_TRACE_ME approach.
357 // However, it presents a problem:
358 // http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html
359 // When the application calls fork() from a signal handler and any of the
360 // fork handlers registered by pthread_atfork() calls a function that is
361 // not asynch-signal-safe, the behavior is undefined.
362 // We may need to provide two pathways within this function, one of which
363 // doesn't fork and instead uses PT_TRACE_ME.
364 int pid = fork();
365 int status;
366 bool present = false;
368 if (pid == -1) // If fork failed...
369 {
370 // perror("fork");
371 }
372 else if (pid == 0) // If we are the child process...
373 {
374 int ppid = getppid();
376 #if defined(OVR_OS_LINUX)
377 if (ptrace(PTRACE_ATTACH, ppid, nullptr, nullptr) == 0)
378 #else
379 if (ptrace(PT_ATTACH, ppid, nullptr, nullptr) == 0)
380 #endif
381 {
382 waitpid(ppid, nullptr, 0);
384 #if defined(OVR_OS_LINUX)
385 ptrace(PTRACE_CONT, getppid(), nullptr, nullptr);
386 ptrace(PTRACE_DETACH, getppid(), nullptr, nullptr);
387 #else
388 ptrace(PT_CONTINUE, getppid(), nullptr, nullptr);
389 ptrace(PT_DETACH, getppid(), nullptr, nullptr);
390 #endif
391 }
392 else
393 {
394 // ptrace failed so the debugger is present.
395 present = true;
396 }
398 exit(present ? 1 : 0); // The WEXITSTATUS call below will read this exit value.
399 }
400 else // Else we are the original process.
401 {
402 waitpid(pid, &status, 0);
403 present = WEXITSTATUS(status) ? true : false; // Read the exit value from the child's call to exit.
404 }
406 return present;
408 #elif defined(PT_TRACE_ME) && !defined(OVR_OS_ANDROID)
409 return (ptrace(PT_TRACE_ME, 0, 1, 0) < 0);
411 #else
412 return false;
413 #endif
414 }
417 // Exits the process with the given exit code.
418 void ExitProcess(intptr_t processReturnValue)
419 {
420 exit((int)processReturnValue);
421 }
424 void* SafeMMapAlloc(size_t size)
425 {
426 #if defined(OVR_OS_MS)
427 return VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // size is rounded up to a page. // Returned memory is 0-filled.
429 #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
430 #if !defined(MAP_FAILED)
431 #define MAP_FAILED ((void*)-1)
432 #endif
434 void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); // Returned memory is 0-filled.
435 if(result == MAP_FAILED) // mmap returns MAP_FAILED (-1) upon failure.
436 result = nullptr;
437 return result;
438 #endif
439 }
442 void SafeMMapFree(const void* memory, size_t size)
443 {
444 #if defined(OVR_OS_MS)
445 OVR_UNUSED(size);
446 VirtualFree(const_cast<void*>(memory), 0, MEM_RELEASE);
448 #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
449 size_t pageSize = getpagesize();
450 size = (((size + (pageSize - 1)) / pageSize) * pageSize);
451 munmap(const_cast<void*>(memory), size); // Must supply the size to munmap.
452 #endif
453 }
456 // Note that we can't just return sizeof(void*) == 8, as we may have the case of a
457 // 32 bit app running on a 64 bit operating system.
458 static bool Is64BitOS()
459 {
460 #if (OVR_PTR_SIZE >= 8)
461 return true;
463 #elif defined(OVR_OS_WIN32) || defined(OVR_OS_WIN64)
464 BOOL is64BitOS = FALSE;
465 bool IsWow64ProcessPresent = (GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "IsWow64Process") != nullptr);
466 return (IsWow64ProcessPresent && IsWow64Process(GetCurrentProcess(), &is64BitOS) && is64BitOS);
468 #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
469 utsname utsName;
470 memset(&utsName, 0, sizeof(utsName));
471 return (uname(&utsName) == 0) && (strcmp(utsName.machine, "x86_64") == 0);
473 #else
474 return false;
475 #endif
476 }
479 // The output will always be 0-terminated.
480 // Returns the required strlen of the output.
481 // Returns (size_t)-1 on failure.
482 size_t SpawnShellCommand(const char* shellCommand, char* output, size_t outputCapacity)
483 {
484 #if defined(OVR_OS_UNIX) || defined(OVR_OS_APPLE)
485 FILE* pFile = popen(shellCommand, "r");
487 if(pFile)
488 {
489 size_t requiredLength = 0;
490 char buffer[256];
492 while(fgets(buffer, sizeof(buffer), pFile)) // fgets 0-terminates the buffer.
493 {
494 size_t length = OVR_strlen(buffer);
495 requiredLength += length;
497 if(outputCapacity)
498 {
499 OVR_strlcpy(output, buffer, outputCapacity);
500 length = MIN(outputCapacity, length);
501 }
503 output += length;
504 outputCapacity -= length;
505 }
507 pclose(pFile);
508 return requiredLength;
509 }
510 #else
511 // To do. Properly solving this on Windows requires a bit of code.
512 OVR_UNUSED(shellCommand);
513 OVR_UNUSED(output);
514 OVR_UNUSED(outputCapacity);
515 #endif
517 return (size_t)-1;
518 }
521 // Retrieves a directory path which ends with a path separator.
522 // Returns the required strlen of the path.
523 // Guarantees the presence of the directory upon returning true.
524 static size_t GetUserDocumentsDirectory(char* directoryPath, size_t directoryPathCapacity)
525 {
526 #if defined(OVR_OS_MS)
527 wchar_t pathW[MAX_PATH + 1]; // +1 because we append a path separator.
528 HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, nullptr, SHGFP_TYPE_CURRENT, pathW);
530 if(SUCCEEDED(hr))
531 {
532 OVR_UNUSED(directoryPathCapacity);
534 intptr_t requiredUTF8Length = OVR::UTF8Util::GetEncodeStringSize(pathW); // Returns required strlen.
535 if(requiredUTF8Length < MAX_PATH) // We need space for a trailing path separator.
536 {
537 OVR::UTF8Util::EncodeString(directoryPath, pathW, -1);
538 OVR::OVR_strlcat(directoryPath, "\\", directoryPathCapacity);
539 }
541 return (requiredUTF8Length + 1);
542 }
544 #elif defined(OVR_OS_MAC)
545 // This is the same location that Apple puts its OS-generated .crash files.
546 const char* home = getenv("HOME");
547 size_t requiredStrlen = OVR::OVR_snprintf(directoryPath, directoryPathCapacity, "%s/Library/Logs/DiagnosticReports/", home ? home : "/Users/Shared/Logs/DiagnosticReports/");
548 // To do: create the directory if it doesn't already exist.
549 return requiredStrlen;
551 #elif defined(OVR_OS_UNIX) || defined(OVR_OS_MAC)
552 const char* home = getenv("HOME");
553 size_t requiredStrlen = OVR::OVR_snprintf(directoryPath, directoryPathCapacity, "%s/Library/", home ? home : "/Users/Shared/");
554 // To do: create the directory if it doesn't already exist.
555 return requiredStrlen;
556 #endif
558 return 0;
559 }
562 // Retrieves the name of the given thread.
563 // To do: Move this to OVR_Threads.h
564 bool GetThreadName(OVR::ThreadHandle threadHandle, char* threadName, size_t threadNameCapacity)
565 {
566 #if defined(OVR_OS_APPLE) || defined(OVR_OS_LINUX)
567 int result = pthread_getname_np((pthread_t)threadHandle, threadName, threadNameCapacity);
568 if(result == 0)
569 return true;
570 #else
571 // This is not currently possible on Windows, as only the debugger stores the thread name. We could potentially use a vectored
572 // exception handler to catch all thread name exceptions (0x406d1388) and record them in a static list at runtime. To detect
573 // thread exit we could use WMI Win32_ThreadStopTrace. Maintain a list of thread names between these two events.
574 OVR_UNUSED(threadHandle);
575 OVR_UNUSED(threadNameCapacity);
576 #endif
578 if(threadNameCapacity)
579 threadName[0] = 0;
581 return false;
582 }
585 OVR::ThreadSysId ConvertThreadHandleToThreadSysId(OVR::ThreadHandle threadHandle)
586 {
587 #if defined(OVR_OS_WIN64)
588 return (OVR::ThreadSysId)::GetThreadId(threadHandle); // Requires THREAD_QUERY_INFORMATION privileges.
590 #elif defined(OVR_OS_WIN32)
591 typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE);
593 static volatile bool sInitialized = false;
594 static GetThreadIdFunc spGetThreadIdFunc = nullptr;
595 static NtQueryInformationThreadFunc spNtQueryInformationThread = nullptr;
597 if(!sInitialized)
598 {
599 HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
600 if(hKernel32)
601 spGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId");
603 if(!spGetThreadIdFunc)
604 {
605 HMODULE hNTDLL = GetModuleHandleA("ntdll.dll");
607 if(hNTDLL)
608 spNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
609 }
611 sInitialized = true;
612 }
614 if(spGetThreadIdFunc)
615 return (OVR::ThreadSysId)spGetThreadIdFunc(threadHandle);
617 if(spNtQueryInformationThread)
618 {
619 THREAD_BASIC_INFORMATION tbi;
621 if(spNtQueryInformationThread(threadHandle, 0, &tbi, sizeof(tbi), nullptr) == 0)
622 return (OVR::ThreadSysId)tbi.UniqueThreadId;
623 }
625 return OVR_THREADSYSID_INVALID;
627 #elif defined(OVR_OS_APPLE)
628 mach_port_t threadSysId = pthread_mach_thread_np((pthread_t)threadHandle); // OS 10.4 and later.
629 return (ThreadSysId)threadSysId;
631 #elif defined(OVR_OS_LINUX)
633 // 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.
634 OVR_UNUSED(threadHandle);
635 return OVR_THREADSYSID_INVALID;
637 #else
638 OVR_UNUSED(threadHandle);
639 return OVR_THREADSYSID_INVALID;
640 #endif
641 }
644 OVR::ThreadHandle ConvertThreadSysIdToThreadHandle(OVR::ThreadSysId threadSysId)
645 {
646 #if defined(OVR_OS_MS)
647 // We currently request the given rights because that's what users of this function typically need it for. Ideally there would
648 // 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.
649 // 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.
650 OVR::ThreadHandle threadHandle = ::OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, TRUE, (DWORD)threadSysId);
652 if(threadHandle == OVR_THREADHANDLE_INVALID)
653 {
654 threadHandle = ::OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, TRUE, (DWORD)threadSysId);
656 if(threadHandle == OVR_THREADHANDLE_INVALID)
657 threadHandle = ::OpenThread(THREAD_QUERY_INFORMATION, TRUE, (DWORD)threadSysId);
658 }
660 return threadHandle;
661 #elif defined(OVR_OS_MAC)
662 return (ThreadHandle)pthread_from_mach_thread_np((mach_port_t)threadSysId);
663 #else
664 return (ThreadHandle)threadSysId;
665 #endif
666 }
669 void FreeThreadHandle(OVR::ThreadHandle threadHandle)
670 {
671 #if defined(OVR_OS_MS)
672 if(threadHandle != OVR_THREADHANDLE_INVALID)
673 ::CloseHandle(threadHandle);
674 #else
675 OVR_UNUSED(threadHandle);
676 #endif
677 }
680 OVR::ThreadSysId GetCurrentThreadSysId()
681 {
682 #if defined(OVR_OS_MS)
683 return ::GetCurrentThreadId();
684 #elif defined(OVR_OS_APPLE)
685 return (ThreadSysId)mach_thread_self();
686 #else
687 return (ThreadSysId)pthread_self();
688 #endif
689 }
693 static void GetCurrentProcessFilePath(char* appPath, size_t appPathCapacity)
694 {
695 appPath[0] = 0;
697 #if defined(OVR_OS_MS)
698 wchar_t pathW[MAX_PATH];
699 GetModuleFileNameW(0, pathW, (DWORD)OVR_ARRAY_COUNT(pathW));
701 size_t requiredUTF8Length = (size_t)OVR::UTF8Util::GetEncodeStringSize(pathW); // Returns required strlen.
703 if(requiredUTF8Length < appPathCapacity)
704 {
705 OVR::UTF8Util::EncodeString(appPath, pathW, -1);
706 }
707 else
708 {
709 appPath[0] = 0;
710 }
712 #elif defined(OVR_OS_APPLE)
713 struct BunderFolder
714 {
715 // Returns true if pStr ends with pFind, case insensitively.
716 // To do: Move OVR_striend to OVRKernel/Std.h
717 static bool OVR_striend(const char* pStr, const char* pFind, size_t strLength = (size_t)-1, size_t findLength = (size_t)-1)
718 {
719 if(strLength == (size_t)-1)
720 strLength = OVR_strlen(pStr);
721 if(findLength == (size_t)-1)
722 findLength = OVR_strlen(pFind);
723 if(strLength >= findLength)
724 return (OVR_stricmp(pStr + strLength - findLength, pFind) == 0);
725 return false;
726 }
728 static bool IsBundleFolder(const char* filePath)
729 {
730 // https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/AboutBundles/AboutBundles.html#//apple_ref/doc/uid/10000123i-CH100-SW1
731 static const char* extensionArray[] = { ".app", ".bundle", ".framework", ".plugin", ".kext" };
733 for(size_t i = 0; i < OVR_ARRAY_COUNT(extensionArray); i++)
734 {
735 if(OVR_striend(filePath, extensionArray[i]))
736 return true;
737 }
739 return false;
740 }
741 };
743 char appPathTemp[PATH_MAX];
744 uint32_t appPathTempCapacity32 = PATH_MAX;
745 size_t requiredStrlen = appPathCapacity;
747 if(_NSGetExecutablePath(appPathTemp, &appPathTempCapacity32) == 0)
748 {
749 char appPathTempReal[PATH_MAX];
751 if(realpath(appPathTemp, appPathTempReal)) // If the path is a symbolic link, this converts it to the real path.
752 {
753 // To consider: Enable reading the internal bundle executable path. An application on Mac may in
754 // fact be within a file bundle, which is an private file system within a file. With Objective C
755 // we could use: [[NSWorkspace sharedWorkspace] isFilePackageAtPath:fullPath];
756 bool shouldReadTheBunderPath = false;
758 if(shouldReadTheBunderPath)
759 {
760 // We recursively call dirname() until we find .app/.bundle/.plugin as a directory name.
761 OVR_strlcpy(appPathTemp, appPathTempReal, OVR_ARRAY_COUNT(appPathTemp));
762 bool found = BunderFolder::IsBundleFolder(appPathTemp);
764 while(!found && OVR_strcmp(appPathTemp, ".") && OVR_strcmp(appPathTemp, "/"))
765 {
766 OVR_strlcpy(appPathTemp, dirname(appPathTemp), OVR_ARRAY_COUNT(appPathTemp));
767 found = BunderFolder::IsBundleFolder(appPathTemp);
768 }
770 if(found) // If somewhere above we found a parent bundle container...
771 requiredStrlen = OVR_strlcpy(appPath, appPathTemp, appPathCapacity);
772 else
773 requiredStrlen = OVR_strlcpy(appPath, appPathTempReal, appPathCapacity);
774 }
775 else
776 {
777 requiredStrlen = OVR_strlcpy(appPath, appPathTempReal, appPathCapacity);
778 }
779 }
780 }
782 if(requiredStrlen >= appPathCapacity)
783 appPath[0] = '\0';
785 #elif defined(OVR_OS_LINUX)
786 ssize_t length = readlink("/proc/self/exe", appPath, appPathCapacity);
788 if((length != -1) && ((size_t)length < (appPathCapacity - 1)))
789 {
790 appPath[length] = '\0';
791 }
792 #endif
793 }
796 static const char* GetFileNameFromPath(const char* filePath)
797 {
798 #if defined(OVR_OS_MS)
799 const char* lastPathSeparator = max(strrchr(filePath, '\\'), strrchr(filePath, '/')); // Microsoft APIs are inconsistent with respect to allowing / as a path separator.
800 #else
801 const char* lastPathSeparator = strrchr(filePath, '/');
802 #endif
804 if(lastPathSeparator)
805 return lastPathSeparator + 1;
807 return filePath;
808 }
812 static void FormatDateTime(char* buffer, size_t bufferCapacity, time_t timeValue, bool getDate, bool getTime, bool localDateTime, bool fileNameSafeCharacters = false)
813 {
814 char temp[128];
815 const tm* pTime = localDateTime ? localtime(&timeValue) : gmtime(&timeValue);
817 if(bufferCapacity)
818 buffer[0] = 0;
820 if(getDate)
821 {
822 const char* format = fileNameSafeCharacters ? "%Y-%m-%d" : "%Y/%m/%d";
823 strftime(temp, OVR_ARRAY_COUNT(temp), format, pTime);
824 OVR::OVR_strlcpy(buffer, temp, bufferCapacity);
825 }
827 if(getTime)
828 {
829 const char* format = fileNameSafeCharacters ? " %H.%M.%S" : " %H:%M:%S";
830 strftime(temp, OVR_ARRAY_COUNT(temp), (getDate ? format : format + 1), pTime);
831 OVR::OVR_strlcat(buffer, temp, bufferCapacity);
832 }
833 }
836 static void GetOSVersionName(char* versionName, size_t versionNameCapacity)
837 {
838 #if defined(OVR_OS_MS)
839 const char* name = "unknown";
841 OSVERSIONINFOEXW vi;
842 memset(&vi, 0, sizeof(vi));
843 vi.dwOSVersionInfoSize = sizeof(vi);
845 if(GetVersionExW((LPOSVERSIONINFOW)&vi))
846 {
847 if(vi.dwMajorVersion >= 7)
848 {
849 // Unknown recent version.
850 }
851 if(vi.dwMajorVersion >= 6)
852 {
853 if(vi.dwMinorVersion >= 4)
854 name = "Windows 10";
855 else if(vi.dwMinorVersion >= 3)
856 {
857 if(vi.wProductType == VER_NT_WORKSTATION)
858 name = "Windows 8.1";
859 else
860 name = "Windows Server 2012 R2";
861 }
862 else if(vi.dwMinorVersion >= 2)
863 {
864 if(vi.wProductType == VER_NT_WORKSTATION)
865 name = "Windows 8";
866 else
867 name = "Windows Server 2012";
868 }
869 else if(vi.dwMinorVersion >= 1)
870 {
871 if(vi.wProductType == VER_NT_WORKSTATION)
872 name = "Windows 7";
873 else
874 name = "Windows Server 2008 R2";
875 }
876 else
877 {
878 if(vi.wProductType == VER_NT_WORKSTATION)
879 name = "Windows Vista";
880 else
881 name = "Windows Server 2008";
882 }
883 }
884 else if(vi.dwMajorVersion >= 5)
885 {
886 if(vi.dwMinorVersion == 0)
887 name = "Windows 2000";
888 else if(vi.dwMinorVersion == 1)
889 name = "Windows XP";
890 else // vi.dwMinorVersion == 2
891 {
892 if(GetSystemMetrics(SM_SERVERR2) != 0)
893 name = "Windows Server 2003 R2";
894 else if(vi.wSuiteMask & VER_SUITE_WH_SERVER)
895 name = "Windows Home Server";
896 if(GetSystemMetrics(SM_SERVERR2) == 0)
897 name = "Windows Server 2003";
898 else
899 name = "Windows XP Professional x64 Edition";
900 }
901 }
902 else
903 name = "Windows 98 or earlier";
904 }
906 OVR_strlcpy(versionName, name, versionNameCapacity);
908 #elif defined(OVR_OS_UNIX) || defined(OVR_OS_APPLE)
909 utsname utsName;
910 memset(&utsName, 0, sizeof(utsName));
912 if(uname(&utsName) == 0)
913 OVR_snprintf(versionName, versionNameCapacity, "%s %s %s %s", utsName.sysname, utsName.release, utsName.version, utsName.machine);
914 else
915 OVR_snprintf(versionName, versionNameCapacity, "Unix");
916 #endif
917 }
922 void CreateException(CreateExceptionType exceptionType)
923 {
924 char buffer[1024] = {};
926 switch(exceptionType)
927 {
928 case kCETAccessViolation:
929 {
930 int* pNullPtr = reinterpret_cast<int*>((rand() / 2) / RAND_MAX);
931 pNullPtr[0] = 0; // This line should generate an exception.
932 sprintf(buffer, "%p", pNullPtr);
933 break;
934 }
936 case kCETDivideByZero:
937 {
938 int smallValue = 1;
939 int largeValue = (1000 * exceptionType);
940 int divByZero = (smallValue / largeValue); // This line should generate a div/0 exception.
941 sprintf(buffer, "%d", divByZero);
942 break;
943 }
945 case kCETIllegalInstruction:
946 {
947 #if defined(OVR_CPU_X86) || (defined(OVR_CPU_X86_64) && !defined(OVR_CC_MSVC)) // (if x86) or (if x64 and any computer but VC++)...
948 #if defined(OVR_CC_MSVC)
949 __asm ud2
950 #else // e.g. GCC
951 asm volatile("ud2");
952 #endif
954 #elif defined(OVR_CPU_X86_64) && (defined(OVR_OS_MS) && defined(PAGE_EXECUTE_READWRITE))
955 // VC++ for x64 doesn't support inline asm.
956 void* pVoid = _AddressOfReturnAddress();
957 void** ppVoid = reinterpret_cast<void**>(pVoid);
958 void* pReturnAddress = *ppVoid;
959 DWORD dwProtectPrev = 0;
961 if(VirtualProtect(pReturnAddress, 2, PAGE_EXECUTE_READWRITE, &dwProtectPrev)) // If we can set the memory to be executable...
962 {
963 // Modify the code we return to.
964 uint8_t asm_ud2[] = { 0x0f, 0x0b };
965 memcpy(pReturnAddress, asm_ud2, sizeof(asm_ud2));
966 VirtualProtect(pReturnAddress, 2, dwProtectPrev, &dwProtectPrev);
967 }
968 else
969 {
970 // To do: Fix this.
971 }
973 #else
974 // To do: Fix this.
975 #endif
977 break;
978 }
980 case kCETStackCorruption:
981 {
982 size_t size = (sizeof(buffer) * 16) - (rand() % 16);
983 char* pOutsizeStack = buffer - ((sizeof(buffer) * 16) + (rand() % 16));
985 memset(buffer, 0, size);
986 memset(pOutsizeStack, 0, size); // This line should generate an exception, or an exception will be generated upon return from this function.
987 break;
988 }
990 case kCETStackOverflow:
991 {
992 CreateException(exceptionType); // Call ourselves recursively. This line should generate a div/0 exception.
993 sprintf(buffer, "%d", exceptionType);
994 break;
995 }
997 case kCETAlignment:
998 {
999 // Not all platforms generate alignment exceptions. Some internally handle it.
1000 void* pAligned = malloc(16);
1001 char* pMisaligned = (char*)pAligned + 1;
1002 uint64_t* pMisaligned64 = reinterpret_cast<uint64_t*>(pMisaligned);
1004 *pMisaligned64 = 0; // This line should generate an exception.
1005 free(pAligned);
1006 break;
1009 case kCETFPU:
1010 // Platforms usually have FPU exceptions disabled. In order to test FPU exceptions we will need to at least
1011 // temporarily disable them before executing code here to generate such exceptions.
1012 // To do.
1013 break;
1015 case kCETTrap:
1016 // To do. This is hardware-specific.
1017 break;
1024 #if defined(OVR_OS_MS)
1025 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);
1026 typedef PVOID (WINAPI * SymFunctionTableAccess64Type)(HANDLE hProcess, DWORD64 dwAddr);
1027 typedef DWORD64 (WINAPI * SymGetModuleBase64Type)(HANDLE hProcess, DWORD64 dwAddr);
1028 typedef DWORD (WINAPI * SymSetOptionsType)(DWORD SymOptions);
1029 typedef BOOL (WINAPI * SymInitializeWType)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess);
1030 typedef BOOL (WINAPI * SymCleanupType)(HANDLE hProcess);
1031 typedef DWORD64 (WINAPI * SymLoadModule64Type)(HANDLE hProcess, HANDLE hFile, PCSTR ImageName, PCSTR ModuleName, DWORD64 BaseOfDll, DWORD SizeOfDll);
1032 typedef BOOL (WINAPI * SymFromAddrType)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFO Symbol);
1033 typedef BOOL (WINAPI * SymGetLineFromAddr64Type)(HANDLE hProcess, DWORD64 qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64);
1035 StackWalk64Type pStackWalk64;
1036 SymFunctionTableAccess64Type pSymFunctionTableAccess64;
1037 SymGetModuleBase64Type pSymGetModuleBase64;
1038 SymSetOptionsType pSymSetOptions;
1039 SymInitializeWType pSymInitializeW;
1040 SymCleanupType pSymCleanup;
1041 SymLoadModule64Type pSymLoadModule64;
1042 SymFromAddrType pSymFromAddr;
1043 SymGetLineFromAddr64Type pSymGetLineFromAddr64;
1044 #endif
1048 SymbolLookup::SymbolLookup()
1049 : initialized(false),
1050 allowMemoryAllocation(true),
1051 moduleListUpdated(false),
1052 moduleInfoArray(),
1053 moduleInfoArraySize(0)
1057 SymbolLookup::~SymbolLookup()
1059 Shutdown();
1062 void SymbolLookup::AddSourceCodeDirectory(const char* pDirectory)
1064 OVR_UNUSED(pDirectory);
1067 bool SymbolLookup::Initialize()
1069 if(!initialized)
1071 #if defined(OVR_OS_MS)
1072 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms679294%28v=vs.85%29.aspx
1073 HANDLE hProcess = GetCurrentProcess();
1074 HMODULE hDbgHelp = LoadLibraryW(L"DbgHelp.dll"); // It's best if the application supplies a recent version of this.
1076 if(hDbgHelp)
1078 pStackWalk64 = (StackWalk64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "StackWalk64");
1079 pSymFunctionTableAccess64 = (SymFunctionTableAccess64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymFunctionTableAccess64");
1080 pSymGetModuleBase64 = (SymGetModuleBase64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymGetModuleBase64");
1081 pSymSetOptions = (SymSetOptionsType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymSetOptions");
1082 pSymInitializeW = (SymInitializeWType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymInitializeW");
1083 pSymCleanup = (SymCleanupType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymCleanup");
1084 pSymLoadModule64 = (SymLoadModule64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymLoadModule64");
1085 pSymFromAddr = (SymFromAddrType)(uintptr_t)::GetProcAddress(hDbgHelp, "SymFromAddr");
1086 pSymGetLineFromAddr64 = (SymGetLineFromAddr64Type)(uintptr_t)::GetProcAddress(hDbgHelp, "SymGetLineFromAddr64");
1089 pSymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
1091 // To consider: Use a manually created search path:
1092 // wchar_t searchPathW[4096]; // Semicolon-separated strings.
1093 // The current working directory of the application.
1094 // The directory of the application itself (GetModuleFileName).
1095 // The _NT_SYMBOL_PATH environment variable.
1096 // The _NT_ALTERNATE_SYMBOL_PATH environment variable.
1098 if(pSymInitializeW(hProcess, nullptr /*searchPathW*/, FALSE))
1100 initialized = true;
1102 #endif
1105 return true;
1108 void SymbolLookup::Shutdown()
1110 if(initialized)
1112 initialized = false;
1114 #if defined(OVR_OS_MS)
1115 HANDLE hProcess = GetCurrentProcess();
1117 // SymCleanup should handle this for us.
1118 //if(moduleListUpdated)
1119 //{
1120 // for(size_t i = 0; i < moduleInfoArraySize; i++)
1121 // pSymUnloadModule64(hProcess, moduleInfoArray[i].baseAddress);
1122 //}
1124 moduleInfoArraySize = 0;
1126 pSymCleanup(hProcess);
1127 #endif
1132 void SymbolLookup::EnableMemoryAllocation(bool enabled)
1134 allowMemoryAllocation = enabled;
1138 OVR_DISABLE_MSVC_WARNING(4740) // flow in or out of inline asm code suppresses global optimization
1139 OVR_DISABLE_MSVC_WARNING(4748) // /GS can not protect parameters and local variables from local buffer overrun because optimizations are disabled in function
1142 size_t SymbolLookup::GetBacktrace(void* addressArray[], size_t addressArrayCapacity, size_t skipCount, void* platformThreadContext, OVR::ThreadSysId threadSysIdHelp)
1144 #if defined(OVR_OS_WIN64) || (defined(OVR_OS_MS) && defined(OVR_OS_CONSOLE))
1145 OVR_UNUSED(threadSysIdHelp);
1147 if(platformThreadContext == nullptr)
1148 return RtlCaptureStackBackTrace(1, (ULONG)addressArrayCapacity, addressArray, nullptr);
1150 // We need to get the call stack of another thread.
1151 size_t frameIndex = 0;
1152 CONTEXT context;
1153 PRUNTIME_FUNCTION pRuntimeFunction;
1154 ULONG64 imageBase = 0;
1155 ULONG64 imageBasePrev = 0;
1157 memcpy(&context, (CONTEXT*)platformThreadContext, sizeof(CONTEXT));
1158 context.ContextFlags = CONTEXT_CONTROL;
1160 if(context.Rip && (frameIndex < addressArrayCapacity))
1161 addressArray[frameIndex++] = (void*)(uintptr_t)context.Rip;
1163 while(context.Rip && (frameIndex < addressArrayCapacity))
1165 imageBasePrev = imageBase;
1166 pRuntimeFunction = (PRUNTIME_FUNCTION)RtlLookupFunctionEntry(context.Rip, &imageBase, nullptr);
1168 if(pRuntimeFunction)
1170 VOID* handlerData = nullptr;
1171 ULONG64 establisherFramePointers[2] = { 0, 0 };
1172 RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, context.Rip, pRuntimeFunction, &context, &handlerData, establisherFramePointers, nullptr);
1174 else
1176 context.Rip = (ULONG64)(*(PULONG64)context.Rsp);
1177 context.Rsp += 8;
1180 if(context.Rip && (frameIndex < addressArrayCapacity))
1182 if(skipCount)
1183 --skipCount;
1184 else
1185 addressArray[frameIndex++] = (void*)(uintptr_t)context.Rip;
1189 return frameIndex;
1191 #elif defined(OVR_OS_WIN32)
1192 OVR_UNUSED(threadSysIdHelp);
1194 size_t frameIndex = 0;
1196 if(pStackWalk64)
1198 CONTEXT context;
1200 if(platformThreadContext)
1202 memcpy(&context, platformThreadContext, sizeof(context));
1203 context.ContextFlags = CONTEXT_CONTROL;
1205 else
1207 memset(&context, 0, sizeof(context));
1208 context.ContextFlags = CONTEXT_CONTROL;
1210 __asm {
1211 mov context.Ebp, EBP
1212 mov context.Esp, ESP
1213 call GetEIP
1214 GetEIP:
1215 pop context.Eip
1219 STACKFRAME64 sf;
1220 memset(&sf, 0, sizeof(sf));
1221 sf.AddrPC.Offset = context.Eip;
1222 sf.AddrPC.Mode = AddrModeFlat;
1223 sf.AddrStack.Offset = context.Esp;
1224 sf.AddrStack.Mode = AddrModeFlat;
1225 sf.AddrFrame.Offset = context.Ebp;
1226 sf.AddrFrame.Mode = AddrModeFlat;
1228 const HANDLE hCurrentProcess = ::GetCurrentProcess();
1229 const HANDLE hCurrentThread = ::GetCurrentThread();
1231 if(!platformThreadContext) // If we are reading the current thread's call stack then we ignore this current function.
1232 skipCount++;
1234 while(frameIndex < addressArrayCapacity)
1236 if(!pStackWalk64(IMAGE_FILE_MACHINE_I386, hCurrentProcess, hCurrentThread, &sf, &context, nullptr, pSymFunctionTableAccess64, pSymGetModuleBase64, nullptr))
1237 break;
1239 if(sf.AddrFrame.Offset == 0)
1240 break;
1242 if(skipCount)
1243 --skipCount;
1244 else
1245 addressArray[frameIndex++] = ((void*)(uintptr_t)sf.AddrPC.Offset);
1249 return frameIndex;
1251 #elif defined(OVR_OS_APPLE)
1252 struct StackFrame
1254 StackFrame* pParentStackFrame;
1255 void* pReturnPC;
1256 };
1258 void* pInstruction;
1259 StackFrame* pStackFrame;
1260 size_t frameIndex = 0;
1262 if(platformThreadContext)
1264 #if defined(OVR_CPU_ARM)
1265 arm_thread_state_t* pThreadState = (arm_thread_state_t*)platformThreadContext;
1266 pStackFrame = (StackFrame*)pThreadState->__fp;
1267 pInstruction = (void*) pThreadState->__pc;
1268 #define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0x1) == 0)
1269 #elif defined(OVR_CPU_X86_64)
1270 x86_thread_state_t* pThreadState = (x86_thread_state_t*)platformThreadContext;
1271 pInstruction = (void*) pThreadState->uts.ts64.__rip;
1272 pStackFrame = (StackFrame*)pThreadState->uts.ts64.__rbp;
1273 #define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 0)
1274 #elif defined(OVR_CPU_X86)
1275 x86_thread_state_t* pThreadState = (x86_thread_state_t*)platformThreadContext;
1276 pInstruction = (void*) pThreadState->uts.ts32.__eip;
1277 pStackFrame = (StackFrame*)pThreadState->uts.ts32.__ebp;
1278 #define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 8)
1279 #endif
1281 if(frameIndex < addressArrayCapacity)
1282 addressArray[frameIndex++] = pInstruction;
1284 else // Else get the current values...
1286 pStackFrame = (StackFrame*)__builtin_frame_address(0);
1287 GetInstructionPointer(pInstruction);
1290 pthread_t threadSelf = pthread_self();
1291 void* pCurrentStackBase = pthread_get_stackaddr_np(threadSelf);
1292 void* pCurrentStackLimit = (void*)((uintptr_t)pCurrentStackBase - pthread_get_stacksize_np(threadSelf));
1293 bool threadIsCurrent = (platformThreadContext == nullptr) || (((void*)pStackFrame > pCurrentStackLimit) && ((void*)pStackFrame <= pCurrentStackBase));
1294 StackFrame* pStackBase;
1295 StackFrame* pStackLimit;
1297 if(threadIsCurrent)
1299 pStackBase = (StackFrame*)pCurrentStackBase;
1300 pStackLimit = (StackFrame*)pCurrentStackLimit;
1302 else if(threadSysIdHelp)
1304 pthread_t threadHandle = pthread_from_mach_thread_np((mach_port_t)threadSysIdHelp);
1305 pStackBase = (StackFrame*)pthread_get_stackaddr_np(threadHandle);
1306 pStackLimit = (StackFrame*)((uintptr_t)pStackBase - pthread_get_stacksize_np(threadHandle));
1308 else
1309 { // We guess what the limits are.
1310 pStackBase = pStackFrame + ((384 * 1024) / sizeof(StackFrame));
1311 pStackLimit = pStackFrame - ((384 * 1024) / sizeof(StackFrame));
1314 if((frameIndex < addressArrayCapacity) && pStackFrame && FrameIsAligned(pStackFrame))
1316 addressArray[frameIndex++] = pStackFrame->pReturnPC;
1318 while(pStackFrame && pStackFrame->pReturnPC && (frameIndex < addressArrayCapacity))
1320 pStackFrame = pStackFrame->pParentStackFrame;
1322 if(pStackFrame && FrameIsAligned(pStackFrame) && pStackFrame->pReturnPC && (pStackFrame > pStackLimit) && (pStackFrame < pStackBase))
1324 if(skipCount)
1325 --skipCount;
1326 else
1327 addressArray[frameIndex++] = pStackFrame->pReturnPC;
1329 else
1330 break;
1334 return frameIndex;
1336 #elif defined(OVR_OS_LINUX) && (defined( __LIBUNWIND__) || defined(LIBUNWIND_AVAIL))
1337 // Libunwind-based solution. Requires installation of libunwind package.
1338 // Libunwind isn't always safe for threads that are in signal handlers.
1339 // An approach to get the callstack of another thread is to use signal injection into the target thread.
1341 OVR_UNUSED(platformThreadContext);
1342 OVR_UNUSED(threadSysIdHelp);
1344 size_t frameIndex = 0;
1345 unw_cursor_t cursor;
1346 unw_context_t uc;
1347 unw_word_t ip, sp;
1349 unw_getcontext(&uc); // This gets the current thread's context. We could alternatively initialize another thread's context with it.
1350 unw_init_local(&cursor, &uc);
1352 while((unw_step(&cursor) > 0) && (frameIndex < addressArrayCapacity))
1354 // We can get the function name here too on some platforms with unw_get_proc_info() and unw_get_proc_name().
1356 if(skipCount)
1357 --skipCount;
1358 else
1360 unw_get_reg(&cursor, UNW_REG_IP, &ip);
1361 addressArray[frameIndex++] = (void*)ip;
1365 return frameIndex;
1366 #else
1367 OVR_UNUSED(addressArray);
1368 OVR_UNUSED(addressArrayCapacity);
1369 OVR_UNUSED(skipCount);
1370 OVR_UNUSED(platformThreadContext);
1371 OVR_UNUSED(threadSysIdHelp);
1373 return 0;
1374 #endif
1378 size_t SymbolLookup::GetBacktraceFromThreadHandle(void* addressArray[], size_t addressArrayCapacity, size_t skipCount, OVR::ThreadHandle threadHandle)
1380 #if defined(OVR_OS_MS)
1381 size_t count = 0;
1382 DWORD threadSysId = (DWORD)ConvertThreadHandleToThreadSysId(threadHandle);
1384 // Compare to 0, compare to the self 'pseudohandle' and compare to the self id.
1385 if((threadHandle == OVR_THREADHANDLE_INVALID) || (threadHandle == ::GetCurrentThread()) || (threadSysId == ::GetCurrentThreadId())) // If threadSysId refers to the current thread...
1386 return GetBacktrace(addressArray, addressArrayCapacity, skipCount, nullptr);
1388 // We are working with another thread. We need to suspend it and get its CONTEXT.
1389 // Suspending other threads is risky, as they may be in some state that cannot be safely blocked.
1390 BOOL result = false;
1391 DWORD suspendResult = ::SuspendThread(threadHandle); // Requires that the handle have THREAD_SUSPEND_RESUME rights.
1393 if(suspendResult != (DWORD)-1) // Returns previous suspend count, or -1 if failed.
1395 CONTEXT context;
1396 context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; // Requires that the handle have THREAD_GET_CONTEXT rights.
1397 result = ::GetThreadContext(threadHandle, &context);
1398 count = GetBacktrace(addressArray, addressArrayCapacity, skipCount, &context);
1399 suspendResult = ::ResumeThread(threadHandle);
1400 OVR_ASSERT_AND_UNUSED(suspendResult != (DWORD)-1, suspendResult);
1403 return count;
1405 #elif defined(OVR_OS_APPLE)
1406 mach_port_t threadSysID = pthread_mach_thread_np((pthread_t)threadHandle); // Convert pthread_t to mach thread id.
1407 return GetBacktraceFromThreadSysId(addressArray, addressArrayCapacity, skipCount, (OVR::ThreadSysId)threadSysID);
1409 #elif defined(OVR_OS_LINUX)
1410 // To do.
1411 OVR_UNUSED(addressArray);
1412 OVR_UNUSED(addressArrayCapacity);
1413 OVR_UNUSED(skipCount);
1414 OVR_UNUSED(threadHandle);
1415 return 0;
1416 #endif
1420 size_t SymbolLookup::GetBacktraceFromThreadSysId(void* addressArray[], size_t addressArrayCapacity, size_t skipCount, OVR::ThreadSysId threadSysId)
1422 #if defined(OVR_OS_MS)
1423 OVR::ThreadHandle threadHandle = ConvertThreadSysIdToThreadHandle(threadSysId);
1424 if(threadHandle)
1426 size_t count = GetBacktraceFromThreadHandle(addressArray, addressArrayCapacity, skipCount, threadHandle);
1427 FreeThreadHandle(threadHandle);
1428 return count;
1430 return 0;
1432 #elif defined(OVR_OS_APPLE)
1433 mach_port_t threadCurrent = pthread_mach_thread_np(pthread_self());
1434 mach_port_t thread = (mach_port_t)threadSysId;
1436 if(thread == threadCurrent)
1438 return GetBacktrace(addressArray, addressArrayCapacity, skipCount, nullptr);
1440 else
1442 kern_return_t result = thread_suspend(thread); // Do we need to do this if it's an thread who exception is being handled?
1443 size_t count = 0;
1445 if(result == KERN_SUCCESS)
1447 #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
1448 x86_thread_state_t threadState;
1449 #elif defined(OVR_CPU_ARM)
1450 arm_thread_state_t threadState;
1451 #endif
1452 mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;
1454 result = thread_get_state(thread, MACHINE_THREAD_STATE, (natural_t*)(uintptr_t)&threadState, &stateCount);
1456 if(result == KERN_SUCCESS)
1457 count = GetBacktrace(addressArray, addressArrayCapacity, skipCount, &threadState, threadSysId);
1459 thread_resume(thread);
1461 return count;
1465 return 0;
1467 #elif defined(OVR_OS_LINUX)
1468 // To do.
1469 OVR_UNUSED(addressArray);
1470 OVR_UNUSED(addressArrayCapacity);
1471 OVR_UNUSED(skipCount);
1472 OVR_UNUSED(threadSysId);
1473 return 0;
1474 #endif
1478 // We need to return the required moduleInfoArrayCapacity.
1479 size_t SymbolLookup::GetModuleInfoArray(ModuleInfo* pModuleInfoArray, size_t moduleInfoArrayCapacity)
1481 #if defined(OVR_OS_MS)
1482 size_t moduleCountRequired = 0; // The count we would copy to pModuleInfoArray if moduleInfoArrayCapacity was enough.
1483 size_t moduleCount = 0; // The count we actually copy to pModuleInfoArray. Will be <= moduleInfoArrayCapacity.
1484 HANDLE hProcess = GetCurrentProcess();
1485 HMODULE hModuleArray[200];
1486 DWORD cbNeeded = 0;
1487 MODULEINFO mi;
1489 if(EnumProcessModules(hProcess, hModuleArray, sizeof(hModuleArray), &cbNeeded))
1491 moduleCountRequired = ((cbNeeded / sizeof(HMODULE)) < OVR_ARRAY_COUNT(hModuleArray)) ? (cbNeeded / sizeof(HMODULE)) : OVR_ARRAY_COUNT(hModuleArray);
1492 moduleCount = MIN(moduleCountRequired, OVR_ARRAY_COUNT(hModuleArray));
1493 moduleCount = MIN(moduleCount, moduleInfoArrayCapacity);
1495 for(size_t i = 0; i < moduleCount; i++)
1497 ModuleInfo& moduleInfo = pModuleInfoArray[i];
1499 memset(&mi, 0, sizeof(mi));
1500 BOOL result = GetModuleInformation(hProcess, hModuleArray[i], &mi, sizeof(mi));
1502 if(result)
1504 wchar_t pathW[MAX_PATH];
1505 char pathA[MAX_PATH * 3]; // *3 to handle UTF8 multibyte encoding.
1507 moduleInfo.handle = hModuleArray[i];
1508 moduleInfo.baseAddress = (uintptr_t)mi.lpBaseOfDll;
1509 moduleInfo.size = mi.SizeOfImage;
1511 GetModuleFileNameW(hModuleArray[i], pathW, OVR_ARRAY_COUNT(pathW));
1512 OVR::UTF8Util::EncodeString(pathA, pathW, -1); // Problem: DecodeString provides no way to specify the destination capacity.
1513 OVR::OVR_strlcpy(moduleInfo.filePath, pathA, OVR_ARRAY_COUNT(moduleInfo.filePath));
1515 const char* fileName = GetFileNameFromPath(pathA);
1516 OVR::OVR_strlcpy(moduleInfo.name, fileName, OVR_ARRAY_COUNT(moduleInfo.name));
1518 else
1520 moduleInfo.handle = 0;
1521 moduleInfo.baseAddress = 0;
1522 moduleInfo.size = 0;
1523 moduleInfo.filePath[0] = 0;
1524 moduleInfo.name[0] = 0;
1529 return moduleCountRequired;
1531 #elif defined(OVR_OS_MAC)
1532 size_t moduleCountRequired = 0;
1533 size_t moduleCount = 0;
1535 struct MacModuleInfo // This struct exists solely so we can have a local function within this function..
1537 static void AddMacModuleInfo(ModuleInfo* pModuleInfoArrayL, size_t& moduleCountRequiredL, size_t& moduleCountL, size_t moduleInfoArrayCapacityL,
1538 const char* pTypeFilterL, const char* pModulePath, uintptr_t currentSegmentPos, const MachHeader* pMachHeader, uint64_t offset)
1540 for(size_t i = 0; i < pMachHeader->ncmds; i++)
1542 const SegmentCommand* pSegmentCommand = reinterpret_cast<const SegmentCommand*>(currentSegmentPos);
1544 if(pSegmentCommand->cmd == kLCSegment)
1546 const size_t segnameSize = (sizeof(pSegmentCommand->segname) + 1); // +1 so we can have a trailing '\0'.
1547 char segname[segnameSize];
1549 memcpy(segname, pSegmentCommand->segname, sizeof(pSegmentCommand->segname));
1550 segname[segnameSize - 1] = '\0';
1552 if(!pTypeFilterL || OVR_strncmp(segname, pTypeFilterL, sizeof(segname)))
1554 moduleCountRequiredL++;
1556 if(moduleCountL < moduleInfoArrayCapacityL)
1558 ModuleInfo& info = pModuleInfoArrayL[moduleCountL++];
1560 info.baseAddress = (uint64_t)(pSegmentCommand->vmaddr + offset);
1561 info.handle = reinterpret_cast<ModuleHandle>((uintptr_t)info.baseAddress);
1562 info.size = (uint64_t)pSegmentCommand->vmsize;
1563 OVR_strlcpy(info.filePath, pModulePath, OVR_ARRAY_COUNT(info.filePath));
1564 OVR_strlcpy(info.name, GetFileNameFromPath(pModulePath), OVR_ARRAY_COUNT(info.name));
1566 info.permissions[0] = (pSegmentCommand->initprot & VM_PROT_READ) ? 'r' : '-';
1567 info.permissions[1] = (pSegmentCommand->initprot & VM_PROT_WRITE) ? 'w' : '-';
1568 info.permissions[2] = (pSegmentCommand->initprot & VM_PROT_EXECUTE) ? 'x' : '-';
1569 info.permissions[3] = '/';
1570 info.permissions[4] = (pSegmentCommand->maxprot & VM_PROT_READ) ? 'r' : '-';
1571 info.permissions[5] = (pSegmentCommand->maxprot & VM_PROT_WRITE) ? 'w' : '-';
1572 info.permissions[6] = (pSegmentCommand->maxprot & VM_PROT_EXECUTE) ? 'x' : '-';
1573 info.permissions[7] = '\0';
1575 OVR_strlcpy(info.type, pSegmentCommand->segname, OVR_ARRAY_COUNT(info.type));
1580 currentSegmentPos += pSegmentCommand->cmdsize;
1583 };
1585 // Iterate dyld_all_image_infos->infoArray
1586 const struct dyld_all_image_infos* pAllImageInfos = _dyld_get_all_image_infos();
1588 for(uint32_t i = 0; i < pAllImageInfos->infoArrayCount; i++)
1590 const char* pModulePath = pAllImageInfos->infoArray[i].imageFilePath;
1592 if(pModulePath && *pModulePath)
1594 uintptr_t currentSegmentPos = (uintptr_t)pAllImageInfos->infoArray[i].imageLoadAddress;
1595 const MachHeader* pMachHeader = reinterpret_cast<const MachHeader*>(currentSegmentPos);
1596 uint64_t offset = (uint64_t)_dyld_get_image_vmaddr_slide(i);
1598 currentSegmentPos += sizeof(*pMachHeader);
1600 MacModuleInfo::AddMacModuleInfo(pModuleInfoArray, moduleCountRequired, moduleCount, moduleInfoArrayCapacity,
1601 nullptr /*"__TEXT"*/, pModulePath, currentSegmentPos, pMachHeader, offset);
1605 // In addition to iterating dyld_all_image_infos->infoArray we need to also iterate /usr/lib/dyld entries.
1606 const MachHeader* pMachHeader = (const MachHeader*)pAllImageInfos->dyldImageLoadAddress;
1607 uintptr_t currentSegmentPos = (uintptr_t)pMachHeader + sizeof(*pMachHeader);
1608 char modulePath[OVR_MAX_PATH] = "";
1609 pid_t pid = getpid();
1610 int filenameLen = proc_regionfilename((int)pid, currentSegmentPos, modulePath, (uint32_t)sizeof(modulePath));
1612 if(filenameLen > 0)
1613 MacModuleInfo::AddMacModuleInfo(pModuleInfoArray, moduleCountRequired, moduleCount, moduleInfoArrayCapacity,
1614 "__TEXT", modulePath, currentSegmentPos, pMachHeader, 0);
1616 return moduleCountRequired;
1618 #elif defined(EA_PLATFORM_LINUX)
1619 // One approach is to read /proc/self/maps, which is supported by Linux (though not BSD).
1620 // Linux glibc dladdr() can tell us what module an arbitrary function address comes from, but can't tell us the list of modules.
1621 OVR_UNUSED(pModuleInfoArray);
1622 OVR_UNUSED(moduleInfoArrayCapacity);
1623 return 0;
1625 #else
1626 OVR_UNUSED(pModuleInfoArray);
1627 OVR_UNUSED(moduleInfoArrayCapacity);
1628 return 0;
1629 #endif
1633 size_t SymbolLookup::GetThreadList(ThreadHandle* threadHandleArray, ThreadSysId* threadSysIdArray, size_t threadArrayCapacity)
1635 size_t countRequired = 0;
1636 size_t count = 0;
1638 #if defined(OVR_OS_MS)
1639 // Print a list of threads.
1640 DWORD currentProcessId = GetCurrentProcessId();
1641 HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, currentProcessId); // ICreateToolhelp32Snapshot actually ignores currentProcessId.
1643 if(hThreadSnap != INVALID_HANDLE_VALUE)
1645 THREADENTRY32 te32;
1646 te32.dwSize = sizeof(THREADENTRY32);
1648 if(Thread32First(hThreadSnap, &te32))
1650 do
1652 if(te32.th32OwnerProcessID == currentProcessId)
1654 HANDLE hThread = ConvertThreadSysIdToThreadHandle(te32.th32ThreadID);
1656 if(hThread)
1658 ++countRequired;
1660 if((threadHandleArray || threadSysIdArray) && (count < threadArrayCapacity))
1662 if(threadHandleArray)
1663 threadHandleArray[count] = hThread; // The caller must call CloseHandle on this thread, or call DoneThreadList on the returned array.
1664 if(threadSysIdArray)
1665 threadSysIdArray[count] = ConvertThreadHandleToThreadSysId(hThread);
1666 ++count;
1669 if(!threadHandleArray) // If we aren't giving this back to the user...
1670 FreeThreadHandle(hThread);
1673 } while(Thread32Next(hThreadSnap, &te32));
1676 CloseHandle(hThreadSnap);
1679 #elif defined(OVR_OS_APPLE)
1680 mach_port_t taskSelf = mach_task_self();
1681 thread_act_port_array_t threadArray;
1682 mach_msg_type_number_t threadCount;
1684 kern_return_t result = task_threads(taskSelf, &threadArray, &threadCount);
1686 if(result == KERN_SUCCESS)
1688 for(mach_msg_type_number_t i = 0; i < threadCount; i++)
1690 ++countRequired;
1692 if((threadHandleArray || threadSysIdArray) && (count < threadArrayCapacity))
1694 if(threadHandleArray)
1695 threadHandleArray[count] = pthread_from_mach_thread_np(threadArray[i]);
1696 if(threadSysIdArray)
1697 threadSysIdArray[count] = threadArray[i];
1698 ++count;
1702 vm_deallocate(taskSelf, (vm_address_t)threadArray, threadCount * sizeof(thread_act_t));
1705 #elif defined(OVR_OS_LINUX)
1706 // To do.
1707 OVR_UNUSED(count);
1708 OVR_UNUSED(threadHandleArray);
1709 OVR_UNUSED(threadSysIdArray);
1710 OVR_UNUSED(threadArrayCapacity);
1711 #endif
1713 return countRequired;
1717 void SymbolLookup::DoneThreadList(ThreadHandle* threadHandleArray, ThreadSysId* threadSysIdArray, size_t threadArrayCount)
1719 #if defined(OVR_OS_MS)
1720 for(size_t i = 0; i != threadArrayCount; ++i)
1722 if(threadHandleArray[i])
1724 CloseHandle(threadHandleArray[i]);
1725 threadHandleArray[i] = OVR_THREADHANDLE_INVALID;
1729 OVR_UNUSED(threadSysIdArray);
1730 #else
1731 OVR_UNUSED(threadHandleArray);
1732 OVR_UNUSED(threadSysIdArray);
1733 OVR_UNUSED(threadArrayCount);
1734 #endif
1738 // Writes a given thread's callstack wity symbols to the given output.
1739 // It may not be safe to call this from an exception handler, as sOutput allocates memory.
1740 bool SymbolLookup::ReportThreadCallstack(OVR::String& sOutput, size_t skipCount, ThreadSysId threadSysId)
1742 if(!threadSysId)
1743 threadSysId = GetCurrentThreadSysId();
1745 void* addressArray[64];
1746 size_t addressCount = GetBacktraceFromThreadSysId(addressArray, OVR_ARRAY_COUNT(addressArray), skipCount, threadSysId);
1748 // Print the header
1749 char headerBuffer[256];
1750 char threadName[32];
1751 char threadHandleStr[24];
1752 char threadSysIdStr[24];
1753 char stackBaseStr[24];
1754 char stackLimitStr[24];
1755 void* pStackBase;
1756 void* pStackLimit;
1757 //void* pStackCurrent; // Current stack pointer. To do: support reporting this.
1758 ThreadHandle threadHandle = ConvertThreadSysIdToThreadHandle(threadSysId);
1759 OVR::GetThreadStackBounds(pStackBase, pStackLimit, threadHandle);
1761 Thread::GetThreadName(threadName, OVR_ARRAY_COUNT(threadName), threadName);
1762 SprintfThreadHandle(threadHandleStr, OVR_ARRAY_COUNT(threadHandleStr), threadHandle);
1763 SprintfThreadSysId(threadSysIdStr, OVR_ARRAY_COUNT(threadSysIdStr), threadSysId);
1764 SprintfAddress(stackBaseStr, OVR_ARRAY_COUNT(stackBaseStr), pStackBase);
1765 SprintfAddress(stackLimitStr, OVR_ARRAY_COUNT(stackLimitStr), pStackLimit);
1767 if(threadName[0])
1768 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);
1769 else
1770 OVR_snprintf(headerBuffer, OVR_ARRAY_COUNT(headerBuffer), "Thread handle: %s, id: %s, stack base: %s, stack limit: %s\r\n", threadHandleStr, threadSysIdStr, stackBaseStr, stackLimitStr);
1772 sOutput += headerBuffer;
1774 // Print the backtrace info
1775 char backtraceBuffer[1024]; // Sometimes function symbol names are very long.
1776 SymbolInfo symbolInfo;
1777 const char* pModuleName;
1779 if(addressCount == 0)
1781 sOutput += "<Unable to read backtrace>\r\n";
1783 else
1785 for(size_t i = 0; i < addressCount; ++i)
1787 LookupSymbol((uint64_t)addressArray[i], symbolInfo);
1789 if(symbolInfo.pModuleInfo && symbolInfo.pModuleInfo->name[0])
1790 pModuleName = symbolInfo.pModuleInfo->name;
1791 else
1792 pModuleName = "(unknown module)";
1794 char addressStr[24];
1795 SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), addressArray[i]);
1797 if(symbolInfo.filePath[0])
1798 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);
1799 else
1800 OVR_snprintf(backtraceBuffer, OVR_ARRAY_COUNT(backtraceBuffer), "%-2u %-24s %s %s+%d\r\n", (unsigned)i, pModuleName, addressStr, symbolInfo.function, symbolInfo.functionOffset);
1802 sOutput += backtraceBuffer;
1806 FreeThreadHandle(threadHandle);
1808 return (addressCount > 0);
1812 // Writes all thread's callstacks with symbols to the given output.
1813 // It may not be safe to call this from an exception handler, as sOutput allocates memory.
1814 bool SymbolLookup::ReportThreadCallstacks(OVR::String& sOutput, size_t skipCount)
1816 ThreadSysId threadSysIdArray[64];
1817 size_t threadSysIdCount = GetThreadList(nullptr, threadSysIdArray, OVR_ARRAY_COUNT(threadSysIdArray));
1819 if(threadSysIdCount > OVR_ARRAY_COUNT(threadSysIdArray))
1820 threadSysIdCount = OVR_ARRAY_COUNT(threadSysIdArray);
1822 for(size_t i = 0; i < threadSysIdCount; i++)
1824 String sTemp;
1825 ReportThreadCallstack(sTemp, skipCount, threadSysIdArray[i]);
1826 if(i > 0)
1827 sOutput += "\r\n";
1828 sOutput += sTemp;
1831 return (threadSysIdCount > 0);
1835 bool SymbolLookup::RefreshModuleList()
1837 if(!moduleListUpdated)
1839 #if defined(OVR_OS_MS)
1840 // We can't rely on SymRefreshModuleList because it's present in DbgHelp 6.5,
1841 // which doesn't distribute with Windows 7.
1843 // Currently we support only refreshing the list once ever. With a little effort we could revise this code to
1844 // support re-refreshing the list at runtime to account for the possibility that modules have recently been
1845 // added or removed.
1846 if(pSymLoadModule64)
1848 const size_t requiredCount = GetModuleInfoArray(moduleInfoArray, OVR_ARRAY_COUNT(moduleInfoArray));
1849 moduleInfoArraySize = MIN(requiredCount, OVR_ARRAY_COUNT(moduleInfoArray));
1851 HANDLE hProcess = GetCurrentProcess();
1853 for(size_t i = 0; i < moduleInfoArraySize; i++)
1854 pSymLoadModule64(hProcess, nullptr, moduleInfoArray[i].filePath, nullptr, moduleInfoArray[i].baseAddress, (DWORD)moduleInfoArray[i].size);
1856 moduleListUpdated = true;
1858 #else
1859 const size_t requiredCount = GetModuleInfoArray(moduleInfoArray, OVR_ARRAY_COUNT(moduleInfoArray));
1860 moduleInfoArraySize = MIN(requiredCount, OVR_ARRAY_COUNT(moduleInfoArray));
1861 moduleListUpdated = true;
1862 #endif
1865 return true;
1869 bool SymbolLookup::LookupSymbol(uint64_t address, SymbolInfo& symbolInfo)
1871 return LookupSymbols(&address, &symbolInfo, 1);
1875 bool SymbolLookup::LookupSymbols(uint64_t* addressArray, SymbolInfo* pSymbolInfoArray, size_t arraySize)
1877 if(!moduleListUpdated)
1879 RefreshModuleList();
1882 #if defined(OVR_OS_MS)
1883 union SYMBOL_INFO_UNION
1885 SYMBOL_INFO msSymbolInfo;
1886 char suffixPadding[sizeof(SYMBOL_INFO) + 1024];
1887 };
1889 for(size_t i = 0; i < arraySize; i++)
1891 uint64_t& address = addressArray[i];
1892 SymbolInfo& symbolInfo = pSymbolInfoArray[i];
1894 // Copy the address and ModuleInfo
1895 symbolInfo.address = addressArray[i];
1896 symbolInfo.pModuleInfo = GetModuleInfoForAddress(address); // We could also use siu.msSymbolInfo.ModBase to get the module slightly faster.
1898 // Get the function/offset.
1899 SYMBOL_INFO_UNION siu;
1900 memset(&siu, 0, sizeof(siu));
1901 siu.msSymbolInfo.SizeOfStruct = sizeof(siu.msSymbolInfo);
1902 siu.msSymbolInfo.MaxNameLen = sizeof(siu.suffixPadding) - sizeof(SYMBOL_INFO) + 1; // +1 because SYMBOL_INFO itself has Name[1].
1904 HANDLE hProcess = GetCurrentProcess();
1905 DWORD64 displacement64 = 0;
1906 bool bResult = (pSymFromAddr != nullptr) && (pSymFromAddr(hProcess, address, &displacement64, &siu.msSymbolInfo) != FALSE);
1908 if(bResult)
1910 symbolInfo.size = siu.msSymbolInfo.Size;
1911 OVR_strlcpy(symbolInfo.function, siu.msSymbolInfo.Name, OVR_ARRAY_COUNT(symbolInfo.function));
1912 symbolInfo.functionOffset = (int32_t)displacement64;
1914 else
1916 symbolInfo.size = kMISizeInvalid;
1917 symbolInfo.function[0] = 0;
1918 symbolInfo.functionOffset = kMIFunctionOffsetInvalid;
1921 // Get the file/line
1922 IMAGEHLP_LINE64 iLine64;
1923 DWORD displacement = 0;
1924 memset(&iLine64, 0, sizeof(iLine64));
1925 iLine64.SizeOfStruct = sizeof(iLine64);
1927 bResult = (pSymGetLineFromAddr64 != nullptr) && (pSymGetLineFromAddr64(hProcess, address, &displacement, &iLine64) != FALSE);
1929 if(bResult)
1931 OVR_strlcpy(symbolInfo.filePath, iLine64.FileName, OVR_ARRAY_COUNT(symbolInfo.filePath));
1932 symbolInfo.fileLineNumber = (int32_t)iLine64.LineNumber;
1934 else
1936 symbolInfo.filePath[0] = 0;
1937 symbolInfo.fileLineNumber = kMILineNumberInvalid;
1940 // To do: get the source code when possible. We need to use the user-registered directory paths and the symbolInfo.filePath
1941 // and find the given file in the tree(s), then open the file and find the symbolInfo.fileLineNumber line (and surrounding lines).
1942 // symbolInfo.sourceCode[1024]
1943 symbolInfo.sourceCode[0] = '\0';
1946 #elif defined(OVR_OS_APPLE)
1947 // Apple has an internal CoreSymbolication library which could help with this.
1948 // Third party implementations of the CoreSymbolication header are available and could be used
1949 // to get file/line info better than other means. It used Objective C, so we'll need a .m or .mm file.
1951 memset(pSymbolInfoArray, 0, arraySize * sizeof(SymbolInfo));
1953 for(size_t i = 0; i < arraySize; i++)
1955 pSymbolInfoArray[i].address = addressArray[i];
1956 pSymbolInfoArray[i].pModuleInfo = GetModuleInfoForAddress(addressArray[i]);
1959 // Problem: backtrace_symbols allocates memory from malloc. If you got into a SIGSEGV due to
1960 // malloc arena corruption (quite common) you will likely fault in backtrace_symbols.
1961 // To do: Use allowMemoryAllocation here.
1963 #if (OVR_PTR_SIZE == 4)
1964 // backtrace_symbols takes a void* array, but we have a uint64_t array. So for 32 bit we
1965 // need to convert the 64 bit array to 32 bit temporarily for the backtrace_symbols call.
1966 void* ptr32Array[256]; // To do: Remove this limit.
1967 for(size_t i = 0, iEnd = MIN(arraySize, OVR_ARRAY_COUNT(ptr32Array)); i < iEnd; i++)
1968 ptr32Array[i] = reinterpret_cast<void*>(addressArray[i]);
1969 char** symbolArray = backtrace_symbols(reinterpret_cast<void**>(ptr32Array), (int)arraySize);
1970 #else
1971 char** symbolArray = backtrace_symbols(reinterpret_cast<void**>(addressArray), (int)arraySize);
1972 #endif
1974 if(symbolArray)
1976 for(size_t i = 0; i < arraySize; i++)
1979 // Generates a string like this: "0 OculusWorldDemo 0x000000010000cfd5 _ZN18OculusWorldDemoApp9OnStartupEiPPKc + 213"
1980 static_assert(OVR_ARRAY_COUNT(pSymbolInfoArray[i].function) == 128, "Need to change the string format size below");
1982 sscanf(symbolArray[i], "%*d %*s %*x %128s + %d", pSymbolInfoArray[i].function, &pSymbolInfoArray[i].functionOffset);
1984 if(allowMemoryAllocation)
1986 int status = 0;
1987 char* strDemangled = abi::__cxa_demangle(pSymbolInfoArray[i].function, nullptr, nullptr, &status);
1989 if(strDemangled)
1991 OVR_strlcpy(pSymbolInfoArray[i].function, strDemangled, OVR_ARRAY_COUNT(pSymbolInfoArray[i].function));
1992 free(strDemangled);
1997 free(symbolArray);
2000 // To consider: use CoreSybolication to get file/line info instead. atos is a bit slow and cumbersome.
2001 // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/atos.1.html
2002 // atos -p <pid> <addr> <addr> ...
2003 // atos -o <binary image path> -l <load-address> <addr> <addr> ...
2004 // Generates output like this: "OVR::CreateException(OVR::CreateExceptionType) (in OculusWorldDemo) (ExceptionHandler.cpp:598)"
2005 for(size_t i = 0; i < arraySize; i++)
2007 struct stat statStruct;
2009 if(pSymbolInfoArray[i].pModuleInfo && pSymbolInfoArray[i].pModuleInfo->filePath[0] && (stat(pSymbolInfoArray[i].pModuleInfo->filePath, &statStruct) == 0))
2011 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.
2012 OVR_snprintf(command, OVR_ARRAY_COUNT(command), "atos -o %s -l 0x%llx 0x%llx",
2013 pSymbolInfoArray[i].pModuleInfo->filePath, (int64_t)pSymbolInfoArray[i].pModuleInfo->baseAddress, (int64_t)pSymbolInfoArray[i].address);
2015 char output[512];
2016 if(SpawnShellCommand(command, output, OVR_ARRAY_COUNT(output)) != (size_t)-1)
2018 char* pLastOpenParen = strrchr(output, '(');
2019 char* pColon = strrchr(output, ':');
2021 if(pLastOpenParen && (pColon > pLastOpenParen))
2023 *pColon = '\0';
2024 OVR_strlcpy(pSymbolInfoArray[i].filePath, pLastOpenParen + 1, OVR_ARRAY_COUNT(pSymbolInfoArray[i].filePath));
2030 #elif defined(OVR_OS_LINUX)
2031 // We can use libunwind's unw_get_proc_name to try to get function name info. It can work regardless of relocation.
2032 // Use backtrace_symbols and addr2line. Need to watch out for module load-time relocation.
2033 // Ned to pass the -rdynamic flag to the linker. It will cause the linker to out in the link
2034 // tables the name of all the none static functions in your code, not just the exported ones.
2035 OVR_UNUSED(addressArray);
2036 OVR_UNUSED(pSymbolInfoArray);
2037 OVR_UNUSED(arraySize);
2038 #endif
2040 return true; // To do: Return true only if something was found.
2044 const ModuleInfo* SymbolLookup::GetModuleInfoForAddress(uint64_t address)
2046 // This is a linear seach. To consider: it would be significantly faster to search by
2047 // address if we ordered it by base address and did a binary search.
2048 for(size_t i = 0; i < moduleInfoArraySize; ++i)
2050 const ModuleInfo& mi = moduleInfoArray[i];
2052 if((mi.baseAddress <= address) && (address < (mi.baseAddress + mi.size)))
2053 return &mi;
2056 return nullptr;
2062 ExceptionInfo::ExceptionInfo()
2063 : time()
2064 , timeVal(0)
2065 , backtrace()
2066 , backtraceCount(0)
2067 , threadHandle(OVR_THREADHANDLE_INVALID)
2068 , threadSysId(OVR_THREADSYSID_INVALID)
2069 , threadName()
2070 , pExceptionInstructionAddress(nullptr)
2071 , pExceptionMemoryAddress(nullptr)
2072 , cpuContext()
2073 , exceptionDescription()
2074 , symbolInfo()
2075 #if defined(OVR_OS_MS)
2076 , exceptionRecord()
2077 #elif defined(OVR_OS_APPLE)
2078 , exceptionType(0)
2079 , cpuExceptionId(0)
2080 , cpuExceptionIdError(0)
2081 , machExceptionDetail()
2082 , machExceptionDetailCount(0)
2083 #endif
2089 ExceptionHandler::ExceptionHandler()
2090 : enabled(false)
2091 , reportPrivacyEnabled(true)
2092 , exceptionResponse(kERHandle)
2093 , exceptionListener(nullptr)
2094 , exceptionListenerUserValue(0)
2095 , appDescription()
2096 , codeBasePathArray()
2097 , reportFilePath()
2098 , miniDumpFlags(0)
2099 , miniDumpFilePath()
2100 , file(nullptr)
2101 , scratchBuffer()
2102 , exceptionOccurred(false)
2103 , handlingBusy(0)
2104 , reportFilePathActual()
2105 , minidumpFilePathActual()
2106 , terminateReturnValue(0)
2107 , exceptionInfo()
2108 #if defined(OVR_OS_MS)
2109 , vectoredHandle(nullptr)
2110 , previousFilter(nullptr)
2111 , pExceptionPointers(nullptr)
2112 #elif defined(OVR_OS_MAC)
2113 , machHandlerInitialized(false)
2114 , machExceptionPort(0)
2115 , machExceptionPortsSaved()
2116 , machThreadShouldContinue(false)
2117 , machThreadExecuting(false)
2118 , machThread((pthread_t)OVR_THREADHANDLE_INVALID)
2119 #endif
2121 SetExceptionPaths("default", "default");
2125 ExceptionHandler::~ExceptionHandler()
2127 if(enabled)
2129 Enable(false);
2134 #if defined(OVR_OS_MS)
2135 static ExceptionHandler* sExceptionHandler = nullptr;
2137 LONG WINAPI Win32ExceptionFilter(LPEXCEPTION_POINTERS pExceptionPointers)
2139 if(sExceptionHandler)
2140 return (LONG)sExceptionHandler->ExceptionFilter(pExceptionPointers);
2141 return EXCEPTION_CONTINUE_SEARCH;
2144 LONG ExceptionHandler::ExceptionFilter(LPEXCEPTION_POINTERS pExceptionPointers)
2146 // Exception codes < 0x80000000 are not true exceptions but rather are debugger notifications. They include DBG_TERMINATE_THREAD,
2147 // DBG_TERMINATE_PROCESS, DBG_CONTROL_BREAK, DBG_COMMAND_EXCEPTION, DBG_CONTROL_C, DBG_PRINTEXCEPTION_C, DBG_RIPEXCEPTION,
2148 // and 0x406d1388 (thread named, http://blogs.msdn.com/b/stevejs/archive/2005/12/19/505815.aspx).
2150 if(pExceptionPointers->ExceptionRecord->ExceptionCode < 0x80000000)
2151 return EXCEPTION_CONTINUE_SEARCH;
2153 // VC++ C++ exceptions use code 0xe06d7363 ('Emsc')
2154 // http://support.microsoft.com/kb/185294
2155 // http://blogs.msdn.com/b/oldnewthing/archive/2010/07/30/10044061.aspx
2156 if(pExceptionPointers->ExceptionRecord->ExceptionCode == 0xe06d7363)
2157 return EXCEPTION_CONTINUE_SEARCH;
2159 if(handlingBusy.CompareAndSet_Acquire(0, 1)) // If we can successfully change it from 0 to 1.
2161 exceptionOccurred = true;
2163 this->pExceptionPointers = pExceptionPointers;
2165 // Disable the handler while we do this processing.
2166 ULONG result = RemoveVectoredExceptionHandler(vectoredHandle);
2167 OVR_ASSERT_AND_UNUSED(result != 0, result);
2169 // Time
2170 exceptionInfo.timeVal = time(nullptr);
2171 exceptionInfo.time = *gmtime(&exceptionInfo.timeVal);
2173 // Thread id
2174 // This is the thread id of the current thread and not the exception thread.
2175 if(!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &exceptionInfo.threadHandle, 0, true, DUPLICATE_SAME_ACCESS))
2176 exceptionInfo.threadHandle = 0;
2177 exceptionInfo.threadSysId = ConvertThreadHandleToThreadSysId(exceptionInfo.threadHandle);
2179 OVR::GetThreadName(exceptionInfo.threadHandle, exceptionInfo.threadName, OVR_ARRAY_COUNT(exceptionInfo.threadName));
2181 // Backtraces
2182 exceptionInfo.backtraceCount = symbolLookup.GetBacktrace(exceptionInfo.backtrace, OVR_ARRAY_COUNT(exceptionInfo.backtrace));
2184 // Context
2185 exceptionInfo.cpuContext = *pExceptionPointers->ContextRecord;
2186 exceptionInfo.exceptionRecord = *pExceptionPointers->ExceptionRecord;
2187 exceptionInfo.pExceptionInstructionAddress = exceptionInfo.exceptionRecord.ExceptionAddress;
2188 if((exceptionInfo.exceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) || (exceptionInfo.exceptionRecord.ExceptionCode == EXCEPTION_IN_PAGE_ERROR))
2189 exceptionInfo.pExceptionMemoryAddress = (void*)exceptionInfo.exceptionRecord.ExceptionInformation[1]; // ExceptionInformation[0] indicates if it was a read (0), write (1), or data execution attempt (8).
2190 else
2191 exceptionInfo.pExceptionMemoryAddress = pExceptionPointers->ExceptionRecord->ExceptionAddress;
2193 WriteExceptionDescription();
2195 if(miniDumpFilePath[0])
2196 WriteMiniDump();
2198 if(reportFilePath[0])
2199 WriteReport();
2201 if(exceptionListener)
2202 exceptionListener->HandleException(exceptionListenerUserValue, this, &exceptionInfo, reportFilePathActual);
2204 if(exceptionInfo.threadHandle)
2206 CloseHandle(exceptionInfo.threadHandle);
2207 exceptionInfo.threadHandle = 0;
2210 // Restore the handler that we temporarily disabled above.
2211 vectoredHandle = AddVectoredExceptionHandler(1, Win32ExceptionFilter);
2213 handlingBusy.Store_Release(0);
2216 if(exceptionResponse == ExceptionHandler::kERTerminate)
2218 TerminateProcess(GetCurrentProcess(), (UINT)terminateReturnValue);
2219 return terminateReturnValue;
2221 else if(exceptionResponse == ExceptionHandler::kERThrow)
2222 return EXCEPTION_CONTINUE_SEARCH;
2223 else if(exceptionResponse == ExceptionHandler::kERContinue)
2224 return EXCEPTION_CONTINUE_EXECUTION;
2225 return EXCEPTION_EXECUTE_HANDLER;
2228 #endif // defined(OVR_OS_MS)
2231 #if defined(OVR_OS_APPLE)
2232 // http://www.opensource.apple.com/source/xnu/xnu-2050.22.13/
2233 // http://www.opensource.apple.com/source/xnu/xnu-2050.22.13/osfmk/man/
2234 // http://www.opensource.apple.com/source/Libc/Libc-825.26/
2235 // https://mikeash.com/pyblog/friday-qa-2013-01-11-mach-exception-handlers.html
2237 void* ExceptionHandler::MachHandlerThreadFunction()
2239 __Request__mach_exception_raise_state_identity_t msg;
2240 __Reply__mach_exception_raise_state_identity_t reply;
2241 mach_msg_return_t result;
2243 machThreadExecuting = true;
2244 pthread_setname_np("ExceptionHandler");
2246 while(machThreadShouldContinue)
2248 mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_LARGE;
2249 natural_t timeout = 0; // Would be better to support a non-zero time.
2251 if(timeout)
2252 options |= MACH_RCV_TIMEOUT;
2254 result = mach_msg(&msg.Head, options, 0, sizeof(msg), machExceptionPort, timeout, MACH_PORT_NULL);
2256 if(msg.Head.msgh_id != sMachCancelMessageType)
2258 if(result == MACH_MSG_SUCCESS)
2260 if(mach_exc_server_OVR(&msg.Head, &reply.Head) == 0) //This will call our HandleMachException function.
2261 result = ~MACH_MSG_SUCCESS;
2264 // Send the reply
2265 if(result == MACH_MSG_SUCCESS)
2267 result = mach_msg(&reply.Head, MACH_SEND_MSG, reply.Head.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
2269 if(result != MACH_MSG_SUCCESS)
2271 // Failure.
2277 machThreadExecuting = false;
2279 return nullptr;
2283 kern_return_t ExceptionHandler::HandleMachException(mach_port_t /*machPort*/, mach_port_t threadSysId, mach_port_t machTask,
2284 exception_type_t machExceptionType, mach_exception_data_type_t* pExceptionDetail,
2285 mach_msg_type_number_t exceptionDetailCount, int* /*pMachExceptionFlavor*/, thread_state_t threadStatePrev,
2286 mach_msg_type_number_t /*threadStatePrevCount*/, thread_state_t /*threadStateNew*/,
2287 mach_msg_type_number_t* /*pThreadStateNewCount*/)
2289 // We don't want to handle exceptions for other processes.
2290 if(machTask != mach_task_self())
2291 return ForwardMachException(threadSysId, machTask, machExceptionType, pExceptionDetail, exceptionDetailCount);
2293 if(handlingBusy.CompareAndSet_Acquire(0, 1)) // If we can successfully change it from 0 to 1.
2295 exceptionOccurred = true;
2297 // Disable the handler while we do this processing.
2298 // To do.
2300 // Time
2301 exceptionInfo.timeVal = time(nullptr);
2302 exceptionInfo.time = *gmtime(&exceptionInfo.timeVal);
2304 // Thread id
2305 exceptionInfo.threadHandle = pthread_from_mach_thread_np(threadSysId);
2306 exceptionInfo.threadSysId = threadSysId;
2307 pthread_getname_np((pthread_t)exceptionInfo.threadHandle, exceptionInfo.threadName, sizeof(exceptionInfo.threadName));
2309 // Backtraces
2310 exceptionInfo.backtraceCount = symbolLookup.GetBacktraceFromThreadSysId(exceptionInfo.backtrace, OVR_ARRAY_COUNT(exceptionInfo.backtrace), 0, threadSysId);
2312 // Context
2313 #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
2314 // We can read x86_THREAD_STATE directly fromk threadStatePrev.
2315 exceptionInfo.cpuContext.threadState = *reinterpret_cast<x86_thread_state_t*>(threadStatePrev);
2317 mach_msg_type_number_t stateCount = x86_FLOAT_STATE_COUNT;
2318 thread_get_state(threadSysId, x86_FLOAT_STATE, (natural_t*)&exceptionInfo.cpuContext.floatState, &stateCount);
2320 stateCount = x86_DEBUG_STATE_COUNT;
2321 thread_get_state(threadSysId, x86_DEBUG_STATE, (natural_t*)&exceptionInfo.cpuContext.debugState, &stateCount);
2323 stateCount = x86_AVX_STATE_COUNT;
2324 thread_get_state(threadSysId, x86_AVX_STATE, (natural_t*)&exceptionInfo.cpuContext.avxState, &stateCount);
2326 stateCount = x86_EXCEPTION_STATE_COUNT;
2327 thread_get_state(threadSysId, x86_EXCEPTION_STATE, (natural_t*)&exceptionInfo.cpuContext.exceptionState, &stateCount);
2329 #if defined(OVR_CPU_X86)
2330 exceptionInfo.pExceptionInstructionAddress = (void*)exceptionInfo.cpuContext.threadState.uts.ts32.__eip;
2331 exceptionInfo.pExceptionMemoryAddress = (void*)exceptionInfo.cpuContext.exceptionState.ues.es32.__faultvaddr;
2332 exceptionInfo.cpuExceptionId = exceptionInfo.cpuContext.exceptionState.ues.es32.__trapno;
2333 exceptionInfo.cpuExceptionIdError = exceptionInfo.cpuContext.exceptionState.ues.es32.__err;
2334 #else
2335 exceptionInfo.pExceptionInstructionAddress = (void*)exceptionInfo.cpuContext.threadState.uts.ts64.__rip;
2336 exceptionInfo.pExceptionMemoryAddress = (void*)exceptionInfo.cpuContext.exceptionState.ues.es64.__faultvaddr;
2337 exceptionInfo.cpuExceptionId = exceptionInfo.cpuContext.exceptionState.ues.es64.__trapno;
2338 exceptionInfo.cpuExceptionIdError = exceptionInfo.cpuContext.exceptionState.ues.es64.__err;
2339 #endif
2340 #endif
2342 exceptionInfo.exceptionType = machExceptionType;
2344 exceptionInfo.machExceptionDetailCount = MIN(exceptionDetailCount, OVR_ARRAY_COUNT(exceptionInfo.machExceptionDetail));
2345 for(int i = 0; i < exceptionInfo.machExceptionDetailCount; i++)
2346 exceptionInfo.machExceptionDetail[i] = pExceptionDetail[i];
2348 WriteExceptionDescription();
2350 if(reportFilePath[0])
2351 WriteReport();
2353 if(miniDumpFilePath[0])
2354 WriteMiniDump();
2356 if(exceptionListener)
2357 exceptionListener->HandleException(exceptionListenerUserValue, this, &exceptionInfo, reportFilePathActual);
2359 // Re-restore the handler.
2360 // To do.
2362 handlingBusy.Store_Release(0);
2365 kern_return_t result = KERN_FAILURE; // By default pass on the exception to another handler after we are done here.
2367 if(exceptionResponse == ExceptionHandler::kERTerminate)
2368 ::exit(terminateReturnValue);
2369 else if(exceptionResponse == ExceptionHandler::kERThrow)
2370 ForwardMachException(threadSysId, machTask, machExceptionType, pExceptionDetail, exceptionDetailCount);
2371 else if(exceptionResponse == ExceptionHandler::kERDefault)
2372 ::exit(terminateReturnValue);
2373 else if(exceptionResponse == ExceptionHandler::kERContinue)
2374 result = KERN_SUCCESS; // This will trigger a re-execution of the function.
2376 return result;
2380 bool ExceptionHandler::InitMachExceptionHandler()
2382 if(!machHandlerInitialized)
2384 mach_port_t machTaskSelf = mach_task_self();
2385 kern_return_t result = MACH_MSG_SUCCESS;
2386 exception_mask_t mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_CRASH;
2388 if(machExceptionPort == MACH_PORT_NULL)
2390 result = mach_port_allocate(machTaskSelf, MACH_PORT_RIGHT_RECEIVE, &machExceptionPort);
2392 if(result == MACH_MSG_SUCCESS)
2394 result = mach_port_insert_right(machTaskSelf, machExceptionPort, machExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
2396 if(result == MACH_MSG_SUCCESS)
2397 result = task_get_exception_ports(machTaskSelf, mask, machExceptionPortsSaved.masks, &machExceptionPortsSaved.count,
2398 machExceptionPortsSaved.ports, machExceptionPortsSaved.behaviors, machExceptionPortsSaved.flavors);
2402 if(result == MACH_MSG_SUCCESS)
2404 result = task_set_exception_ports(machTaskSelf, mask, machExceptionPort, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE);
2406 if(result == MACH_MSG_SUCCESS)
2408 machThreadShouldContinue = true;
2410 pthread_attr_t attr;
2411 pthread_attr_init(&attr);
2413 result = pthread_create(&machThread, &attr, MachHandlerThreadFunctionStatic, (void*)this);
2414 pthread_attr_destroy(&attr);
2416 machHandlerInitialized = (result == 0);
2420 if(!machHandlerInitialized)
2421 ShutdownMachExceptionHandler();
2424 return machHandlerInitialized;
2428 void ExceptionHandler::ShutdownMachExceptionHandler()
2430 if(machThreadExecuting)
2432 machThreadShouldContinue = false; // Tell it to stop.
2434 // Cancel the current exception handler thread (which is probably blocking in a call to mach_msg) by sending it a cencel message.
2435 struct CancelMessage
2437 mach_msg_header_t msgHeader;
2438 };
2440 CancelMessage msg;
2441 memset(&msg.msgHeader, 0, sizeof(CancelMessage));
2442 msg.msgHeader.msgh_id = sMachCancelMessageType;
2443 msg.msgHeader.msgh_size = sizeof(CancelMessage);
2444 msg.msgHeader.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MAKE_SEND);
2445 msg.msgHeader.msgh_remote_port = machExceptionPort;
2446 msg.msgHeader.msgh_local_port = MACH_PORT_NULL;
2448 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);
2450 if(result == MACH_MSG_SUCCESS)
2452 const double threeSecondsLater = ovr_GetTimeInSeconds() + 3.f;
2454 while(machThreadExecuting && (ovr_GetTimeInSeconds() < threeSecondsLater))
2456 timespec ts = { 0, 1000000000 };
2457 nanosleep(&ts, nullptr);
2461 void* joinResult = nullptr;
2462 pthread_join(machThread, &joinResult);
2463 machThread = 0;
2466 if(machExceptionPort != MACH_PORT_NULL)
2468 // Restore the previous ports
2469 kern_return_t result = KERN_SUCCESS;
2470 mach_port_t machTaskSelf = mach_task_self();
2472 for(unsigned i = 0; (i < machExceptionPortsSaved.count) && (result == KERN_SUCCESS); i++)
2474 result = task_set_exception_ports(machTaskSelf, machExceptionPortsSaved.masks[i], machExceptionPortsSaved.ports[i],
2475 machExceptionPortsSaved.behaviors[i], machExceptionPortsSaved.flavors[i]);
2478 mach_port_deallocate(machTaskSelf, machExceptionPort);
2479 machExceptionPort = MACH_PORT_NULL;
2482 machHandlerInitialized = false;
2486 kern_return_t ExceptionHandler::ForwardMachException(mach_port_t thread, mach_port_t task, exception_type_t exceptionType,
2487 mach_exception_data_t pExceptionDetail, mach_msg_type_number_t exceptionDetailCount)
2489 kern_return_t result = KERN_FAILURE;
2490 mach_msg_type_number_t i;
2492 for(i = 0; i < machExceptionPortsSaved.count; i++)
2494 if(machExceptionPortsSaved.masks[i] & (1 << exceptionType))
2495 break;
2498 if(i < machExceptionPortsSaved.count)
2500 mach_port_t port = machExceptionPortsSaved.ports[i];
2501 exception_behavior_t behavior = machExceptionPortsSaved.behaviors[i];
2502 thread_state_flavor_t flavor = machExceptionPortsSaved.flavors[i];
2503 mach_msg_type_number_t threadStateCount = THREAD_STATE_MAX;
2504 thread_state_data_t threadState;
2506 if(behavior != EXCEPTION_DEFAULT)
2507 thread_get_state(thread, flavor, threadState, &threadStateCount);
2509 switch(behavior)
2511 case EXCEPTION_DEFAULT:
2512 result = mach_exception_raise_OVR(port, thread, task, exceptionType, pExceptionDetail, exceptionDetailCount);
2513 break;
2515 case EXCEPTION_STATE:
2516 result = mach_exception_raise_state_OVR(port, exceptionType, pExceptionDetail, exceptionDetailCount,
2517 &flavor, threadState, threadStateCount, threadState, &threadStateCount);
2518 break;
2520 case EXCEPTION_STATE_IDENTITY:
2521 result = mach_exception_raise_state_identity_OVR(port, thread, task, exceptionType, pExceptionDetail,
2522 exceptionDetailCount, &flavor, threadState, threadStateCount, threadState, &threadStateCount);
2523 break;
2525 default:
2526 result = KERN_FAILURE;
2527 break;
2530 if(behavior != EXCEPTION_DEFAULT)
2531 result = thread_set_state(thread, flavor, threadState, threadStateCount);
2534 return result;
2538 #endif // OVR_OS_APPLE
2541 bool ExceptionHandler::Enable(bool enable)
2543 #if defined(OVR_OS_MS)
2544 if(enable && !enabled)
2546 OVR_ASSERT(vectoredHandle == nullptr);
2547 vectoredHandle = AddVectoredExceptionHandler(1, Win32ExceptionFilter); // Windows call.
2548 enabled = (vectoredHandle != nullptr);
2549 OVR_ASSERT(enabled);
2550 sExceptionHandler = this;
2551 return enabled;
2553 else if(!enable && enabled)
2555 if(sExceptionHandler == this)
2556 sExceptionHandler = nullptr;
2557 OVR_ASSERT(vectoredHandle != nullptr);
2558 ULONG result = RemoveVectoredExceptionHandler(vectoredHandle); // Windows call.
2559 OVR_ASSERT_AND_UNUSED(result != 0, result);
2560 vectoredHandle = nullptr;
2561 enabled = false;
2562 return true;
2565 #elif defined(OVR_OS_APPLE)
2567 if(enable && !enabled)
2569 enabled = InitMachExceptionHandler();
2570 OVR_ASSERT(enabled);
2571 sExceptionHandler = this;
2572 return enabled;
2574 else if(!enable && enabled)
2576 if(sExceptionHandler == this)
2577 sExceptionHandler = nullptr;
2578 ShutdownMachExceptionHandler();
2579 enabled = false;
2580 return true;
2582 #else
2583 OVR_UNUSED(enable);
2584 #endif
2586 return true;
2590 void ExceptionHandler::EnableReportPrivacy(bool enable)
2592 reportPrivacyEnabled = enable;
2595 void ExceptionHandler::WriteExceptionDescription()
2597 #if defined(OVR_OS_MS)
2598 // There is some extra information available for AV exception.
2599 if(exceptionInfo.exceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
2601 const char* error = (exceptionInfo.exceptionRecord.ExceptionInformation[0] == 0) ? "reading" :
2602 ((exceptionInfo.exceptionRecord.ExceptionInformation[0] == 1) ? "writing" : "executing");
2604 char addressStr[24];
2605 SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), exceptionInfo.pExceptionMemoryAddress);
2606 OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription), "ACCESS_VIOLATION %s address %s", error, addressStr);
2608 else
2610 exceptionInfo.exceptionDescription[0] = 0;
2612 // Process "standard" exceptions, other than 'access violation'
2613 #define FORMAT_EXCEPTION(x) \
2614 case EXCEPTION_##x: \
2615 OVR::OVR_strlcpy(exceptionInfo.exceptionDescription, #x, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription)); \
2616 break;
2618 switch(exceptionInfo.exceptionRecord.ExceptionCode)
2620 //FORMAT_EXCEPTION(ACCESS_VIOLATION) Already handled above.
2621 FORMAT_EXCEPTION(DATATYPE_MISALIGNMENT)
2622 FORMAT_EXCEPTION(BREAKPOINT)
2623 FORMAT_EXCEPTION(SINGLE_STEP)
2624 FORMAT_EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
2625 FORMAT_EXCEPTION(FLT_DENORMAL_OPERAND)
2626 FORMAT_EXCEPTION(FLT_DIVIDE_BY_ZERO)
2627 FORMAT_EXCEPTION(FLT_INEXACT_RESULT)
2628 FORMAT_EXCEPTION(FLT_INVALID_OPERATION)
2629 FORMAT_EXCEPTION(FLT_OVERFLOW)
2630 FORMAT_EXCEPTION(FLT_STACK_CHECK)
2631 FORMAT_EXCEPTION(FLT_UNDERFLOW)
2632 FORMAT_EXCEPTION(INT_DIVIDE_BY_ZERO)
2633 FORMAT_EXCEPTION(INT_OVERFLOW)
2634 FORMAT_EXCEPTION(PRIV_INSTRUCTION)
2635 FORMAT_EXCEPTION(IN_PAGE_ERROR)
2636 FORMAT_EXCEPTION(ILLEGAL_INSTRUCTION)
2637 FORMAT_EXCEPTION(NONCONTINUABLE_EXCEPTION)
2638 FORMAT_EXCEPTION(STACK_OVERFLOW)
2639 FORMAT_EXCEPTION(INVALID_DISPOSITION)
2640 FORMAT_EXCEPTION(GUARD_PAGE)
2641 FORMAT_EXCEPTION(INVALID_HANDLE)
2642 #if defined(EXCEPTION_POSSIBLE_DEADLOCK) && defined(STATUS_POSSIBLE_DEADLOCK) // This type seems to be non-existant in practice.
2643 FORMAT_EXCEPTION(POSSIBLE_DEADLOCK)
2644 #endif
2647 // If not one of the "known" exceptions, try to get the string from NTDLL.DLL's message table.
2648 if(exceptionInfo.exceptionDescription[0] == 0)
2650 char addressStr[24];
2651 SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), exceptionInfo.pExceptionMemoryAddress);
2653 #if !defined(OVR_OS_CONSOLE) // If FormatMessage is supported...
2654 char buffer[384];
2655 DWORD capacity = OVR_ARRAY_COUNT(buffer);
2657 const size_t length = (size_t)FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
2658 GetModuleHandleW(L"NTDLL.DLL"), exceptionInfo.exceptionRecord.ExceptionCode, 0, buffer, capacity, nullptr);
2659 if(length)
2660 OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription),
2661 "%s at instruction %s", buffer, addressStr);
2662 #endif
2664 // If everything else failed just show the hex code.
2665 if(exceptionInfo.exceptionDescription[0] == 0)
2666 OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription),
2667 "Unknown exception 0x%08x at instruction %s", exceptionInfo.exceptionRecord.ExceptionCode, addressStr);
2671 #elif defined(OVR_OS_APPLE)
2672 struct MachExceptionInfo
2674 static const char* GetCPUExceptionIdString(uint32_t cpuExceptionId)
2676 const char* id;
2678 #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
2679 switch (cpuExceptionId)
2681 case 0: id = "integer div/0"; break;
2682 case 1: id = "breakpoint fault"; break;
2683 case 2: id = "non-maskable interrupt"; break;
2684 case 3: id = "int 3"; break;
2685 case 4: id = "overflow"; break;
2686 case 5: id = "bounds check failure"; break;
2687 case 6: id = "invalid instruction"; break;
2688 case 7: id = "coprocessor unavailable"; break;
2689 case 8: id = "exception within exception"; break;
2690 case 9: id = "coprocessor segment overrun"; break;
2691 case 10: id = "invalid task switch"; break;
2692 case 11: id = "segment not present"; break;
2693 case 12: id = "stack exception"; break;
2694 case 13: id = "general protection fault"; break;
2695 case 14: id = "page fault"; break;
2696 case 16: id = "coprocessor error"; break;
2697 default: id = "<unknown>"; break;
2699 #else
2700 // To do: Support ARM or others.
2701 #endif
2703 return id;
2706 static const char* GetMachExceptionTypeString(uint64_t exceptionCause)
2708 switch (exceptionCause)
2710 case EXC_ARITHMETIC: return "EXC_ARITHMETIC";
2711 case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS";
2712 case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION";
2713 case EXC_BREAKPOINT: return "EXC_BREAKPOINT";
2714 case EXC_CRASH: return "EXC_CRASH";
2715 case EXC_EMULATION: return "EXC_EMULATION";
2716 case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL";
2717 case EXC_RPC_ALERT: return "EXC_RPC_ALERT";
2718 case EXC_SOFTWARE: return "EXC_SOFTWARE";
2719 case EXC_SYSCALL: return "EXC_SYSCALL";
2720 };
2722 return "EXC_<unknown>";
2725 static const char* GetMachExceptionIdString(uint64_t machExceptionId, uint64_t code0)
2727 const char* id = "<unknown>";
2729 #if defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64)
2730 switch (machExceptionId)
2732 case EXC_ARITHMETIC:
2733 switch (code0)
2735 case EXC_I386_BOUND: id = "EXC_I386_BOUND"; break;
2736 case EXC_I386_DIV: id = "EXC_I386_DIV"; break;
2737 case EXC_I386_EMERR: id = "EXC_I386_EMERR"; break;
2738 case EXC_I386_EXTERR: id = "EXC_I386_EXTERR"; break;
2739 case EXC_I386_EXTOVR: id = "EXC_I386_EXTOVR"; break;
2740 case EXC_I386_INTO: id = "EXC_I386_INTO"; break;
2741 case EXC_I386_NOEXT: id = "EXC_I386_NOEXT"; break;
2742 case EXC_I386_SSEEXTERR: id = "EXC_I386_SSEEXTERR"; break;
2744 break;
2746 case EXC_BAD_INSTRUCTION:
2747 if(code0 == EXC_I386_INVOP)
2748 id = "EXC_I386_INVOP";
2749 break;
2751 case EXC_BREAKPOINT:
2752 if(code0 == EXC_I386_BPT)
2753 id = "EXC_I386_BPT";
2754 else if(code0 == EXC_I386_SGL)
2755 id = "EXC_I386_SGL";
2756 break;
2757 };
2758 #else
2759 // To do.
2760 #endif
2762 return id;
2764 };
2766 OVR::OVR_snprintf(exceptionInfo.exceptionDescription, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription),
2767 "Mach exception type: %llu (%s)\r\n", exceptionInfo.exceptionType, MachExceptionInfo::GetMachExceptionTypeString(exceptionInfo.exceptionType));
2769 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",
2770 exceptionInfo.cpuExceptionId, MachExceptionInfo::GetCPUExceptionIdString(exceptionInfo.cpuExceptionId), exceptionInfo.cpuExceptionIdError, exceptionInfo.pExceptionMemoryAddress);
2771 OVR::OVR_strlcat(exceptionInfo.exceptionDescription, scratchBuffer, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription));
2774 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],
2775 MachExceptionInfo::GetMachExceptionIdString(exceptionInfo.exceptionType, exceptionInfo.machExceptionDetail[0]),
2776 (uint64_t)exceptionInfo.machExceptionDetail[1], (uint64_t)exceptionInfo.machExceptionDetail[1]);
2777 OVR::OVR_strlcat(exceptionInfo.exceptionDescription, scratchBuffer, OVR_ARRAY_COUNT(exceptionInfo.exceptionDescription));
2778 #else
2779 // To do.
2780 exceptionInfo.exceptionDescription[0] = 0;
2781 #endif
2785 void ExceptionHandler::WriteReportLine(const char* pLine)
2787 fwrite(pLine, strlen(pLine), 1, file);
2791 void ExceptionHandler::WriteReportLineF(const char* format, ...)
2793 va_list args;
2794 va_start(args, format);
2795 int length = OVR_vsnprintf(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), format, args);
2796 if(length >= (int)OVR_ARRAY_COUNT(scratchBuffer)) // If we didn't have enough space...
2797 length = (OVR_ARRAY_COUNT(scratchBuffer) - 1); // ... use what we have.
2798 va_end(args);
2800 fwrite(scratchBuffer, length, 1, file);
2804 // Thread <name> <handle> <id>
2805 // 0 <module> <address> <function> <file>:<line>
2806 // 1 <module> <address> <function> <file>:<line>
2807 // . . .
2808 //
2809 void ExceptionHandler::WriteThreadCallstack(ThreadHandle threadHandle, ThreadSysId threadSysId, const char* additionalInfo)
2811 // We intentionally do not directly use the SymbolInfo::ReportThreadCallstack function because that function allocates memory,
2812 // which we cannot do due to possibly being within an exception handler.
2814 // Print the header
2815 char threadName[32];
2816 char threadHandleStr[32];
2817 char threadSysIdStr[32];
2818 char stackBaseStr[24];
2819 char stackLimitStr[24];
2820 char stackCurrentStr[24];
2821 void* pStackBase;
2822 void* pStackLimit;
2823 bool isExceptionThread = (threadSysId == exceptionInfo.threadSysId);
2825 #if defined(OVR_OS_MS) && (OVR_PTR_SIZE == 8)
2826 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.
2827 #elif defined(OVR_OS_MS)
2828 void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.Esp : nullptr;
2829 #elif defined(OVR_OS_MAC) && (OVR_PTR_SIZE == 8)
2830 void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.threadState.uts.ts64.__rsp : nullptr;
2831 #elif defined(OVR_OS_MAC)
2832 void* pStackCurrent = (threadSysId == exceptionInfo.threadSysId) ? (void*)exceptionInfo.cpuContext.threadState.uts.ts32.__esp : nullptr;
2833 #elif defined(OVR_OS_LINUX)
2834 void* pStackCurrent = nullptr; // To do.
2835 #endif
2837 OVR::GetThreadStackBounds(pStackBase, pStackLimit, threadHandle);
2839 OVR::Thread::GetThreadName(threadName, OVR_ARRAY_COUNT(threadName), threadName);
2840 SprintfThreadHandle(threadHandleStr, OVR_ARRAY_COUNT(threadHandleStr), threadHandle);
2841 SprintfThreadSysId(threadSysIdStr, OVR_ARRAY_COUNT(threadSysIdStr), threadSysId);
2842 SprintfAddress(stackBaseStr, OVR_ARRAY_COUNT(stackBaseStr), pStackBase);
2843 SprintfAddress(stackLimitStr, OVR_ARRAY_COUNT(stackLimitStr), pStackLimit);
2844 SprintfAddress(stackCurrentStr, OVR_ARRAY_COUNT(stackCurrentStr), pStackCurrent);
2846 if(threadName[0])
2847 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 : "");
2848 else
2849 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 : "");
2851 // Print the backtrace info
2852 void* addressArray[64];
2853 size_t addressCount = symbolLookup.GetBacktraceFromThreadSysId(addressArray, OVR_ARRAY_COUNT(addressArray), 0, threadSysId);
2854 SymbolInfo symbolInfo;
2855 const char* pModuleName;
2856 size_t backtraceSkipCount = 0;
2858 if(isExceptionThread)
2860 // If this thread is the exception thread, skip some frames.
2861 #if defined(OVR_OS_MS)
2862 size_t i, iEnd = MIN(16, addressCount);
2864 for(i = 0; i < iEnd; i++)
2866 symbolLookup.LookupSymbol((uint64_t)addressArray[i], symbolInfo);
2867 if(strstr(symbolInfo.function, "UserExceptionDispatcher") != nullptr)
2868 break;
2871 if(i < iEnd) // If found...
2872 backtraceSkipCount = i;
2873 else if(addressCount >= 9) // Else default to 9, which is coincidentally what works.
2874 backtraceSkipCount = 9;
2875 else
2876 backtraceSkipCount = 0;
2878 addressArray[backtraceSkipCount] = exceptionInfo.pExceptionInstructionAddress;
2879 #endif
2882 if(addressCount == 0)
2884 WriteReportLine("<Unable to read backtrace>\r\n\r\n");
2886 else
2888 for(size_t i = backtraceSkipCount; i < addressCount; ++i)
2890 symbolLookup.LookupSymbol((uint64_t)addressArray[i], symbolInfo);
2892 if(symbolInfo.pModuleInfo && symbolInfo.pModuleInfo->name[0])
2893 pModuleName = symbolInfo.pModuleInfo->name;
2894 else
2895 pModuleName = "(unknown module)";
2897 char addressStr[24];
2898 SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), addressArray[i]);
2900 if(symbolInfo.filePath[0])
2901 WriteReportLineF("%-2u %-24s %s %s+%d %s:%d\r\n%s", (unsigned)i, pModuleName, addressStr,
2902 symbolInfo.function, symbolInfo.functionOffset, symbolInfo.filePath,
2903 symbolInfo.fileLineNumber, (i + 1) == addressCount ? "\r\n" : "");
2904 else
2905 WriteReportLineF("%-2u %-24s %s %s+%d\r\n%s", (unsigned)i, pModuleName, addressStr,
2906 symbolInfo.function, symbolInfo.functionOffset, (i + 1) == addressCount ? "\r\n" : ""); // If this is the last line, append another \r\n.
2912 void ExceptionHandler::WriteReport()
2914 // It's important that we don't allocate any memory here if we can help it.
2915 using namespace OVR;
2917 if(strstr(reportFilePath, "%s")) // If the user-specified file path includes a date/time component...
2919 char dateTimeBuffer[64];
2920 FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(dateTimeBuffer), exceptionInfo.timeVal, true, true, false, true);
2921 OVR_snprintf(reportFilePathActual, OVR_ARRAY_COUNT(reportFilePathActual), reportFilePath, dateTimeBuffer);
2923 else
2925 OVR_strlcpy(reportFilePathActual, reportFilePath, OVR_ARRAY_COUNT(reportFilePathActual));
2928 file = fopen(reportFilePathActual, "w");
2929 OVR_ASSERT(file != nullptr);
2930 if(!file)
2931 return;
2933 symbolLookup.Initialize();
2936 // Exception information
2937 WriteReportLine("Exception Info\r\n");
2939 WriteReportLineF("Exception report file: %s\r\n", reportFilePathActual);
2941 #if defined(OVR_OS_MS)
2942 if(miniDumpFilePath[0])
2943 WriteReportLineF("Exception minidump file: %s\r\n", minidumpFilePathActual);
2944 #endif
2946 char dateTimeBuffer[64];
2947 FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(dateTimeBuffer), exceptionInfo.timeVal, true, true, false, false);
2948 WriteReportLineF("Time (GMT): %s\r\n", dateTimeBuffer);
2950 FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(scratchBuffer), exceptionInfo.timeVal, true, true, true, false);
2951 WriteReportLineF("Time (local): %s\r\n", dateTimeBuffer);
2952 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.
2954 SprintfThreadHandle(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), exceptionInfo.threadHandle);
2955 OVR_strlcat(scratchBuffer, "\r\n", OVR_ARRAY_COUNT(scratchBuffer));
2956 WriteReportLine("Thread handle: ");
2957 WriteReportLine(scratchBuffer);
2959 SprintfThreadSysId(scratchBuffer, OVR_ARRAY_COUNT(scratchBuffer), exceptionInfo.threadSysId);
2960 OVR_strlcat(scratchBuffer, "\r\n", OVR_ARRAY_COUNT(scratchBuffer));
2961 WriteReportLine("Thread sys id: ");
2962 WriteReportLine(scratchBuffer);
2964 char addressStr[24];
2965 SprintfAddress(addressStr, OVR_ARRAY_COUNT(addressStr), exceptionInfo.pExceptionInstructionAddress);
2966 WriteReportLineF("Exception instruction address: %s (see callstack below)\r\n", addressStr);
2967 WriteReportLineF("Exception description: %s\r\n", exceptionInfo.exceptionDescription);
2969 if(symbolLookup.LookupSymbol((uint64_t)exceptionInfo.pExceptionInstructionAddress, exceptionInfo.symbolInfo))
2971 if(exceptionInfo.symbolInfo.filePath[0])
2972 WriteReportLineF("Exception location: %s (%d)\r\n", exceptionInfo.symbolInfo.filePath, exceptionInfo.symbolInfo.fileLineNumber);
2973 else
2974 WriteReportLineF("Exception location: %s (%d)\r\n", exceptionInfo.symbolInfo.function, exceptionInfo.symbolInfo.functionOffset);
2977 // To consider: print exceptionInfo.cpuContext registers
2980 // OVR information
2981 WriteReportLine("\r\nOVR Info\r\n");
2982 WriteReportLineF("OVR time: %f\r\n", ovr_GetTimeInSeconds());
2983 WriteReportLineF("OVR version: %s\r\n", ovr_GetVersionString());
2985 // OVR util information
2986 // The following would be useful to use if they didn't allocate memory, which we can't do.
2987 // To do: see if we can have versions of the functions below which don't allocate memory
2988 // or allocate it safely (e.g. use an alternative heap).
2989 // String OVR::GetDisplayDriverVersion();
2990 // String OVR::GetCameraDriverVersion();
2992 // OVR HMD information
2993 WriteReportLine("\r\nOVR HMD Info\r\n");
2995 const OVR::List<OVR::CAPI::HMDState>& hmdStateList = OVR::CAPI::HMDState::GetHMDStateList();
2996 const OVR::CAPI::HMDState* pHMDState = hmdStateList.GetFirst();
2998 if(hmdStateList.IsNull(pHMDState))
3000 WriteReportLine("No HMDs found.\r\n");
3003 while(!hmdStateList.IsNull(pHMDState))
3005 if(pHMDState->pProfile)
3007 const char* user = pHMDState->pProfile->GetValue(OVR_KEY_USER);
3009 if(user)
3010 WriteReportLineF("Profile user: %s\r\n", reportPrivacyEnabled ? "<disabled by report privacy settings>" : user);
3011 else
3012 WriteReportLine("Null profile user\r\n");
3014 float NeckEyeDistance[2];
3015 float EyeToNoseDistance[2];
3016 float MaxEyeToPlateDist[2];
3017 pHMDState->pProfile->GetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, NeckEyeDistance, 2);
3018 pHMDState->pProfile->GetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, EyeToNoseDistance, 2);
3019 pHMDState->pProfile->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, MaxEyeToPlateDist, 2);
3021 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",
3022 pHMDState->pProfile->GetFloatValue(OVR_KEY_PLAYER_HEIGHT, 0.f),
3023 pHMDState->pProfile->GetFloatValue(OVR_KEY_EYE_HEIGHT, 0.f),
3024 pHMDState->pProfile->GetFloatValue(OVR_KEY_IPD, 0.f),
3025 NeckEyeDistance[0], NeckEyeDistance[1],
3026 pHMDState->pProfile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, 0),
3027 EyeToNoseDistance[0], EyeToNoseDistance[1],
3028 MaxEyeToPlateDist[0], MaxEyeToPlateDist[1],
3029 pHMDState->pProfile->GetBoolValue(OVR_KEY_CUSTOM_EYE_RENDER, false) ? "yes" : "no");
3031 // Not currently used:
3032 // OVR_KEY_NAME
3033 // OVR_KEY_GENDER
3034 // OVR_KEY_EYE_CUP
3035 // OVR_KEY_CAMERA_POSITION
3037 else
3039 WriteReportLine("Null HMD profile\r\n");
3042 if(pHMDState->pHmdDesc) // This should usually be true.
3044 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",
3045 0, (unsigned)pHMDState->pHmdDesc->Type, pHMDState->pHmdDesc->ProductName, pHMDState->pHmdDesc->Manufacturer, pHMDState->pHmdDesc->VendorId,
3046 pHMDState->pHmdDesc->ProductId, pHMDState->pHmdDesc->SerialNumber, pHMDState->pHmdDesc->FirmwareMajor, pHMDState->pHmdDesc->FirmwareMinor,
3047 pHMDState->pHmdDesc->Resolution.w, pHMDState->pHmdDesc->Resolution.h, pHMDState->pHmdDesc->DisplayDeviceName, pHMDState->pHmdDesc->DisplayId);
3049 // HSW display state
3050 ovrHSWDisplayState hswDS;
3051 ovrHmd_GetHSWDisplayState(pHMDState->pHmdDesc, &hswDS);
3052 WriteReportLineF("HSW displayed for hmd: %s\r\n", hswDS.Displayed ? "yes" : "no");
3055 char threadIdStr[24];
3056 SprintfAddress(threadIdStr, OVR_ARRAY_COUNT(threadIdStr), pHMDState->BeginFrameThreadId);
3058 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",
3059 pHMDState->EnabledHmdCaps, pHMDState->EnabledServiceHmdCaps, pHMDState->LatencyTestActive ? "yes" : "no", pHMDState->LastFrameTimeSeconds, pHMDState->LastGetFrameTimeSeconds, pHMDState->RenderingConfigured ? "yes" : "no",
3060 pHMDState->BeginFrameCalled ? "yes" : "no", threadIdStr);
3062 if(pHMDState->pLastError)
3064 WriteReportLineF("OVR last error for hmd: %s\r\n", pHMDState->pLastError);
3067 pHMDState = hmdStateList.GetNext(pHMDState);
3070 #if defined(OVR_OS_WIN32)
3072 WriteReportLine("\r\nApp Info\r\n");
3074 // Print the app path.
3075 char appPath[MAX_PATH];
3076 GetCurrentProcessFilePath(appPath, OVR_ARRAY_COUNT(appPath));
3077 WriteReportLineF("Process path: %s\r\n", appPath);
3079 #if (OVR_PTR_SIZE == 4)
3080 WriteReportLine("App format: 32 bit\r\n");
3081 #else
3082 WriteReportLine("App format: 64 bit\r\n");
3083 #endif
3085 // Print the app version
3086 wchar_t pathW[MAX_PATH] = {};
3087 GetModuleFileNameW(0, pathW, (DWORD)OVR_ARRAY_COUNT(pathW));
3088 DWORD dwUnused;
3089 DWORD dwSize = GetFileVersionInfoSizeW(pathW, &dwUnused);
3090 scratchBuffer[0] = 0;
3092 if(dwSize > 0)
3094 void* const pVersionData = SafeMMapAlloc(dwSize);
3096 if(pVersionData)
3098 if(GetFileVersionInfoW(pathW, 0, dwSize, pVersionData))
3100 VS_FIXEDFILEINFO* pFFI;
3101 UINT size;
3103 if(VerQueryValueA(pVersionData, "\\", (void**)&pFFI, &size))
3105 WriteReportLineF("App version: %u.%u.%u.%u\r\n",
3106 HIWORD(pFFI->dwFileVersionMS), LOWORD(pFFI->dwFileVersionMS),
3107 HIWORD(pFFI->dwFileVersionLS), LOWORD(pFFI->dwFileVersionLS));
3111 SafeMMapFree(pVersionData, dwSize);
3115 if(!scratchBuffer[0]) // If version info couldn't be found or read...
3116 WriteReportLine("App version info not present\r\n");
3120 WriteReportLine("\r\nSystem Info\r\n");
3122 OSVERSIONINFOEXW vi;
3123 memset(&vi, 0, sizeof(vi));
3124 vi.dwOSVersionInfoSize = sizeof(vi);
3125 GetVersionExW((LPOSVERSIONINFOW)&vi); // Cast to the older type.
3127 char osVersionName[256];
3128 GetOSVersionName(osVersionName, OVR_ARRAY_COUNT(osVersionName));
3129 WriteReportLineF("OS name: %s, version: %u.%u build %u, %s, platform id: %u, service pack: %ls\r\n",
3130 osVersionName, vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber, Is64BitOS() ? "64 bit" : "32 bit",
3131 vi.dwPlatformId, vi.szCSDVersion[0] ? vi.szCSDVersion : L"<none>");
3133 WriteReportLineF("Debugger present: %s\r\n", OVRIsDebuggerPresent() ? "yes" : "no");
3135 // System info
3136 SYSTEM_INFO systemInfo;
3137 GetNativeSystemInfo(&systemInfo);
3139 WriteReportLineF("Processor count: %u\r\n", systemInfo.dwNumberOfProcessors);
3141 // Windows Vista and later:
3142 // BOOL WINAPI GetLogicalProcessorInformation(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer, PDWORD ReturnLength);
3144 if(systemInfo.wProcessorArchitecture == 0)
3145 WriteReportLineF("Processor type: x86\r\n");
3146 else if(systemInfo.wProcessorArchitecture == 9)
3147 WriteReportLineF("Processor type: x86-64\r\n");
3148 else if(systemInfo.wProcessorArchitecture == 10)
3149 WriteReportLineF("Processor type: x86 on x86-64\r\n");
3151 WriteReportLineF("Processor level: %u\r\n", systemInfo.wProcessorLevel);
3152 WriteReportLineF("Processor revision: %u\r\n", systemInfo.wProcessorRevision);
3154 // Memory information
3155 MEMORYSTATUSEX memoryStatusEx;
3156 memset(&memoryStatusEx, 0, sizeof(memoryStatusEx));
3157 memoryStatusEx.dwLength = sizeof(memoryStatusEx);
3158 GlobalMemoryStatusEx(&memoryStatusEx);
3160 WriteReportLineF("Memory load: %d%%\r\n", memoryStatusEx.dwMemoryLoad);
3161 WriteReportLineF("Total physical memory: %I64d MiB\r\n", memoryStatusEx.ullTotalPhys / (1024 * 1024)); // Or are Mebibytes equal to (1024 * 1000)
3162 WriteReportLineF("Available physical memory: %I64d MiB\r\n", memoryStatusEx.ullAvailPhys / (1024 * 1024));
3163 WriteReportLineF("Total page file memory: %I64d MiB\r\n", memoryStatusEx.ullTotalPageFile / (1024 * 1024));
3164 WriteReportLineF("Available page file memory: %I64d MiB\r\n", memoryStatusEx.ullAvailPageFile / (1024 * 1024));
3165 WriteReportLineF("Total virtual memory: %I64d MiB\r\n", memoryStatusEx.ullTotalVirtual / (1024 * 1024));
3166 WriteReportLineF("Free virtual memory: %I64d MiB\r\n", memoryStatusEx.ullAvailVirtual / (1024 * 1024));
3168 DISPLAY_DEVICE dd;
3169 memset(&dd, 0, sizeof(DISPLAY_DEVICE));
3170 dd.cb = sizeof(DISPLAY_DEVICE);
3172 for(int i = 0; EnumDisplayDevicesW(nullptr, (DWORD)i, &dd, EDD_GET_DEVICE_INTERFACE_NAME); ++i)
3174 WriteReportLineF("Display Device %d name: %ls, context: %ls, primary: %s, mirroring: %s\r\n",
3175 i, dd.DeviceName, dd.DeviceString, (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no", (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) ? "yes" : "no");
3179 // Print video card information
3180 // http://msdn.microsoft.com/en-us/library/aa394512%28v=vs.85%29.aspx
3182 IWbemLocator* pIWbemLocator = nullptr;
3183 BSTR bstrServer = nullptr;
3184 IWbemServices* pIWbemServices = nullptr;
3185 BSTR bstrWQL = nullptr;
3186 BSTR bstrPath = nullptr;
3187 IEnumWbemClassObject* pEnum = nullptr;
3189 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
3191 HRESULT hr = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator);
3192 if(FAILED(hr))
3193 goto End;
3195 bstrServer = SysAllocString(L"\\\\.\\root\\cimv2");
3196 hr = pIWbemLocator->ConnectServer(bstrServer, nullptr, nullptr, 0L, 0L, nullptr, nullptr, &pIWbemServices);
3197 if(FAILED(hr))
3198 goto End;
3200 hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL,
3201 RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DEFAULT);
3202 if(FAILED(hr))
3203 goto End;
3205 bstrWQL = SysAllocString(L"WQL");
3206 bstrPath = SysAllocString(L"select * from Win32_VideoController");
3207 hr = pIWbemServices->ExecQuery(bstrWQL, bstrPath, WBEM_FLAG_FORWARD_ONLY, nullptr, &pEnum);
3208 if(FAILED(hr))
3209 goto End;
3211 ULONG uReturned;
3212 IWbemClassObject* pObj = nullptr;
3213 hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);
3214 if(FAILED(hr))
3215 goto End;
3217 WriteReportLine("\r\nDisplay adapter list\r\n");
3219 for(unsigned i = 0; SUCCEEDED(hr) && uReturned; i++)
3221 char sString[256];
3222 VARIANT var;
3224 if(i > 0)
3225 WriteReportLine("\r\n");
3227 WriteReportLineF("Info for display adapter %u\r\n", i);
3229 hr = pObj->Get(L"Name", 0, &var, nullptr, nullptr);
3230 if(SUCCEEDED(hr))
3232 WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
3233 WriteReportLineF("Display Adapter Name: %s\r\n", sString);
3236 hr = pObj->Get(L"AdapterRAM", 0, &var, nullptr, nullptr);
3237 if(SUCCEEDED(hr))
3239 WriteReportLineF("Display Adapter RAM: %u %s\r\n",
3240 ((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"));
3243 hr = pObj->Get(L"DeviceID", 0, &var, nullptr, nullptr);
3244 if(SUCCEEDED(hr))
3246 WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
3247 WriteReportLineF("Display Adapter DeviceID: %s\r\n", sString);
3250 hr = pObj->Get(L"DriverVersion", 0, &var, nullptr, nullptr);
3251 if(SUCCEEDED(hr))
3253 WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
3254 WriteReportLineF("Display Adapter DriverVersion: %s\r\n", sString);
3257 hr = pObj->Get(L"DriverDate", 0, &var, nullptr, nullptr);
3258 if(SUCCEEDED(hr))
3260 // http://technet.microsoft.com/en-us/library/ee156576.aspx
3261 wchar_t year[5] = { var.bstrVal[0], var.bstrVal[1], var.bstrVal[2], var.bstrVal[3], 0 };
3262 wchar_t month[3] = { var.bstrVal[4], var.bstrVal[5], 0 };
3263 wchar_t monthDay[3] = { var.bstrVal[6], var.bstrVal[7], 0 };
3265 WriteReportLineF("Display Adapter DriverDate (US format): %ls/%ls/%ls\r\n", month, monthDay, year);
3268 // VideoProcessor
3269 hr = pObj->Get(L"VideoProcessor", 0, &var, nullptr, nullptr);
3270 if(SUCCEEDED(hr))
3272 WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
3273 WriteReportLineF("Display Adapter VideoProcessor %s\r\n", sString);
3276 hr = pObj->Get(L"VideoModeDescription", 0, &var, nullptr, nullptr);
3277 if(SUCCEEDED(hr))
3279 WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sString, sizeof(sString), nullptr, nullptr);
3280 WriteReportLineF("Display Adapter VideoModeDescription: %s\r\n", sString);
3283 pObj->Release();
3285 hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);
3288 End:
3289 if(pEnum)
3290 pEnum->Release();
3291 if(bstrPath)
3292 SysFreeString(bstrPath);
3293 if(bstrWQL)
3294 SysFreeString(bstrWQL);
3295 if(pIWbemServices)
3296 pIWbemServices->Release();
3297 if(bstrServer)
3298 SysFreeString(bstrServer);
3299 if(pIWbemLocator)
3300 pIWbemLocator->Release();
3302 CoUninitialize();
3306 // Print a list of threads.
3307 DWORD currentProcessId = GetCurrentProcessId();
3308 HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, currentProcessId); // ICreateToolhelp32Snapshot actually ignores currentProcessId.
3310 if(hThreadSnap != INVALID_HANDLE_VALUE)
3312 THREADENTRY32 te32;
3313 te32.dwSize = sizeof(THREADENTRY32);
3315 if(Thread32First(hThreadSnap, &te32))
3317 WriteReportLine("\r\nThread list\r\n");
3319 do {
3320 if(te32.th32OwnerProcessID == currentProcessId)
3322 HANDLE hThread = ConvertThreadSysIdToThreadHandle(te32.th32ThreadID);
3324 if(hThread)
3326 char buffer[96]; // Can't use scratchBuffer, because it's used by WriteThreadCallstack.
3327 OVR_snprintf(buffer, OVR_ARRAY_COUNT(buffer), "base priority: %ld, delta priority: %ld", te32.tpBasePri, te32.tpDeltaPri);
3329 bool threadIsExceptionThread = (te32.th32ThreadID == (DWORD)exceptionInfo.threadSysId);
3330 if(threadIsExceptionThread)
3331 OVR_strlcat(buffer, ", exception thread", OVR_ARRAY_COUNT(buffer));
3333 WriteThreadCallstack(hThread, (OVR::ThreadSysId)te32.th32ThreadID, buffer);
3334 FreeThreadHandle(hThread);
3337 } while(Thread32Next(hThreadSnap, &te32));
3340 CloseHandle(hThreadSnap);
3345 // Print a list of the current modules within this process.
3346 // DbgHelp.dll also provides a EnumerateLoadedModules64 function.
3347 // To do: Convert the code below to use the GetModuleInfoArray function which we now have.
3348 #if defined(OVR_OS_CONSOLE)
3349 struct MODULEINFO {
3350 LPVOID lpBaseOfDll;
3351 DWORD SizeOfImage;
3352 LPVOID EntryPoint;
3353 };
3354 HMODULE hModule = LoadLibraryW(L"toolhelpx.dll");
3355 #else
3356 HMODULE hModule = LoadLibraryW(L"psapi.dll");
3357 #endif
3359 if(hModule)
3361 typedef BOOL (WINAPI * ENUMPROCESSMODULES) (HANDLE hProcess, HMODULE* phModule, DWORD cb, LPDWORD lpcbNeeded);
3362 typedef DWORD (WINAPI * GETMODULEBASENAME) (HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
3363 typedef DWORD (WINAPI * GETMODULEFILENAMEEX) (HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
3364 typedef BOOL (WINAPI * GETMODULEINFORMATION)(HANDLE hProcess, HMODULE hModule, MODULEINFO* pmi, DWORD nSize);
3366 #if defined(OVR_OS_CONSOLE)
3367 ENUMPROCESSMODULES pEnumProcessModules = (ENUMPROCESSMODULES) (uintptr_t)GetProcAddress(hModule, "K32EnumProcessModules");
3368 GETMODULEBASENAME pGetModuleBaseName = (GETMODULEBASENAME) (uintptr_t)GetProcAddress(hModule, "K32GetModuleBaseNameW");
3369 GETMODULEFILENAMEEX pGetModuleFileNameEx = (GETMODULEFILENAMEEX) (uintptr_t)GetProcAddress(hModule, "K32GetModuleFileNameExW");
3370 GETMODULEINFORMATION pGetModuleInformation = (GETMODULEINFORMATION)(uintptr_t)GetProcAddress(hModule, "K32GetModuleInformation");
3371 #else
3372 ENUMPROCESSMODULES pEnumProcessModules = (ENUMPROCESSMODULES) (uintptr_t)GetProcAddress(hModule, "EnumProcessModules");
3373 GETMODULEBASENAME pGetModuleBaseName = (GETMODULEBASENAME) (uintptr_t)GetProcAddress(hModule, "GetModuleBaseNameW");
3374 GETMODULEFILENAMEEX pGetModuleFileNameEx = (GETMODULEFILENAMEEX) (uintptr_t)GetProcAddress(hModule, "GetModuleFileNameExW");
3375 GETMODULEINFORMATION pGetModuleInformation = (GETMODULEINFORMATION)(uintptr_t)GetProcAddress(hModule, "GetModuleInformation");
3376 #endif
3378 HANDLE hProcess = GetCurrentProcess();
3379 HMODULE hModuleArray[200];
3380 DWORD cbNeeded;
3382 if(pEnumProcessModules(hProcess, hModuleArray, sizeof(hModuleArray), &cbNeeded))
3384 size_t actualModuleCount = (cbNeeded / sizeof(HMODULE));
3386 if(actualModuleCount > OVR_ARRAY_COUNT(hModuleArray)) //If hModuleArray's capacity was not enough...
3387 actualModuleCount = OVR_ARRAY_COUNT(hModuleArray);
3389 // Print a header
3390 WriteReportLine("\r\nModule list\r\n");
3392 #if (OVR_PTR_SIZE == 4)
3393 WriteReportLine("Base Size Entrypoint Name Path\r\n");
3394 #else
3395 WriteReportLine("Base Size Entrypoint Name Path\r\n");
3396 #endif
3398 // And go through the list one by one
3399 for(size_t i = 0; i < actualModuleCount; i++)
3401 MODULEINFO mi;
3402 size_t length;
3404 if(!pGetModuleInformation(hProcess, hModuleArray[i], &mi, sizeof(mi)))
3406 mi.EntryPoint = nullptr;
3407 mi.lpBaseOfDll = nullptr;
3408 mi.SizeOfImage = 0;
3411 // Write the base name.
3412 wchar_t name[MAX_PATH + 3];
3413 name[0] = '"';
3414 if(pGetModuleBaseName(hProcess, hModuleArray[i], name + 1, MAX_PATH))
3415 length = wcslen(name);
3416 else
3418 wcscpy(name + 1, L"(unknown)");
3419 length = 10;
3422 name[length] = '"';
3423 name[length + 1] = '\0';
3425 // Write the path
3426 wchar_t path[MAX_PATH + 3];
3427 path[0] = '"';
3428 if(pGetModuleFileNameEx(hProcess, hModuleArray[i], path + 1, MAX_PATH))
3429 length = wcslen(path);
3430 else
3432 wcscpy(path + 1, L"(unknown)");
3433 length = 10;
3435 path[length] = '"';
3436 path[length + 1] = '\0';
3438 #if (OVR_PTR_SIZE == 4)
3439 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);
3440 #else
3441 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);
3442 #endif
3449 // Print a list of processes.
3450 // DbgHelp.dll provides a SymEnumProcesses function, but it's available with DbgHelp.dll v6.2 which doesn't ship with Windows until Windows 8.
3451 WriteReportLine("\r\nProcess list\r\n");
3453 if(reportPrivacyEnabled)
3454 WriteReportLine("Disabled by report privacy settings\r\n");
3455 else
3457 HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
3459 if(hProcessSnapshot != INVALID_HANDLE_VALUE)
3461 PROCESSENTRY32W pe32;
3462 memset(&pe32, 0, sizeof(pe32));
3463 pe32.dwSize = sizeof(pe32);
3465 if(Process32FirstW(hProcessSnapshot, &pe32))
3467 WriteReportLine("Process Id File\r\n");
3469 do {
3470 // Try to get the full path to the process, as pe32.szExeFile holds only the process file name.
3471 // This will typically fail with a privilege error unless this process has debug privileges: http://support.microsoft.com/kb/131065/en-us
3472 wchar_t filePathW[MAX_PATH];
3473 const wchar_t* pFilePathW = pe32.szExeFile;
3474 HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID); // With Windows Vista+ we can use PROCESS_QUERY_LIMITED_INFORMATION.
3475 if(hProcess)
3477 if(GetProcessImageFileName(hProcess, filePathW, (DWORD)OVR_ARRAY_COUNT(filePathW)))
3478 pFilePathW = filePathW;
3481 WriteReportLineF("0x%08x %ls\r\n", pe32.th32ProcessID, pFilePathW);
3482 } while(Process32NextW(hProcessSnapshot, &pe32));
3485 CloseHandle(hProcessSnapshot);
3487 else
3489 WriteReportLine("Unable to read process list\r\n");
3494 #elif defined(OVR_OS_APPLE)
3496 WriteReportLine("\r\nApp Info\r\n");
3498 // App path
3499 const pid_t processId = getpid();
3500 WriteReportLineF("Process id: ", "%lld (0x%llx)\r\n", (int64_t)processId, (int64_t)processId);
3502 char appPath[PATH_MAX];
3503 GetCurrentProcessFilePath(appPath, OVR_ARRAY_COUNT(appPath));
3504 WriteReportLineF("Process path: %s\r\n", appPath);
3506 #if (OVR_PTR_SIZE == 4)
3507 WriteReportLine("App format: 32 bit\r\n");
3508 #else
3509 WriteReportLine("App format: 64 bit\r\n");
3510 #endif
3512 // App version
3513 // To do.
3515 // System Info
3516 WriteReportLine("\r\nSystem Info\r\n");
3518 char osVersionName[256];
3519 GetOSVersionName(osVersionName, OVR_ARRAY_COUNT(osVersionName));
3520 WriteReportLineF("OS name: %s, %s\r\n", osVersionName, Is64BitOS() ? "64 bit" : "32 bit");
3522 int name[2];
3523 int intValue;
3524 size_t length;
3525 char tempBuffer[256];
3527 name[0] = CTL_KERN;
3528 name[1] = KERN_OSTYPE;
3529 length = sizeof(tempBuffer);
3530 tempBuffer[0] = 0;
3531 if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
3533 WriteReportLineF("KERN_OSTYPE: %s\r\n", tempBuffer);
3536 name[0] = CTL_KERN;
3537 name[1] = KERN_OSREV;
3538 length = sizeof(intValue);
3539 intValue = 0;
3540 if(sysctl(name, 2, &intValue, &length, nullptr, 0) == 0)
3542 WriteReportLineF("KERN_OSREV: %d\r\n", intValue);
3545 name[0] = CTL_KERN;
3546 name[1] = KERN_OSRELEASE;
3547 length = sizeof(tempBuffer);
3548 tempBuffer[0] = 0;
3549 if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
3550 WriteReportLineF("KERN_OSRELEASE: %s\r\n", tempBuffer);
3552 name[0] = CTL_HW;
3553 name[1] = HW_MACHINE;
3554 length = sizeof(tempBuffer);
3555 tempBuffer[0] = 0;
3556 if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
3557 WriteReportLineF("HW_MACHINE: %s\r\n", tempBuffer);
3559 name[0] = CTL_HW;
3560 name[1] = HW_MODEL;
3561 length = sizeof(tempBuffer);
3562 tempBuffer[0] = 0;
3563 if(sysctl(name, 2, tempBuffer, &length, nullptr, 0) == 0)
3564 WriteReportLineF("sHW_MODEL: %s\r\n", tempBuffer);
3566 name[0] = CTL_HW;
3567 name[1] = HW_NCPU;
3568 length = sizeof(intValue);
3569 intValue = 0;
3570 if(sysctl(name, 2, &intValue, &length, nullptr, 0) == 0)
3571 WriteReportLineF("HW_NCPU: %d\r\n", intValue);
3573 length = sizeof(tempBuffer);
3574 tempBuffer[0] = 0;
3575 if(sysctlbyname("machdep.cpu.brand_string", &tempBuffer, &length, nullptr, 0) == 0)
3576 WriteReportLineF("machdep.cpu.brand_string: %s\r\n", tempBuffer);
3578 length = sizeof(tempBuffer);
3579 tempBuffer[0] = 0;
3580 if(sysctlbyname("hw.acpi.thermal.tz0.temperature", &tempBuffer, &length, nullptr, 0) == 0)
3581 WriteReportLineF("hw.acpi.thermal.tz0.temperature: %s\r\n", tempBuffer);
3583 host_basic_info_data_t hostinfo;
3584 mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
3585 kern_return_t kr = host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostinfo, &count);
3587 if(kr == KERN_SUCCESS)
3589 const uint64_t memoryMib = (uint64_t)hostinfo.max_mem / (1024 * 1024);
3590 WriteReportLineF("System memory: %lld Mib (%.1f Gib)\r\n", memoryMib, (double)memoryMib / 1024);
3593 // Video card info
3594 // To do.
3596 // Thread list
3597 mach_port_t taskSelf = mach_task_self();
3598 thread_act_port_array_t threadArray;
3599 mach_msg_type_number_t threadCount;
3601 kern_return_t result = task_threads(taskSelf, &threadArray, &threadCount);
3603 if(result == KERN_SUCCESS)
3605 WriteReportLine("\r\nThread list\r\n");
3607 for(mach_msg_type_number_t i = 0; i < threadCount; i++)
3609 union TBIUnion{
3610 natural_t words[THREAD_INFO_MAX];
3611 thread_basic_info tbi;
3612 };
3614 TBIUnion tbiUnion;
3615 mach_port_t thread = threadArray[i];
3616 pthread_t pthread = pthread_from_mach_thread_np(thread); // We assume the thread was created through pthreads.
3618 char threadState[32] = "unknown";
3619 mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
3620 result = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&tbiUnion, &threadInfoCount);
3622 if(result == KERN_SUCCESS)
3624 const char* state;
3626 switch (tbiUnion.tbi.run_state)
3628 case TH_STATE_HALTED: state = "halted"; break;
3629 case TH_STATE_RUNNING: state = "running"; break;
3630 case TH_STATE_STOPPED: state = "stopped"; break;
3631 case TH_STATE_UNINTERRUPTIBLE: state = "uninterruptible"; break;
3632 case TH_STATE_WAITING: state = "waiting"; break;
3633 default: state = "<unknown>"; break;
3636 OVR_snprintf(threadState, OVR_ARRAY_COUNT(threadState), "%s", state);
3637 if(tbiUnion.tbi.flags & TH_FLAGS_IDLE)
3638 OVR_strlcat(threadState, ", idle", sizeof(threadState));
3639 if(tbiUnion.tbi.flags & TH_FLAGS_SWAPPED)
3640 OVR_strlcat(threadState, ", swapped", sizeof(threadState));
3643 thread_identifier_info threadIdentifierInfo;
3644 memset(&threadIdentifierInfo, 0, sizeof(threadIdentifierInfo));
3646 mach_msg_type_number_t threadIdentifierInfoCount = THREAD_IDENTIFIER_INFO_COUNT;
3647 thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&threadIdentifierInfo, &threadIdentifierInfoCount);
3649 proc_threadinfo procThreadInfo;
3650 memset(&procThreadInfo, 0, sizeof(procThreadInfo));
3651 result = proc_pidinfo(processId, PROC_PIDTHREADINFO, threadIdentifierInfo.thread_handle, &procThreadInfo, sizeof(procThreadInfo));
3652 OVR_UNUSED(result);
3654 char buffer[256]; // Can't use scratchBuffer, because it's used by WriteThreadCallstack.
3655 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);
3657 bool threadIsExceptionThread = (thread == exceptionInfo.threadSysId);
3658 if(threadIsExceptionThread)
3659 OVR_strlcat(buffer, ", exception thread", OVR_ARRAY_COUNT(buffer));
3661 WriteThreadCallstack(pthread, thread, buffer);
3664 vm_deallocate(taskSelf, (vm_address_t)threadArray, threadCount * sizeof(thread_act_t));
3668 WriteReportLine("\r\nModule list\r\n");
3670 const size_t mifCapacity = 256;
3671 const size_t mifAllocSize = mifCapacity * sizeof(ModuleInfo);
3672 ModuleInfo* moduleInfoArray = (ModuleInfo*)SafeMMapAlloc(mifAllocSize);
3674 if(moduleInfoArray)
3676 #if (OVR_PTR_SIZE == 4)
3677 WriteReportLine("Base Size Name Path\r\n");
3678 #else
3679 WriteReportLine("Base Size Name Path\r\n");
3680 #endif
3682 size_t moduleCount = symbolLookup.GetModuleInfoArray(moduleInfoArray, mifCapacity);
3683 if(moduleCount > mifCapacity)
3684 moduleCount = mifCapacity;
3686 for(size_t i = 0; i < moduleCount; i++)
3688 const ModuleInfo& mi = moduleInfoArray[i];
3690 #if (OVR_PTR_SIZE == 4)
3691 WriteReportLineF("0x%08x, 0x%08x %-24s %s\r\n", (uint32_t)mi.baseAddress, (uint32_t)mi.size, mi.name, mi.filePath);
3692 #else
3693 WriteReportLineF("0x%016llx 0x%016llx %-24s %s\r\n", (uint64_t)mi.baseAddress, (uint64_t)mi.size, mi.name, mi.filePath);
3694 #endif
3697 SafeMMapFree(moduleInfoArray, mifAllocSize);
3701 WriteReportLine("\r\nProcess list\r\n");
3703 if(reportPrivacyEnabled)
3704 WriteReportLine("Disabled by report privacy settings\r\n");
3705 else
3707 WriteReportLine("Process Id File\r\n");
3709 pid_t pidArray[1024];
3710 int processCount = proc_listpids(PROC_ALL_PIDS, 0, pidArray, sizeof(pidArray)); // Important that we use sizeof not OVR_ARRAY_COUNT.
3711 char processFilePath[PATH_MAX];
3713 for(int i = 0; i < processCount; i++)
3715 if(proc_pidpath(pidArray[i], processFilePath, sizeof(processFilePath)) > 0)
3716 WriteReportLineF("%-10d %s\r\n", pidArray[i], processFilePath);
3719 if(!processCount)
3720 WriteReportLine("Unable to read process list\r\n");
3723 #elif defined(OVR_OS_UNIX)
3724 Is64BitOS();
3725 GetCurrentProcessFilePath(nullptr, 0);
3726 GetFileNameFromPath(nullptr);
3727 GetOSVersionName(nullptr, 0);
3729 #endif // OVR_OS_MS
3731 symbolLookup.Shutdown();
3733 fclose(file);
3734 file = nullptr;
3738 void ExceptionHandler::WriteMiniDump()
3740 if(strstr(miniDumpFilePath, "%s")) // If the user-specified file path includes a date/time component...
3742 char dateTimeBuffer[64];
3743 FormatDateTime(dateTimeBuffer, OVR_ARRAY_COUNT(dateTimeBuffer), exceptionInfo.timeVal, true, true, false, true);
3744 OVR_snprintf(minidumpFilePathActual, OVR_ARRAY_COUNT(minidumpFilePathActual), miniDumpFilePath, dateTimeBuffer);
3746 else
3748 OVR_strlcpy(minidumpFilePathActual, miniDumpFilePath, OVR_ARRAY_COUNT(minidumpFilePathActual));
3751 #if defined(OVR_OS_WIN32) || defined(OVR_OS_WIN64) || (defined(OVR_OS_MS) && defined(OVR_OS_CONSOLE))
3752 #if defined(OVR_OS_CONSOLE)
3753 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);
3754 HMODULE hModuleDbgHelp = LoadLibraryW(L"toolhelpx.dll");
3755 #else
3756 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);
3757 HMODULE hModuleDbgHelp = LoadLibraryW(L"DbgHelp.dll");
3758 #endif
3760 MINIDUMPWRITEDUMP pMiniDumpWriteDump = hModuleDbgHelp ? (MINIDUMPWRITEDUMP)(void*)GetProcAddress(hModuleDbgHelp, "MiniDumpWriteDump") : nullptr;
3762 if(pMiniDumpWriteDump)
3764 wchar_t miniDumpFilePathW[OVR_MAX_PATH];
3765 OVR::UTF8Util::DecodeString(miniDumpFilePathW, minidumpFilePathActual, -1); // Problem: DecodeString provides no way to specify the destination capacity.
3767 HANDLE hFile = CreateFileW(miniDumpFilePathW, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);
3769 if(hFile != INVALID_HANDLE_VALUE)
3771 MINIDUMP_EXCEPTION_INFORMATION minidumpExceptionInfo = { ::GetCurrentThreadId(), pExceptionPointers, TRUE };
3773 #if defined(OVR_OS_CONSOLE)
3774 BOOL result = pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
3775 (MINIDUMP_TYPE)miniDumpType, &exceptionInfo,
3776 (CONST PMINIDUMP_USER_STREAM_INFORMATION)nullptr, nullptr);
3777 #else
3778 BOOL result = pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
3779 (MINIDUMP_TYPE)miniDumpFlags, &minidumpExceptionInfo,
3780 (CONST PMINIDUMP_USER_STREAM_INFORMATION)nullptr, (CONST PMINIDUMP_CALLBACK_INFORMATION)nullptr);
3781 #endif
3783 OVR_ASSERT_AND_UNUSED(result, result);
3784 CloseHandle(hFile);
3785 hFile = 0;
3787 else
3789 OVR_ASSERT(pMiniDumpWriteDump); // OVR_FAIL_F(("ExceptionHandler::WriteMiniDump: Failed to create minidump file at %s", minidumpFilePathActual));
3793 FreeLibrary(hModuleDbgHelp);
3794 #else
3795 // Some platforms support various forms or exception reports and core dumps which are automatically generated upon us
3796 // returning from our own exception handling. We might want to put something here if we are using a custom version of
3797 // this, such as Google Breakpad.
3798 #endif
3802 void ExceptionHandler::SetExceptionListener(ExceptionListener* pExceptionListener, uintptr_t userValue)
3804 exceptionListener = pExceptionListener;
3805 exceptionListenerUserValue = userValue;
3809 void ExceptionHandler::SetAppDescription(const char* pAppDescription)
3811 appDescription = pAppDescription;
3815 void ExceptionHandler::SetExceptionPaths(const char* exceptionReportPath, const char* exceptionMiniDumpFilePath)
3817 char tempPath[OVR_MAX_PATH];
3819 if(exceptionReportPath)
3821 if(OVR_stricmp(exceptionReportPath, "default") == 0)
3823 GetUserDocumentsDirectory(tempPath, OVR_ARRAY_COUNT(tempPath));
3824 OVR::OVR_strlcat(tempPath, "Exception Report (%s).txt", OVR_ARRAY_COUNT(tempPath));
3825 exceptionReportPath = tempPath;
3828 OVR_strlcpy(reportFilePath, exceptionReportPath, OVR_ARRAY_COUNT(reportFilePath));
3830 else
3832 reportFilePath[0] = '\0';
3835 if(exceptionMiniDumpFilePath)
3837 if(OVR_stricmp(exceptionMiniDumpFilePath, "default") == 0)
3839 GetUserDocumentsDirectory(tempPath, OVR_ARRAY_COUNT(tempPath));
3840 OVR::OVR_strlcat(tempPath, "Exception Minidump (%s).mdmp", OVR_ARRAY_COUNT(tempPath));
3841 exceptionMiniDumpFilePath = tempPath;
3844 OVR_strlcpy(miniDumpFilePath, exceptionMiniDumpFilePath, OVR_ARRAY_COUNT(miniDumpFilePath));
3846 else
3848 miniDumpFilePath[0] = '\0';
3853 void ExceptionHandler::SetCodeBaseDirectoryPaths(const char* codeBaseDirectoryPathArray[], size_t codeBaseDirectoryPathCount)
3855 for(size_t i = 0, iCount = OVR::Alg::Min<size_t>(codeBaseDirectoryPathCount, OVR_ARRAY_COUNT(codeBasePathArray)); i != iCount; ++i)
3857 codeBasePathArray[i] = codeBaseDirectoryPathArray[i];
3861 const char* ExceptionHandler::GetExceptionUIText(const char* exceptionReportPath)
3863 char* uiText = nullptr;
3864 OVR::SysFile file(exceptionReportPath, SysFile::Open_Read, SysFile::Mode_ReadWrite);
3866 if(file.IsValid())
3868 size_t length = (size_t)file.GetLength();
3869 uiText = (char*)OVR::SafeMMapAlloc(length + 1);
3871 if(uiText)
3873 file.Read((uint8_t*)uiText, (int)length);
3874 uiText[length] = '\0';
3875 file.Close();
3877 // Currently on Mac our message box implementation is unable to display arbitrarily large amounts of text.
3878 // So we reduce its size to a more summary version before presenting.
3879 #if defined(OVR_OS_MAC)
3880 struct Find { static char* PreviousChar(char* p, char c){ while(*p != c) p--; return p; } }; // Assumes the given c is present prior to p.
3882 // Print that the full report is at <file path>
3883 // Exception Info section
3884 // Exception thread callstack.
3885 char empty[] = "";
3886 char* pExceptionInfoBegin = strstr(uiText, "Exception Info") ? strstr(uiText, "Exception Info") : empty;
3887 char* pExceptionInfoEnd = (pExceptionInfoBegin == empty) ? (empty + 1) : strstr(uiText, "\r\n\r\n");
3888 char* pExceptionThreadArea = strstr(uiText, ", exception thread");
3889 char* pExceptionThreadBegin = pExceptionThreadArea ? Find::PreviousChar(pExceptionThreadArea, '\n') + 1 : empty;
3890 char* pExceptionThreadEnd = (pExceptionThreadBegin == empty) ? (empty + 1) : strstr(pExceptionThreadArea, "\r\n\r\n");
3892 if(!pExceptionInfoEnd)
3893 pExceptionInfoEnd = pExceptionInfoBegin;
3894 *pExceptionInfoEnd = '\0';
3896 if(!pExceptionThreadEnd)
3897 pExceptionThreadEnd = pExceptionThreadBegin;
3898 *pExceptionThreadEnd = '\0';
3900 size_t uiTextBriefLength = OVR_snprintf(nullptr, 0, "Full report:%s\n\nSummary report:\n%s\n\n%s", exceptionReportPath, pExceptionInfoBegin, pExceptionThreadBegin);
3901 char* uiTextBrief = (char*)OVR::SafeMMapAlloc(uiTextBriefLength + 1);
3903 if(uiTextBrief)
3905 OVR_snprintf(uiTextBrief, uiTextBriefLength + 1, "Full report:%s\n\nSummary report:\n%s\n\n%s", exceptionReportPath, pExceptionInfoBegin, pExceptionThreadBegin);
3906 OVR::SafeMMapFree(uiText, length);
3907 uiText = uiTextBrief;
3909 #endif
3913 return uiText;
3916 void ExceptionHandler::FreeExceptionUIText(const char* messageBoxText)
3918 OVR::SafeMMapFree(messageBoxText, OVR_strlen(messageBoxText));
3922 } // namespace OVR
3925 OVR_RESTORE_MSVC_WARNING()