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