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..8810d833581272545a76e8d08a3bd7625157447b |
--- /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 "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). |
+const size_t kKnownNSExceptionCount = 25; |
Mark Mentovai
2016/12/01 19:02:33
Can constexpr these two also.
Robert Sesek
2016/12/06 23:18:09
Did this one, but not the latter (per comment in o
|
+ |
+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. |
+ static NSString* const kKnownNSExceptionNames[] = { |
Mark Mentovai
2016/12/01 19:02:33
static doesn’t contribute to anything here.
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ // 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, |
+ |
+ nil |
Mark Mentovai
2016/12/01 19:02:33
Instead of a nil-terminated list, just use arraysi
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ }; |
+ |
+ // Make sure our array hasn't outgrown our abilities to track it. |
+ DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount); |
Mark Mentovai
2016/12/01 19:02:33
constexpr benefit: static_cast instead of DCHECK!
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ |
+ NSString* name = [exception name]; |
+ for (int i = 0; kKnownNSExceptionNames[i]; ++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* kExceptionSinkholes[] = { |
Mark Mentovai
2016/12/01 19:02:33
const the pointers too.
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ "CFRunLoopRunSpecific", |
+ "_dispatch_client_callout", |
+}; |
+ |
+id ObjcExceptionPreprocessor(id exception) { |
Mark Mentovai
2016/12/01 19:02:32
Should be static.
Or introduce an unnamed namespa
Robert Sesek
2016/12/06 23:18:09
Made static because the anon namespace adds extra
|
+ 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 |
Mark Mentovai
2016/12/01 19:02:33
Oh good, new stuff!
|
+ // 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); |
+ |
+ for (;;) { |
+ int result = unw_step(&cursor); |
Mark Mentovai
2016/12/01 19:02:33
Why not fold this into the loop condition?: while
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ if (result <= 0) { |
+ break; |
+ } |
+ |
+ 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 name_len; |
Mark Mentovai
2016/12/01 19:02:33
This is not name_len, it’s the offset of unw_get_r
Robert Sesek
2016/12/06 23:18:09
Oops, done.
|
+ if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name), |
+ &name_len) != 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 at the point of throw. |
Mark Mentovai
2016/12/01 19:02:33
by crashing here at the point of throw
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ for (const auto& sinkhole : kExceptionSinkholes) { |
+ if (strcmp(sinkhole, proc_name) == 0) { |
+ LOG(FATAL) << "Terminating from Objective-C exception: " |
Mark Mentovai
2016/12/01 19:02:33
It might be neat if we could do something to get t
Robert Sesek
2016/12/06 23:18:09
I considered this but it may make things less clea
|
+ << base::SysNSStringToUTF8([exception name]) |
+ << ": " << base::SysNSStringToUTF8([exception reason]); |
+ } |
+ } |
+ |
+ 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() { |
+ if (!g_next_preprocessor) |
Mark Mentovai
2016/12/01 19:02:33
If there was no previous preprocessor, this won’t
Robert Sesek
2016/12/06 23:18:09
Done.
|
+ return; |
+ |
+ objc_setExceptionPreprocessor(g_next_preprocessor); |
+ g_next_preprocessor = nullptr; |
+} |
+ |
+} // namespace chrome |