diff options
Diffstat (limited to 'xpcom/base/nsDebugImpl.cpp')
-rw-r--r-- | xpcom/base/nsDebugImpl.cpp | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp new file mode 100644 index 0000000000..4023efd0ec --- /dev/null +++ b/xpcom/base/nsDebugImpl.cpp @@ -0,0 +1,675 @@ +/* -*- 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 "mozilla/IntentionalCrash.h" +#include "mozilla/Printf.h" +#include "mozilla/ProfilerMarkers.h" + +#include "MainThreadUtils.h" +#include "nsDebugImpl.h" +#include "nsDebug.h" +#include "nsExceptionHandler.h" +#include "nsString.h" +#include "nsXULAppAPI.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 "mozilla/StackWalk.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 + +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 +#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::CrashWithOOM() { + NS_ABORT_OOM(-1); + return NS_OK; +} + +// From toolkit/library/rust/lib.rs +extern "C" void intentional_panic(const char* message); + +NS_IMETHODIMP +nsDebugImpl::RustPanic(const char* aMessage) { + intentional_panic(aMessage); + return NS_OK; +} + +// From toolkit/library/rust/lib.rs +extern "C" void debug_log(const char* target, const char* message); + +NS_IMETHODIMP +nsDebugImpl::RustLog(const char* aTarget, const char* aMessage) { + debug_log(aTarget, aMessage); + 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(__OpenBSD__) && defined(MOZ_SANDBOX) + // no access to KERN_PROC_PID sysctl when pledge'd + return NS_OK; +#endif +#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; +} + +/* static */ const char* nsDebugImpl::GetMultiprocessMode() { + return sMultiprocessDescription; +} + +/** + * 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 final : public mozilla::PrintfTarget { + FixedBuffer() : curlen(0) { buffer[0] = '\0'; } + + char buffer[764]; + uint32_t curlen; + + bool append(const char* sp, size_t len) override; +}; + +bool FixedBuffer::append(const char* aBuf, size_t aLen) { + if (!aLen) { + return true; + } + + if (curlen + aLen >= sizeof(buffer)) { + aLen = sizeof(buffer) - curlen - 1; + } + + if (aLen) { + memcpy(buffer + curlen, aBuf, aLen); + curlen += aLen; + buffer[curlen] = '\0'; + } + + return true; +} + +namespace geckoprofiler::markers { + +struct DebugBreakMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("DebugBreak"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint32_t aSeverity, + const ProfilerString8View& aStr, + const ProfilerString8View& aExpr, + const ProfilerString8View& aFile, + int32_t aLine) { + nsAutoCString 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; + } + aWriter.StringProperty("Severity", sevString); + // The 'name' property is searchable on the front-end. + if (aStr.Length() != 0) { + aWriter.StringProperty("Message", aStr); + aWriter.StringProperty("name", aStr); + } else if (aExpr.Length() != 0) { + aWriter.StringProperty("name", aExpr); + } + if (aExpr.Length() != 0) { + aWriter.StringProperty("Expression", aExpr); + } + if (aFile.Length() != 0) { + aWriter.StringProperty("File", aFile); + } + if (aLine != 0) { + aWriter.IntProperty("Line", aLine); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::TimelineOverview, MS::Location::MarkerChart, + MS::Location::MarkerTable}; + schema.SetAllLabels("{marker.data.Severity}: {marker.data.name}"); + schema.AddKeyFormat("Message", MS::Format::String); + schema.AddKeyFormat("Severity", MS::Format::String); + schema.AddKeyFormat("Expression", MS::Format::String); + schema.AddKeyFormat("File", MS::Format::String); + schema.AddKeyFormat("Line", MS::Format::Integer); + return schema; + } +}; + +} // namespace geckoprofiler::markers + +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; + } + + nonPIDBuf.print("%s: ", sevString); + if (aStr) { + nonPIDBuf.print("%s: ", aStr); + } + if (aExpr) { + nonPIDBuf.print("'%s', ", aExpr); + } + if (aFile || aLine != -1) { + nonPIDBuf.print("file %s:%d", aFile ? aFile : "<unknown>", + aLine != -1 ? aLine : 0); + } + + // Print "[PID]" or "[Desc PID]" at the beginning of the message. + buf.print("["); + if (sMultiprocessDescription) { + buf.print("%s ", sMultiprocessDescription); + } + + bool isMainthread = (NS_IsMainThreadTLSInitialized() && NS_IsMainThread()); + PRThread* currentThread = PR_GetCurrentThread(); + const char* currentThreadName = + isMainthread ? "Main Thread" : PR_GetThreadName(currentThread); + if (currentThreadName) { + buf.print("%" PRIPID ", %s] %s", base::GetCurrentProcId(), + currentThreadName, nonPIDBuf.buffer); + } else { + buf.print("%" PRIPID ", Unnamed thread %p] %s", base::GetCurrentProcId(), + currentThread, nonPIDBuf.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 + + PROFILER_MARKER("NS_DebugBreak", OTHER, MarkerStack::Capture(), + DebugBreakMarker, aSeverity, + ProfilerString8View::WrapNullTerminatedString(aStr), + ProfilerString8View::WrapNullTerminatedString(aExpr), + ProfilerString8View::WrapNullTerminatedString(aFile), aLine); + + // 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: { + // 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( + CrashReporter::Annotation::AbortMessage, + nsDependentCString(nonPIDBuf.buffer)); + } + +#if defined(DEBUG) && defined(_WIN32) + RealBreak(); +#endif +#if defined(DEBUG) + MozWalkTheStack(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: + MozWalkTheStack(stderr); + return; + + case NS_ASSERT_STACK_AND_ABORT: + MozWalkTheStack(stderr); + // Fall through to abort + [[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) { + NoteIntentionalCrash(XRE_GetProcessTypeString()); + MOZ_CRASH_UNSAFE(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(__aarch64__) + asm("brk #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__) || defined(__aarch64__) + RealBreak(); +#elif defined(SOLARIS) + RealBreak(); +#else +# warning do not know how to break on this platform +#endif +} + +nsresult nsDebugImpl::Create(const nsIID& aIID, void** aInstancePtr) { + static const nsDebugImpl* sImpl; + + 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) { + CrashReporter::AnnotateOOMAllocationSize(aSize); + MOZ_CRASH("OOM"); +} |