ovr_sdk

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