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