Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1455)

Unified Diff: chrome/browser/mac/exception_processor.mm

Issue 2543813003: [Mac] Modify the ObjC exception preprocessor to make some exceptions fatal. (Closed)
Patch Set: Address comments Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/mac/exception_processor.h ('k') | chrome/browser/mac/exception_processor_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/mac/exception_processor.mm
diff --git a/chrome/browser/mac/exception_processor.mm b/chrome/browser/mac/exception_processor.mm
new file mode 100644
index 0000000000000000000000000000000000000000..2b0cf21654cfd045eaa664254a21619e8d925d6c
--- /dev/null
+++ b/chrome/browser/mac/exception_processor.mm
@@ -0,0 +1,177 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/mac/exception_processor.h"
+
+#import <Foundation/Foundation.h>
+#include <libunwind.h>
+#include <objc/objc-exception.h>
+
+#include <type_traits>
+
+#include "base/compiler_specific.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/common/crash_keys.h"
+
+namespace chrome {
+
+// Maximum number of known named exceptions we'll support. There is
+// no central registration, but I only find about 75 possibilities in
+// the system frameworks, and many of them are probably not
+// interesting to track in aggregate (those relating to distributed
+// objects, for instance).
+constexpr size_t kKnownNSExceptionCount = 25;
+
+const size_t kUnknownNSException = kKnownNSExceptionCount;
+
+size_t BinForException(NSException* exception) {
+ // A list of common known exceptions. The list position will
+ // determine where they live in the histogram, so never move them
+ // around, only add to the end.
+ NSString* const kKnownNSExceptionNames[] = {
+ // Grab-bag exception, not very common. CFArray (or other
+ // container) mutated while being enumerated is one case seen in
+ // production.
+ NSGenericException,
+
+ // Out-of-range on NSString or NSArray. Quite common.
+ NSRangeException,
+
+ // Invalid arg to method, unrecognized selector. Quite common.
+ NSInvalidArgumentException,
+
+ // malloc() returned null in object creation, I think. Turns out
+ // to be very uncommon in production, because of the OOM killer.
+ NSMallocException,
+
+ // This contains things like windowserver errors, trying to draw
+ // views which aren't in windows, unable to read nib files. By
+ // far the most common exception seen on the crash server.
+ NSInternalInconsistencyException,
+ };
+
+ // Make sure our array hasn't outgrown our abilities to track it.
+ static_assert(arraysize(kKnownNSExceptionNames) < kKnownNSExceptionCount,
+ "Cannot track more exceptions");
+
+ NSString* name = [exception name];
+ for (size_t i = 0; i < arraysize(kKnownNSExceptionNames); ++i) {
+ if (name == kKnownNSExceptionNames[i]) {
+ return i;
+ }
+ }
+ return kUnknownNSException;
+}
+
+void RecordExceptionWithUma(NSException* exception) {
+ UMA_HISTOGRAM_ENUMERATION("OSX.NSException",
+ BinForException(exception), kUnknownNSException);
+}
+
+static objc_exception_preprocessor g_next_preprocessor = nullptr;
+
+static const char* const kExceptionSinkholes[] = {
+ "CFRunLoopRunSpecific",
+ "_dispatch_client_callout",
+};
+
+// This function is used to make it clear to the crash processor that this is
+// a forced exception crash.
+static NOINLINE void TERMINATING_FROM_UNCAUGHT_NSEXCEPTION(id exception) {
+ LOG(FATAL) << "Terminating from Objective-C exception: "
+ << base::SysNSStringToUTF8([exception name])
+ << ": " << base::SysNSStringToUTF8([exception reason]);
+}
+
+static id ObjcExceptionPreprocessor(id exception) {
+ static bool seen_first_exception = false;
+
+ // Record UMA and crash keys about the exception.
+ RecordExceptionWithUma(exception);
+
+ const char* const kExceptionKey =
+ seen_first_exception ? crash_keys::mac::kLastNSException
+ : crash_keys::mac::kFirstNSException;
+ NSString* value = [NSString stringWithFormat:@"%@ reason %@",
+ [exception name], [exception reason]];
+ base::debug::SetCrashKeyValue(kExceptionKey, base::SysNSStringToUTF8(value));
+
+ const char* const kExceptionTraceKey =
+ seen_first_exception ? crash_keys::mac::kLastNSExceptionTrace
+ : crash_keys::mac::kFirstNSExceptionTrace;
+ // This exception preprocessor runs prior to the one in libobjc, which sets
+ // the -[NSException callStackReturnAddresses].
+ base::debug::SetCrashKeyToStackTrace(kExceptionTraceKey,
+ base::debug::StackTrace());
+
+ seen_first_exception = true;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Unwind the stack looking for any exception handlers. If an exception
+ // handler is encountered, test to see if it is a function known to catch-
+ // and-rethrow as a "top-level" exception handler. Various routines in
+ // Cocoa do this, and it obscures the crashing stack, since the original
+ // throw location is no longer present on the stack (just the re-throw) when
+ // Crashpad captures the crash report.
+ unw_context_t context;
+ unw_getcontext(&context);
+
+ unw_cursor_t cursor;
+ unw_init_local(&cursor, &context);
+
+ while (unw_step(&cursor) > 0) {
+ unw_proc_info_t frame_info;
+ if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) {
+ continue;
+ }
+
+ // This frame has an exception handler.
+ if (frame_info.handler != 0) {
+ char proc_name[64];
+ unw_word_t offset;
+ if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name),
+ &offset) != UNW_ESUCCESS) {
+ continue;
+ }
+
+ // Check if the function is one that is known to obscure (by way of
+ // catch-and-rethrow) exception stack traces. If it is, sinkhole it
+ // by crashing here at the point of throw.
+ for (const auto& sinkhole : kExceptionSinkholes) {
+ if (strcmp(sinkhole, proc_name) == 0) {
+ TERMINATING_FROM_UNCAUGHT_NSEXCEPTION(exception);
+ }
+ }
+
+ break;
+ }
+ }
+
+ // Forward to the next preprocessor.
+ if (g_next_preprocessor)
+ return g_next_preprocessor(exception);
+
+ return exception;
+}
+
+void InstallObjcExceptionPreprocessor() {
+ if (g_next_preprocessor)
+ return;
+
+ g_next_preprocessor =
+ objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor);
+}
+
+void UninstallObjcExceptionPreprocessor() {
+ objc_setExceptionPreprocessor(g_next_preprocessor);
+ g_next_preprocessor = nullptr;
+}
+
+} // namespace chrome
« no previous file with comments | « chrome/browser/mac/exception_processor.h ('k') | chrome/browser/mac/exception_processor_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698