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