MachTask.cpp revision f14269add7f6fb84da7ccfd866f8e9f7e81068be
1//===-- MachTask.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// 11// MachTask.cpp 12// debugserver 13// 14// Created by Greg Clayton on 12/5/08. 15// 16//===----------------------------------------------------------------------===// 17 18#include "MachTask.h" 19 20// C Includes 21 22#include <mach-o/dyld_images.h> 23#include <mach/mach_vm.h> 24 25// C++ Includes 26#include <sstream> 27 28// Other libraries and framework includes 29// Project includes 30#include "CFUtils.h" 31#include "DNB.h" 32#include "DNBError.h" 33#include "DNBLog.h" 34#include "MachProcess.h" 35#include "DNBDataRef.h" 36#include "stack_logging.h" 37 38#ifdef WITH_SPRINGBOARD 39 40#include <CoreFoundation/CoreFoundation.h> 41#include <SpringBoardServices/SpringBoardServer.h> 42#include <SpringBoardServices/SBSWatchdogAssertion.h> 43 44#endif 45 46//---------------------------------------------------------------------- 47// MachTask constructor 48//---------------------------------------------------------------------- 49MachTask::MachTask(MachProcess *process) : 50 m_process (process), 51 m_task (TASK_NULL), 52 m_vm_memory (), 53 m_exception_thread (0), 54 m_exception_port (MACH_PORT_NULL) 55{ 56 memset(&m_exc_port_info, 0, sizeof(m_exc_port_info)); 57} 58 59//---------------------------------------------------------------------- 60// Destructor 61//---------------------------------------------------------------------- 62MachTask::~MachTask() 63{ 64 Clear(); 65} 66 67 68//---------------------------------------------------------------------- 69// MachTask::Suspend 70//---------------------------------------------------------------------- 71kern_return_t 72MachTask::Suspend() 73{ 74 DNBError err; 75 task_t task = TaskPort(); 76 err = ::task_suspend (task); 77 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 78 err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task); 79 return err.Error(); 80} 81 82 83//---------------------------------------------------------------------- 84// MachTask::Resume 85//---------------------------------------------------------------------- 86kern_return_t 87MachTask::Resume() 88{ 89 struct task_basic_info task_info; 90 task_t task = TaskPort(); 91 if (task == TASK_NULL) 92 return KERN_INVALID_ARGUMENT; 93 94 DNBError err; 95 err = BasicInfo(task, &task_info); 96 97 if (err.Success()) 98 { 99 // task_resume isn't counted like task_suspend calls are, are, so if the 100 // task is not suspended, don't try and resume it since it is already 101 // running 102 if (task_info.suspend_count > 0) 103 { 104 err = ::task_resume (task); 105 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 106 err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task); 107 } 108 } 109 return err.Error(); 110} 111 112//---------------------------------------------------------------------- 113// MachTask::ExceptionPort 114//---------------------------------------------------------------------- 115mach_port_t 116MachTask::ExceptionPort() const 117{ 118 return m_exception_port; 119} 120 121//---------------------------------------------------------------------- 122// MachTask::ExceptionPortIsValid 123//---------------------------------------------------------------------- 124bool 125MachTask::ExceptionPortIsValid() const 126{ 127 return MACH_PORT_VALID(m_exception_port); 128} 129 130 131//---------------------------------------------------------------------- 132// MachTask::Clear 133//---------------------------------------------------------------------- 134void 135MachTask::Clear() 136{ 137 // Do any cleanup needed for this task 138 m_task = TASK_NULL; 139 m_exception_thread = 0; 140 m_exception_port = MACH_PORT_NULL; 141 142} 143 144 145//---------------------------------------------------------------------- 146// MachTask::SaveExceptionPortInfo 147//---------------------------------------------------------------------- 148kern_return_t 149MachTask::SaveExceptionPortInfo() 150{ 151 return m_exc_port_info.Save(TaskPort()); 152} 153 154//---------------------------------------------------------------------- 155// MachTask::RestoreExceptionPortInfo 156//---------------------------------------------------------------------- 157kern_return_t 158MachTask::RestoreExceptionPortInfo() 159{ 160 return m_exc_port_info.Restore(TaskPort()); 161} 162 163 164//---------------------------------------------------------------------- 165// MachTask::ReadMemory 166//---------------------------------------------------------------------- 167nub_size_t 168MachTask::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf) 169{ 170 nub_size_t n = 0; 171 task_t task = TaskPort(); 172 if (task != TASK_NULL) 173 { 174 n = m_vm_memory.Read(task, addr, buf, size); 175 176 DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, size = %llu, buf = %p) => %llu bytes read", (uint64_t)addr, (uint64_t)size, buf, (uint64_t)n); 177 if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) 178 { 179 DNBDataRef data((uint8_t*)buf, n, false); 180 data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); 181 } 182 } 183 return n; 184} 185 186 187//---------------------------------------------------------------------- 188// MachTask::WriteMemory 189//---------------------------------------------------------------------- 190nub_size_t 191MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf) 192{ 193 nub_size_t n = 0; 194 task_t task = TaskPort(); 195 if (task != TASK_NULL) 196 { 197 n = m_vm_memory.Write(task, addr, buf, size); 198 DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %llu, buf = %p) => %llu bytes written", (uint64_t)addr, (uint64_t)size, buf, (uint64_t)n); 199 if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) 200 { 201 DNBDataRef data((uint8_t*)buf, n, false); 202 data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); 203 } 204 } 205 return n; 206} 207 208//---------------------------------------------------------------------- 209// MachTask::MemoryRegionInfo 210//---------------------------------------------------------------------- 211int 212MachTask::GetMemoryRegionInfo (nub_addr_t addr, DNBRegionInfo *region_info) 213{ 214 task_t task = TaskPort(); 215 if (task == TASK_NULL) 216 return -1; 217 218 int ret = m_vm_memory.GetMemoryRegionInfo(task, addr, region_info); 219 DNBLogThreadedIf(LOG_MEMORY, "MachTask::MemoryRegionInfo ( addr = 0x%8.8llx ) => %i (start = 0x%8.8llx, size = 0x%8.8llx, permissions = %u)", 220 (uint64_t)addr, 221 ret, 222 (uint64_t)region_info->addr, 223 (uint64_t)region_info->size, 224 region_info->permissions); 225 return ret; 226} 227 228#define TIME_VALUE_TO_TIMEVAL(a, r) do { \ 229(r)->tv_sec = (a)->seconds; \ 230(r)->tv_usec = (a)->microseconds; \ 231} while (0) 232 233// We should consider moving this into each MacThread. 234static void update_used_time(task_t task, int &num_threads, uint64_t **threads_id, uint64_t **threads_used_usec, struct timeval ¤t_used_time) 235{ 236 kern_return_t kr; 237 thread_act_array_t threads; 238 mach_msg_type_number_t tcnt; 239 240 kr = task_threads(task, &threads, &tcnt); 241 if (kr != KERN_SUCCESS) 242 return; 243 244 num_threads = tcnt; 245 *threads_id = (uint64_t *)malloc(num_threads * sizeof(uint64_t)); 246 *threads_used_usec = (uint64_t *)malloc(num_threads * sizeof(uint64_t)); 247 248 for (int i = 0; i < tcnt; i++) { 249 thread_identifier_info_data_t identifier_info; 250 mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; 251 kr = thread_info(threads[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&identifier_info, &count); 252 if (kr != KERN_SUCCESS) continue; 253 254 thread_basic_info_data_t basic_info; 255 count = THREAD_BASIC_INFO_COUNT; 256 kr = thread_info(threads[i], THREAD_BASIC_INFO, (thread_info_t)&basic_info, &count); 257 if (kr != KERN_SUCCESS) continue; 258 259 if ((basic_info.flags & TH_FLAGS_IDLE) == 0) { 260 (*threads_id)[i] = identifier_info.thread_id; 261 262 struct timeval tv; 263 struct timeval thread_tv; 264 TIME_VALUE_TO_TIMEVAL(&basic_info.user_time, &tv); 265 TIME_VALUE_TO_TIMEVAL(&basic_info.user_time, &thread_tv); 266 timeradd(¤t_used_time, &tv, ¤t_used_time); 267 TIME_VALUE_TO_TIMEVAL(&basic_info.system_time, &tv); 268 timeradd(&thread_tv, &tv, &thread_tv); 269 timeradd(¤t_used_time, &tv, ¤t_used_time); 270 uint64_t used_usec = thread_tv.tv_sec * 1000000ULL + thread_tv.tv_usec; 271 (*threads_used_usec)[i] = used_usec; 272 } 273 274 kr = mach_port_deallocate(mach_task_self(), threads[i]); 275 } 276 kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)(uintptr_t)threads, tcnt * sizeof(*threads)); 277} 278 279std::string 280MachTask::GetProfileData () 281{ 282 std::string result; 283 task_t task = TaskPort(); 284 if (task == TASK_NULL) 285 return result; 286 287 struct task_basic_info task_info; 288 DNBError err; 289 err = BasicInfo(task, &task_info); 290 291 if (!err.Success()) 292 return result; 293 294 uint64_t elapsed_usec = 0; 295 uint64_t task_used_usec = 0; 296 int num_threads = 0; 297 uint64_t *threads_used_usec = NULL; 298 uint64_t *threads_id = NULL; 299 300 // Get current used time. 301 struct timeval current_used_time; 302 struct timeval tv; 303 TIME_VALUE_TO_TIMEVAL(&task_info.user_time, ¤t_used_time); 304 TIME_VALUE_TO_TIMEVAL(&task_info.system_time, &tv); 305 timeradd(¤t_used_time, &tv, ¤t_used_time); 306 task_used_usec = current_used_time.tv_sec * 1000000ULL + current_used_time.tv_usec; 307 update_used_time(task, num_threads, &threads_id, &threads_used_usec, current_used_time); 308 309 struct timeval current_elapsed_time; 310 int res = gettimeofday(¤t_elapsed_time, NULL); 311 if (res == 0) 312 { 313 elapsed_usec = current_elapsed_time.tv_sec * 1000000ULL + current_elapsed_time.tv_usec; 314 } 315 316 struct vm_statistics vm_stats; 317 uint64_t physical_memory; 318 mach_vm_size_t rprvt = 0; 319 mach_vm_size_t rsize = 0; 320 mach_vm_size_t vprvt = 0; 321 mach_vm_size_t vsize = 0; 322 mach_vm_size_t dirty_size = 0; 323 if (m_vm_memory.GetMemoryProfile(task, task_info, m_process->GetCPUType(), m_process->ProcessID(), vm_stats, physical_memory, rprvt, rsize, vprvt, vsize, dirty_size)) 324 { 325 std::ostringstream profile_data_stream; 326 327 profile_data_stream << "elapsed_usec:" << elapsed_usec << ';'; 328 profile_data_stream << "task_used_usec:" << task_used_usec << ';'; 329 330 profile_data_stream << "threads_info:" << num_threads; 331 for (int i=0; i<num_threads; i++) { 332 profile_data_stream << ',' << threads_id[i]; 333 profile_data_stream << ',' << threads_used_usec[i]; 334 } 335 profile_data_stream << ';'; 336 337 profile_data_stream << "wired:" << vm_stats.wire_count * vm_page_size << ';'; 338 profile_data_stream << "active:" << vm_stats.active_count * vm_page_size << ';'; 339 profile_data_stream << "inactive:" << vm_stats.inactive_count * vm_page_size << ';'; 340 uint64_t total_used_count = vm_stats.wire_count + vm_stats.inactive_count + vm_stats.active_count; 341 profile_data_stream << "used:" << total_used_count * vm_page_size << ';'; 342 profile_data_stream << "free:" << vm_stats.free_count * vm_page_size << ';'; 343 profile_data_stream << "total:" << physical_memory << ';'; 344 345 profile_data_stream << "rprvt:" << rprvt << ';'; 346 profile_data_stream << "rsize:" << rsize << ';'; 347 profile_data_stream << "vprvt:" << vprvt << ';'; 348 profile_data_stream << "vsize:" << vsize << ';'; 349 profile_data_stream << "dirty:" << dirty_size << ';'; 350 profile_data_stream << "end;"; 351 352 result = profile_data_stream.str(); 353 } 354 355 free(threads_id); 356 free(threads_used_usec); 357 358 return result; 359} 360 361 362//---------------------------------------------------------------------- 363// MachTask::TaskPortForProcessID 364//---------------------------------------------------------------------- 365task_t 366MachTask::TaskPortForProcessID (DNBError &err) 367{ 368 if (m_task == TASK_NULL && m_process != NULL) 369 m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err); 370 return m_task; 371} 372 373//---------------------------------------------------------------------- 374// MachTask::TaskPortForProcessID 375//---------------------------------------------------------------------- 376task_t 377MachTask::TaskPortForProcessID (pid_t pid, DNBError &err, uint32_t num_retries, uint32_t usec_interval) 378{ 379 if (pid != INVALID_NUB_PROCESS) 380 { 381 DNBError err; 382 mach_port_t task_self = mach_task_self (); 383 task_t task = TASK_NULL; 384 for (uint32_t i=0; i<num_retries; i++) 385 { 386 err = ::task_for_pid ( task_self, pid, &task); 387 388 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 389 { 390 char str[1024]; 391 ::snprintf (str, 392 sizeof(str), 393 "::task_for_pid ( target_tport = 0x%4.4x, pid = %d, &task ) => err = 0x%8.8x (%s)", 394 task_self, 395 pid, 396 err.Error(), 397 err.AsString() ? err.AsString() : "success"); 398 if (err.Fail()) 399 err.SetErrorString(str); 400 err.LogThreaded(str); 401 } 402 403 if (err.Success()) 404 return task; 405 406 // Sleep a bit and try again 407 ::usleep (usec_interval); 408 } 409 } 410 return TASK_NULL; 411} 412 413 414//---------------------------------------------------------------------- 415// MachTask::BasicInfo 416//---------------------------------------------------------------------- 417kern_return_t 418MachTask::BasicInfo(struct task_basic_info *info) 419{ 420 return BasicInfo (TaskPort(), info); 421} 422 423//---------------------------------------------------------------------- 424// MachTask::BasicInfo 425//---------------------------------------------------------------------- 426kern_return_t 427MachTask::BasicInfo(task_t task, struct task_basic_info *info) 428{ 429 if (info == NULL) 430 return KERN_INVALID_ARGUMENT; 431 432 DNBError err; 433 mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; 434 err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count); 435 const bool log_process = DNBLogCheckLogBit(LOG_TASK); 436 if (log_process || err.Fail()) 437 err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count); 438 if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success()) 439 { 440 float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; 441 float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; 442 DNBLogThreaded ("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8llx, resident_size = 0x%8.8llx, user_time = %f, system_time = %f }", 443 info->suspend_count, 444 (uint64_t)info->virtual_size, 445 (uint64_t)info->resident_size, 446 user, 447 system); 448 } 449 return err.Error(); 450} 451 452 453//---------------------------------------------------------------------- 454// MachTask::IsValid 455// 456// Returns true if a task is a valid task port for a current process. 457//---------------------------------------------------------------------- 458bool 459MachTask::IsValid () const 460{ 461 return MachTask::IsValid(TaskPort()); 462} 463 464//---------------------------------------------------------------------- 465// MachTask::IsValid 466// 467// Returns true if a task is a valid task port for a current process. 468//---------------------------------------------------------------------- 469bool 470MachTask::IsValid (task_t task) 471{ 472 if (task != TASK_NULL) 473 { 474 struct task_basic_info task_info; 475 return BasicInfo(task, &task_info) == KERN_SUCCESS; 476 } 477 return false; 478} 479 480 481bool 482MachTask::StartExceptionThread(DNBError &err) 483{ 484 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__); 485 task_t task = TaskPortForProcessID(err); 486 if (MachTask::IsValid(task)) 487 { 488 // Got the mach port for the current process 489 mach_port_t task_self = mach_task_self (); 490 491 // Allocate an exception port that we will use to track our child process 492 err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port); 493 if (err.Fail()) 494 return false; 495 496 // Add the ability to send messages on the new exception port 497 err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND); 498 if (err.Fail()) 499 return false; 500 501 // Save the original state of the exception ports for our child process 502 SaveExceptionPortInfo(); 503 504 // We weren't able to save the info for our exception ports, we must stop... 505 if (m_exc_port_info.mask == 0) 506 { 507 err.SetErrorString("failed to get exception port info"); 508 return false; 509 } 510 511 // Set the ability to get all exceptions on this port 512 err = ::task_set_exception_ports (task, m_exc_port_info.mask, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); 513 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) 514 { 515 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 )", 516 task, 517 m_exc_port_info.mask, 518 m_exception_port, 519 (EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES), 520 THREAD_STATE_NONE); 521 } 522 523 if (err.Fail()) 524 return false; 525 526 // Create the exception thread 527 err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this); 528 return err.Success(); 529 } 530 else 531 { 532 DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__); 533 } 534 return false; 535} 536 537kern_return_t 538MachTask::ShutDownExcecptionThread() 539{ 540 DNBError err; 541 542 err = RestoreExceptionPortInfo(); 543 544 // NULL our our exception port and let our exception thread exit 545 mach_port_t exception_port = m_exception_port; 546 m_exception_port = NULL; 547 548 err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX); 549 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 550 err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread); 551 552 err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX); 553 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 554 err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread); 555 556 // Deallocate our exception port that we used to track our child process 557 mach_port_t task_self = mach_task_self (); 558 err = ::mach_port_deallocate (task_self, exception_port); 559 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 560 err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port); 561 562 return err.Error(); 563} 564 565 566void * 567MachTask::ExceptionThread (void *arg) 568{ 569 if (arg == NULL) 570 return NULL; 571 572 MachTask *mach_task = (MachTask*) arg; 573 MachProcess *mach_proc = mach_task->Process(); 574 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg); 575 576 // We keep a count of the number of consecutive exceptions received so 577 // we know to grab all exceptions without a timeout. We do this to get a 578 // bunch of related exceptions on our exception port so we can process 579 // then together. When we have multiple threads, we can get an exception 580 // per thread and they will come in consecutively. The main loop in this 581 // thread can stop periodically if needed to service things related to this 582 // process. 583 // flag set in the options, so we will wait forever for an exception on 584 // our exception port. After we get one exception, we then will use the 585 // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current 586 // exceptions for our process. After we have received the last pending 587 // exception, we will get a timeout which enables us to then notify 588 // our main thread that we have an exception bundle avaiable. We then wait 589 // for the main thread to tell this exception thread to start trying to get 590 // exceptions messages again and we start again with a mach_msg read with 591 // infinite timeout. 592 uint32_t num_exceptions_received = 0; 593 DNBError err; 594 task_t task = mach_task->TaskPort(); 595 mach_msg_timeout_t periodic_timeout = 0; 596 597#ifdef WITH_SPRINGBOARD 598 mach_msg_timeout_t watchdog_elapsed = 0; 599 mach_msg_timeout_t watchdog_timeout = 60 * 1000; 600 pid_t pid = mach_proc->ProcessID(); 601 CFReleaser<SBSWatchdogAssertionRef> watchdog; 602 603 if (mach_proc->ProcessUsingSpringBoard()) 604 { 605 // Request a renewal for every 60 seconds if we attached using SpringBoard 606 watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60)); 607 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get()); 608 609 if (watchdog.get()) 610 { 611 ::SBSWatchdogAssertionRenew (watchdog.get()); 612 613 CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get()); 614 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval); 615 if (watchdogRenewalInterval > 0.0) 616 { 617 watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000; 618 if (watchdog_timeout > 3000) 619 watchdog_timeout -= 1000; // Give us a second to renew our timeout 620 else if (watchdog_timeout > 1000) 621 watchdog_timeout -= 250; // Give us a quarter of a second to renew our timeout 622 } 623 } 624 if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout) 625 periodic_timeout = watchdog_timeout; 626 } 627#endif // #ifdef WITH_SPRINGBOARD 628 629 while (mach_task->ExceptionPortIsValid()) 630 { 631 ::pthread_testcancel (); 632 633 MachException::Message exception_message; 634 635 636 if (num_exceptions_received > 0) 637 { 638 // No timeout, just receive as many exceptions as we can since we already have one and we want 639 // to get all currently available exceptions for this task 640 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0); 641 } 642 else if (periodic_timeout > 0) 643 { 644 // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms) 645 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout); 646 } 647 else 648 { 649 // We don't need to parse all current exceptions or stop periodically, 650 // just wait for an exception forever. 651 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0); 652 } 653 654 if (err.Error() == MACH_RCV_INTERRUPTED) 655 { 656 // If we have no task port we should exit this thread 657 if (!mach_task->ExceptionPortIsValid()) 658 { 659 DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled..."); 660 break; 661 } 662 663 // Make sure our task is still valid 664 if (MachTask::IsValid(task)) 665 { 666 // Task is still ok 667 DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing..."); 668 continue; 669 } 670 else 671 { 672 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..."); 673 mach_proc->SetState(eStateExited); 674 // Our task has died, exit the thread. 675 break; 676 } 677 } 678 else if (err.Error() == MACH_RCV_TIMED_OUT) 679 { 680 if (num_exceptions_received > 0) 681 { 682 // We were receiving all current exceptions with a timeout of zero 683 // it is time to go back to our normal looping mode 684 num_exceptions_received = 0; 685 686 // Notify our main thread we have a complete exception message 687 // bundle available. 688 mach_proc->ExceptionMessageBundleComplete(); 689 690 // in case we use a timeout value when getting exceptions... 691 // Make sure our task is still valid 692 if (MachTask::IsValid(task)) 693 { 694 // Task is still ok 695 DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing..."); 696 continue; 697 } 698 else 699 { 700 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..."); 701 mach_proc->SetState(eStateExited); 702 // Our task has died, exit the thread. 703 break; 704 } 705 continue; 706 } 707 708#ifdef WITH_SPRINGBOARD 709 if (watchdog.get()) 710 { 711 watchdog_elapsed += periodic_timeout; 712 if (watchdog_elapsed >= watchdog_timeout) 713 { 714 DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get()); 715 ::SBSWatchdogAssertionRenew (watchdog.get()); 716 watchdog_elapsed = 0; 717 } 718 } 719#endif 720 } 721 else if (err.Error() != KERN_SUCCESS) 722 { 723 DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now..."); 724 // TODO: notify of error? 725 } 726 else 727 { 728 if (exception_message.CatchExceptionRaise(task)) 729 { 730 ++num_exceptions_received; 731 mach_proc->ExceptionMessageReceived(exception_message); 732 } 733 } 734 } 735 736#ifdef WITH_SPRINGBOARD 737 if (watchdog.get()) 738 { 739 // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we 740 // all are up and running on systems that support it. The SBS framework has a #define 741 // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now 742 // so it should still build either way. 743 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get()); 744 ::SBSWatchdogAssertionRelease (watchdog.get()); 745 } 746#endif // #ifdef WITH_SPRINGBOARD 747 748 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg); 749 return NULL; 750} 751 752 753// So the TASK_DYLD_INFO used to just return the address of the all image infos 754// as a single member called "all_image_info". Then someone decided it would be 755// a good idea to rename this first member to "all_image_info_addr" and add a 756// size member called "all_image_info_size". This of course can not be detected 757// using code or #defines. So to hack around this problem, we define our own 758// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it. 759 760struct hack_task_dyld_info { 761 mach_vm_address_t all_image_info_addr; 762 mach_vm_size_t all_image_info_size; 763}; 764 765nub_addr_t 766MachTask::GetDYLDAllImageInfosAddress (DNBError& err) 767{ 768 struct hack_task_dyld_info dyld_info; 769 mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 770 // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info. 771 // If it is, then make COUNT smaller to match. 772 if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) 773 count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)); 774 775 task_t task = TaskPortForProcessID (err); 776 if (err.Success()) 777 { 778 err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); 779 if (err.Success()) 780 { 781 // We now have the address of the all image infos structure 782 return dyld_info.all_image_info_addr; 783 } 784 } 785 return INVALID_NUB_ADDRESS; 786} 787 788 789//---------------------------------------------------------------------- 790// MachTask::AllocateMemory 791//---------------------------------------------------------------------- 792nub_addr_t 793MachTask::AllocateMemory (size_t size, uint32_t permissions) 794{ 795 mach_vm_address_t addr; 796 task_t task = TaskPort(); 797 if (task == TASK_NULL) 798 return INVALID_NUB_ADDRESS; 799 800 DNBError err; 801 err = ::mach_vm_allocate (task, &addr, size, TRUE); 802 if (err.Error() == KERN_SUCCESS) 803 { 804 // Set the protections: 805 vm_prot_t mach_prot = VM_PROT_NONE; 806 if (permissions & eMemoryPermissionsReadable) 807 mach_prot |= VM_PROT_READ; 808 if (permissions & eMemoryPermissionsWritable) 809 mach_prot |= VM_PROT_WRITE; 810 if (permissions & eMemoryPermissionsExecutable) 811 mach_prot |= VM_PROT_EXECUTE; 812 813 814 err = ::mach_vm_protect (task, addr, size, 0, mach_prot); 815 if (err.Error() == KERN_SUCCESS) 816 { 817 m_allocations.insert (std::make_pair(addr, size)); 818 return addr; 819 } 820 ::mach_vm_deallocate (task, addr, size); 821 } 822 return INVALID_NUB_ADDRESS; 823} 824 825//---------------------------------------------------------------------- 826// MachTask::DeallocateMemory 827//---------------------------------------------------------------------- 828nub_bool_t 829MachTask::DeallocateMemory (nub_addr_t addr) 830{ 831 task_t task = TaskPort(); 832 if (task == TASK_NULL) 833 return false; 834 835 // We have to stash away sizes for the allocations... 836 allocation_collection::iterator pos, end = m_allocations.end(); 837 for (pos = m_allocations.begin(); pos != end; pos++) 838 { 839 if ((*pos).first == addr) 840 { 841 m_allocations.erase(pos); 842#define ALWAYS_ZOMBIE_ALLOCATIONS 0 843 if (ALWAYS_ZOMBIE_ALLOCATIONS || getenv ("DEBUGSERVER_ZOMBIE_ALLOCATIONS")) 844 { 845 ::mach_vm_protect (task, (*pos).first, (*pos).second, 0, VM_PROT_NONE); 846 return true; 847 } 848 else 849 return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS; 850 } 851 852 } 853 return false; 854} 855 856static void foundStackLog(mach_stack_logging_record_t record, void *context) { 857 *((bool*)context) = true; 858} 859 860bool 861MachTask::HasMallocLoggingEnabled () 862{ 863 bool found = false; 864 865 __mach_stack_logging_enumerate_records(m_task, 0x0, foundStackLog, &found); 866 return found; 867} 868 869struct history_enumerator_impl_data 870{ 871 MachMallocEvent *buffer; 872 uint32_t *position; 873 uint32_t count; 874}; 875 876static void history_enumerator_impl(mach_stack_logging_record_t record, void* enum_obj) 877{ 878 history_enumerator_impl_data *data = (history_enumerator_impl_data*)enum_obj; 879 880 if (*data->position >= data->count) 881 return; 882 883 data->buffer[*data->position].m_base_address = record.address; 884 data->buffer[*data->position].m_size = record.argument; 885 data->buffer[*data->position].m_event_id = record.stack_identifier; 886 data->buffer[*data->position].m_event_type = record.type_flags == stack_logging_type_alloc ? eMachMallocEventTypeAlloc : 887 record.type_flags == stack_logging_type_dealloc ? eMachMallocEventTypeDealloc : 888 eMachMallocEventTypeOther; 889 *data->position+=1; 890} 891 892bool 893MachTask::EnumerateMallocRecords (MachMallocEvent *event_buffer, 894 uint32_t buffer_size, 895 uint32_t *count) 896{ 897 return EnumerateMallocRecords(0, 898 event_buffer, 899 buffer_size, 900 count); 901} 902 903bool 904MachTask::EnumerateMallocRecords (mach_vm_address_t address, 905 MachMallocEvent *event_buffer, 906 uint32_t buffer_size, 907 uint32_t *count) 908{ 909 if (!event_buffer || !count) 910 return false; 911 912 if (buffer_size == 0) 913 return false; 914 915 *count = 0; 916 history_enumerator_impl_data data = { event_buffer, count, buffer_size }; 917 __mach_stack_logging_enumerate_records(m_task, address, history_enumerator_impl, &data); 918 return (*count > 0); 919} 920 921bool 922MachTask::EnumerateMallocFrames (MachMallocEventId event_id, 923 mach_vm_address_t *function_addresses_buffer, 924 uint32_t buffer_size, 925 uint32_t *count) 926{ 927 if (!function_addresses_buffer || !count) 928 return false; 929 930 if (buffer_size == 0) 931 return false; 932 933 __mach_stack_logging_frames_for_uniqued_stack(m_task, event_id, &function_addresses_buffer[0], buffer_size, count); 934 *count -= 1; 935 if (function_addresses_buffer[*count-1] < vm_page_size) 936 *count -= 1; 937 return (*count > 0); 938} 939