MachTask.cpp revision 24943d2ee8bfaa7cf5893e4709143924157a5c1e
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// Other libraries and framework includes 27// Project includes 28#include "CFUtils.h" 29#include "DNB.h" 30#include "DNBError.h" 31#include "DNBLog.h" 32#include "MachProcess.h" 33#include "DNBDataRef.h" 34 35#if defined (__arm__) 36 37#include <CoreFoundation/CoreFoundation.h> 38#include <SpringBoardServices/SpringBoardServer.h> 39#include <SpringBoardServices/SBSWatchdogAssertion.h> 40 41#endif 42 43//---------------------------------------------------------------------- 44// MachTask constructor 45//---------------------------------------------------------------------- 46MachTask::MachTask(MachProcess *process) : 47 m_process (process), 48 m_task (TASK_NULL), 49 m_vm_memory (), 50 m_exception_thread (0), 51 m_exception_port (MACH_PORT_NULL) 52{ 53 memset(&m_exc_port_info, 0, sizeof(m_exc_port_info)); 54 55} 56 57//---------------------------------------------------------------------- 58// Destructor 59//---------------------------------------------------------------------- 60MachTask::~MachTask() 61{ 62 Clear(); 63} 64 65 66//---------------------------------------------------------------------- 67// MachTask::Suspend 68//---------------------------------------------------------------------- 69kern_return_t 70MachTask::Suspend() 71{ 72 DNBError err; 73 task_t task = TaskPort(); 74 err = ::task_suspend (task); 75 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 76 err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task); 77 return err.Error(); 78} 79 80 81//---------------------------------------------------------------------- 82// MachTask::Resume 83//---------------------------------------------------------------------- 84kern_return_t 85MachTask::Resume() 86{ 87 struct task_basic_info task_info; 88 task_t task = TaskPort(); 89 90 DNBError err; 91 err = BasicInfo(task, &task_info); 92 93 if (err.Success()) 94 { 95 integer_t i; 96 for (i=0; i<task_info.suspend_count; i++) 97 { 98 err = ::task_resume (task); 99 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 100 err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task); 101 } 102 } 103 return err.Error(); 104} 105 106//---------------------------------------------------------------------- 107// MachTask::ExceptionPort 108//---------------------------------------------------------------------- 109mach_port_t 110MachTask::ExceptionPort() const 111{ 112 return m_exception_port; 113} 114 115//---------------------------------------------------------------------- 116// MachTask::ExceptionPortIsValid 117//---------------------------------------------------------------------- 118bool 119MachTask::ExceptionPortIsValid() const 120{ 121 return MACH_PORT_VALID(m_exception_port); 122} 123 124 125//---------------------------------------------------------------------- 126// MachTask::Clear 127//---------------------------------------------------------------------- 128void 129MachTask::Clear() 130{ 131 // Do any cleanup needed for this task 132 m_task = TASK_NULL; 133 m_exception_thread = 0; 134 m_exception_port = MACH_PORT_NULL; 135 136} 137 138 139//---------------------------------------------------------------------- 140// MachTask::SaveExceptionPortInfo 141//---------------------------------------------------------------------- 142kern_return_t 143MachTask::SaveExceptionPortInfo() 144{ 145 return m_exc_port_info.Save(TaskPort()); 146} 147 148//---------------------------------------------------------------------- 149// MachTask::RestoreExceptionPortInfo 150//---------------------------------------------------------------------- 151kern_return_t 152MachTask::RestoreExceptionPortInfo() 153{ 154 return m_exc_port_info.Restore(TaskPort()); 155} 156 157 158//---------------------------------------------------------------------- 159// MachTask::ReadMemory 160//---------------------------------------------------------------------- 161nub_size_t 162MachTask::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf) 163{ 164 nub_size_t n = 0; 165 task_t task = TaskPort(); 166 if (task != TASK_NULL) 167 { 168 n = m_vm_memory.Read(task, addr, buf, size); 169 170 DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes read", (uint64_t)addr, size, buf, n); 171 if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) 172 { 173 DNBDataRef data((uint8_t*)buf, n, false); 174 data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); 175 } 176 } 177 return n; 178} 179 180 181//---------------------------------------------------------------------- 182// MachTask::WriteMemory 183//---------------------------------------------------------------------- 184nub_size_t 185MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf) 186{ 187 nub_size_t n = 0; 188 task_t task = TaskPort(); 189 if (task != TASK_NULL) 190 { 191 n = m_vm_memory.Write(task, addr, buf, size); 192 DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes written", (uint64_t)addr, size, buf, n); 193 if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) 194 { 195 DNBDataRef data((uint8_t*)buf, n, false); 196 data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); 197 } 198 } 199 return n; 200} 201 202//---------------------------------------------------------------------- 203// MachTask::TaskPortForProcessID 204//---------------------------------------------------------------------- 205task_t 206MachTask::TaskPortForProcessID (DNBError &err) 207{ 208 if (m_task == TASK_NULL && m_process != NULL) 209 m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err); 210 return m_task; 211} 212 213//---------------------------------------------------------------------- 214// MachTask::TaskPortForProcessID 215//---------------------------------------------------------------------- 216task_t 217MachTask::TaskPortForProcessID (pid_t pid, DNBError &err) 218{ 219 task_t task = TASK_NULL; 220 if (pid != INVALID_NUB_PROCESS) 221 { 222 mach_port_t task_self = mach_task_self (); 223 err = ::task_for_pid ( task_self, pid, &task); 224 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 225 { 226 char str[1024]; 227 ::snprintf (str, 228 sizeof(str), 229 "::task_for_pid ( task_self, pid = %d, task => TASK_NULL (0x%4.4x) ) uid=%u, euid=%u gid=%u egid=%u (%s)", 230 pid, 231 task, 232 getuid(), 233 geteuid(), 234 getgid(), 235 getegid(), 236 err.AsString() ? err.AsString() : "success"); 237 if (err.Fail()) 238 err.SetErrorString(str); 239 err.LogThreaded(str); 240 } 241 } 242 return task; 243} 244 245 246//---------------------------------------------------------------------- 247// MachTask::BasicInfo 248//---------------------------------------------------------------------- 249kern_return_t 250MachTask::BasicInfo(struct task_basic_info *info) 251{ 252 return BasicInfo (TaskPort(), info); 253} 254 255//---------------------------------------------------------------------- 256// MachTask::BasicInfo 257//---------------------------------------------------------------------- 258kern_return_t 259MachTask::BasicInfo(task_t task, struct task_basic_info *info) 260{ 261 if (info == NULL) 262 return KERN_INVALID_ARGUMENT; 263 264 DNBError err; 265 mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; 266 err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count); 267 const bool log_process = DNBLogCheckLogBit(LOG_TASK); 268 if (log_process || err.Fail()) 269 err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count); 270 if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success()) 271 { 272 float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; 273 float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; 274 DNBLogThreaded("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8x, resident_size = 0x%8.8x, user_time = %f, system_time = %f }", 275 info->suspend_count, info->virtual_size, info->resident_size, user, system); 276 } 277 return err.Error(); 278} 279 280 281//---------------------------------------------------------------------- 282// MachTask::IsValid 283// 284// Returns true if a task is a valid task port for a current process. 285//---------------------------------------------------------------------- 286bool 287MachTask::IsValid () const 288{ 289 return MachTask::IsValid(TaskPort()); 290} 291 292//---------------------------------------------------------------------- 293// MachTask::IsValid 294// 295// Returns true if a task is a valid task port for a current process. 296//---------------------------------------------------------------------- 297bool 298MachTask::IsValid (task_t task) 299{ 300 if (task != TASK_NULL) 301 { 302 struct task_basic_info task_info; 303 return BasicInfo(task, &task_info) == KERN_SUCCESS; 304 } 305 return false; 306} 307 308 309bool 310MachTask::StartExceptionThread(DNBError &err) 311{ 312 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__); 313 task_t task = TaskPortForProcessID(err); 314 if (MachTask::IsValid(task)) 315 { 316 // Got the mach port for the current process 317 mach_port_t task_self = mach_task_self (); 318 319 // Allocate an exception port that we will use to track our child process 320 err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port); 321 if (err.Fail()) 322 return false; 323 324 // Add the ability to send messages on the new exception port 325 err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND); 326 if (err.Fail()) 327 return false; 328 329 // Save the original state of the exception ports for our child process 330 SaveExceptionPortInfo(); 331 332 // Set the ability to get all exceptions on this port 333 err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); 334 if (err.Fail()) 335 return false; 336 337 // Create the exception thread 338 err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this); 339 return err.Success(); 340 } 341 else 342 { 343 DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__); 344 } 345 return false; 346} 347 348kern_return_t 349MachTask::ShutDownExcecptionThread() 350{ 351 DNBError err; 352 353 err = RestoreExceptionPortInfo(); 354 355 // NULL our our exception port and let our exception thread exit 356 mach_port_t exception_port = m_exception_port; 357 m_exception_port = NULL; 358 359 err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX); 360 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 361 err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread); 362 363 err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX); 364 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 365 err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread); 366 367 // Deallocate our exception port that we used to track our child process 368 mach_port_t task_self = mach_task_self (); 369 err = ::mach_port_deallocate (task_self, exception_port); 370 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) 371 err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port); 372 exception_port = NULL; 373 374 return err.Error(); 375} 376 377 378void * 379MachTask::ExceptionThread (void *arg) 380{ 381 if (arg == NULL) 382 return NULL; 383 384 MachTask *mach_task = (MachTask*) arg; 385 MachProcess *mach_proc = mach_task->Process(); 386 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg); 387 388 // We keep a count of the number of consecutive exceptions received so 389 // we know to grab all exceptions without a timeout. We do this to get a 390 // bunch of related exceptions on our exception port so we can process 391 // then together. When we have multiple threads, we can get an exception 392 // per thread and they will come in consecutively. The main loop in this 393 // thread can stop periodically if needed to service things related to this 394 // process. 395 // flag set in the options, so we will wait forever for an exception on 396 // our exception port. After we get one exception, we then will use the 397 // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current 398 // exceptions for our process. After we have received the last pending 399 // exception, we will get a timeout which enables us to then notify 400 // our main thread that we have an exception bundle avaiable. We then wait 401 // for the main thread to tell this exception thread to start trying to get 402 // exceptions messages again and we start again with a mach_msg read with 403 // infinite timeout. 404 uint32_t num_exceptions_received = 0; 405 DNBError err; 406 task_t task = mach_task->TaskPort(); 407 mach_msg_timeout_t periodic_timeout = 0; 408 409#if defined (__arm__) 410 mach_msg_timeout_t watchdog_elapsed = 0; 411 mach_msg_timeout_t watchdog_timeout = 60 * 1000; 412 pid_t pid = mach_proc->ProcessID(); 413 CFReleaser<SBSWatchdogAssertionRef> watchdog; 414 415 if (mach_proc->ProcessUsingSpringBoard()) 416 { 417 // Request a renewal for every 60 seconds if we attached using SpringBoard 418 watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60)); 419 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get()); 420 421 if (watchdog.get()) 422 { 423 ::SBSWatchdogAssertionRenew (watchdog.get()); 424 425 CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get()); 426 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval); 427 if (watchdogRenewalInterval > 0.0) 428 { 429 watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000; 430 if (watchdog_timeout > 3000) 431 watchdog_timeout -= 1000; // Give us a second to renew our timeout 432 else if (watchdog_timeout > 1000) 433 watchdog_timeout -= 250; // Give us a quarter of a second to renew our timeout 434 } 435 } 436 if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout) 437 periodic_timeout = watchdog_timeout; 438 } 439#endif // #if defined (__arm__) 440 441 while (mach_task->ExceptionPortIsValid()) 442 { 443 ::pthread_testcancel (); 444 445 MachException::Message exception_message; 446 447 448 if (num_exceptions_received > 0) 449 { 450 // No timeout, just receive as many exceptions as we can since we already have one and we want 451 // to get all currently available exceptions for this task 452 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0); 453 } 454 else if (periodic_timeout > 0) 455 { 456 // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms) 457 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout); 458 } 459 else 460 { 461 // We don't need to parse all current exceptions or stop periodically, 462 // just wait for an exception forever. 463 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0); 464 } 465 466 if (err.Error() == MACH_RCV_INTERRUPTED) 467 { 468 // If we have no task port we should exit this thread 469 if (!mach_task->ExceptionPortIsValid()) 470 { 471 DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled..."); 472 break; 473 } 474 475 // Make sure our task is still valid 476 if (MachTask::IsValid(task)) 477 { 478 // Task is still ok 479 DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing..."); 480 continue; 481 } 482 else 483 { 484 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..."); 485 mach_proc->SetState(eStateExited); 486 // Our task has died, exit the thread. 487 break; 488 } 489 } 490 else if (err.Error() == MACH_RCV_TIMED_OUT) 491 { 492 if (num_exceptions_received > 0) 493 { 494 // We were receiving all current exceptions with a timeout of zero 495 // it is time to go back to our normal looping mode 496 num_exceptions_received = 0; 497 498 // Notify our main thread we have a complete exception message 499 // bundle available. 500 mach_proc->ExceptionMessageBundleComplete(); 501 502 // in case we use a timeout value when getting exceptions... 503 // Make sure our task is still valid 504 if (MachTask::IsValid(task)) 505 { 506 // Task is still ok 507 DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing..."); 508 continue; 509 } 510 else 511 { 512 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..."); 513 mach_proc->SetState(eStateExited); 514 // Our task has died, exit the thread. 515 break; 516 } 517 continue; 518 } 519 520#if defined (__arm__) 521 if (watchdog.get()) 522 { 523 watchdog_elapsed += periodic_timeout; 524 if (watchdog_elapsed >= watchdog_timeout) 525 { 526 DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get()); 527 ::SBSWatchdogAssertionRenew (watchdog.get()); 528 watchdog_elapsed = 0; 529 } 530 } 531#endif 532 } 533 else if (err.Error() != KERN_SUCCESS) 534 { 535 DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now..."); 536 // TODO: notify of error? 537 } 538 else 539 { 540 if (exception_message.CatchExceptionRaise()) 541 { 542 ++num_exceptions_received; 543 mach_proc->ExceptionMessageReceived(exception_message); 544 } 545 } 546 } 547 548#if defined (__arm__) 549 if (watchdog.get()) 550 { 551 // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we 552 // all are up and running on systems that support it. The SBS framework has a #define 553 // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now 554 // so it should still build either way. 555 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get()); 556 ::SBSWatchdogAssertionRelease (watchdog.get()); 557 } 558#endif // #if defined (__arm__) 559 560 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg); 561 return NULL; 562} 563 564 565// So the TASK_DYLD_INFO used to just return the address of the all image infos 566// as a single member called "all_image_info". Then someone decided it would be 567// a good idea to rename this first member to "all_image_info_addr" and add a 568// size member called "all_image_info_size". This of course can not be detected 569// using code or #defines. So to hack around this problem, we define our own 570// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it. 571 572struct hack_task_dyld_info { 573 mach_vm_address_t all_image_info_addr; 574 mach_vm_size_t all_image_info_size; 575}; 576 577nub_addr_t 578MachTask::GetDYLDAllImageInfosAddress (DNBError& err) 579{ 580 struct hack_task_dyld_info dyld_info; 581 mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 582 // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info. 583 // If it is, then make COUNT smaller to match. 584 if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) 585 count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)); 586 587 task_t task = TaskPortForProcessID (err); 588 if (err.Success()) 589 { 590 err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); 591 if (err.Success()) 592 { 593 // We now have the address of the all image infos structure 594 return dyld_info.all_image_info_addr; 595 } 596 } 597 return INVALID_NUB_ADDRESS; 598} 599 600 601//---------------------------------------------------------------------- 602// MachTask::AllocateMemory 603//---------------------------------------------------------------------- 604nub_addr_t 605MachTask::AllocateMemory (size_t size, uint32_t permissions) 606{ 607 mach_vm_address_t addr; 608 task_t task = TaskPort(); 609 if (task == TASK_NULL) 610 return INVALID_NUB_ADDRESS; 611 612 DNBError err; 613 err = ::mach_vm_allocate (task, &addr, size, TRUE); 614 if (err.Error() == KERN_SUCCESS) 615 { 616 // Set the protections: 617 vm_prot_t mach_prot = 0; 618 if (permissions & eMemoryPermissionsReadable) 619 mach_prot |= VM_PROT_READ; 620 if (permissions & eMemoryPermissionsWritable) 621 mach_prot |= VM_PROT_WRITE; 622 if (permissions & eMemoryPermissionsExecutable) 623 mach_prot |= VM_PROT_EXECUTE; 624 625 626 err = ::mach_vm_protect (task, addr, size, 0, mach_prot); 627 if (err.Error() == KERN_SUCCESS) 628 { 629 m_allocations.insert (std::make_pair(addr, size)); 630 return addr; 631 } 632 ::mach_vm_deallocate (task, addr, size); 633 } 634 return INVALID_NUB_ADDRESS; 635} 636 637//---------------------------------------------------------------------- 638// MachTask::DeallocateMemory 639//---------------------------------------------------------------------- 640nub_bool_t 641MachTask::DeallocateMemory (nub_addr_t addr) 642{ 643 task_t task = TaskPort(); 644 if (task == TASK_NULL) 645 return false; 646 647 // We have to stash away sizes for the allocations... 648 allocation_collection::iterator pos, end = m_allocations.end(); 649 for (pos = m_allocations.begin(); pos != end; pos++) 650 { 651 if ((*pos).first == addr) 652 { 653 m_allocations.erase(pos); 654 return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS; 655 } 656 657 } 658 return false; 659} 660 661