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