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