Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / nsDebugImpl.cpp
blob4023efd0ec334eca979773a5eb51ab3ff296d284
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Chromium headers must come before Mozilla headers.
8 #include "base/process_util.h"
10 #include "mozilla/Atomics.h"
11 #include "mozilla/IntentionalCrash.h"
12 #include "mozilla/Printf.h"
13 #include "mozilla/ProfilerMarkers.h"
15 #include "MainThreadUtils.h"
16 #include "nsDebugImpl.h"
17 #include "nsDebug.h"
18 #include "nsExceptionHandler.h"
19 #include "nsString.h"
20 #include "nsXULAppAPI.h"
21 #include "prerror.h"
22 #include "prerr.h"
23 #include "prenv.h"
25 #ifdef ANDROID
26 # include <android/log.h>
27 #endif
29 #ifdef _WIN32
30 /* for getenv() */
31 # include <stdlib.h>
32 #endif
34 #include "mozilla/StackWalk.h"
36 #if defined(XP_UNIX)
37 # include <signal.h>
38 #endif
40 #if defined(XP_WIN)
41 # include <tchar.h>
42 # include "nsString.h"
43 #endif
45 #if defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) || \
46 defined(__NetBSD__) || defined(__OpenBSD__)
47 # include <stdbool.h>
48 # include <unistd.h>
49 # include <sys/param.h>
50 # include <sys/sysctl.h>
51 #endif
53 #if defined(__OpenBSD__)
54 # include <sys/proc.h>
55 #endif
57 #if defined(__DragonFly__) || defined(__FreeBSD__)
58 # include <sys/user.h>
59 #endif
61 #if defined(__NetBSD__)
62 # undef KERN_PROC
63 # define KERN_PROC KERN_PROC2
64 # define KINFO_PROC struct kinfo_proc2
65 #else
66 # define KINFO_PROC struct kinfo_proc
67 #endif
69 #if defined(XP_MACOSX)
70 # define KP_FLAGS kp_proc.p_flag
71 #elif defined(__DragonFly__)
72 # define KP_FLAGS kp_flags
73 #elif defined(__FreeBSD__)
74 # define KP_FLAGS ki_flag
75 #elif defined(__OpenBSD__) && !defined(_P_TRACED)
76 # define KP_FLAGS p_psflags
77 # define P_TRACED PS_TRACED
78 #else
79 # define KP_FLAGS p_flag
80 #endif
82 static void Abort(const char* aMsg);
84 static void RealBreak();
86 static void Break(const char* aMsg);
88 #if defined(_WIN32)
89 # include <windows.h>
90 # include <signal.h>
91 # include <malloc.h> // for _alloca
92 #endif
94 using namespace mozilla;
96 static const char* sMultiprocessDescription = nullptr;
98 static Atomic<int32_t> gAssertionCount;
100 NS_IMPL_QUERY_INTERFACE(nsDebugImpl, nsIDebug2)
102 NS_IMETHODIMP_(MozExternalRefCountType)
103 nsDebugImpl::AddRef() { return 2; }
105 NS_IMETHODIMP_(MozExternalRefCountType)
106 nsDebugImpl::Release() { return 1; }
108 NS_IMETHODIMP
109 nsDebugImpl::Assertion(const char* aStr, const char* aExpr, const char* aFile,
110 int32_t aLine) {
111 NS_DebugBreak(NS_DEBUG_ASSERTION, aStr, aExpr, aFile, aLine);
112 return NS_OK;
115 NS_IMETHODIMP
116 nsDebugImpl::Warning(const char* aStr, const char* aFile, int32_t aLine) {
117 NS_DebugBreak(NS_DEBUG_WARNING, aStr, nullptr, aFile, aLine);
118 return NS_OK;
121 NS_IMETHODIMP
122 nsDebugImpl::Break(const char* aFile, int32_t aLine) {
123 NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, aFile, aLine);
124 return NS_OK;
127 NS_IMETHODIMP
128 nsDebugImpl::Abort(const char* aFile, int32_t aLine) {
129 NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, aFile, aLine);
130 return NS_OK;
133 NS_IMETHODIMP
134 nsDebugImpl::CrashWithOOM() {
135 NS_ABORT_OOM(-1);
136 return NS_OK;
139 // From toolkit/library/rust/lib.rs
140 extern "C" void intentional_panic(const char* message);
142 NS_IMETHODIMP
143 nsDebugImpl::RustPanic(const char* aMessage) {
144 intentional_panic(aMessage);
145 return NS_OK;
148 // From toolkit/library/rust/lib.rs
149 extern "C" void debug_log(const char* target, const char* message);
151 NS_IMETHODIMP
152 nsDebugImpl::RustLog(const char* aTarget, const char* aMessage) {
153 debug_log(aTarget, aMessage);
154 return NS_OK;
157 NS_IMETHODIMP
158 nsDebugImpl::GetIsDebugBuild(bool* aResult) {
159 #ifdef DEBUG
160 *aResult = true;
161 #else
162 *aResult = false;
163 #endif
164 return NS_OK;
167 NS_IMETHODIMP
168 nsDebugImpl::GetAssertionCount(int32_t* aResult) {
169 *aResult = gAssertionCount;
170 return NS_OK;
173 NS_IMETHODIMP
174 nsDebugImpl::GetIsDebuggerAttached(bool* aResult) {
175 *aResult = false;
177 #if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
178 // no access to KERN_PROC_PID sysctl when pledge'd
179 return NS_OK;
180 #endif
181 #if defined(XP_WIN)
182 *aResult = ::IsDebuggerPresent();
183 #elif defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) || \
184 defined(__NetBSD__) || defined(__OpenBSD__)
185 // Specify the info we're looking for
186 int mib[] = {
187 CTL_KERN,
188 KERN_PROC,
189 KERN_PROC_PID,
190 getpid(),
191 # if defined(__NetBSD__) || defined(__OpenBSD__)
192 sizeof(KINFO_PROC),
194 # endif
196 u_int mibSize = sizeof(mib) / sizeof(int);
198 KINFO_PROC info;
199 size_t infoSize = sizeof(info);
200 memset(&info, 0, infoSize);
202 if (sysctl(mib, mibSize, &info, &infoSize, nullptr, 0)) {
203 // if the call fails, default to false
204 *aResult = false;
205 return NS_OK;
208 if (info.KP_FLAGS & P_TRACED) {
209 *aResult = true;
211 #endif
213 return NS_OK;
216 /* static */
217 void nsDebugImpl::SetMultiprocessMode(const char* aDesc) {
218 sMultiprocessDescription = aDesc;
221 /* static */ const char* nsDebugImpl::GetMultiprocessMode() {
222 return sMultiprocessDescription;
226 * Implementation of the nsDebug methods. Note that this code is
227 * always compiled in, in case some other module that uses it is
228 * compiled with debugging even if this library is not.
230 enum nsAssertBehavior {
231 NS_ASSERT_UNINITIALIZED,
232 NS_ASSERT_WARN,
233 NS_ASSERT_SUSPEND,
234 NS_ASSERT_STACK,
235 NS_ASSERT_TRAP,
236 NS_ASSERT_ABORT,
237 NS_ASSERT_STACK_AND_ABORT
240 static nsAssertBehavior GetAssertBehavior() {
241 static nsAssertBehavior gAssertBehavior = NS_ASSERT_UNINITIALIZED;
242 if (gAssertBehavior != NS_ASSERT_UNINITIALIZED) {
243 return gAssertBehavior;
246 gAssertBehavior = NS_ASSERT_WARN;
248 const char* assertString = PR_GetEnv("XPCOM_DEBUG_BREAK");
249 if (!assertString || !*assertString) {
250 return gAssertBehavior;
252 if (!strcmp(assertString, "warn")) {
253 return gAssertBehavior = NS_ASSERT_WARN;
255 if (!strcmp(assertString, "suspend")) {
256 return gAssertBehavior = NS_ASSERT_SUSPEND;
258 if (!strcmp(assertString, "stack")) {
259 return gAssertBehavior = NS_ASSERT_STACK;
261 if (!strcmp(assertString, "abort")) {
262 return gAssertBehavior = NS_ASSERT_ABORT;
264 if (!strcmp(assertString, "trap") || !strcmp(assertString, "break")) {
265 return gAssertBehavior = NS_ASSERT_TRAP;
267 if (!strcmp(assertString, "stack-and-abort")) {
268 return gAssertBehavior = NS_ASSERT_STACK_AND_ABORT;
271 fprintf(stderr, "Unrecognized value of XPCOM_DEBUG_BREAK\n");
272 return gAssertBehavior;
275 struct FixedBuffer final : public mozilla::PrintfTarget {
276 FixedBuffer() : curlen(0) { buffer[0] = '\0'; }
278 char buffer[764];
279 uint32_t curlen;
281 bool append(const char* sp, size_t len) override;
284 bool FixedBuffer::append(const char* aBuf, size_t aLen) {
285 if (!aLen) {
286 return true;
289 if (curlen + aLen >= sizeof(buffer)) {
290 aLen = sizeof(buffer) - curlen - 1;
293 if (aLen) {
294 memcpy(buffer + curlen, aBuf, aLen);
295 curlen += aLen;
296 buffer[curlen] = '\0';
299 return true;
302 namespace geckoprofiler::markers {
304 struct DebugBreakMarker {
305 static constexpr Span<const char> MarkerTypeName() {
306 return MakeStringSpan("DebugBreak");
308 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
309 uint32_t aSeverity,
310 const ProfilerString8View& aStr,
311 const ProfilerString8View& aExpr,
312 const ProfilerString8View& aFile,
313 int32_t aLine) {
314 nsAutoCString sevString("WARNING");
315 switch (aSeverity) {
316 case NS_DEBUG_ASSERTION:
317 sevString = "ASSERTION";
318 break;
320 case NS_DEBUG_BREAK:
321 sevString = "BREAK";
322 break;
324 case NS_DEBUG_ABORT:
325 sevString = "ABORT";
326 break;
328 aWriter.StringProperty("Severity", sevString);
329 // The 'name' property is searchable on the front-end.
330 if (aStr.Length() != 0) {
331 aWriter.StringProperty("Message", aStr);
332 aWriter.StringProperty("name", aStr);
333 } else if (aExpr.Length() != 0) {
334 aWriter.StringProperty("name", aExpr);
336 if (aExpr.Length() != 0) {
337 aWriter.StringProperty("Expression", aExpr);
339 if (aFile.Length() != 0) {
340 aWriter.StringProperty("File", aFile);
342 if (aLine != 0) {
343 aWriter.IntProperty("Line", aLine);
346 static MarkerSchema MarkerTypeDisplay() {
347 using MS = MarkerSchema;
348 MS schema{MS::Location::TimelineOverview, MS::Location::MarkerChart,
349 MS::Location::MarkerTable};
350 schema.SetAllLabels("{marker.data.Severity}: {marker.data.name}");
351 schema.AddKeyFormat("Message", MS::Format::String);
352 schema.AddKeyFormat("Severity", MS::Format::String);
353 schema.AddKeyFormat("Expression", MS::Format::String);
354 schema.AddKeyFormat("File", MS::Format::String);
355 schema.AddKeyFormat("Line", MS::Format::Integer);
356 return schema;
360 } // namespace geckoprofiler::markers
362 EXPORT_XPCOM_API(void)
363 NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr,
364 const char* aFile, int32_t aLine) {
365 FixedBuffer nonPIDBuf;
366 FixedBuffer buf;
367 const char* sevString = "WARNING";
369 switch (aSeverity) {
370 case NS_DEBUG_ASSERTION:
371 sevString = "###!!! ASSERTION";
372 break;
374 case NS_DEBUG_BREAK:
375 sevString = "###!!! BREAK";
376 break;
378 case NS_DEBUG_ABORT:
379 sevString = "###!!! ABORT";
380 break;
382 default:
383 aSeverity = NS_DEBUG_WARNING;
386 nonPIDBuf.print("%s: ", sevString);
387 if (aStr) {
388 nonPIDBuf.print("%s: ", aStr);
390 if (aExpr) {
391 nonPIDBuf.print("'%s', ", aExpr);
393 if (aFile || aLine != -1) {
394 nonPIDBuf.print("file %s:%d", aFile ? aFile : "<unknown>",
395 aLine != -1 ? aLine : 0);
398 // Print "[PID]" or "[Desc PID]" at the beginning of the message.
399 buf.print("[");
400 if (sMultiprocessDescription) {
401 buf.print("%s ", sMultiprocessDescription);
404 bool isMainthread = (NS_IsMainThreadTLSInitialized() && NS_IsMainThread());
405 PRThread* currentThread = PR_GetCurrentThread();
406 const char* currentThreadName =
407 isMainthread ? "Main Thread" : PR_GetThreadName(currentThread);
408 if (currentThreadName) {
409 buf.print("%" PRIPID ", %s] %s", base::GetCurrentProcId(),
410 currentThreadName, nonPIDBuf.buffer);
411 } else {
412 buf.print("%" PRIPID ", Unnamed thread %p] %s", base::GetCurrentProcId(),
413 currentThread, nonPIDBuf.buffer);
416 // errors on platforms without a debugdlg ring a bell on stderr
417 #if !defined(XP_WIN)
418 if (aSeverity != NS_DEBUG_WARNING) {
419 fprintf(stderr, "\07");
421 #endif
423 #ifdef ANDROID
424 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer);
425 #endif
427 PROFILER_MARKER("NS_DebugBreak", OTHER, MarkerStack::Capture(),
428 DebugBreakMarker, aSeverity,
429 ProfilerString8View::WrapNullTerminatedString(aStr),
430 ProfilerString8View::WrapNullTerminatedString(aExpr),
431 ProfilerString8View::WrapNullTerminatedString(aFile), aLine);
433 // Write the message to stderr unless it's a warning and MOZ_IGNORE_WARNINGS
434 // is set.
435 if (!(PR_GetEnv("MOZ_IGNORE_WARNINGS") && aSeverity == NS_DEBUG_WARNING)) {
436 fprintf(stderr, "%s\n", buf.buffer);
437 fflush(stderr);
440 switch (aSeverity) {
441 case NS_DEBUG_WARNING:
442 return;
444 case NS_DEBUG_BREAK:
445 Break(buf.buffer);
446 return;
448 case NS_DEBUG_ABORT: {
449 // Updating crash annotations in the child causes us to do IPC. This can
450 // really cause trouble if we're asserting from within IPC code. So we
451 // have to do without the annotations in that case.
452 if (XRE_IsParentProcess()) {
453 // Don't include the PID in the crash report annotation to
454 // allow faceting on crash-stats.mozilla.org.
455 nsCString note("xpcom_runtime_abort(");
456 note += nonPIDBuf.buffer;
457 note += ")";
458 CrashReporter::AppendAppNotesToCrashReport(note);
459 CrashReporter::AnnotateCrashReport(
460 CrashReporter::Annotation::AbortMessage,
461 nsDependentCString(nonPIDBuf.buffer));
464 #if defined(DEBUG) && defined(_WIN32)
465 RealBreak();
466 #endif
467 #if defined(DEBUG)
468 MozWalkTheStack(stderr);
469 #endif
470 Abort(buf.buffer);
471 return;
475 // Now we deal with assertions
476 gAssertionCount++;
478 switch (GetAssertBehavior()) {
479 case NS_ASSERT_WARN:
480 return;
482 case NS_ASSERT_SUSPEND:
483 #ifdef XP_UNIX
484 fprintf(stderr, "Suspending process; attach with the debugger.\n");
485 kill(0, SIGSTOP);
486 #else
487 Break(buf.buffer);
488 #endif
489 return;
491 case NS_ASSERT_STACK:
492 MozWalkTheStack(stderr);
493 return;
495 case NS_ASSERT_STACK_AND_ABORT:
496 MozWalkTheStack(stderr);
497 // Fall through to abort
498 [[fallthrough]];
500 case NS_ASSERT_ABORT:
501 Abort(buf.buffer);
502 return;
504 case NS_ASSERT_TRAP:
505 case NS_ASSERT_UNINITIALIZED: // Default to "trap" behavior
506 Break(buf.buffer);
507 return;
511 static void Abort(const char* aMsg) {
512 NoteIntentionalCrash(XRE_GetProcessTypeString());
513 MOZ_CRASH_UNSAFE(aMsg);
516 static void RealBreak() {
517 #if defined(_WIN32)
518 ::DebugBreak();
519 #elif defined(XP_MACOSX)
520 raise(SIGTRAP);
521 #elif defined(__GNUC__) && \
522 (defined(__i386__) || defined(__i386) || defined(__x86_64__))
523 asm("int $3");
524 #elif defined(__arm__)
525 asm(
526 # ifdef __ARM_ARCH_4T__
527 /* ARMv4T doesn't support the BKPT instruction, so if the compiler target
528 * is ARMv4T, we want to ensure the assembler will understand that ARMv5T
529 * instruction, while keeping the resulting object tagged as ARMv4T.
531 ".arch armv5t\n"
532 ".object_arch armv4t\n"
533 # endif
534 "BKPT #0");
535 #elif defined(__aarch64__)
536 asm("brk #0");
537 #elif defined(SOLARIS)
538 # if defined(__i386__) || defined(__i386) || defined(__x86_64__)
539 asm("int $3");
540 # else
541 raise(SIGTRAP);
542 # endif
543 #else
544 # warning do not know how to break on this platform
545 #endif
548 // Abort() calls this function, don't call it!
549 static void Break(const char* aMsg) {
550 #if defined(_WIN32)
551 static int ignoreDebugger;
552 if (!ignoreDebugger) {
553 const char* shouldIgnoreDebugger = getenv("XPCOM_DEBUG_DLG");
554 ignoreDebugger =
555 1 + (shouldIgnoreDebugger && !strcmp(shouldIgnoreDebugger, "1"));
557 if ((ignoreDebugger == 2) || !::IsDebuggerPresent()) {
558 DWORD code = IDRETRY;
560 /* Create the debug dialog out of process to avoid the crashes caused by
561 * Windows events leaking into our event loop from an in process dialog.
562 * We do this by launching windbgdlg.exe (built in xpcom/windbgdlg).
563 * See http://bugzilla.mozilla.org/show_bug.cgi?id=54792
565 PROCESS_INFORMATION pi;
566 STARTUPINFOW si;
567 wchar_t executable[MAX_PATH];
568 wchar_t* pName;
570 memset(&pi, 0, sizeof(pi));
572 memset(&si, 0, sizeof(si));
573 si.cb = sizeof(si);
574 si.wShowWindow = SW_SHOW;
576 // 2nd arg of CreateProcess is in/out
577 wchar_t* msgCopy = (wchar_t*)_alloca((strlen(aMsg) + 1) * sizeof(wchar_t));
578 wcscpy(msgCopy, NS_ConvertUTF8toUTF16(aMsg).get());
580 if (GetModuleFileNameW(GetModuleHandleW(L"xpcom.dll"), executable,
581 MAX_PATH) &&
582 (pName = wcsrchr(executable, '\\')) != nullptr &&
583 wcscpy(pName + 1, L"windbgdlg.exe") &&
584 CreateProcessW(executable, msgCopy, nullptr, nullptr, false,
585 DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, nullptr,
586 nullptr, &si, &pi)) {
587 WaitForSingleObject(pi.hProcess, INFINITE);
588 GetExitCodeProcess(pi.hProcess, &code);
589 CloseHandle(pi.hProcess);
590 CloseHandle(pi.hThread);
593 switch (code) {
594 case IDABORT:
595 // This should exit us
596 raise(SIGABRT);
597 // If we are ignored exit this way..
598 _exit(3);
600 case IDIGNORE:
601 return;
605 RealBreak();
606 #elif defined(XP_MACOSX)
607 /* Note that we put this Mac OS X test above the GNUC/x86 test because the
608 * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86
609 * impls to be the same.
611 RealBreak();
612 #elif defined(__GNUC__) && \
613 (defined(__i386__) || defined(__i386) || defined(__x86_64__))
614 RealBreak();
615 #elif defined(__arm__) || defined(__aarch64__)
616 RealBreak();
617 #elif defined(SOLARIS)
618 RealBreak();
619 #else
620 # warning do not know how to break on this platform
621 #endif
624 nsresult nsDebugImpl::Create(const nsIID& aIID, void** aInstancePtr) {
625 static const nsDebugImpl* sImpl;
627 if (!sImpl) {
628 sImpl = new nsDebugImpl();
631 return const_cast<nsDebugImpl*>(sImpl)->QueryInterface(aIID, aInstancePtr);
634 ////////////////////////////////////////////////////////////////////////////////
636 nsresult NS_ErrorAccordingToNSPR() {
637 PRErrorCode err = PR_GetError();
638 switch (err) {
639 case PR_OUT_OF_MEMORY_ERROR:
640 return NS_ERROR_OUT_OF_MEMORY;
641 case PR_WOULD_BLOCK_ERROR:
642 return NS_BASE_STREAM_WOULD_BLOCK;
643 case PR_FILE_NOT_FOUND_ERROR:
644 return NS_ERROR_FILE_NOT_FOUND;
645 case PR_READ_ONLY_FILESYSTEM_ERROR:
646 return NS_ERROR_FILE_READ_ONLY;
647 case PR_NOT_DIRECTORY_ERROR:
648 return NS_ERROR_FILE_NOT_DIRECTORY;
649 case PR_IS_DIRECTORY_ERROR:
650 return NS_ERROR_FILE_IS_DIRECTORY;
651 case PR_LOOP_ERROR:
652 return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
653 case PR_FILE_EXISTS_ERROR:
654 return NS_ERROR_FILE_ALREADY_EXISTS;
655 case PR_FILE_IS_LOCKED_ERROR:
656 return NS_ERROR_FILE_IS_LOCKED;
657 case PR_FILE_TOO_BIG_ERROR:
658 return NS_ERROR_FILE_TOO_BIG;
659 case PR_NO_DEVICE_SPACE_ERROR:
660 return NS_ERROR_FILE_NO_DEVICE_SPACE;
661 case PR_NAME_TOO_LONG_ERROR:
662 return NS_ERROR_FILE_NAME_TOO_LONG;
663 case PR_DIRECTORY_NOT_EMPTY_ERROR:
664 return NS_ERROR_FILE_DIR_NOT_EMPTY;
665 case PR_NO_ACCESS_RIGHTS_ERROR:
666 return NS_ERROR_FILE_ACCESS_DENIED;
667 default:
668 return NS_ERROR_FAILURE;
672 void NS_ABORT_OOM(size_t aSize) {
673 CrashReporter::AnnotateOOMAllocationSize(aSize);
674 MOZ_CRASH("OOM");