michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Chromium headers must come before Mozilla headers. michael@0: #include "base/process_util.h" michael@0: michael@0: #include "mozilla/Atomics.h" michael@0: michael@0: #include "nsDebugImpl.h" michael@0: #include "nsDebug.h" michael@0: #ifdef MOZ_CRASHREPORTER michael@0: # include "nsExceptionHandler.h" michael@0: #endif michael@0: #include "nsString.h" michael@0: #include "prprf.h" michael@0: #include "prlog.h" michael@0: #include "nsError.h" michael@0: #include "prerror.h" michael@0: #include "prerr.h" michael@0: #include "prenv.h" michael@0: michael@0: #ifdef ANDROID michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef _WIN32 michael@0: /* for getenv() */ michael@0: #include michael@0: #endif michael@0: michael@0: #include "nsTraceRefcnt.h" michael@0: michael@0: #if defined(XP_UNIX) michael@0: #include michael@0: #endif michael@0: michael@0: #if defined(XP_WIN) michael@0: #include michael@0: #include "nsString.h" michael@0: #ifdef MOZ_METRO michael@0: #include "nsWindowsHelpers.h" michael@0: #endif michael@0: #endif michael@0: michael@0: #if defined(XP_MACOSX) michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #include "mozilla/mozalloc_abort.h" michael@0: michael@0: static void michael@0: Abort(const char *aMsg); michael@0: michael@0: static void michael@0: RealBreak(); michael@0: michael@0: static void michael@0: Break(const char *aMsg); michael@0: michael@0: #if defined(_WIN32) michael@0: #include michael@0: #include michael@0: #include // for _alloca michael@0: #elif defined(XP_UNIX) michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: static const char *sMultiprocessDescription = nullptr; michael@0: michael@0: static Atomic gAssertionCount; michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(nsDebugImpl, nsIDebug, nsIDebug2) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsDebugImpl::AddRef() michael@0: { michael@0: return 2; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsDebugImpl::Release() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::Assertion(const char *aStr, const char *aExpr, michael@0: const char *aFile, int32_t aLine) michael@0: { michael@0: NS_DebugBreak(NS_DEBUG_ASSERTION, aStr, aExpr, aFile, aLine); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::Warning(const char *aStr, const char *aFile, int32_t aLine) michael@0: { michael@0: NS_DebugBreak(NS_DEBUG_WARNING, aStr, nullptr, aFile, aLine); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::Break(const char *aFile, int32_t aLine) michael@0: { michael@0: NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, aFile, aLine); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::Abort(const char *aFile, int32_t aLine) michael@0: { michael@0: NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, aFile, aLine); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::GetIsDebugBuild(bool* aResult) michael@0: { michael@0: #ifdef DEBUG michael@0: *aResult = true; michael@0: #else michael@0: *aResult = false; michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::GetAssertionCount(int32_t* aResult) michael@0: { michael@0: *aResult = gAssertionCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDebugImpl::GetIsDebuggerAttached(bool* aResult) michael@0: { michael@0: *aResult = false; michael@0: michael@0: #if defined(XP_WIN) michael@0: *aResult = ::IsDebuggerPresent(); michael@0: #elif defined(XP_MACOSX) michael@0: // Specify the info we're looking for michael@0: int mib[4]; michael@0: mib[0] = CTL_KERN; michael@0: mib[1] = KERN_PROC; michael@0: mib[2] = KERN_PROC_PID; michael@0: mib[3] = getpid(); michael@0: size_t mibSize = sizeof(mib) / sizeof(int); michael@0: michael@0: struct kinfo_proc info; michael@0: size_t infoSize = sizeof(info); michael@0: memset(&info, 0, infoSize); michael@0: michael@0: if (sysctl(mib, mibSize, &info, &infoSize, nullptr, 0)) { michael@0: // if the call fails, default to false michael@0: *aResult = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (info.kp_proc.p_flag & P_TRACED) { michael@0: *aResult = true; michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsDebugImpl::SetMultiprocessMode(const char *aDesc) michael@0: { michael@0: sMultiprocessDescription = aDesc; michael@0: } michael@0: michael@0: /** michael@0: * Implementation of the nsDebug methods. Note that this code is michael@0: * always compiled in, in case some other module that uses it is michael@0: * compiled with debugging even if this library is not. michael@0: */ michael@0: static PRLogModuleInfo* gDebugLog; michael@0: michael@0: static void InitLog(void) michael@0: { michael@0: if (0 == gDebugLog) { michael@0: gDebugLog = PR_NewLogModule("nsDebug"); michael@0: } michael@0: } michael@0: michael@0: enum nsAssertBehavior { michael@0: NS_ASSERT_UNINITIALIZED, michael@0: NS_ASSERT_WARN, michael@0: NS_ASSERT_SUSPEND, michael@0: NS_ASSERT_STACK, michael@0: NS_ASSERT_TRAP, michael@0: NS_ASSERT_ABORT, michael@0: NS_ASSERT_STACK_AND_ABORT michael@0: }; michael@0: michael@0: static nsAssertBehavior GetAssertBehavior() michael@0: { michael@0: static nsAssertBehavior gAssertBehavior = NS_ASSERT_UNINITIALIZED; michael@0: if (gAssertBehavior != NS_ASSERT_UNINITIALIZED) michael@0: return gAssertBehavior; michael@0: michael@0: #if defined(XP_WIN) && defined(MOZ_METRO) michael@0: if (IsRunningInWindowsMetro()) michael@0: gAssertBehavior = NS_ASSERT_WARN; michael@0: else michael@0: gAssertBehavior = NS_ASSERT_TRAP; michael@0: #elif defined(XP_WIN) michael@0: gAssertBehavior = NS_ASSERT_TRAP; michael@0: #else michael@0: gAssertBehavior = NS_ASSERT_WARN; michael@0: #endif michael@0: michael@0: const char *assertString = PR_GetEnv("XPCOM_DEBUG_BREAK"); michael@0: if (!assertString || !*assertString) michael@0: return gAssertBehavior; michael@0: michael@0: if (!strcmp(assertString, "warn")) michael@0: return gAssertBehavior = NS_ASSERT_WARN; michael@0: michael@0: if (!strcmp(assertString, "suspend")) michael@0: return gAssertBehavior = NS_ASSERT_SUSPEND; michael@0: michael@0: if (!strcmp(assertString, "stack")) michael@0: return gAssertBehavior = NS_ASSERT_STACK; michael@0: michael@0: if (!strcmp(assertString, "abort")) michael@0: return gAssertBehavior = NS_ASSERT_ABORT; michael@0: michael@0: if (!strcmp(assertString, "trap") || !strcmp(assertString, "break")) michael@0: return gAssertBehavior = NS_ASSERT_TRAP; michael@0: michael@0: if (!strcmp(assertString, "stack-and-abort")) michael@0: return gAssertBehavior = NS_ASSERT_STACK_AND_ABORT; michael@0: michael@0: fprintf(stderr, "Unrecognized value of XPCOM_DEBUG_BREAK\n"); michael@0: return gAssertBehavior; michael@0: } michael@0: michael@0: struct FixedBuffer michael@0: { michael@0: FixedBuffer() : curlen(0) { buffer[0] = '\0'; } michael@0: michael@0: char buffer[1000]; michael@0: uint32_t curlen; michael@0: }; michael@0: michael@0: static int michael@0: StuffFixedBuffer(void *closure, const char *buf, uint32_t len) michael@0: { michael@0: if (!len) michael@0: return 0; michael@0: michael@0: FixedBuffer *fb = (FixedBuffer*) closure; michael@0: michael@0: // strip the trailing null, we add it again later michael@0: if (buf[len - 1] == '\0') michael@0: --len; michael@0: michael@0: if (fb->curlen + len >= sizeof(fb->buffer)) michael@0: len = sizeof(fb->buffer) - fb->curlen - 1; michael@0: michael@0: if (len) { michael@0: memcpy(fb->buffer + fb->curlen, buf, len); michael@0: fb->curlen += len; michael@0: fb->buffer[fb->curlen] = '\0'; michael@0: } michael@0: michael@0: return len; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(void) michael@0: NS_DebugBreak(uint32_t aSeverity, const char *aStr, const char *aExpr, michael@0: const char *aFile, int32_t aLine) michael@0: { michael@0: InitLog(); michael@0: michael@0: FixedBuffer buf; michael@0: PRLogModuleLevel ll = PR_LOG_WARNING; michael@0: const char *sevString = "WARNING"; michael@0: michael@0: switch (aSeverity) { michael@0: case NS_DEBUG_ASSERTION: michael@0: sevString = "###!!! ASSERTION"; michael@0: ll = PR_LOG_ERROR; michael@0: break; michael@0: michael@0: case NS_DEBUG_BREAK: michael@0: sevString = "###!!! BREAK"; michael@0: ll = PR_LOG_ALWAYS; michael@0: break; michael@0: michael@0: case NS_DEBUG_ABORT: michael@0: sevString = "###!!! ABORT"; michael@0: ll = PR_LOG_ALWAYS; michael@0: break; michael@0: michael@0: default: michael@0: aSeverity = NS_DEBUG_WARNING; michael@0: }; michael@0: michael@0: # define PrintToBuffer(...) PR_sxprintf(StuffFixedBuffer, &buf, __VA_ARGS__) michael@0: michael@0: // Print "[PID]" or "[Desc PID]" at the beginning of the message. michael@0: PrintToBuffer("["); michael@0: if (sMultiprocessDescription) { michael@0: PrintToBuffer("%s ", sMultiprocessDescription); michael@0: } michael@0: PrintToBuffer("%d] ", base::GetCurrentProcId()); michael@0: michael@0: PrintToBuffer("%s: ", sevString); michael@0: michael@0: if (aStr) michael@0: PrintToBuffer("%s: ", aStr); michael@0: michael@0: if (aExpr) michael@0: PrintToBuffer("'%s', ", aExpr); michael@0: michael@0: if (aFile) michael@0: PrintToBuffer("file %s, ", aFile); michael@0: michael@0: if (aLine != -1) michael@0: PrintToBuffer("line %d", aLine); michael@0: michael@0: # undef PrintToBuffer michael@0: michael@0: // Write out the message to the debug log michael@0: PR_LOG(gDebugLog, ll, ("%s", buf.buffer)); michael@0: PR_LogFlush(); michael@0: michael@0: // errors on platforms without a debugdlg ring a bell on stderr michael@0: #if !defined(XP_WIN) michael@0: if (ll != PR_LOG_WARNING) michael@0: fprintf(stderr, "\07"); michael@0: #endif michael@0: michael@0: #ifdef ANDROID michael@0: __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer); michael@0: #endif michael@0: michael@0: // Write the message to stderr unless it's a warning and MOZ_IGNORE_WARNINGS michael@0: // is set. michael@0: if (!(PR_GetEnv("MOZ_IGNORE_WARNINGS") && aSeverity == NS_DEBUG_WARNING)) { michael@0: fprintf(stderr, "%s\n", buf.buffer); michael@0: fflush(stderr); michael@0: } michael@0: michael@0: switch (aSeverity) { michael@0: case NS_DEBUG_WARNING: michael@0: return; michael@0: michael@0: case NS_DEBUG_BREAK: michael@0: Break(buf.buffer); michael@0: return; michael@0: michael@0: case NS_DEBUG_ABORT: { michael@0: #if defined(MOZ_CRASHREPORTER) michael@0: nsCString note("xpcom_runtime_abort("); michael@0: note += buf.buffer; michael@0: note += ")"; michael@0: CrashReporter::AppendAppNotesToCrashReport(note); michael@0: CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AbortMessage"), michael@0: nsDependentCString(buf.buffer)); michael@0: #endif // MOZ_CRASHREPORTER michael@0: michael@0: #if defined(DEBUG) && defined(_WIN32) michael@0: RealBreak(); michael@0: #endif michael@0: #ifdef DEBUG michael@0: nsTraceRefcnt::WalkTheStack(stderr); michael@0: #endif michael@0: Abort(buf.buffer); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Now we deal with assertions michael@0: gAssertionCount++; michael@0: michael@0: switch (GetAssertBehavior()) { michael@0: case NS_ASSERT_WARN: michael@0: return; michael@0: michael@0: case NS_ASSERT_SUSPEND: michael@0: #ifdef XP_UNIX michael@0: fprintf(stderr, "Suspending process; attach with the debugger.\n"); michael@0: kill(0, SIGSTOP); michael@0: #else michael@0: Break(buf.buffer); michael@0: #endif michael@0: return; michael@0: michael@0: case NS_ASSERT_STACK: michael@0: nsTraceRefcnt::WalkTheStack(stderr); michael@0: return; michael@0: michael@0: case NS_ASSERT_STACK_AND_ABORT: michael@0: nsTraceRefcnt::WalkTheStack(stderr); michael@0: // Fall through to abort michael@0: michael@0: case NS_ASSERT_ABORT: michael@0: Abort(buf.buffer); michael@0: return; michael@0: michael@0: case NS_ASSERT_TRAP: michael@0: case NS_ASSERT_UNINITIALIZED: // Default to "trap" behavior michael@0: Break(buf.buffer); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: Abort(const char *aMsg) michael@0: { michael@0: mozalloc_abort(aMsg); michael@0: } michael@0: michael@0: static void michael@0: RealBreak() michael@0: { michael@0: #if defined(_WIN32) michael@0: ::DebugBreak(); michael@0: #elif defined(XP_MACOSX) michael@0: raise(SIGTRAP); michael@0: #elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__)) michael@0: asm("int $3"); michael@0: #elif defined(__arm__) michael@0: asm( michael@0: #ifdef __ARM_ARCH_4T__ michael@0: /* ARMv4T doesn't support the BKPT instruction, so if the compiler target michael@0: * is ARMv4T, we want to ensure the assembler will understand that ARMv5T michael@0: * instruction, while keeping the resulting object tagged as ARMv4T. michael@0: */ michael@0: ".arch armv5t\n" michael@0: ".object_arch armv4t\n" michael@0: #endif michael@0: "BKPT #0"); michael@0: #elif defined(SOLARIS) michael@0: #if defined(__i386__) || defined(__i386) || defined(__x86_64__) michael@0: asm("int $3"); michael@0: #else michael@0: raise(SIGTRAP); michael@0: #endif michael@0: #else michael@0: #warning do not know how to break on this platform michael@0: #endif michael@0: } michael@0: michael@0: // Abort() calls this function, don't call it! michael@0: static void michael@0: Break(const char *aMsg) michael@0: { michael@0: #if defined(_WIN32) michael@0: static int ignoreDebugger; michael@0: if (!ignoreDebugger) { michael@0: const char *shouldIgnoreDebugger = getenv("XPCOM_DEBUG_DLG"); michael@0: ignoreDebugger = 1 + (shouldIgnoreDebugger && !strcmp(shouldIgnoreDebugger, "1")); michael@0: } michael@0: if ((ignoreDebugger == 2) || !::IsDebuggerPresent()) { michael@0: DWORD code = IDRETRY; michael@0: michael@0: /* Create the debug dialog out of process to avoid the crashes caused by michael@0: * Windows events leaking into our event loop from an in process dialog. michael@0: * We do this by launching windbgdlg.exe (built in xpcom/windbgdlg). michael@0: * See http://bugzilla.mozilla.org/show_bug.cgi?id=54792 michael@0: */ michael@0: PROCESS_INFORMATION pi; michael@0: STARTUPINFOW si; michael@0: wchar_t executable[MAX_PATH]; michael@0: wchar_t* pName; michael@0: michael@0: memset(&pi, 0, sizeof(pi)); michael@0: michael@0: memset(&si, 0, sizeof(si)); michael@0: si.cb = sizeof(si); michael@0: si.wShowWindow = SW_SHOW; michael@0: michael@0: // 2nd arg of CreateProcess is in/out michael@0: wchar_t *msgCopy = (wchar_t*) _alloca((strlen(aMsg) + 1)*sizeof(wchar_t)); michael@0: wcscpy(msgCopy, NS_ConvertUTF8toUTF16(aMsg).get()); michael@0: michael@0: if(GetModuleFileNameW(GetModuleHandleW(L"xpcom.dll"), executable, MAX_PATH) && michael@0: nullptr != (pName = wcsrchr(executable, '\\')) && michael@0: nullptr != wcscpy(pName + 1, L"windbgdlg.exe") && michael@0: CreateProcessW(executable, msgCopy, nullptr, nullptr, michael@0: false, DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, michael@0: nullptr, nullptr, &si, &pi)) { michael@0: WaitForSingleObject(pi.hProcess, INFINITE); michael@0: GetExitCodeProcess(pi.hProcess, &code); michael@0: CloseHandle(pi.hProcess); michael@0: CloseHandle(pi.hThread); michael@0: } michael@0: michael@0: switch(code) { michael@0: case IDABORT: michael@0: //This should exit us michael@0: raise(SIGABRT); michael@0: //If we are ignored exit this way.. michael@0: _exit(3); michael@0: michael@0: case IDIGNORE: michael@0: return; michael@0: } michael@0: } michael@0: michael@0: RealBreak(); michael@0: #elif defined(XP_MACOSX) michael@0: /* Note that we put this Mac OS X test above the GNUC/x86 test because the michael@0: * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86 michael@0: * impls to be the same. michael@0: */ michael@0: RealBreak(); michael@0: #elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__)) michael@0: RealBreak(); michael@0: #elif defined(__arm__) michael@0: RealBreak(); michael@0: #elif defined(SOLARIS) michael@0: RealBreak(); michael@0: #else michael@0: #warning do not know how to break on this platform michael@0: #endif michael@0: } michael@0: michael@0: static const nsDebugImpl kImpl; michael@0: michael@0: nsresult michael@0: nsDebugImpl::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) michael@0: { michael@0: if (NS_WARN_IF(outer)) michael@0: return NS_ERROR_NO_AGGREGATION; michael@0: michael@0: return const_cast(&kImpl)-> michael@0: QueryInterface(aIID, aInstancePtr); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsresult michael@0: NS_ErrorAccordingToNSPR() michael@0: { michael@0: PRErrorCode err = PR_GetError(); michael@0: switch (err) { michael@0: case PR_OUT_OF_MEMORY_ERROR: return NS_ERROR_OUT_OF_MEMORY; michael@0: case PR_WOULD_BLOCK_ERROR: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: case PR_FILE_NOT_FOUND_ERROR: return NS_ERROR_FILE_NOT_FOUND; michael@0: case PR_READ_ONLY_FILESYSTEM_ERROR: return NS_ERROR_FILE_READ_ONLY; michael@0: case PR_NOT_DIRECTORY_ERROR: return NS_ERROR_FILE_NOT_DIRECTORY; michael@0: case PR_IS_DIRECTORY_ERROR: return NS_ERROR_FILE_IS_DIRECTORY; michael@0: case PR_LOOP_ERROR: return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; michael@0: case PR_FILE_EXISTS_ERROR: return NS_ERROR_FILE_ALREADY_EXISTS; michael@0: case PR_FILE_IS_LOCKED_ERROR: return NS_ERROR_FILE_IS_LOCKED; michael@0: case PR_FILE_TOO_BIG_ERROR: return NS_ERROR_FILE_TOO_BIG; michael@0: case PR_NO_DEVICE_SPACE_ERROR: return NS_ERROR_FILE_NO_DEVICE_SPACE; michael@0: case PR_NAME_TOO_LONG_ERROR: return NS_ERROR_FILE_NAME_TOO_LONG; michael@0: case PR_DIRECTORY_NOT_EMPTY_ERROR: return NS_ERROR_FILE_DIR_NOT_EMPTY; michael@0: case PR_NO_ACCESS_RIGHTS_ERROR: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: default: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: void michael@0: NS_ABORT_OOM(size_t size) michael@0: { michael@0: #ifdef MOZ_CRASHREPORTER michael@0: CrashReporter::AnnotateOOMAllocationSize(size); michael@0: #endif michael@0: MOZ_CRASH(); michael@0: } michael@0: