1 //===-- MachException.cpp ---------------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // Created by Greg Clayton on 6/18/07. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "MachException.h" 15 #include "MachProcess.h" 16 #include "DNB.h" 17 #include "DNBError.h" 18 #include <sys/types.h> 19 #include "DNBLog.h" 20 #include "PThreadMutex.h" 21 #include "SysSignal.h" 22 #include <errno.h> 23 #include <sys/ptrace.h> 24 25 // Routine mach_exception_raise 26 extern "C" 27 kern_return_t catch_mach_exception_raise 28 ( 29 mach_port_t exception_port, 30 mach_port_t thread, 31 mach_port_t task, 32 exception_type_t exception, 33 mach_exception_data_t code, 34 mach_msg_type_number_t codeCnt 35 ); 36 37 extern "C" 38 kern_return_t catch_mach_exception_raise_state 39 ( 40 mach_port_t exception_port, 41 exception_type_t exception, 42 const mach_exception_data_t code, 43 mach_msg_type_number_t codeCnt, 44 int *flavor, 45 const thread_state_t old_state, 46 mach_msg_type_number_t old_stateCnt, 47 thread_state_t new_state, 48 mach_msg_type_number_t *new_stateCnt 49 ); 50 51 // Routine mach_exception_raise_state_identity 52 extern "C" 53 kern_return_t catch_mach_exception_raise_state_identity 54 ( 55 mach_port_t exception_port, 56 mach_port_t thread, 57 mach_port_t task, 58 exception_type_t exception, 59 mach_exception_data_t code, 60 mach_msg_type_number_t codeCnt, 61 int *flavor, 62 thread_state_t old_state, 63 mach_msg_type_number_t old_stateCnt, 64 thread_state_t new_state, 65 mach_msg_type_number_t *new_stateCnt 66 ); 67 68 extern "C" boolean_t mach_exc_server( 69 mach_msg_header_t *InHeadP, 70 mach_msg_header_t *OutHeadP); 71 72 // Any access to the g_message variable should be done by locking the 73 // g_message_mutex first, using the g_message variable, then unlocking 74 // the g_message_mutex. See MachException::Message::CatchExceptionRaise() 75 // for sample code. 76 77 static MachException::Data *g_message = NULL; 78 //static pthread_mutex_t g_message_mutex = PTHREAD_MUTEX_INITIALIZER; 79 80 81 extern "C" 82 kern_return_t 83 catch_mach_exception_raise_state 84 ( 85 mach_port_t exc_port, 86 exception_type_t exc_type, 87 const mach_exception_data_t exc_data, 88 mach_msg_type_number_t exc_data_count, 89 int * flavor, 90 const thread_state_t old_state, 91 mach_msg_type_number_t old_stateCnt, 92 thread_state_t new_state, 93 mach_msg_type_number_t * new_stateCnt 94 ) 95 { 96 if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 97 { 98 DNBLogThreaded ("::%s ( exc_port = 0x%4.4x, exc_type = %d ( %s ), exc_data = 0x%llx, exc_data_count = %d)", 99 __FUNCTION__, 100 exc_port, 101 exc_type, MachException::Name(exc_type), 102 (uint64_t)exc_data, 103 exc_data_count); 104 } 105 return KERN_FAILURE; 106 } 107 108 extern "C" 109 kern_return_t 110 catch_mach_exception_raise_state_identity 111 ( 112 mach_port_t exc_port, 113 mach_port_t thread_port, 114 mach_port_t task_port, 115 exception_type_t exc_type, 116 mach_exception_data_t exc_data, 117 mach_msg_type_number_t exc_data_count, 118 int * flavor, 119 thread_state_t old_state, 120 mach_msg_type_number_t old_stateCnt, 121 thread_state_t new_state, 122 mach_msg_type_number_t *new_stateCnt 123 ) 124 { 125 kern_return_t kret; 126 if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 127 { 128 DNBLogThreaded("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { 0x%llx, 0x%llx })", 129 __FUNCTION__, 130 exc_port, 131 thread_port, 132 task_port, 133 exc_type, MachException::Name(exc_type), 134 exc_data_count, 135 (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD), 136 (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD)); 137 } 138 kret = mach_port_deallocate (mach_task_self (), task_port); 139 kret = mach_port_deallocate (mach_task_self (), thread_port); 140 141 return KERN_FAILURE; 142 } 143 144 extern "C" 145 kern_return_t 146 catch_mach_exception_raise 147 ( 148 mach_port_t exc_port, 149 mach_port_t thread_port, 150 mach_port_t task_port, 151 exception_type_t exc_type, 152 mach_exception_data_t exc_data, 153 mach_msg_type_number_t exc_data_count) 154 { 155 if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 156 { 157 DNBLogThreaded ("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { 0x%llx, 0x%llx })", 158 __FUNCTION__, 159 exc_port, 160 thread_port, 161 task_port, 162 exc_type, MachException::Name(exc_type), 163 exc_data_count, 164 (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD), 165 (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD)); 166 } 167 168 if (task_port == g_message->task_port) 169 { 170 g_message->task_port = task_port; 171 g_message->thread_port = thread_port; 172 g_message->exc_type = exc_type; 173 g_message->exc_data.resize(exc_data_count); 174 ::memcpy (&g_message->exc_data[0], exc_data, g_message->exc_data.size() * sizeof (mach_exception_data_type_t)); 175 return KERN_SUCCESS; 176 } 177 return KERN_FAILURE; 178 } 179 180 181 void 182 MachException::Message::Dump() const 183 { 184 DNBLogThreadedIf(LOG_EXCEPTIONS, 185 " exc_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = 0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = 0x%8.8x } ", 186 exc_msg.hdr.msgh_bits, 187 exc_msg.hdr.msgh_size, 188 exc_msg.hdr.msgh_remote_port, 189 exc_msg.hdr.msgh_local_port, 190 exc_msg.hdr.msgh_reserved, 191 exc_msg.hdr.msgh_id); 192 193 DNBLogThreadedIf(LOG_EXCEPTIONS, 194 "reply_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = 0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = 0x%8.8x }", 195 reply_msg.hdr.msgh_bits, 196 reply_msg.hdr.msgh_size, 197 reply_msg.hdr.msgh_remote_port, 198 reply_msg.hdr.msgh_local_port, 199 reply_msg.hdr.msgh_reserved, 200 reply_msg.hdr.msgh_id); 201 202 state.Dump(); 203 } 204 205 bool 206 MachException::Data::GetStopInfo(struct DNBThreadStopInfo *stop_info) const 207 { 208 // Zero out the structure. 209 memset(stop_info, 0, sizeof(struct DNBThreadStopInfo)); 210 // We always stop with a mach exceptions 211 stop_info->reason = eStopTypeException; 212 // Save the EXC_XXXX exception type 213 stop_info->details.exception.type = exc_type; 214 215 // Fill in a text description 216 const char * exc_name = MachException::Name(exc_type); 217 char *desc = stop_info->description; 218 const char *end_desc = desc + DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH; 219 if (exc_name) 220 desc += snprintf(desc, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH, "%s", exc_name); 221 else 222 desc += snprintf(desc, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH, "%i", exc_type); 223 224 stop_info->details.exception.data_count = exc_data.size(); 225 226 int soft_signal = SoftSignal(); 227 if (soft_signal) 228 { 229 if (desc < end_desc) 230 { 231 const char *sig_str = SysSignal::Name(soft_signal); 232 desc += snprintf(desc, end_desc - desc, " EXC_SOFT_SIGNAL( %i ( %s ))", soft_signal, sig_str ? sig_str : "unknown signal"); 233 } 234 } 235 else 236 { 237 // No special disassembly for exception data, just 238 size_t idx; 239 if (desc < end_desc) 240 { 241 desc += snprintf(desc, end_desc - desc, " data[%zu] = {", stop_info->details.exception.data_count); 242 243 for (idx = 0; desc < end_desc && idx < stop_info->details.exception.data_count; ++idx) 244 desc += snprintf(desc, end_desc - desc, "0x%llx%c", (uint64_t)exc_data[idx], ((idx + 1 == stop_info->details.exception.data_count) ? '}' : ',')); 245 } 246 } 247 248 // Copy the exception data 249 size_t i; 250 for (i=0; i<stop_info->details.exception.data_count; i++) 251 stop_info->details.exception.data[i] = exc_data[i]; 252 253 return true; 254 } 255 256 257 void 258 MachException::Data::DumpStopReason() const 259 { 260 int soft_signal = SoftSignal(); 261 if (soft_signal) 262 { 263 const char *signal_str = SysSignal::Name(soft_signal); 264 if (signal_str) 265 DNBLog("signal(%s)", signal_str); 266 else 267 DNBLog("signal(%i)", soft_signal); 268 return; 269 } 270 DNBLog("%s", Name(exc_type)); 271 } 272 273 kern_return_t 274 MachException::Message::Receive(mach_port_t port, mach_msg_option_t options, mach_msg_timeout_t timeout, mach_port_t notify_port) 275 { 276 DNBError err; 277 const bool log_exceptions = DNBLogCheckLogBit(LOG_EXCEPTIONS); 278 mach_msg_timeout_t mach_msg_timeout = options & MACH_RCV_TIMEOUT ? timeout : 0; 279 if (log_exceptions && ((options & MACH_RCV_TIMEOUT) == 0)) 280 { 281 // Dump this log message if we have no timeout in case it never returns 282 DNBLogThreaded ("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = 0, rcv_size = %zu, rcv_name = %#x, timeout = %u, notify = %#x)", 283 exc_msg.hdr.msgh_bits, 284 exc_msg.hdr.msgh_size, 285 exc_msg.hdr.msgh_remote_port, 286 exc_msg.hdr.msgh_local_port, 287 exc_msg.hdr.msgh_reserved, 288 exc_msg.hdr.msgh_id, 289 options, 290 sizeof (exc_msg.data), 291 port, 292 mach_msg_timeout, 293 notify_port); 294 } 295 296 err = ::mach_msg (&exc_msg.hdr, 297 options, // options 298 0, // Send size 299 sizeof (exc_msg.data), // Receive size 300 port, // exception port to watch for exception on 301 mach_msg_timeout, // timeout in msec (obeyed only if MACH_RCV_TIMEOUT is ORed into the options parameter) 302 notify_port); 303 304 // Dump any errors we get 305 if (log_exceptions) 306 { 307 err.LogThreaded("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", 308 exc_msg.hdr.msgh_bits, 309 exc_msg.hdr.msgh_size, 310 exc_msg.hdr.msgh_remote_port, 311 exc_msg.hdr.msgh_local_port, 312 exc_msg.hdr.msgh_reserved, 313 exc_msg.hdr.msgh_id, 314 options, 315 0, 316 sizeof (exc_msg.data), 317 port, 318 mach_msg_timeout, 319 notify_port); 320 } 321 return err.Error(); 322 } 323 324 bool 325 MachException::Message::CatchExceptionRaise(task_t task) 326 { 327 bool success = false; 328 // locker will keep a mutex locked until it goes out of scope 329 // PThreadMutex::Locker locker(&g_message_mutex); 330 // DNBLogThreaded("calling mach_exc_server"); 331 state.task_port = task; 332 g_message = &state; 333 // The exc_server function is the MIG generated server handling function 334 // to handle messages from the kernel relating to the occurrence of an 335 // exception in a thread. Such messages are delivered to the exception port 336 // set via thread_set_exception_ports or task_set_exception_ports. When an 337 // exception occurs in a thread, the thread sends an exception message to 338 // its exception port, blocking in the kernel waiting for the receipt of a 339 // reply. The exc_server function performs all necessary argument handling 340 // for this kernel message and calls catch_exception_raise, 341 // catch_exception_raise_state or catch_exception_raise_state_identity, 342 // which should handle the exception. If the called routine returns 343 // KERN_SUCCESS, a reply message will be sent, allowing the thread to 344 // continue from the point of the exception; otherwise, no reply message 345 // is sent and the called routine must have dealt with the exception 346 // thread directly. 347 if (mach_exc_server (&exc_msg.hdr, &reply_msg.hdr)) 348 { 349 success = true; 350 } 351 else if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 352 { 353 DNBLogThreaded("mach_exc_server returned zero..."); 354 } 355 g_message = NULL; 356 return success; 357 } 358 359 360 361 kern_return_t 362 MachException::Message::Reply(MachProcess *process, int signal) 363 { 364 // Reply to the exception... 365 DNBError err; 366 367 // If we had a soft signal, we need to update the thread first so it can 368 // continue without signaling 369 int soft_signal = state.SoftSignal(); 370 if (soft_signal) 371 { 372 int state_pid = -1; 373 if (process->Task().TaskPort() == state.task_port) 374 { 375 // This is our task, so we can update the signal to send to it 376 state_pid = process->ProcessID(); 377 soft_signal = signal; 378 } 379 else 380 { 381 err = ::pid_for_task(state.task_port, &state_pid); 382 } 383 384 assert (state_pid != -1); 385 if (state_pid != -1) 386 { 387 errno = 0; 388 if (::ptrace (PT_THUPDATE, state_pid, (caddr_t)state.thread_port, soft_signal) != 0) 389 err.SetError(errno, DNBError::POSIX); 390 else 391 err.Clear(); 392 393 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) 394 err.LogThreaded("::ptrace (request = PT_THUPDATE, pid = 0x%4.4x, tid = 0x%4.4x, signal = %i)", state_pid, state.thread_port, soft_signal); 395 } 396 } 397 398 DNBLogThreadedIf(LOG_EXCEPTIONS, "::mach_msg ( msg->{bits = %#x, size = %u, remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", 399 reply_msg.hdr.msgh_bits, 400 reply_msg.hdr.msgh_size, 401 reply_msg.hdr.msgh_remote_port, 402 reply_msg.hdr.msgh_local_port, 403 reply_msg.hdr.msgh_reserved, 404 reply_msg.hdr.msgh_id, 405 MACH_SEND_MSG | MACH_SEND_INTERRUPT, 406 reply_msg.hdr.msgh_size, 407 0, 408 MACH_PORT_NULL, 409 MACH_MSG_TIMEOUT_NONE, 410 MACH_PORT_NULL); 411 412 err = ::mach_msg ( &reply_msg.hdr, 413 MACH_SEND_MSG | MACH_SEND_INTERRUPT, 414 reply_msg.hdr.msgh_size, 415 0, 416 MACH_PORT_NULL, 417 MACH_MSG_TIMEOUT_NONE, 418 MACH_PORT_NULL); 419 420 if (err.Fail()) 421 { 422 if (err.Error() == MACH_SEND_INTERRUPTED) 423 { 424 if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 425 err.LogThreaded("::mach_msg() - send interrupted"); 426 // TODO: keep retrying to reply??? 427 } 428 else 429 { 430 if (state.task_port == process->Task().TaskPort()) 431 { 432 if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 433 err.LogThreaded("::mach_msg() - failed (task)"); 434 abort (); 435 } 436 else 437 { 438 if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) 439 err.LogThreaded("::mach_msg() - failed (child of task)"); 440 } 441 } 442 } 443 444 return err.Error(); 445 } 446 447 448 void 449 MachException::Data::Dump() const 450 { 451 const char *exc_type_name = MachException::Name(exc_type); 452 DNBLogThreadedIf(LOG_EXCEPTIONS, " state { task_port = 0x%4.4x, thread_port = 0x%4.4x, exc_type = %i (%s) ...", task_port, thread_port, exc_type, exc_type_name ? exc_type_name : "???"); 453 454 const size_t exc_data_count = exc_data.size(); 455 // Dump any special exception data contents 456 int soft_signal = SoftSignal(); 457 if (soft_signal != 0) 458 { 459 const char *sig_str = SysSignal::Name(soft_signal); 460 DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data: EXC_SOFT_SIGNAL (%i (%s))", soft_signal, sig_str ? sig_str : "unknown signal"); 461 } 462 else 463 { 464 // No special disassembly for this data, just dump the data 465 size_t idx; 466 for (idx = 0; idx < exc_data_count; ++idx) 467 { 468 DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data[%zu]: 0x%llx", idx, (uint64_t)exc_data[idx]); 469 } 470 } 471 } 472 473 #define PREV_EXC_MASK_ALL (EXC_MASK_BAD_ACCESS | \ 474 EXC_MASK_BAD_INSTRUCTION | \ 475 EXC_MASK_ARITHMETIC | \ 476 EXC_MASK_EMULATION | \ 477 EXC_MASK_SOFTWARE | \ 478 EXC_MASK_BREAKPOINT | \ 479 EXC_MASK_SYSCALL | \ 480 EXC_MASK_MACH_SYSCALL | \ 481 EXC_MASK_RPC_ALERT | \ 482 EXC_MASK_MACHINE) 483 484 485 kern_return_t 486 MachException::PortInfo::Save (task_t task) 487 { 488 DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Save ( task = 0x%4.4x )", task); 489 // Be careful to be able to have debugserver built on a newer OS than what 490 // it is currently running on by being able to start with all exceptions 491 // and back off to just what is supported on the current system 492 DNBError err; 493 494 mask = EXC_MASK_ALL; 495 496 count = (sizeof (ports) / sizeof (ports[0])); 497 err = ::task_get_exception_ports (task, mask, masks, &count, ports, behaviors, flavors); 498 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) 499 err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, mask, count); 500 501 if (err.Error() == KERN_INVALID_ARGUMENT && mask != PREV_EXC_MASK_ALL) 502 { 503 mask = PREV_EXC_MASK_ALL; 504 count = (sizeof (ports) / sizeof (ports[0])); 505 err = ::task_get_exception_ports (task, mask, masks, &count, ports, behaviors, flavors); 506 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) 507 err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, mask, count); 508 } 509 if (err.Fail()) 510 { 511 mask = 0; 512 count = 0; 513 } 514 return err.Error(); 515 } 516 517 kern_return_t 518 MachException::PortInfo::Restore (task_t task) 519 { 520 DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Restore( task = 0x%4.4x )", task); 521 uint32_t i = 0; 522 DNBError err; 523 if (count > 0) 524 { 525 for (i = 0; i < count; i++) 526 { 527 err = ::task_set_exception_ports (task, masks[i], ports[i], behaviors[i], flavors[i]); 528 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) 529 { 530 err.LogThreaded("::task_set_exception_ports ( task = 0x%4.4x, exception_mask = 0x%8.8x, new_port = 0x%4.4x, behavior = 0x%8.8x, new_flavor = 0x%8.8x )", task, masks[i], ports[i], behaviors[i], flavors[i]); 531 // Bail if we encounter any errors 532 } 533 534 if (err.Fail()) 535 break; 536 } 537 } 538 count = 0; 539 return err.Error(); 540 } 541 542 const char * 543 MachException::Name(exception_type_t exc_type) 544 { 545 switch (exc_type) 546 { 547 case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS"; 548 case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION"; 549 case EXC_ARITHMETIC: return "EXC_ARITHMETIC"; 550 case EXC_EMULATION: return "EXC_EMULATION"; 551 case EXC_SOFTWARE: return "EXC_SOFTWARE"; 552 case EXC_BREAKPOINT: return "EXC_BREAKPOINT"; 553 case EXC_SYSCALL: return "EXC_SYSCALL"; 554 case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL"; 555 case EXC_RPC_ALERT: return "EXC_RPC_ALERT"; 556 #ifdef EXC_CRASH 557 case EXC_CRASH: return "EXC_CRASH"; 558 #endif 559 default: 560 break; 561 } 562 return NULL; 563 } 564 565 566 567