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