void Frame::setNameValue() const { if (name_val_set == nv_set || name_val_set == nv_err) return; if (!walker) { setLastError(err_nosymlookup, "No Walker object was associated with this frame"); sw_printf("[%s:%u] - Error, No walker found.\n", FILE__, __LINE__); name_val_set = nv_err; return; } SymbolLookup *lookup = walker->getSymbolLookup(); if (!lookup) { setLastError(err_nosymlookup, "No SymbolLookup object was associated with the Walker"); sw_printf("[%s:%u] - Error, No symbol lookup found.\n", FILE__, __LINE__); name_val_set = nv_err; return; } bool result = lookup->lookupAtAddr(getRA(), sym_name, sym_value); if (!result) { sw_printf("[%s:%u] - Error, returned by lookupAtAddr().\n", FILE__, __LINE__); name_val_set = nv_err; } sw_printf("[%s:%u] - Successfully looked up symbol for frame %p\n", FILE__, __LINE__, this); name_val_set = nv_set; }
LastErrorTLS::LastErrorTLS() { Symbols.Initialize(); // Must be at end of function PushDestroyCallbacks(); }
void OVRError::SetCurrentValues() { OVRTime = Timer::GetSeconds(); // It would be better if we called ovr_GetTimeInSeconds, but that doesn't have a constant header to use. ClockTime = std::chrono::system_clock::now(); #if defined(OVR_ERROR_ENABLE_BACKTRACES) if (Symbols.IsInitialized()) { void* addressArray[32]; size_t n = Symbols.GetBacktrace(addressArray, OVR_ARRAY_COUNT(addressArray), 2, nullptr, OVR_THREADSYSID_INVALID); Backtrace.Clear(); Backtrace.Append(addressArray, n); } #endif }
void Frame::setNameValue() const { if (name_val_set == nv_set || name_val_set == nv_err) return; if (!walker) { setLastError(err_nosymlookup, "No Walker object was associated with this frame"); sw_printf("[%s:%u] - Error, No walker found.\n", FILE__, __LINE__); name_val_set = nv_err; return; } SymbolLookup *lookup = walker->getSymbolLookup(); if (!lookup) { setLastError(err_nosymlookup, "No SymbolLookup object was associated with the Walker"); sw_printf("[%s:%u] - Error, No symbol lookup found.\n", FILE__, __LINE__); name_val_set = nv_err; return; } // Here we lookup return address minus 1 to handle the following special case: // // Suppose A calls B and B is a non-returnning function, and the call to B in A // is the last instruction of A. Then the compiler may generate code where // another function C is immediately after A. In such case, the return address // will be the entry address of C. And if we look up function by the return // address, we will get C rather than A. bool result = lookup->lookupAtAddr(getRA() - 1, sym_name, sym_value); if (!result) { sw_printf("[%s:%u] - Error, returned by lookupAtAddr().\n", FILE__, __LINE__); name_val_set = nv_err; } sw_printf("[%s:%u] - Successfully looked up symbol for frame %p\n", FILE__, __LINE__, this); name_val_set = nv_set; }
int WatchDogObserver::Run() { OVR_DEBUG_LOG(("[WatchDogObserver] Starting")); SetThreadName("WatchDog"); while (!TerminationEvent.Wait(WakeupInterval)) { Lock::Locker locker(&ListLock); const uint32_t t1 = GetFastMsTime(); const int count = DogList.GetSizeI(); for (int i = 0; i < count; ++i) { WatchDog* dog = DogList[i]; const int threshold = dog->ThreshholdMilliseconds; const uint32_t t0 = dog->WhenLastFedMilliseconds; // If threshold exceeded, assume there is thread deadlock of some sort. int delta = (int)(t1 - t0); if (delta > threshold) { // Expected behavior: // SingleProcessDebug, SingleProcessRelease, Debug: This is only ever done for internal testing, so we don't want it to trigger the deadlock termination. // Release: This is our release configuration where we want it to terminate itself. // Print a stack trace of all threads if there's no debugger present. const bool debuggerPresent = OVRIsDebuggerPresent(); LogError("{ERR-027} [WatchDogObserver] Deadlock detected: %s", dog->ThreadName.ToCStr()); if (!debuggerPresent) // We don't print threads if a debugger is present because otherwise every time the developer paused the app to debug, it would spit out a long thread trace upon resuming. { if (SymbolLookup::Initialize()) { // symbolLookup is static here to avoid putting 32 KB on the stack // and potentially overflowing the stack. This function is only ever // run by one thread so it should be safe. static SymbolLookup symbolLookup; String threadListOutput, moduleListOutput; symbolLookup.ReportThreadCallstacks(threadListOutput); symbolLookup.ReportModuleInformation(moduleListOutput); LogError("---DEADLOCK STATE---\n\n%s\n\n%s\n---END OF DEADLOCK STATE---", threadListOutput.ToCStr(), moduleListOutput.ToCStr()); } if (IsReporting) { ExceptionHandler::ReportDeadlock(DogList[i]->ThreadName, OrganizationName , ApplicationName); // Disable reporting after the first deadlock report. IsReporting = false; } } if (IsExitingOnDeadlock()) { OVR_ASSERT_M(false, "Watchdog detected a deadlock. Exiting the process."); // This won't have an effect unless asserts are enabled in release builds. OVR::ExitProcess(-1); } } } } OVR_DEBUG_LOG(("[WatchDogObserver] Good night")); return 0; }
LastErrorTLS::~LastErrorTLS() { Symbols.Shutdown(); }
namespace OVR { //----------------------------------------------------------------------------- // LastErrorTLS static SymbolLookup Symbols; LastErrorTLS::LastErrorTLS() { Symbols.Initialize(); // Must be at end of function PushDestroyCallbacks(); } LastErrorTLS::~LastErrorTLS() { Symbols.Shutdown(); } void LastErrorTLS::OnSystemDestroy() { delete this; } // This is an accessor which auto-allocates and initializes the return value if needed. OVRError& LastErrorTLS::LastError() { Lock::Locker autoLock(&TheLock); ThreadId threadId = GetCurrentThreadId(); auto i = TLSDictionary.Find(threadId); if (i == TLSDictionary.End()) { TLSDictionary.Add(threadId, OVRError::Success()); i = TLSDictionary.Find(threadId); } return (*i).Second; } // ****** OVRFormatDateTime // // Prints a date/time like so: // Y-M-d H:M:S [ms:us:ns] // Example output: // 2016-12-25 8:15:01 [392:445:23] // // SysClockTime is of type std::chrono::time_point<std::chrono::system_clock>. // // To consider: Move SysClockTime and OVRFormatDateTime to OVRKernel. // static void OVRFormatDateTime(SysClockTime sysClockTime, OVR::String& dateTimeString) { // Get the basic Date and HMS time. char buffer[128]; struct tm tmResult; const time_t cTime = std::chrono::system_clock::to_time_t(sysClockTime); #if defined(_MSC_VER) localtime_s(&tmResult, &cTime); #else localtime_r(&cTime, &tmResult); #endif strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tmResult); // Append milli:micro:nano time. std::chrono::system_clock::duration timeSinceEpoch = sysClockTime.time_since_epoch(); std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(timeSinceEpoch); timeSinceEpoch -= s; std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(timeSinceEpoch); timeSinceEpoch -= ms; std::chrono::microseconds us = std::chrono::duration_cast<std::chrono::microseconds>(timeSinceEpoch); timeSinceEpoch -= us; std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceEpoch); char buffer2[384]; sprintf(buffer2, "%s [%d:%d:%d]", buffer, (int)ms.count(), (int)us.count(), (int)ns.count()); dateTimeString = buffer2; } static void OVRRemoveTrailingNewlines(String& s) { while(!s.IsEmpty() && ((s.Back() == '\n') || (s.Back() == '\r'))) s.PopBack(); } OVR_SELECTANY const int64_t OVRError::kLogLineUnset; OVRError::OVRError() { Reset(); } OVRError::OVRError(ovrResult code) : OVRError() { Code = code; } OVRError::OVRError(ovrResult code, const char* pFormat, ...) : OVRError(code) { va_list argList; va_start(argList, pFormat); StringBuffer strbuff; strbuff.AppendFormatV(pFormat, argList); SetDescription(strbuff.ToCStr()); va_end(argList); } OVRError::OVRError(const OVRError& ovrError) { operator=(ovrError); } OVRError::OVRError(OVRError&& ovrError) { operator=(std::move(ovrError)); } OVRError::~OVRError() { // Empty } OVRError& OVRError::operator=(const OVRError& ovrError) { Code = ovrError.Code; SysCode = ovrError.SysCode; Description = ovrError.Description; Context = ovrError.Context; OVRTime = ovrError.OVRTime; ClockTime = ovrError.ClockTime; LogLine = ovrError.LogLine; SourceFilePath = ovrError.SourceFilePath; SourceFileLine = ovrError.SourceFileLine; Backtrace = ovrError.Backtrace; AlreadyLogged = ovrError.AlreadyLogged; return *this; } OVRError& OVRError::operator=(OVRError&& ovrError) { Code = ovrError.Code; SysCode = ovrError.SysCode; Description = std::move(ovrError.Description); Context = std::move(ovrError.Context); OVRTime = ovrError.OVRTime; ClockTime = ovrError.ClockTime; LogLine = ovrError.LogLine; SourceFilePath = std::move(ovrError.SourceFilePath); SourceFileLine = ovrError.SourceFileLine; Backtrace = std::move(ovrError.Backtrace); AlreadyLogged = ovrError.AlreadyLogged; return *this; } void OVRError::SetCurrentValues() { OVRTime = Timer::GetSeconds(); // It would be better if we called ovr_GetTimeInSeconds, but that doesn't have a constant header to use. ClockTime = std::chrono::system_clock::now(); #if defined(OVR_ERROR_ENABLE_BACKTRACES) if (Symbols.IsInitialized()) { void* addressArray[32]; size_t n = Symbols.GetBacktrace(addressArray, OVR_ARRAY_COUNT(addressArray), 2, nullptr, OVR_THREADSYSID_INVALID); Backtrace.Clear(); Backtrace.Append(addressArray, n); } #endif } void OVRError::Reset() { Code = ovrSuccess; SysCode = ovrSysErrorCodeSuccess; Description.Clear(); Context.Clear(); OVRTime = 0; ClockTime = SysClockTime(); LogLine = kLogLineUnset; SourceFilePath.Clear(); SourceFileLine = 0; Backtrace.ClearAndRelease(); AlreadyLogged = false; } String OVRError::GetErrorString() const { StringBuffer stringBuffer("OVR Error:\n"); // Code OVR::String errorCodeString; GetErrorCodeString(Code, false, errorCodeString); stringBuffer.AppendFormat(" Code: %d -- %s\n", Code, errorCodeString.ToCStr()); // SysCode if (SysCode != ovrSysErrorCodeSuccess) { OVR::String sysErrorString; GetSysErrorCodeString(SysCode, false, sysErrorString); OVRRemoveTrailingNewlines(sysErrorString); stringBuffer.AppendFormat(" System error: %d (%x) -- %s\n", (int)SysCode, (int)SysCode, sysErrorString.ToCStr()); } // Description if (Description.GetLength()) { stringBuffer.AppendFormat(" Description: %s\n", Description.ToCStr()); } // OVRTime stringBuffer.AppendFormat(" OVRTime: %f\n", OVRTime); // SysClockTime OVR::String sysClockTimeString; OVRFormatDateTime(ClockTime, sysClockTimeString); stringBuffer.AppendFormat(" Time: %s\n", sysClockTimeString.ToCStr()); // Context if (Context.GetLength()) { stringBuffer.AppendFormat(" Context: %s\n", Context.ToCStr()); } // If LogLine is set, if (LogLine != kLogLineUnset) { stringBuffer.AppendFormat(" LogLine: %lld\n", LogLine); } // FILE/LINE if (SourceFilePath.GetLength()) { stringBuffer.AppendFormat(" File/Line: %s:%d\n", SourceFilePath.ToCStr(), SourceFileLine); } // Backtrace if (Backtrace.GetSize()) { // We can trace symbols in a debug build here or we can trace just addresses. See other code for // examples of how to trace symbols. stringBuffer.AppendFormat(" Backtrace: "); for (size_t i = 0, iEnd = Backtrace.GetSize(); i != iEnd; ++i) stringBuffer.AppendFormat(" %p", Backtrace[i]); stringBuffer.AppendChar('\n'); } return OVR::String(stringBuffer.ToCStr(), stringBuffer.GetSize()); } void OVRError::SetCode(ovrResult code) { Code = code; } ovrResult OVRError::GetCode() const { return Code; } void OVRError::SetSysCode(ovrSysErrorCode sysCode) { SysCode = sysCode; } ovrSysErrorCode OVRError::GetSysCode() const { return SysCode; } void OVRError::SetDescription(const char* pDescription) { if (pDescription) { Description = pDescription; OVRRemoveTrailingNewlines(Description); // Users sometimes send text with trailing newlines, which have no purpose in the error report. } else Description.Clear(); } String OVRError::GetDescription() const { return Description; } void OVRError::SetContext(const char* pContext) { if (pContext) { Context = pContext; OVRRemoveTrailingNewlines(Context); } else Context.Clear(); } String OVRError::GetContext() const { return Context; } void OVRError::SetOVRTime(double ovrTime) { OVRTime = ovrTime; } double OVRError::GetOVRTime() const { return OVRTime; } void OVRError::SetSysClockTime(const SysClockTime& clockTime) { ClockTime = clockTime; } SysClockTime OVRError::GetSysClockTime() const { return ClockTime; } void OVRError::SetLogLine(int64_t logLine) { LogLine = logLine; } int64_t OVRError::GetLogLine() const { return LogLine; } void OVRError::SetSource(const char* pSourceFilePath, int sourceFileLine) { if (pSourceFilePath) SourceFilePath = pSourceFilePath; else SourceFilePath.Clear(); SourceFileLine = sourceFileLine; } std::pair<OVR::String, int> OVRError::GetSource() const { return std::make_pair(SourceFilePath, SourceFileLine); } OVRError::AddressArray OVRError::GetBacktrace() const { return Backtrace; } void LogError(OVRError& ovrError) { // If not already logged, if (!ovrError.IsAlreadyLogged()) { Logger.LogError(ovrError.GetErrorString()); ovrError.SetAlreadyLogged(); } } void SetError(OVRError& ovrError) { // Record that the current thread's last error is this error. If we wanted to support // chaining of errors such that multiple OVRErrors could be concurrent in a thread // (e.g. one that occurred deep in the call chain and a higher level version of it higher // in the call chain), we could handle that here. LastErrorTLS::GetInstance()->LastError() = ovrError; } static OVRErrorCallback ErrorCallback; void SetErrorCallback(OVRErrorCallback callback) { ErrorCallback = callback; } OVRError MakeError(ovrResult errorCode, ovrSysErrorCode sysCode, const char* pSourceFile, int sourceLine, bool logError, bool assertError, const char* pContext, const char* pDescriptionFormat, ...) { OVRError ovrError(errorCode); ovrError.SetCurrentValues(); // Sets the current time, etc. ovrError.SetSysCode(sysCode); va_list argList; va_start(argList, pDescriptionFormat); StringBuffer strbuff; strbuff.AppendFormatV(pDescriptionFormat, argList); va_end(argList); ovrError.SetDescription(strbuff.ToCStr()); ovrError.SetContext(pContext); ovrError.SetSource(pSourceFile, sourceLine); // Set the TLS last error. LastErrorTLS::GetInstance()->LastError() = ovrError; int silencerOptions = ovrlog::ErrorSilencer::GetSilenceOptions(); if (silencerOptions & ovrlog::ErrorSilencer::CompletelySilenceLogs) { logError = false; } if (silencerOptions & ovrlog::ErrorSilencer::PreventErrorAsserts) { assertError = false; } // If logging the error: if (logError) { Logger.LogError(ovrError.GetDescription().ToCStr()); } // If asserting the error: if (assertError) { // Assert in debug mode to alert unit tester/developer of the error as it occurs. OVR_FAIL_M(ovrError.GetDescription().ToCStr()); } if (ErrorCallback) { const bool quiet = !logError && !assertError; ErrorCallback(ovrError, quiet); } return ovrError; } typedef std::unordered_map<ovrResult, const char*> ResultNameMap; static ResultNameMap& GetResultNameMap() { static ResultNameMap resultNameMap; return resultNameMap; } const char* GetErrorCodeName(ovrResult errorCode) { if (errorCode == ovrSuccess) { return "ovrSuccess"; // Speed up a common case } ResultNameMap& resultNameMap = GetResultNameMap(); const char* str = resultNameMap[errorCode]; if (!str) { OVR_FAIL_M("Unknown result code. It should have been registered"); return "UnknownResultCode"; } return str; } void OVRError::RegisterResultCodeName(ovrResult number, const char* name) { ResultNameMap& resultNameMap = GetResultNameMap(); if (resultNameMap.find(number) != resultNameMap.end()) { OVR_FAIL_M("Duplicate result code registered"); return; } resultNameMap[number] = name; } bool GetErrorCodeString(ovrResult resultIn, bool prefixErrorCode, OVR::String& sResult) { char codeBuffer[256]; const char* errorCodeName = GetErrorCodeName(resultIn); if (prefixErrorCode) { snprintf(codeBuffer, OVR_ARRAY_COUNT(codeBuffer), "0x%llx (%lld) %s", (uint64_t)resultIn, (int64_t)resultIn, errorCodeName); } else { snprintf(codeBuffer, OVR_ARRAY_COUNT(codeBuffer), "%s", errorCodeName); } sResult = codeBuffer; return true; } #if defined(OVR_OS_WIN32) static const wchar_t* OVR_DXGetErrorStringW(HRESULT dwDXGIErrorCode) { switch (dwDXGIErrorCode) { case DXGI_ERROR_DEVICE_HUNG: return L"DXGI_ERROR_DEVICE_HUNG"; // The application's device failed due to badly formed commands sent by the application. This is an design-time issue that should be investigated and fixed. case DXGI_ERROR_DEVICE_REMOVED: return L"DXGI_ERROR_DEVICE_REMOVED"; // The video card has been physically removed from the system, or a driver upgrade for the video card has occurred. The application should destroy and recreate the device. For help debugging the problem, call ID3D10Device::GetDeviceRemovedReason. case DXGI_ERROR_DEVICE_RESET: return L"DXGI_ERROR_DEVICE_RESET"; // The device failed due to a badly formed command. This is a run-time issue; The application should destroy and recreate the device. case DXGI_ERROR_DRIVER_INTERNAL_ERROR: return L"DXGI_ERROR_DRIVER_INTERNAL_ERROR"; // The driver encountered a problem and was put into the device removed state. case DXGI_ERROR_FRAME_STATISTICS_DISJOINT: return L"DXGI_ERROR_FRAME_STATISTICS_DISJOINT"; // An event (for example, a power cycle) interrupted the gathering of presentation statistics. case DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE: return L"DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE"; // The application attempted to acquire exclusive ownership of an output, but failed because some other application (or device within the application) already acquired ownership. case DXGI_ERROR_INVALID_CALL: return L"DXGI_ERROR_INVALID_CALL"; // The application provided invalid parameter data; this must be debugged and fixed before the application is released. case DXGI_ERROR_MORE_DATA: return L"DXGI_ERROR_MORE_DATA"; // The buffer supplied by the application is not big enough to hold the requested data. case DXGI_ERROR_NONEXCLUSIVE: return L"DXGI_ERROR_NONEXCLUSIVE"; // A global counter resource is in use, and the Direct3D device can't currently use the counter resource. case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE: return L"DXGI_ERROR_NOT_CURRENTLY_AVAILABLE"; // The resource or request is not currently available, but it might become available later. case DXGI_ERROR_NOT_FOUND: return L"DXGI_ERROR_NOT_FOUND"; // When calling IDXGIObject::GetPrivateData, the GUID passed in is not recognized as one previously passed to IDXGIObject::SetPrivateData or IDXGIObject::SetPrivateDataInterface. When calling IDXGIFactory::EnumAdapters or IDXGIAdapter::EnumOutputs, the enumerated ordinal is out of range. case DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED: return L"DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED"; // Reserved case DXGI_ERROR_REMOTE_OUTOFMEMORY: return L"DXGI_ERROR_REMOTE_OUTOFMEMORY"; // Reserved case DXGI_ERROR_WAS_STILL_DRAWING: return L"DXGI_ERROR_WAS_STILL_DRAWING"; // The GPU was busy at the moment when a call was made to perform an operation, and did not execute or schedule the operation. case DXGI_ERROR_UNSUPPORTED: return L"DXGI_ERROR_UNSUPPORTED"; // The requested functionality is not supported by the device or the driver. case DXGI_ERROR_ACCESS_LOST: return L"DXGI_ERROR_ACCESS_LOST"; // The desktop duplication interface is invalid. The desktop duplication interface typically becomes invalid when a different type of image is displayed on the desktop. case DXGI_ERROR_WAIT_TIMEOUT: return L"DXGI_ERROR_WAIT_TIMEOUT"; // The time-out interval elapsed before the next desktop frame was available. case DXGI_ERROR_SESSION_DISCONNECTED: return L"DXGI_ERROR_SESSION_DISCONNECTED"; // The Remote Desktop Services session is currently disconnected. case DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE: return L"DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE"; // The DXGI output (monitor) to which the swap chain content was restricted is now disconnected or changed. case DXGI_ERROR_CANNOT_PROTECT_CONTENT: return L"DXGI_ERROR_CANNOT_PROTECT_CONTENT"; // DXGI can't provide content protection on the swap chain. This error is typically caused by an older driver, or when you use a swap chain that is incompatible with content protection. case DXGI_ERROR_ACCESS_DENIED: return L"DXGI_ERROR_ACCESS_DENIED"; // You tried to use a resource to which you did not have the required access privileges. This error is most typically caused when you write to a shared resource with read-only access. case DXGI_ERROR_NAME_ALREADY_EXISTS: return L"DXGI_ERROR_NAME_ALREADY_EXISTS"; // The supplied name of a resource in a call to IDXGIResource1::CreateSharedHandle is already associated with some other resource. case DXGI_ERROR_SDK_COMPONENT_MISSING: return L"DXGI_ERROR_SDK_COMPONENT_MISSING"; // The operation depends on an SDK component that is missing or mismatched. } return nullptr; } #endif bool GetSysErrorCodeString(ovrSysErrorCode sysErrorCode, bool prefixErrorCode, OVR::String& sResult) { char errorBuffer[1024]; errorBuffer[0] = '\0'; if (prefixErrorCode) { char prefixBuffer[64]; snprintf(prefixBuffer, OVR_ARRAY_COUNT(prefixBuffer), "0x%llx (%lld): ", (uint64_t)sysErrorCode, (int64_t)sysErrorCode); sResult = prefixBuffer; } else { sResult.Clear(); } #if defined(OVR_OS_WIN32) // Note: It may be useful to use FORMAT_MESSAGE_FROM_HMODULE here to get a module-specific error string if our source of errors // ends up including more than just system-native errors. For example, a third party module with custom errors defined in it. WCHAR errorBufferW[1024]; DWORD errorBufferWCapacity = OVR_ARRAY_COUNT(errorBufferW); DWORD length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, (DWORD)sysErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorBufferW, errorBufferWCapacity, nullptr); if (!length) // If failed... { if (HRESULT_FACILITY(sysErrorCode) == _FACDXGI) // If it is a DXGI error... { // This situation occurs on Windows 7. You can't use FORMAT_MESSAGE_FROM_HMODULE to solve it either. We can only use DXGetErrorString or manually handle it. const wchar_t* pStr = OVR_DXGetErrorStringW(sysErrorCode); if (pStr) { wcscpy_s(errorBufferW, OVR_ARRAY_COUNT(errorBufferW), pStr); length = (DWORD)wcslen(errorBufferW); } } } if (length) // If errorBufferW contains what we are looking for... { // Need to convert WCHAR errorBuffer to UTF8 char sResult; const auto requiredUTF8Length = OVR::UTF8Util::Strlcpy(errorBuffer, OVR_ARRAY_COUNT(errorBuffer), errorBufferW); if (requiredUTF8Length >= OVR_ARRAY_COUNT(errorBuffer)) // Zero out if too big (XXX truncate instead?) errorBuffer[0] = '\0'; // Else fall through } // Else fall through #else #if (((_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600)) && !_GNU_SOURCE) || defined(__APPLE__) || defined(__BSD__) const int result = strerror_r((int)sysErrorCode, errorBuffer, OVR_ARRAY_COUNT(errorBuffer)); if (result != 0) // If failed... [result is 0 upon success; result will be EINVAL if the code is not recognized; ERANGE if buffer didn't have enough capacity.] errorBuffer[0] = '\0'; // re-null-terminate, in case strerror_r left it in an invalid state. #else const char* result = strerror_r((int)sysErrorCode, errorBuffer, OVR_ARRAY_COUNT(errorBuffer)); if (result == nullptr) // Implementations in practice seem to always return a pointer, though the behavior isn't formally standardized. errorBuffer[0] = '\0'; // re-null-terminate, in case strerror_r left it in an invalid state. #endif #endif // Fall through functionality of simply printing the value as an integer. if (errorBuffer[0]) // If errorBuffer was successfully written above... { sResult += errorBuffer; return true; } sResult += "(unknown)"; // This is not localized. Question: Is there a way to get the error formatting functions above to print this themselves in a localized way? return false; } } // namespace OVR