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