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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/mac/exception_processor.h"
6
7 #import <Foundation/Foundation.h>
8 #include <libunwind.h>
9 #include <objc/objc-exception.h>
10
11 #include <type_traits>
12
13 #include "base/compiler_specific.h"
14 #include "base/debug/crash_logging.h"
15 #include "base/debug/stack_trace.h"
16 #include "base/logging.h"
17 #include "base/macros.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "chrome/common/crash_keys.h"
21
22 namespace chrome {
23
24 // Maximum number of known named exceptions we'll support. There is
25 // no central registration, but I only find about 75 possibilities in
26 // the system frameworks, and many of them are probably not
27 // interesting to track in aggregate (those relating to distributed
28 // objects, for instance).
29 constexpr size_t kKnownNSExceptionCount = 25;
30
31 const size_t kUnknownNSException = kKnownNSExceptionCount;
32
33 size_t BinForException(NSException* exception) {
34 // A list of common known exceptions. The list position will
35 // determine where they live in the histogram, so never move them
36 // around, only add to the end.
37 NSString* const kKnownNSExceptionNames[] = {
38 // Grab-bag exception, not very common. CFArray (or other
39 // container) mutated while being enumerated is one case seen in
40 // production.
41 NSGenericException,
42
43 // Out-of-range on NSString or NSArray. Quite common.
44 NSRangeException,
45
46 // Invalid arg to method, unrecognized selector. Quite common.
47 NSInvalidArgumentException,
48
49 // malloc() returned null in object creation, I think. Turns out
50 // to be very uncommon in production, because of the OOM killer.
51 NSMallocException,
52
53 // This contains things like windowserver errors, trying to draw
54 // views which aren't in windows, unable to read nib files. By
55 // far the most common exception seen on the crash server.
56 NSInternalInconsistencyException,
57 };
58
59 // Make sure our array hasn't outgrown our abilities to track it.
60 static_assert(arraysize(kKnownNSExceptionNames) < kKnownNSExceptionCount,
61 "Cannot track more exceptions");
62
63 NSString* name = [exception name];
64 for (size_t i = 0; i < arraysize(kKnownNSExceptionNames); ++i) {
65 if (name == kKnownNSExceptionNames[i]) {
66 return i;
67 }
68 }
69 return kUnknownNSException;
70 }
71
72 void RecordExceptionWithUma(NSException* exception) {
73 UMA_HISTOGRAM_ENUMERATION("OSX.NSException",
74 BinForException(exception), kUnknownNSException);
75 }
76
77 static objc_exception_preprocessor g_next_preprocessor = nullptr;
78
79 static const char* const kExceptionSinkholes[] = {
80 "CFRunLoopRunSpecific",
81 "_dispatch_client_callout",
82 };
83
84 // This function is used to make it clear to the crash processor that this is
85 // a forced exception crash.
86 static NOINLINE void TERMINATING_FROM_UNCAUGHT_NSEXCEPTION(id exception) {
87 LOG(FATAL) << "Terminating from Objective-C exception: "
88 << base::SysNSStringToUTF8([exception name])
89 << ": " << base::SysNSStringToUTF8([exception reason]);
90 }
91
92 static id ObjcExceptionPreprocessor(id exception) {
93 static bool seen_first_exception = false;
94
95 // Record UMA and crash keys about the exception.
96 RecordExceptionWithUma(exception);
97
98 const char* const kExceptionKey =
99 seen_first_exception ? crash_keys::mac::kLastNSException
100 : crash_keys::mac::kFirstNSException;
101 NSString* value = [NSString stringWithFormat:@"%@ reason %@",
102 [exception name], [exception reason]];
103 base::debug::SetCrashKeyValue(kExceptionKey, base::SysNSStringToUTF8(value));
104
105 const char* const kExceptionTraceKey =
106 seen_first_exception ? crash_keys::mac::kLastNSExceptionTrace
107 : crash_keys::mac::kFirstNSExceptionTrace;
108 // This exception preprocessor runs prior to the one in libobjc, which sets
109 // the -[NSException callStackReturnAddresses].
110 base::debug::SetCrashKeyToStackTrace(kExceptionTraceKey,
111 base::debug::StackTrace());
112
113 seen_first_exception = true;
114
115 //////////////////////////////////////////////////////////////////////////////
116
117 // Unwind the stack looking for any exception handlers. If an exception
118 // handler is encountered, test to see if it is a function known to catch-
119 // and-rethrow as a "top-level" exception handler. Various routines in
120 // Cocoa do this, and it obscures the crashing stack, since the original
121 // throw location is no longer present on the stack (just the re-throw) when
122 // Crashpad captures the crash report.
123 unw_context_t context;
124 unw_getcontext(&context);
125
126 unw_cursor_t cursor;
127 unw_init_local(&cursor, &context);
128
129 while (unw_step(&cursor) > 0) {
130 unw_proc_info_t frame_info;
131 if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) {
132 continue;
133 }
134
135 // This frame has an exception handler.
136 if (frame_info.handler != 0) {
137 char proc_name[64];
138 unw_word_t offset;
139 if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name),
140 &offset) != UNW_ESUCCESS) {
141 continue;
142 }
143
144 // Check if the function is one that is known to obscure (by way of
145 // catch-and-rethrow) exception stack traces. If it is, sinkhole it
146 // by crashing here at the point of throw.
147 for (const auto& sinkhole : kExceptionSinkholes) {
148 if (strcmp(sinkhole, proc_name) == 0) {
149 TERMINATING_FROM_UNCAUGHT_NSEXCEPTION(exception);
150 }
151 }
152
153 break;
154 }
155 }
156
157 // Forward to the next preprocessor.
158 if (g_next_preprocessor)
159 return g_next_preprocessor(exception);
160
161 return exception;
162 }
163
164 void InstallObjcExceptionPreprocessor() {
165 if (g_next_preprocessor)
166 return;
167
168 g_next_preprocessor =
169 objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor);
170 }
171
172 void UninstallObjcExceptionPreprocessor() {
173 objc_setExceptionPreprocessor(g_next_preprocessor);
174 g_next_preprocessor = nullptr;
175 }
176
177 } // namespace chrome
OLDNEW
« 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