OLD | NEW |
| (Empty) |
1 // Copyright 2013 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 "components/crash/app/breakpad_mac.h" | |
6 | |
7 #include <CoreFoundation/CoreFoundation.h> | |
8 #import <Foundation/Foundation.h> | |
9 | |
10 #include "base/auto_reset.h" | |
11 #include "base/base_switches.h" | |
12 #import "base/basictypes.h" | |
13 #include "base/command_line.h" | |
14 #include "base/debug/crash_logging.h" | |
15 #include "base/debug/dump_without_crashing.h" | |
16 #include "base/files/file_path.h" | |
17 #include "base/files/file_util.h" | |
18 #import "base/logging.h" | |
19 #include "base/mac/bundle_locations.h" | |
20 #include "base/mac/foundation_util.h" | |
21 #include "base/mac/mac_util.h" | |
22 #include "base/mac/scoped_cftyperef.h" | |
23 #import "base/mac/scoped_nsautorelease_pool.h" | |
24 #include "base/strings/sys_string_conversions.h" | |
25 #include "base/threading/platform_thread.h" | |
26 #include "base/threading/thread_restrictions.h" | |
27 #import "breakpad/src/client/mac/Framework/Breakpad.h" | |
28 #include "components/crash/app/crash_reporter_client.h" | |
29 | |
30 using crash_reporter::GetCrashReporterClient; | |
31 | |
32 namespace breakpad { | |
33 | |
34 namespace { | |
35 | |
36 BreakpadRef gBreakpadRef = NULL; | |
37 | |
38 void SetCrashKeyValue(NSString* key, NSString* value) { | |
39 // Comment repeated from header to prevent confusion: | |
40 // IMPORTANT: On OS X, the key/value pairs are sent to the crash server | |
41 // out of bounds and not recorded on disk in the minidump, this means | |
42 // that if you look at the minidump file locally you won't see them! | |
43 if (gBreakpadRef == NULL) { | |
44 return; | |
45 } | |
46 | |
47 BreakpadAddUploadParameter(gBreakpadRef, key, value); | |
48 } | |
49 | |
50 void ClearCrashKeyValue(NSString* key) { | |
51 if (gBreakpadRef == NULL) { | |
52 return; | |
53 } | |
54 | |
55 BreakpadRemoveUploadParameter(gBreakpadRef, key); | |
56 } | |
57 | |
58 void SetCrashKeyValueImpl(const base::StringPiece& key, | |
59 const base::StringPiece& value) { | |
60 @autoreleasepool { | |
61 SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()), | |
62 base::SysUTF8ToNSString(value.as_string())); | |
63 } | |
64 } | |
65 | |
66 void ClearCrashKeyValueImpl(const base::StringPiece& key) { | |
67 @autoreleasepool { | |
68 ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string())); | |
69 } | |
70 } | |
71 | |
72 bool FatalMessageHandler(int severity, const char* file, int line, | |
73 size_t message_start, const std::string& str) { | |
74 // Do not handle non-FATAL. | |
75 if (severity != logging::LOG_FATAL) | |
76 return false; | |
77 | |
78 // In case of OOM condition, this code could be reentered when | |
79 // constructing and storing the key. Using a static is not | |
80 // thread-safe, but if multiple threads are in the process of a | |
81 // fatal crash at the same time, this should work. | |
82 static bool guarded = false; | |
83 if (guarded) | |
84 return false; | |
85 | |
86 base::AutoReset<bool> guard(&guarded, true); | |
87 | |
88 // Only log last path component. This matches logging.cc. | |
89 if (file) { | |
90 const char* slash = strrchr(file, '/'); | |
91 if (slash) | |
92 file = slash + 1; | |
93 } | |
94 | |
95 NSString* fatal_key = @"LOG_FATAL"; | |
96 NSString* fatal_value = | |
97 [NSString stringWithFormat:@"%s:%d: %s", | |
98 file, line, str.c_str() + message_start]; | |
99 SetCrashKeyValue(fatal_key, fatal_value); | |
100 | |
101 // Rather than including the code to force the crash here, allow the | |
102 // caller to do it. | |
103 return false; | |
104 } | |
105 | |
106 // BreakpadGenerateAndSendReport() does not report the current | |
107 // thread. This class can be used to spin up a thread to run it. | |
108 class DumpHelper : public base::PlatformThread::Delegate { | |
109 public: | |
110 static void DumpWithoutCrashing() { | |
111 DumpHelper dumper; | |
112 base::PlatformThreadHandle handle; | |
113 if (base::PlatformThread::Create(0, &dumper, &handle)) { | |
114 // The entire point of this is to block so that the correct | |
115 // stack is logged. | |
116 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
117 base::PlatformThread::Join(handle); | |
118 } | |
119 } | |
120 | |
121 private: | |
122 DumpHelper() {} | |
123 | |
124 void ThreadMain() override { | |
125 base::PlatformThread::SetName("CrDumpHelper"); | |
126 BreakpadGenerateAndSendReport(gBreakpadRef); | |
127 } | |
128 | |
129 DISALLOW_COPY_AND_ASSIGN(DumpHelper); | |
130 }; | |
131 | |
132 void SIGABRTHandler(int signal) { | |
133 // The OSX abort() (link below) masks all signals for the process, | |
134 // and all except SIGABRT for the thread. SIGABRT will be masked | |
135 // when the SIGABRT is sent, which means at this point only SIGKILL | |
136 // and SIGSTOP can be delivered. Unmask others so that the code | |
137 // below crashes as desired. | |
138 // | |
139 // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abor
t.c | |
140 sigset_t mask; | |
141 sigemptyset(&mask); | |
142 sigaddset(&mask, signal); | |
143 pthread_sigmask(SIG_SETMASK, &mask, NULL); | |
144 | |
145 // Most interesting operations are not safe in a signal handler, just crash. | |
146 char* volatile death_ptr = NULL; | |
147 *death_ptr = '!'; | |
148 } | |
149 | |
150 } // namespace | |
151 | |
152 bool IsCrashReporterEnabled() { | |
153 return gBreakpadRef != NULL; | |
154 } | |
155 | |
156 // Only called for a branded build of Chrome.app. | |
157 void InitCrashReporter(const std::string& process_type) { | |
158 DCHECK(!gBreakpadRef); | |
159 base::mac::ScopedNSAutoreleasePool autorelease_pool; | |
160 | |
161 // Check whether crash reporting should be enabled. If enterprise | |
162 // configuration management controls crash reporting, it takes precedence. | |
163 // Otherwise, check whether the user has consented to stats and crash | |
164 // reporting. The browser process can make this determination directly. | |
165 // Helper processes may not have access to the disk or to the same data as | |
166 // the browser process, so the browser passes the decision to them on the | |
167 // command line. | |
168 NSBundle* main_bundle = base::mac::FrameworkBundle(); | |
169 bool is_browser = !base::mac::IsBackgroundOnlyProcess(); | |
170 bool enable_breakpad = false; | |
171 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | |
172 | |
173 if (is_browser) { | |
174 // Since the configuration management infrastructure is possibly not | |
175 // initialized when this code runs, read the policy preference directly. | |
176 if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy( | |
177 &enable_breakpad)) { | |
178 // Controlled by the user. The crash reporter may be enabled by | |
179 // preference or through an environment variable, but the kDisableBreakpad | |
180 // switch overrides both. | |
181 enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() || | |
182 GetCrashReporterClient()->IsRunningUnattended(); | |
183 enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad); | |
184 } | |
185 } else { | |
186 // This is a helper process, check the command line switch. | |
187 enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter); | |
188 } | |
189 | |
190 if (!enable_breakpad) { | |
191 VLOG_IF(1, is_browser) << "Breakpad disabled"; | |
192 return; | |
193 } | |
194 | |
195 // Tell Breakpad where crash_inspector and crash_report_sender are. | |
196 NSString* resource_path = [main_bundle resourcePath]; | |
197 NSString *inspector_location = | |
198 [resource_path stringByAppendingPathComponent:@"crash_inspector"]; | |
199 NSString *reporter_bundle_location = | |
200 [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"]; | |
201 NSString *reporter_location = | |
202 [[NSBundle bundleWithPath:reporter_bundle_location] executablePath]; | |
203 | |
204 if (!inspector_location || !reporter_location) { | |
205 VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled"; | |
206 return; | |
207 } | |
208 | |
209 NSDictionary* info_dictionary = [main_bundle infoDictionary]; | |
210 NSMutableDictionary *breakpad_config = | |
211 [[info_dictionary mutableCopy] autorelease]; | |
212 [breakpad_config setObject:inspector_location | |
213 forKey:@BREAKPAD_INSPECTOR_LOCATION]; | |
214 [breakpad_config setObject:reporter_location | |
215 forKey:@BREAKPAD_REPORTER_EXE_LOCATION]; | |
216 | |
217 // In the main application (the browser process), crashes can be passed to | |
218 // the system's Crash Reporter. This allows the system to notify the user | |
219 // when the application crashes, and provide the user with the option to | |
220 // restart it. | |
221 if (is_browser) | |
222 [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT]; | |
223 | |
224 base::FilePath dir_crash_dumps; | |
225 GetCrashReporterClient()->GetCrashDumpLocation(&dir_crash_dumps); | |
226 [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value()) | |
227 forKey:@BREAKPAD_DUMP_DIRECTORY]; | |
228 | |
229 // Temporarily run Breakpad in-process on 10.10 and later because APIs that | |
230 // it depends on got broken (http://crbug.com/386208). | |
231 // This can catch crashes in the browser process only. | |
232 if (is_browser && base::mac::IsOSYosemiteOrLater()) { | |
233 [breakpad_config setObject:[NSNumber numberWithBool:YES] | |
234 forKey:@BREAKPAD_IN_PROCESS]; | |
235 } | |
236 | |
237 // Initialize Breakpad. | |
238 gBreakpadRef = BreakpadCreate(breakpad_config); | |
239 if (!gBreakpadRef) { | |
240 LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed"; | |
241 return; | |
242 } | |
243 | |
244 // Initialize the scoped crash key system. | |
245 base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl, | |
246 &ClearCrashKeyValueImpl); | |
247 GetCrashReporterClient()->RegisterCrashKeys(); | |
248 | |
249 // Set Breakpad metadata values. These values are added to Info.plist during | |
250 // the branded Google Chrome.app build. | |
251 SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]); | |
252 SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]); | |
253 SetCrashKeyValue(@"plat", @"OS X"); | |
254 | |
255 logging::SetLogMessageHandler(&FatalMessageHandler); | |
256 base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing); | |
257 | |
258 // abort() sends SIGABRT, which breakpad does not intercept. | |
259 // Register a signal handler to crash in a way breakpad will | |
260 // intercept. | |
261 struct sigaction sigact; | |
262 memset(&sigact, 0, sizeof(sigact)); | |
263 sigact.sa_handler = SIGABRTHandler; | |
264 CHECK(0 == sigaction(SIGABRT, &sigact, NULL)); | |
265 } | |
266 | |
267 void InitCrashProcessInfo(const std::string& process_type_switch) { | |
268 if (gBreakpadRef == NULL) { | |
269 return; | |
270 } | |
271 | |
272 // Determine the process type. | |
273 NSString* process_type = @"browser"; | |
274 if (!process_type_switch.empty()) { | |
275 process_type = base::SysUTF8ToNSString(process_type_switch); | |
276 } | |
277 | |
278 // Store process type in crash dump. | |
279 SetCrashKeyValue(@"ptype", process_type); | |
280 | |
281 NSString* pid_value = | |
282 [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())]; | |
283 SetCrashKeyValue(@"pid", pid_value); | |
284 } | |
285 | |
286 } // namespace breakpad | |
OLD | NEW |