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