|
1 // Copyright (c) 2006, Google Inc. |
|
2 // All rights reserved. |
|
3 // |
|
4 // Redistribution and use in source and binary forms, with or without |
|
5 // modification, are permitted provided that the following conditions are |
|
6 // met: |
|
7 // |
|
8 // * Redistributions of source code must retain the above copyright |
|
9 // notice, this list of conditions and the following disclaimer. |
|
10 // * Redistributions in binary form must reproduce the above |
|
11 // copyright notice, this list of conditions and the following disclaimer |
|
12 // in the documentation and/or other materials provided with the |
|
13 // distribution. |
|
14 // * Neither the name of Google Inc. nor the names of its |
|
15 // contributors may be used to endorse or promote products derived from |
|
16 // this software without specific prior written permission. |
|
17 // |
|
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 |
|
30 #include <mach/exc.h> |
|
31 #include <mach/mig.h> |
|
32 #include <pthread.h> |
|
33 #include <signal.h> |
|
34 #include <TargetConditionals.h> |
|
35 |
|
36 #include <map> |
|
37 |
|
38 #include "client/mac/handler/exception_handler.h" |
|
39 #include "client/mac/handler/minidump_generator.h" |
|
40 #include "common/mac/macho_utilities.h" |
|
41 #include "common/mac/scoped_task_suspend-inl.h" |
|
42 #include "google_breakpad/common/minidump_exception_mac.h" |
|
43 |
|
44 #ifndef __EXCEPTIONS |
|
45 // This file uses C++ try/catch (but shouldn't). Duplicate the macros from |
|
46 // <c++/4.2.1/exception_defines.h> allowing this file to work properly with |
|
47 // exceptions disabled even when other C++ libraries are used. #undef the try |
|
48 // and catch macros first in case libstdc++ is in use and has already provided |
|
49 // its own definitions. |
|
50 #undef try |
|
51 #define try if (true) |
|
52 #undef catch |
|
53 #define catch(X) if (false) |
|
54 #endif // __EXCEPTIONS |
|
55 |
|
56 #ifndef USE_PROTECTED_ALLOCATIONS |
|
57 #if TARGET_OS_IPHONE |
|
58 #define USE_PROTECTED_ALLOCATIONS 1 |
|
59 #else |
|
60 #define USE_PROTECTED_ALLOCATIONS 0 |
|
61 #endif |
|
62 #endif |
|
63 |
|
64 // If USE_PROTECTED_ALLOCATIONS is activated then the |
|
65 // gBreakpadAllocator needs to be setup in other code |
|
66 // ahead of time. Please see ProtectedMemoryAllocator.h |
|
67 // for more details. |
|
68 #if USE_PROTECTED_ALLOCATIONS |
|
69 #include "protected_memory_allocator.h" |
|
70 extern ProtectedMemoryAllocator *gBreakpadAllocator; |
|
71 #endif |
|
72 |
|
73 namespace google_breakpad { |
|
74 |
|
75 static union { |
|
76 #if USE_PROTECTED_ALLOCATIONS |
|
77 char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); |
|
78 #endif |
|
79 google_breakpad::ExceptionHandler *handler; |
|
80 } gProtectedData; |
|
81 |
|
82 using std::map; |
|
83 |
|
84 // These structures and techniques are illustrated in |
|
85 // Mac OS X Internals, Amit Singh, ch 9.7 |
|
86 struct ExceptionMessage { |
|
87 mach_msg_header_t header; |
|
88 mach_msg_body_t body; |
|
89 mach_msg_port_descriptor_t thread; |
|
90 mach_msg_port_descriptor_t task; |
|
91 NDR_record_t ndr; |
|
92 exception_type_t exception; |
|
93 mach_msg_type_number_t code_count; |
|
94 integer_t code[EXCEPTION_CODE_MAX]; |
|
95 char padding[512]; |
|
96 }; |
|
97 |
|
98 struct ExceptionParameters { |
|
99 ExceptionParameters() : count(0) {} |
|
100 mach_msg_type_number_t count; |
|
101 exception_mask_t masks[EXC_TYPES_COUNT]; |
|
102 mach_port_t ports[EXC_TYPES_COUNT]; |
|
103 exception_behavior_t behaviors[EXC_TYPES_COUNT]; |
|
104 thread_state_flavor_t flavors[EXC_TYPES_COUNT]; |
|
105 }; |
|
106 |
|
107 struct ExceptionReplyMessage { |
|
108 mach_msg_header_t header; |
|
109 NDR_record_t ndr; |
|
110 kern_return_t return_code; |
|
111 }; |
|
112 |
|
113 // Only catch these three exceptions. The other ones are nebulously defined |
|
114 // and may result in treating a non-fatal exception as fatal. |
|
115 exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS | |
|
116 EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; |
|
117 |
|
118 #if !TARGET_OS_IPHONE |
|
119 extern "C" { |
|
120 // Forward declarations for functions that need "C" style compilation |
|
121 boolean_t exc_server(mach_msg_header_t* request, |
|
122 mach_msg_header_t* reply); |
|
123 |
|
124 // This symbol must be visible to dlsym() - see |
|
125 // http://code.google.com/p/google-breakpad/issues/detail?id=345 for details. |
|
126 kern_return_t catch_exception_raise(mach_port_t target_port, |
|
127 mach_port_t failed_thread, |
|
128 mach_port_t task, |
|
129 exception_type_t exception, |
|
130 exception_data_t code, |
|
131 mach_msg_type_number_t code_count) |
|
132 __attribute__((visibility("default"))); |
|
133 } |
|
134 #endif |
|
135 |
|
136 kern_return_t ForwardException(mach_port_t task, |
|
137 mach_port_t failed_thread, |
|
138 exception_type_t exception, |
|
139 exception_data_t code, |
|
140 mach_msg_type_number_t code_count); |
|
141 |
|
142 #if TARGET_OS_IPHONE |
|
143 // Implementation is based on the implementation generated by mig. |
|
144 boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP, |
|
145 mach_msg_header_t* OutHeadP) { |
|
146 OutHeadP->msgh_bits = |
|
147 MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0); |
|
148 OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port; |
|
149 /* Minimal size: routine() will update it if different */ |
|
150 OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); |
|
151 OutHeadP->msgh_local_port = MACH_PORT_NULL; |
|
152 OutHeadP->msgh_id = InHeadP->msgh_id + 100; |
|
153 |
|
154 if (InHeadP->msgh_id != 2401) { |
|
155 ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record; |
|
156 ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID; |
|
157 return FALSE; |
|
158 } |
|
159 |
|
160 #ifdef __MigPackStructs |
|
161 #pragma pack(4) |
|
162 #endif |
|
163 typedef struct { |
|
164 mach_msg_header_t Head; |
|
165 /* start of the kernel processed data */ |
|
166 mach_msg_body_t msgh_body; |
|
167 mach_msg_port_descriptor_t thread; |
|
168 mach_msg_port_descriptor_t task; |
|
169 /* end of the kernel processed data */ |
|
170 NDR_record_t NDR; |
|
171 exception_type_t exception; |
|
172 mach_msg_type_number_t codeCnt; |
|
173 integer_t code[2]; |
|
174 mach_msg_trailer_t trailer; |
|
175 } Request; |
|
176 |
|
177 typedef struct { |
|
178 mach_msg_header_t Head; |
|
179 NDR_record_t NDR; |
|
180 kern_return_t RetCode; |
|
181 } Reply; |
|
182 #ifdef __MigPackStructs |
|
183 #pragma pack() |
|
184 #endif |
|
185 |
|
186 Request* In0P = (Request*)InHeadP; |
|
187 Reply* OutP = (Reply*)OutHeadP; |
|
188 |
|
189 if (In0P->task.name != mach_task_self()) { |
|
190 return FALSE; |
|
191 } |
|
192 OutP->RetCode = ForwardException(In0P->task.name, |
|
193 In0P->thread.name, |
|
194 In0P->exception, |
|
195 In0P->code, |
|
196 In0P->codeCnt); |
|
197 OutP->NDR = NDR_record; |
|
198 return TRUE; |
|
199 } |
|
200 #else |
|
201 boolean_t breakpad_exc_server(mach_msg_header_t* request, |
|
202 mach_msg_header_t* reply) { |
|
203 return exc_server(request, reply); |
|
204 } |
|
205 |
|
206 // Callback from exc_server() |
|
207 kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread, |
|
208 mach_port_t task, |
|
209 exception_type_t exception, |
|
210 exception_data_t code, |
|
211 mach_msg_type_number_t code_count) { |
|
212 if (task != mach_task_self()) { |
|
213 return KERN_FAILURE; |
|
214 } |
|
215 return ForwardException(task, failed_thread, exception, code, code_count); |
|
216 } |
|
217 #endif |
|
218 |
|
219 ExceptionHandler::ExceptionHandler(const string &dump_path, |
|
220 FilterCallback filter, |
|
221 MinidumpCallback callback, |
|
222 void* callback_context, |
|
223 bool install_handler, |
|
224 const char* port_name) |
|
225 : dump_path_(), |
|
226 filter_(filter), |
|
227 callback_(callback), |
|
228 callback_context_(callback_context), |
|
229 directCallback_(NULL), |
|
230 handler_thread_(NULL), |
|
231 handler_port_(MACH_PORT_NULL), |
|
232 previous_(NULL), |
|
233 installed_exception_handler_(false), |
|
234 is_in_teardown_(false), |
|
235 last_minidump_write_result_(false), |
|
236 use_minidump_write_mutex_(false) { |
|
237 // This will update to the ID and C-string pointers |
|
238 set_dump_path(dump_path); |
|
239 MinidumpGenerator::GatherSystemInformation(); |
|
240 #if !TARGET_OS_IPHONE |
|
241 if (port_name) |
|
242 crash_generation_client_.reset(new CrashGenerationClient(port_name)); |
|
243 #endif |
|
244 Setup(install_handler); |
|
245 } |
|
246 |
|
247 // special constructor if we want to bypass minidump writing and |
|
248 // simply get a callback with the exception information |
|
249 ExceptionHandler::ExceptionHandler(DirectCallback callback, |
|
250 void* callback_context, |
|
251 bool install_handler) |
|
252 : dump_path_(), |
|
253 filter_(NULL), |
|
254 callback_(NULL), |
|
255 callback_context_(callback_context), |
|
256 directCallback_(callback), |
|
257 handler_thread_(NULL), |
|
258 handler_port_(MACH_PORT_NULL), |
|
259 previous_(NULL), |
|
260 installed_exception_handler_(false), |
|
261 is_in_teardown_(false), |
|
262 last_minidump_write_result_(false), |
|
263 use_minidump_write_mutex_(false) { |
|
264 MinidumpGenerator::GatherSystemInformation(); |
|
265 Setup(install_handler); |
|
266 } |
|
267 |
|
268 ExceptionHandler::~ExceptionHandler() { |
|
269 Teardown(); |
|
270 } |
|
271 |
|
272 bool ExceptionHandler::WriteMinidump(bool write_exception_stream) { |
|
273 // If we're currently writing, just return |
|
274 if (use_minidump_write_mutex_) |
|
275 return false; |
|
276 |
|
277 use_minidump_write_mutex_ = true; |
|
278 last_minidump_write_result_ = false; |
|
279 |
|
280 // Lock the mutex. Since we just created it, this will return immediately. |
|
281 if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { |
|
282 // Send an empty message to the handle port so that a minidump will |
|
283 // be written |
|
284 bool result = SendMessageToHandlerThread(write_exception_stream ? |
|
285 kWriteDumpWithExceptionMessage : |
|
286 kWriteDumpMessage); |
|
287 if (!result) { |
|
288 pthread_mutex_unlock(&minidump_write_mutex_); |
|
289 return false; |
|
290 } |
|
291 |
|
292 // Wait for the minidump writer to complete its writing. It will unlock |
|
293 // the mutex when completed |
|
294 pthread_mutex_lock(&minidump_write_mutex_); |
|
295 } |
|
296 |
|
297 use_minidump_write_mutex_ = false; |
|
298 UpdateNextID(); |
|
299 return last_minidump_write_result_; |
|
300 } |
|
301 |
|
302 // static |
|
303 bool ExceptionHandler::WriteMinidump(const string &dump_path, |
|
304 bool write_exception_stream, |
|
305 MinidumpCallback callback, |
|
306 void* callback_context) { |
|
307 ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, |
|
308 NULL); |
|
309 return handler.WriteMinidump(write_exception_stream); |
|
310 } |
|
311 |
|
312 // static |
|
313 bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, |
|
314 mach_port_t child_blamed_thread, |
|
315 const string &dump_path, |
|
316 MinidumpCallback callback, |
|
317 void* callback_context) { |
|
318 ScopedTaskSuspend suspend(child); |
|
319 |
|
320 MinidumpGenerator generator(child, MACH_PORT_NULL); |
|
321 string dump_id; |
|
322 string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id); |
|
323 |
|
324 generator.SetExceptionInformation(EXC_BREAKPOINT, |
|
325 #if defined(__i386__) || defined(__x86_64__) |
|
326 EXC_I386_BPT, |
|
327 #elif defined(__ppc__) || defined(__ppc64__) |
|
328 EXC_PPC_BREAKPOINT, |
|
329 #elif defined(__arm__) |
|
330 EXC_ARM_BREAKPOINT, |
|
331 #else |
|
332 #error architecture not supported |
|
333 #endif |
|
334 0, |
|
335 child_blamed_thread); |
|
336 bool result = generator.Write(dump_filename.c_str()); |
|
337 |
|
338 if (callback) { |
|
339 return callback(dump_path.c_str(), dump_id.c_str(), |
|
340 callback_context, result); |
|
341 } |
|
342 return result; |
|
343 } |
|
344 |
|
345 bool ExceptionHandler::WriteMinidumpWithException(int exception_type, |
|
346 int exception_code, |
|
347 int exception_subcode, |
|
348 ucontext_t* task_context, |
|
349 mach_port_t thread_name, |
|
350 bool exit_after_write, |
|
351 bool report_current_thread) { |
|
352 bool result = false; |
|
353 |
|
354 if (directCallback_) { |
|
355 if (directCallback_(callback_context_, |
|
356 exception_type, |
|
357 exception_code, |
|
358 exception_subcode, |
|
359 thread_name) ) { |
|
360 if (exit_after_write) |
|
361 _exit(exception_type); |
|
362 } |
|
363 #if !TARGET_OS_IPHONE |
|
364 } else if (IsOutOfProcess()) { |
|
365 if (exception_type && exception_code) { |
|
366 // If this is a real exception, give the filter (if any) a chance to |
|
367 // decide if this should be sent. |
|
368 if (filter_ && !filter_(callback_context_)) |
|
369 return false; |
|
370 result = crash_generation_client_->RequestDumpForException( |
|
371 exception_type, |
|
372 exception_code, |
|
373 exception_subcode, |
|
374 thread_name); |
|
375 if (result && exit_after_write) { |
|
376 _exit(exception_type); |
|
377 } |
|
378 } |
|
379 #endif |
|
380 } else { |
|
381 string minidump_id; |
|
382 |
|
383 // Putting the MinidumpGenerator in its own context will ensure that the |
|
384 // destructor is executed, closing the newly created minidump file. |
|
385 if (!dump_path_.empty()) { |
|
386 MinidumpGenerator md(mach_task_self(), |
|
387 report_current_thread ? MACH_PORT_NULL : |
|
388 mach_thread_self()); |
|
389 md.SetTaskContext(task_context); |
|
390 if (exception_type && exception_code) { |
|
391 // If this is a real exception, give the filter (if any) a chance to |
|
392 // decide if this should be sent. |
|
393 if (filter_ && !filter_(callback_context_)) |
|
394 return false; |
|
395 |
|
396 md.SetExceptionInformation(exception_type, exception_code, |
|
397 exception_subcode, thread_name); |
|
398 } |
|
399 |
|
400 result = md.Write(next_minidump_path_c_); |
|
401 } |
|
402 |
|
403 // Call user specified callback (if any) |
|
404 if (callback_) { |
|
405 // If the user callback returned true and we're handling an exception |
|
406 // (rather than just writing out the file), then we should exit without |
|
407 // forwarding the exception to the next handler. |
|
408 if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, |
|
409 result)) { |
|
410 if (exit_after_write) |
|
411 _exit(exception_type); |
|
412 } |
|
413 } |
|
414 } |
|
415 |
|
416 return result; |
|
417 } |
|
418 |
|
419 kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread, |
|
420 exception_type_t exception, |
|
421 exception_data_t code, |
|
422 mach_msg_type_number_t code_count) { |
|
423 // At this time, we should have called Uninstall() on the exception handler |
|
424 // so that the current exception ports are the ones that we should be |
|
425 // forwarding to. |
|
426 ExceptionParameters current; |
|
427 |
|
428 current.count = EXC_TYPES_COUNT; |
|
429 mach_port_t current_task = mach_task_self(); |
|
430 task_get_exception_ports(current_task, |
|
431 s_exception_mask, |
|
432 current.masks, |
|
433 ¤t.count, |
|
434 current.ports, |
|
435 current.behaviors, |
|
436 current.flavors); |
|
437 |
|
438 // Find the first exception handler that matches the exception |
|
439 unsigned int found; |
|
440 for (found = 0; found < current.count; ++found) { |
|
441 if (current.masks[found] & (1 << exception)) { |
|
442 break; |
|
443 } |
|
444 } |
|
445 |
|
446 // Nothing to forward |
|
447 if (found == current.count) { |
|
448 fprintf(stderr, "** No previous ports for forwarding!! \n"); |
|
449 exit(KERN_FAILURE); |
|
450 } |
|
451 |
|
452 mach_port_t target_port = current.ports[found]; |
|
453 exception_behavior_t target_behavior = current.behaviors[found]; |
|
454 |
|
455 kern_return_t result; |
|
456 switch (target_behavior) { |
|
457 case EXCEPTION_DEFAULT: |
|
458 result = exception_raise(target_port, failed_thread, task, exception, |
|
459 code, code_count); |
|
460 break; |
|
461 |
|
462 default: |
|
463 fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior); |
|
464 result = KERN_FAILURE; |
|
465 break; |
|
466 } |
|
467 |
|
468 return result; |
|
469 } |
|
470 |
|
471 // static |
|
472 void* ExceptionHandler::WaitForMessage(void* exception_handler_class) { |
|
473 ExceptionHandler* self = |
|
474 reinterpret_cast<ExceptionHandler*>(exception_handler_class); |
|
475 ExceptionMessage receive; |
|
476 |
|
477 // Wait for the exception info |
|
478 while (1) { |
|
479 receive.header.msgh_local_port = self->handler_port_; |
|
480 receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive)); |
|
481 kern_return_t result = mach_msg(&(receive.header), |
|
482 MACH_RCV_MSG | MACH_RCV_LARGE, 0, |
|
483 receive.header.msgh_size, |
|
484 self->handler_port_, |
|
485 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
|
486 |
|
487 |
|
488 if (result == KERN_SUCCESS) { |
|
489 // Uninstall our handler so that we don't get in a loop if the process of |
|
490 // writing out a minidump causes an exception. However, if the exception |
|
491 // was caused by a fork'd process, don't uninstall things |
|
492 |
|
493 // If the actual exception code is zero, then we're calling this handler |
|
494 // in a way that indicates that we want to either exit this thread or |
|
495 // generate a minidump |
|
496 // |
|
497 // While reporting, all threads (except this one) must be suspended |
|
498 // to avoid misleading stacks. If appropriate they will be resumed |
|
499 // afterwards. |
|
500 if (!receive.exception) { |
|
501 // Don't touch self, since this message could have been sent |
|
502 // from its destructor. |
|
503 if (receive.header.msgh_id == kShutdownMessage) |
|
504 return NULL; |
|
505 |
|
506 self->SuspendThreads(); |
|
507 |
|
508 #if USE_PROTECTED_ALLOCATIONS |
|
509 if (gBreakpadAllocator) |
|
510 gBreakpadAllocator->Unprotect(); |
|
511 #endif |
|
512 |
|
513 mach_port_t thread = MACH_PORT_NULL; |
|
514 int exception_type = 0; |
|
515 int exception_code = 0; |
|
516 if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) { |
|
517 thread = receive.thread.name; |
|
518 exception_type = EXC_BREAKPOINT; |
|
519 #if defined(__i386__) || defined(__x86_64__) |
|
520 exception_code = EXC_I386_BPT; |
|
521 #elif defined(__ppc__) || defined(__ppc64__) |
|
522 exception_code = EXC_PPC_BREAKPOINT; |
|
523 #elif defined(__arm__) |
|
524 exception_code = EXC_ARM_BREAKPOINT; |
|
525 #else |
|
526 #error architecture not supported |
|
527 #endif |
|
528 } |
|
529 |
|
530 // Write out the dump and save the result for later retrieval |
|
531 self->last_minidump_write_result_ = |
|
532 self->WriteMinidumpWithException(exception_type, exception_code, |
|
533 0, NULL, thread, |
|
534 false, false); |
|
535 |
|
536 #if USE_PROTECTED_ALLOCATIONS |
|
537 if (gBreakpadAllocator) |
|
538 gBreakpadAllocator->Protect(); |
|
539 #endif |
|
540 |
|
541 self->ResumeThreads(); |
|
542 |
|
543 if (self->use_minidump_write_mutex_) |
|
544 pthread_mutex_unlock(&self->minidump_write_mutex_); |
|
545 } else { |
|
546 // When forking a child process with the exception handler installed, |
|
547 // if the child crashes, it will send the exception back to the parent |
|
548 // process. The check for task == self_task() ensures that only |
|
549 // exceptions that occur in the parent process are caught and |
|
550 // processed. If the exception was not caused by this task, we |
|
551 // still need to call into the exception server and have it return |
|
552 // KERN_FAILURE (see catch_exception_raise) in order for the kernel |
|
553 // to move onto the host exception handler for the child task |
|
554 if (receive.task.name == mach_task_self()) { |
|
555 self->SuspendThreads(); |
|
556 |
|
557 #if USE_PROTECTED_ALLOCATIONS |
|
558 if (gBreakpadAllocator) |
|
559 gBreakpadAllocator->Unprotect(); |
|
560 #endif |
|
561 |
|
562 int subcode = 0; |
|
563 if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1) |
|
564 subcode = receive.code[1]; |
|
565 |
|
566 // Generate the minidump with the exception data. |
|
567 self->WriteMinidumpWithException(receive.exception, receive.code[0], |
|
568 subcode, NULL, receive.thread.name, |
|
569 true, false); |
|
570 |
|
571 #if USE_PROTECTED_ALLOCATIONS |
|
572 // This may have become protected again within |
|
573 // WriteMinidumpWithException, but it needs to be unprotected for |
|
574 // UninstallHandler. |
|
575 if (gBreakpadAllocator) |
|
576 gBreakpadAllocator->Unprotect(); |
|
577 #endif |
|
578 |
|
579 self->UninstallHandler(true); |
|
580 |
|
581 #if USE_PROTECTED_ALLOCATIONS |
|
582 if (gBreakpadAllocator) |
|
583 gBreakpadAllocator->Protect(); |
|
584 #endif |
|
585 } |
|
586 // Pass along the exception to the server, which will setup the |
|
587 // message and call catch_exception_raise() and put the return |
|
588 // code into the reply. |
|
589 ExceptionReplyMessage reply; |
|
590 if (!breakpad_exc_server(&receive.header, &reply.header)) |
|
591 exit(1); |
|
592 |
|
593 // Send a reply and exit |
|
594 mach_msg(&(reply.header), MACH_SEND_MSG, |
|
595 reply.header.msgh_size, 0, MACH_PORT_NULL, |
|
596 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
|
597 } |
|
598 } |
|
599 } |
|
600 |
|
601 return NULL; |
|
602 } |
|
603 |
|
604 // static |
|
605 void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { |
|
606 #if USE_PROTECTED_ALLOCATIONS |
|
607 if (gBreakpadAllocator) |
|
608 gBreakpadAllocator->Unprotect(); |
|
609 #endif |
|
610 gProtectedData.handler->WriteMinidumpWithException( |
|
611 EXC_SOFTWARE, |
|
612 MD_EXCEPTION_CODE_MAC_ABORT, |
|
613 0, |
|
614 static_cast<ucontext_t*>(uc), |
|
615 mach_thread_self(), |
|
616 true, |
|
617 true); |
|
618 #if USE_PROTECTED_ALLOCATIONS |
|
619 if (gBreakpadAllocator) |
|
620 gBreakpadAllocator->Protect(); |
|
621 #endif |
|
622 } |
|
623 |
|
624 bool ExceptionHandler::InstallHandler() { |
|
625 // If a handler is already installed, something is really wrong. |
|
626 if (gProtectedData.handler != NULL) { |
|
627 return false; |
|
628 } |
|
629 if (!IsOutOfProcess()) { |
|
630 struct sigaction sa; |
|
631 memset(&sa, 0, sizeof(sa)); |
|
632 sigemptyset(&sa.sa_mask); |
|
633 sigaddset(&sa.sa_mask, SIGABRT); |
|
634 sa.sa_sigaction = ExceptionHandler::SignalHandler; |
|
635 sa.sa_flags = SA_SIGINFO; |
|
636 |
|
637 scoped_ptr<struct sigaction> old(new struct sigaction); |
|
638 if (sigaction(SIGABRT, &sa, old.get()) == -1) { |
|
639 return false; |
|
640 } |
|
641 old_handler_.swap(old); |
|
642 gProtectedData.handler = this; |
|
643 #if USE_PROTECTED_ALLOCATIONS |
|
644 assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0); |
|
645 mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ); |
|
646 #endif |
|
647 } |
|
648 |
|
649 try { |
|
650 #if USE_PROTECTED_ALLOCATIONS |
|
651 previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) ) |
|
652 ExceptionParameters(); |
|
653 #else |
|
654 previous_ = new ExceptionParameters(); |
|
655 #endif |
|
656 } |
|
657 catch (std::bad_alloc) { |
|
658 return false; |
|
659 } |
|
660 |
|
661 // Save the current exception ports so that we can forward to them |
|
662 previous_->count = EXC_TYPES_COUNT; |
|
663 mach_port_t current_task = mach_task_self(); |
|
664 kern_return_t result = task_get_exception_ports(current_task, |
|
665 s_exception_mask, |
|
666 previous_->masks, |
|
667 &previous_->count, |
|
668 previous_->ports, |
|
669 previous_->behaviors, |
|
670 previous_->flavors); |
|
671 |
|
672 // Setup the exception ports on this task |
|
673 if (result == KERN_SUCCESS) |
|
674 result = task_set_exception_ports(current_task, s_exception_mask, |
|
675 handler_port_, EXCEPTION_DEFAULT, |
|
676 THREAD_STATE_NONE); |
|
677 |
|
678 installed_exception_handler_ = (result == KERN_SUCCESS); |
|
679 |
|
680 return installed_exception_handler_; |
|
681 } |
|
682 |
|
683 bool ExceptionHandler::UninstallHandler(bool in_exception) { |
|
684 kern_return_t result = KERN_SUCCESS; |
|
685 |
|
686 if (old_handler_.get()) { |
|
687 sigaction(SIGABRT, old_handler_.get(), NULL); |
|
688 #if USE_PROTECTED_ALLOCATIONS |
|
689 mprotect(gProtectedData.protected_buffer, PAGE_SIZE, |
|
690 PROT_READ | PROT_WRITE); |
|
691 #endif |
|
692 old_handler_.reset(); |
|
693 gProtectedData.handler = NULL; |
|
694 } |
|
695 |
|
696 if (installed_exception_handler_) { |
|
697 mach_port_t current_task = mach_task_self(); |
|
698 |
|
699 // Restore the previous ports |
|
700 for (unsigned int i = 0; i < previous_->count; ++i) { |
|
701 result = task_set_exception_ports(current_task, previous_->masks[i], |
|
702 previous_->ports[i], |
|
703 previous_->behaviors[i], |
|
704 previous_->flavors[i]); |
|
705 if (result != KERN_SUCCESS) |
|
706 return false; |
|
707 } |
|
708 |
|
709 // this delete should NOT happen if an exception just occurred! |
|
710 if (!in_exception) { |
|
711 #if USE_PROTECTED_ALLOCATIONS |
|
712 previous_->~ExceptionParameters(); |
|
713 #else |
|
714 delete previous_; |
|
715 #endif |
|
716 } |
|
717 |
|
718 previous_ = NULL; |
|
719 installed_exception_handler_ = false; |
|
720 } |
|
721 |
|
722 return result == KERN_SUCCESS; |
|
723 } |
|
724 |
|
725 bool ExceptionHandler::Setup(bool install_handler) { |
|
726 if (pthread_mutex_init(&minidump_write_mutex_, NULL)) |
|
727 return false; |
|
728 |
|
729 // Create a receive right |
|
730 mach_port_t current_task = mach_task_self(); |
|
731 kern_return_t result = mach_port_allocate(current_task, |
|
732 MACH_PORT_RIGHT_RECEIVE, |
|
733 &handler_port_); |
|
734 // Add send right |
|
735 if (result == KERN_SUCCESS) |
|
736 result = mach_port_insert_right(current_task, handler_port_, handler_port_, |
|
737 MACH_MSG_TYPE_MAKE_SEND); |
|
738 |
|
739 if (install_handler && result == KERN_SUCCESS) |
|
740 if (!InstallHandler()) |
|
741 return false; |
|
742 |
|
743 if (result == KERN_SUCCESS) { |
|
744 // Install the handler in its own thread, detached as we won't be joining. |
|
745 pthread_attr_t attr; |
|
746 pthread_attr_init(&attr); |
|
747 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
|
748 int thread_create_result = pthread_create(&handler_thread_, &attr, |
|
749 &WaitForMessage, this); |
|
750 pthread_attr_destroy(&attr); |
|
751 result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS; |
|
752 } |
|
753 |
|
754 return result == KERN_SUCCESS; |
|
755 } |
|
756 |
|
757 bool ExceptionHandler::Teardown() { |
|
758 kern_return_t result = KERN_SUCCESS; |
|
759 is_in_teardown_ = true; |
|
760 |
|
761 if (!UninstallHandler(false)) |
|
762 return false; |
|
763 |
|
764 // Send an empty message so that the handler_thread exits |
|
765 if (SendMessageToHandlerThread(kShutdownMessage)) { |
|
766 mach_port_t current_task = mach_task_self(); |
|
767 result = mach_port_deallocate(current_task, handler_port_); |
|
768 if (result != KERN_SUCCESS) |
|
769 return false; |
|
770 } else { |
|
771 return false; |
|
772 } |
|
773 |
|
774 handler_thread_ = NULL; |
|
775 handler_port_ = MACH_PORT_NULL; |
|
776 pthread_mutex_destroy(&minidump_write_mutex_); |
|
777 |
|
778 return result == KERN_SUCCESS; |
|
779 } |
|
780 |
|
781 bool ExceptionHandler::SendMessageToHandlerThread( |
|
782 HandlerThreadMessage message_id) { |
|
783 ExceptionMessage msg; |
|
784 memset(&msg, 0, sizeof(msg)); |
|
785 msg.header.msgh_id = message_id; |
|
786 if (message_id == kWriteDumpMessage || |
|
787 message_id == kWriteDumpWithExceptionMessage) { |
|
788 // Include this thread's port. |
|
789 msg.thread.name = mach_thread_self(); |
|
790 msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND; |
|
791 msg.thread.type = MACH_MSG_PORT_DESCRIPTOR; |
|
792 } |
|
793 msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding); |
|
794 msg.header.msgh_remote_port = handler_port_; |
|
795 msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, |
|
796 MACH_MSG_TYPE_MAKE_SEND_ONCE); |
|
797 kern_return_t result = mach_msg(&(msg.header), |
|
798 MACH_SEND_MSG | MACH_SEND_TIMEOUT, |
|
799 msg.header.msgh_size, 0, 0, |
|
800 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
|
801 |
|
802 return result == KERN_SUCCESS; |
|
803 } |
|
804 |
|
805 void ExceptionHandler::UpdateNextID() { |
|
806 next_minidump_path_ = |
|
807 (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_)); |
|
808 |
|
809 next_minidump_path_c_ = next_minidump_path_.c_str(); |
|
810 next_minidump_id_c_ = next_minidump_id_.c_str(); |
|
811 } |
|
812 |
|
813 bool ExceptionHandler::SuspendThreads() { |
|
814 thread_act_port_array_t threads_for_task; |
|
815 mach_msg_type_number_t thread_count; |
|
816 |
|
817 if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) |
|
818 return false; |
|
819 |
|
820 // suspend all of the threads except for this one |
|
821 for (unsigned int i = 0; i < thread_count; ++i) { |
|
822 if (threads_for_task[i] != mach_thread_self()) { |
|
823 if (thread_suspend(threads_for_task[i])) |
|
824 return false; |
|
825 } |
|
826 } |
|
827 |
|
828 return true; |
|
829 } |
|
830 |
|
831 bool ExceptionHandler::ResumeThreads() { |
|
832 thread_act_port_array_t threads_for_task; |
|
833 mach_msg_type_number_t thread_count; |
|
834 |
|
835 if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) |
|
836 return false; |
|
837 |
|
838 // resume all of the threads except for this one |
|
839 for (unsigned int i = 0; i < thread_count; ++i) { |
|
840 if (threads_for_task[i] != mach_thread_self()) { |
|
841 if (thread_resume(threads_for_task[i])) |
|
842 return false; |
|
843 } |
|
844 } |
|
845 |
|
846 return true; |
|
847 } |
|
848 |
|
849 } // namespace google_breakpad |