exception_handler.cc revision 983264848d5372d8e64d62eb67f672c71e4b6470
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 <map>
31#include <pthread.h>
32
33#include "client/mac/handler/exception_handler.h"
34#include "client/mac/handler/minidump_generator.h"
35
36namespace google_airbag {
37
38using std::map;
39
40// These structures and techniques are illustrated in
41// Mac OS X Internals, Amit Singh, ch 9.7
42struct ExceptionMessage {
43  mach_msg_header_t           header;
44  mach_msg_body_t             body;
45  mach_msg_port_descriptor_t  thread;
46  mach_msg_port_descriptor_t  task;
47  NDR_record_t                ndr;
48  exception_type_t            exception;
49  mach_msg_type_number_t      code_count;
50  integer_t                   code[EXCEPTION_CODE_MAX];
51  char                        padding[512];
52};
53
54struct ExceptionParameters {
55  ExceptionParameters() : count(0) {}
56  mach_msg_type_number_t count;
57  exception_mask_t masks[EXC_TYPES_COUNT];
58  mach_port_t ports[EXC_TYPES_COUNT];
59  exception_behavior_t behaviors[EXC_TYPES_COUNT];
60  thread_state_flavor_t flavors[EXC_TYPES_COUNT];
61};
62
63struct ExceptionReplyMessage {
64  mach_msg_header_t  header;
65  NDR_record_t       ndr;
66  kern_return_t      return_code;
67};
68
69// Only catch these three exceptions.  The other ones are nebulously defined
70// and may result in treating a non-fatal exception as fatal.
71exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
72EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC;
73
74extern "C"
75{
76  // Forward declarations for functions that need "C" style compilation
77  boolean_t exc_server(mach_msg_header_t *request,
78                       mach_msg_header_t *reply);
79
80  kern_return_t catch_exception_raise(mach_port_t target_port,
81                                      mach_port_t failed_thread,
82                                      mach_port_t task,
83                                      exception_type_t exception,
84                                      exception_data_t code,
85                                      mach_msg_type_number_t code_count);
86
87  kern_return_t ForwardException(mach_port_t task,
88                                 mach_port_t failed_thread,
89                                 exception_type_t exception,
90                                 exception_data_t code,
91                                 mach_msg_type_number_t code_count);
92
93  kern_return_t exception_raise(mach_port_t target_port,
94                                mach_port_t failed_thread,
95                                mach_port_t task,
96                                exception_type_t exception,
97                                exception_data_t exception_code,
98                                mach_msg_type_number_t exception_code_count);
99
100  kern_return_t
101    exception_raise_state(mach_port_t target_port,
102                          mach_port_t failed_thread,
103                          mach_port_t task,
104                          exception_type_t exception,
105                          exception_data_t exception_code,
106                          mach_msg_type_number_t code_count,
107                          thread_state_flavor_t *target_flavor,
108                          thread_state_t thread_state,
109                          mach_msg_type_number_t thread_state_count,
110                          thread_state_t thread_state,
111                          mach_msg_type_number_t *thread_state_count);
112
113  kern_return_t
114    exception_raise_state_identity(mach_port_t target_port,
115                                   mach_port_t failed_thread,
116                                   mach_port_t task,
117                                   exception_type_t exception,
118                                   exception_data_t exception_code,
119                                   mach_msg_type_number_t exception_code_count,
120                                   thread_state_flavor_t *target_flavor,
121                                   thread_state_t thread_state,
122                                   mach_msg_type_number_t thread_state_count,
123                                   thread_state_t thread_state,
124                                   mach_msg_type_number_t *thread_state_count);
125}
126
127ExceptionHandler::ExceptionHandler(const string &dump_path,
128                                   FilterCallback filter,
129                                   MinidumpCallback callback,
130                                   void *callback_context,
131                                   bool install_handler)
132    : dump_path_(),
133      filter_(filter),
134      callback_(callback),
135      callback_context_(callback_context),
136      handler_thread_(NULL),
137      handler_port_(0),
138      previous_(NULL),
139      installed_exception_handler_(false),
140      is_in_teardown_(false),
141      last_minidump_write_result_(false),
142      use_minidump_write_mutex_(false) {
143  // This will update to the ID and C-string pointers
144  set_dump_path(dump_path);
145  MinidumpGenerator::GatherSystemInformation();
146  Setup(install_handler);
147}
148
149ExceptionHandler::~ExceptionHandler() {
150  Teardown();
151}
152
153bool ExceptionHandler::WriteMinidump() {
154  // If we're currently writing, just return
155  if (use_minidump_write_mutex_)
156    return false;
157
158  use_minidump_write_mutex_ = true;
159  last_minidump_write_result_ = false;
160
161  // Lock the mutex.  Since we just created it, this will return immediately.
162  if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
163    // Send an empty message to the handle port so that a minidump will
164    // be written
165    SendEmptyMachMessage();
166
167    // Wait for the minidump writer to complete its writing.  It will unlock
168    // the mutex when completed
169    pthread_mutex_lock(&minidump_write_mutex_);
170  }
171
172  use_minidump_write_mutex_ = false;
173  UpdateNextID();
174  return last_minidump_write_result_;
175}
176
177// static
178bool ExceptionHandler::WriteMinidump(const string &dump_path,
179                                     MinidumpCallback callback,
180                                     void *callback_context) {
181  ExceptionHandler handler(dump_path, NULL, callback, callback_context, false);
182  return handler.WriteMinidump();
183}
184
185bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
186                                                  int exception_code,
187                                                  mach_port_t thread_name) {
188  bool result = false;
189  string minidump_id;
190
191  // Putting the MinidumpGenerator in its own context will ensure that the
192  // destructor is executed, closing the newly created minidump file.
193  if (!dump_path_.empty()) {
194    MinidumpGenerator md;
195    if (exception_type && exception_code) {
196      // If this is a real exception, give the filter (if any) a chance to
197      // decided if this should be sent
198      if (filter_ && !filter_(callback_context_))
199        return false;
200
201      md.SetExceptionInformation(exception_type, exception_code, thread_name);
202    }
203
204    result = md.Write(next_minidump_path_c_);
205  }
206
207  // Call user specified callback (if any)
208  if (callback_) {
209    // If the user callback returned true and we're handling an exception
210    // (rather than just writing out the file), then we should exit without
211    // forwarding the exception to the next handler.
212    if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
213                  result)) {
214      if (exception_type && exception_code)
215        exit(exception_type);
216    }
217  }
218
219  return result;
220}
221
222kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
223                               exception_type_t exception,
224                               exception_data_t code,
225                               mach_msg_type_number_t code_count) {
226  // At this time, we should have called Uninstall() on the exception handler
227  // so that the current exception ports are the ones that we should be
228  // forwarding to.
229  ExceptionParameters current;
230
231  current.count = EXC_TYPES_COUNT;
232  mach_port_t current_task = mach_task_self();
233  kern_return_t result = task_get_exception_ports(current_task,
234                                                  s_exception_mask,
235                                                  current.masks,
236                                                  &current.count,
237                                                  current.ports,
238                                                  current.behaviors,
239                                                  current.flavors);
240
241  // Find the first exception handler that matches the exception
242  unsigned int found;
243  for (found = 0; found < current.count; ++found) {
244    if (current.masks[found] & (1 << exception)) {
245      break;
246    }
247  }
248
249  // Nothing to forward
250  if (found == current.count) {
251    fprintf(stderr, "** No previous ports for forwarding!! \n");
252    exit(KERN_FAILURE);
253  }
254
255  mach_port_t target_port = current.ports[found];
256  exception_behavior_t target_behavior = current.behaviors[found];
257  thread_state_flavor_t target_flavor = current.flavors[found];
258
259  mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX;
260  thread_state_data_t thread_state;
261  switch (target_behavior) {
262    case EXCEPTION_DEFAULT:
263      result = exception_raise(target_port, failed_thread, task, exception,
264                               code, code_count);
265      break;
266
267    case EXCEPTION_STATE:
268      result = thread_get_state(failed_thread, target_flavor, thread_state,
269                                &thread_state_count);
270      if (result == KERN_SUCCESS)
271        result = exception_raise_state(target_port, failed_thread, task,
272                                       exception, code,
273                                       code_count, &target_flavor,
274                                       thread_state, thread_state_count,
275                                       thread_state, &thread_state_count);
276      if (result == KERN_SUCCESS)
277        result = thread_set_state(failed_thread, target_flavor, thread_state,
278                                  thread_state_count);
279      break;
280
281    case EXCEPTION_STATE_IDENTITY:
282      result = thread_get_state(failed_thread, target_flavor, thread_state,
283                                &thread_state_count);
284      if (result == KERN_SUCCESS)
285        result = exception_raise_state_identity(target_port, failed_thread,
286                                                task, exception, code,
287                                                code_count, &target_flavor,
288                                                thread_state,
289                                                thread_state_count,
290                                                thread_state,
291                                                &thread_state_count);
292      if (result == KERN_SUCCESS)
293        result = thread_set_state(failed_thread, target_flavor, thread_state,
294                                  thread_state_count);
295      break;
296
297    default:
298      fprintf(stderr, "** Unknown exception behavior\n");
299      result = KERN_FAILURE;
300      break;
301  }
302
303  return result;
304}
305
306// Callback from exc_server()
307kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
308                                    mach_port_t task,
309                                    exception_type_t exception,
310                                    exception_data_t code,
311                                    mach_msg_type_number_t code_count) {
312  return ForwardException(task, failed_thread, exception, code, code_count);
313}
314
315// static
316void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
317  ExceptionHandler *self =
318    reinterpret_cast<ExceptionHandler *>(exception_handler_class);
319  ExceptionMessage receive;
320
321  // Wait for the exception info
322  while (1) {
323    receive.header.msgh_local_port = self->handler_port_;
324    receive.header.msgh_size = sizeof(receive);
325    kern_return_t result = mach_msg(&(receive.header),
326                                    MACH_RCV_MSG | MACH_RCV_LARGE, 0,
327                                    sizeof(receive), self->handler_port_,
328                                    MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
329
330    if (result == KERN_SUCCESS) {
331      // Uninstall our handler so that we don't get in a loop if the process of
332      // writing out a minidump causes an exception.  However, if the exception
333      // was caused by a fork'd process, don't uninstall things
334      if (receive.task.name == mach_task_self())
335        self->UninstallHandler();
336
337      // If the actual exception code is zero, then we're calling this handler
338      // in a way that indicates that we want to either exit this thread or
339      // generate a minidump
340      if (!receive.exception) {
341        if (self->is_in_teardown_)
342          return NULL;
343
344        // Write out the dump and save the result for later retrieval
345        self->last_minidump_write_result_ =
346          self->WriteMinidumpWithException(0, 0, 0);
347        if (self->use_minidump_write_mutex_)
348          pthread_mutex_unlock(&self->minidump_write_mutex_);
349      } else {
350        // When forking a child process with the exception handler installed,
351        // if the child crashes, it will send the exception back to the parent
352        // process.  The check for task == self_task() ensures that only
353        // exceptions that occur in the parent process are caught and
354        // processed.
355        if (receive.task.name == mach_task_self()) {
356
357          // Generate the minidump with the exception data.
358          self->WriteMinidumpWithException(receive.exception, receive.code[0],
359                                           receive.thread.name);
360
361          // Pass along the exception to the server, which will setup the
362          // message and call catch_exception_raise() and put the KERN_SUCCESS
363          // into the reply.
364          ExceptionReplyMessage reply;
365          if (!exc_server(&receive.header, &reply.header))
366            exit(1);
367
368          // Send a reply and exit
369          result = mach_msg(&(reply.header), MACH_SEND_MSG,
370                            reply.header.msgh_size, 0, MACH_PORT_NULL,
371                            MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
372        } else {
373          // An exception occurred in a child process
374        }
375      }
376    }
377  }
378
379  return NULL;
380}
381
382bool ExceptionHandler::InstallHandler() {
383  try {
384    previous_ = new ExceptionParameters();
385  }
386  catch (std::bad_alloc) {
387    return false;
388  }
389
390  // Save the current exception ports so that we can forward to them
391  previous_->count = EXC_TYPES_COUNT;
392  mach_port_t current_task = mach_task_self();
393  kern_return_t result = task_get_exception_ports(current_task,
394                                                  s_exception_mask,
395                                                  previous_->masks,
396                                                  &previous_->count,
397                                                  previous_->ports,
398                                                  previous_->behaviors,
399                                                  previous_->flavors);
400
401  // Setup the exception ports on this task
402  if (result == KERN_SUCCESS)
403    result = task_set_exception_ports(current_task, s_exception_mask,
404                                      handler_port_, EXCEPTION_DEFAULT,
405                                      THREAD_STATE_NONE);
406
407  installed_exception_handler_ = (result == KERN_SUCCESS);
408
409  return installed_exception_handler_;
410}
411
412bool ExceptionHandler::UninstallHandler() {
413  kern_return_t result = KERN_SUCCESS;
414
415  if (installed_exception_handler_) {
416    mach_port_t current_task = mach_task_self();
417
418    // Restore the previous ports
419    for (unsigned int i = 0; i < previous_->count; ++i) {
420       result = task_set_exception_ports(current_task, previous_->masks[i],
421                                        previous_->ports[i],
422                                        previous_->behaviors[i],
423                                        previous_->flavors[i]);
424      if (result != KERN_SUCCESS)
425        return false;
426    }
427
428    delete previous_;
429    previous_ = NULL;
430    installed_exception_handler_ = false;
431  }
432
433  return result == KERN_SUCCESS;
434}
435
436bool ExceptionHandler::Setup(bool install_handler) {
437  if (pthread_mutex_init(&minidump_write_mutex_, NULL))
438    return false;
439
440  // Create a receive right
441  mach_port_t current_task = mach_task_self();
442  kern_return_t result = mach_port_allocate(current_task,
443                                            MACH_PORT_RIGHT_RECEIVE,
444                                            &handler_port_);
445  // Add send right
446  if (result == KERN_SUCCESS)
447    result = mach_port_insert_right(current_task, handler_port_, handler_port_,
448                                    MACH_MSG_TYPE_MAKE_SEND);
449
450  if (install_handler && result == KERN_SUCCESS)
451    if (!InstallHandler())
452      return false;
453
454  if (result == KERN_SUCCESS) {
455    // Install the handler in its own thread, detached as we won't be joining.
456    pthread_attr_t attr;
457    pthread_attr_init(&attr);
458    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
459    int thread_create_result = pthread_create(&handler_thread_, &attr,
460                                              &WaitForMessage, this);
461    pthread_attr_destroy(&attr);
462    result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
463  }
464
465  return result == KERN_SUCCESS ? true : false;
466}
467
468bool ExceptionHandler::Teardown() {
469  kern_return_t result = KERN_SUCCESS;
470  is_in_teardown_ = true;
471
472  if (!UninstallHandler())
473    return false;
474
475  // Send an empty message so that the handler_thread exits
476  if (SendEmptyMachMessage()) {
477    mach_port_t current_task = mach_task_self();
478    result = mach_port_deallocate(current_task, handler_port_);
479    if (result != KERN_SUCCESS)
480      return false;
481  } else {
482    return false;
483  }
484
485  handler_thread_ = NULL;
486  handler_port_ = NULL;
487  pthread_mutex_destroy(&minidump_write_mutex_);
488
489  return result == KERN_SUCCESS;
490}
491
492bool ExceptionHandler::SendEmptyMachMessage() {
493  ExceptionMessage empty;
494  memset(&empty, 0, sizeof(empty));
495  empty.header.msgh_size = sizeof(empty) - sizeof(empty.padding);
496  empty.header.msgh_remote_port = handler_port_;
497  empty.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
498                                          MACH_MSG_TYPE_MAKE_SEND_ONCE);
499  kern_return_t result = mach_msg(&(empty.header),
500                                  MACH_SEND_MSG | MACH_SEND_TIMEOUT,
501                                  empty.header.msgh_size, 0, 0,
502                                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
503
504  return result == KERN_SUCCESS;
505}
506
507void ExceptionHandler::UpdateNextID() {
508  next_minidump_path_ =
509    (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));
510
511  next_minidump_path_c_ = next_minidump_path_.c_str();
512  next_minidump_id_c_ = next_minidump_id_.c_str();
513}
514
515}  // namespace google_airbag
516