| 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
|
|
|