MachTask.cpp revision 0e8147bd867e4cdaae9400f56d02c7aacd40a9b3
1//===-- MachTask.cpp --------------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//----------------------------------------------------------------------
10//
11//  MachTask.cpp
12//  debugserver
13//
14//  Created by Greg Clayton on 12/5/08.
15//
16//===----------------------------------------------------------------------===//
17
18#include "MachTask.h"
19
20// C Includes
21
22#include <mach-o/dyld_images.h>
23#include <mach/mach_vm.h>
24
25// C++ Includes
26// Other libraries and framework includes
27// Project includes
28#include "CFUtils.h"
29#include "DNB.h"
30#include "DNBError.h"
31#include "DNBLog.h"
32#include "MachProcess.h"
33#include "DNBDataRef.h"
34#include "stack_logging.h"
35
36#if defined (__arm__)
37
38#include <CoreFoundation/CoreFoundation.h>
39#include <SpringBoardServices/SpringBoardServer.h>
40#include <SpringBoardServices/SBSWatchdogAssertion.h>
41
42#endif
43
44//----------------------------------------------------------------------
45// MachTask constructor
46//----------------------------------------------------------------------
47MachTask::MachTask(MachProcess *process) :
48    m_process (process),
49    m_task (TASK_NULL),
50    m_vm_memory (),
51    m_exception_thread (0),
52    m_exception_port (MACH_PORT_NULL)
53{
54    memset(&m_exc_port_info, 0, sizeof(m_exc_port_info));
55
56}
57
58//----------------------------------------------------------------------
59// Destructor
60//----------------------------------------------------------------------
61MachTask::~MachTask()
62{
63    Clear();
64}
65
66
67//----------------------------------------------------------------------
68// MachTask::Suspend
69//----------------------------------------------------------------------
70kern_return_t
71MachTask::Suspend()
72{
73    DNBError err;
74    task_t task = TaskPort();
75    err = ::task_suspend (task);
76    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
77        err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task);
78    return err.Error();
79}
80
81
82//----------------------------------------------------------------------
83// MachTask::Resume
84//----------------------------------------------------------------------
85kern_return_t
86MachTask::Resume()
87{
88    struct task_basic_info task_info;
89    task_t task = TaskPort();
90	if (task == TASK_NULL)
91		return KERN_INVALID_ARGUMENT;
92
93    DNBError err;
94    err = BasicInfo(task, &task_info);
95
96    if (err.Success())
97    {
98		// task_resume isn't counted like task_suspend calls are, are, so if the
99		// task is not suspended, don't try and resume it since it is already
100		// running
101		if (task_info.suspend_count > 0)
102        {
103            err = ::task_resume (task);
104            if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
105                err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task);
106        }
107    }
108    return err.Error();
109}
110
111//----------------------------------------------------------------------
112// MachTask::ExceptionPort
113//----------------------------------------------------------------------
114mach_port_t
115MachTask::ExceptionPort() const
116{
117    return m_exception_port;
118}
119
120//----------------------------------------------------------------------
121// MachTask::ExceptionPortIsValid
122//----------------------------------------------------------------------
123bool
124MachTask::ExceptionPortIsValid() const
125{
126    return MACH_PORT_VALID(m_exception_port);
127}
128
129
130//----------------------------------------------------------------------
131// MachTask::Clear
132//----------------------------------------------------------------------
133void
134MachTask::Clear()
135{
136    // Do any cleanup needed for this task
137    m_task = TASK_NULL;
138    m_exception_thread = 0;
139    m_exception_port = MACH_PORT_NULL;
140
141}
142
143
144//----------------------------------------------------------------------
145// MachTask::SaveExceptionPortInfo
146//----------------------------------------------------------------------
147kern_return_t
148MachTask::SaveExceptionPortInfo()
149{
150    return m_exc_port_info.Save(TaskPort());
151}
152
153//----------------------------------------------------------------------
154// MachTask::RestoreExceptionPortInfo
155//----------------------------------------------------------------------
156kern_return_t
157MachTask::RestoreExceptionPortInfo()
158{
159    return m_exc_port_info.Restore(TaskPort());
160}
161
162
163//----------------------------------------------------------------------
164// MachTask::ReadMemory
165//----------------------------------------------------------------------
166nub_size_t
167MachTask::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf)
168{
169    nub_size_t n = 0;
170    task_t task = TaskPort();
171    if (task != TASK_NULL)
172    {
173        n = m_vm_memory.Read(task, addr, buf, size);
174
175        DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, size = %zu, buf = %p) => %zu bytes read", (uint64_t)addr, size, buf, n);
176        if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8))
177        {
178            DNBDataRef data((uint8_t*)buf, n, false);
179            data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16);
180        }
181    }
182    return n;
183}
184
185
186//----------------------------------------------------------------------
187// MachTask::WriteMemory
188//----------------------------------------------------------------------
189nub_size_t
190MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf)
191{
192    nub_size_t n = 0;
193    task_t task = TaskPort();
194    if (task != TASK_NULL)
195    {
196        n = m_vm_memory.Write(task, addr, buf, size);
197        DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %zu, buf = %p) => %zu bytes written", (uint64_t)addr, size, buf, n);
198        if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8))
199        {
200            DNBDataRef data((uint8_t*)buf, n, false);
201            data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16);
202        }
203    }
204    return n;
205}
206
207//----------------------------------------------------------------------
208// MachTask::TaskPortForProcessID
209//----------------------------------------------------------------------
210task_t
211MachTask::TaskPortForProcessID (DNBError &err)
212{
213    if (m_task == TASK_NULL && m_process != NULL)
214        m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err);
215    return m_task;
216}
217
218//----------------------------------------------------------------------
219// MachTask::TaskPortForProcessID
220//----------------------------------------------------------------------
221task_t
222MachTask::TaskPortForProcessID (pid_t pid, DNBError &err, uint32_t num_retries, uint32_t usec_interval)
223{
224	if (pid != INVALID_NUB_PROCESS)
225	{
226		DNBError err;
227		mach_port_t task_self = mach_task_self ();
228		task_t task = TASK_NULL;
229		for (uint32_t i=0; i<num_retries; i++)
230		{
231			err = ::task_for_pid ( task_self, pid, &task);
232
233            if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
234            {
235                char str[1024];
236                ::snprintf (str,
237                            sizeof(str),
238                            "::task_for_pid ( target_tport = 0x%4.4x, pid = %d, &task ) => err = 0x%8.8x (%s)",
239                            task_self,
240                            pid,
241                            err.Error(),
242                            err.AsString() ? err.AsString() : "success");
243                if (err.Fail())
244                    err.SetErrorString(str);
245                err.LogThreaded(str);
246            }
247
248			if (err.Success())
249				return task;
250
251			// Sleep a bit and try again
252			::usleep (usec_interval);
253		}
254	}
255	return TASK_NULL;
256}
257
258
259//----------------------------------------------------------------------
260// MachTask::BasicInfo
261//----------------------------------------------------------------------
262kern_return_t
263MachTask::BasicInfo(struct task_basic_info *info)
264{
265    return BasicInfo (TaskPort(), info);
266}
267
268//----------------------------------------------------------------------
269// MachTask::BasicInfo
270//----------------------------------------------------------------------
271kern_return_t
272MachTask::BasicInfo(task_t task, struct task_basic_info *info)
273{
274    if (info == NULL)
275        return KERN_INVALID_ARGUMENT;
276
277    DNBError err;
278    mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
279    err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count);
280    const bool log_process = DNBLogCheckLogBit(LOG_TASK);
281    if (log_process || err.Fail())
282        err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count);
283    if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success())
284    {
285        float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
286        float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
287        DNBLogThreaded ("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8llx, resident_size = 0x%8.8llx, user_time = %f, system_time = %f }",
288                        info->suspend_count,
289                        (uint64_t)info->virtual_size,
290                        (uint64_t)info->resident_size,
291                        user,
292                        system);
293    }
294    return err.Error();
295}
296
297
298//----------------------------------------------------------------------
299// MachTask::IsValid
300//
301// Returns true if a task is a valid task port for a current process.
302//----------------------------------------------------------------------
303bool
304MachTask::IsValid () const
305{
306    return MachTask::IsValid(TaskPort());
307}
308
309//----------------------------------------------------------------------
310// MachTask::IsValid
311//
312// Returns true if a task is a valid task port for a current process.
313//----------------------------------------------------------------------
314bool
315MachTask::IsValid (task_t task)
316{
317    if (task != TASK_NULL)
318    {
319        struct task_basic_info task_info;
320        return BasicInfo(task, &task_info) == KERN_SUCCESS;
321    }
322    return false;
323}
324
325
326bool
327MachTask::StartExceptionThread(DNBError &err)
328{
329    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__);
330    task_t task = TaskPortForProcessID(err);
331    if (MachTask::IsValid(task))
332    {
333        // Got the mach port for the current process
334        mach_port_t task_self = mach_task_self ();
335
336        // Allocate an exception port that we will use to track our child process
337        err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port);
338        if (err.Fail())
339            return false;
340
341        // Add the ability to send messages on the new exception port
342        err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
343        if (err.Fail())
344            return false;
345
346        // Save the original state of the exception ports for our child process
347        SaveExceptionPortInfo();
348
349        // Set the ability to get all exceptions on this port
350        err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
351        if (err.Fail())
352            return false;
353
354        // Create the exception thread
355        err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this);
356        return err.Success();
357    }
358    else
359    {
360        DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__);
361    }
362    return false;
363}
364
365kern_return_t
366MachTask::ShutDownExcecptionThread()
367{
368    DNBError err;
369
370    err = RestoreExceptionPortInfo();
371
372    // NULL our our exception port and let our exception thread exit
373    mach_port_t exception_port = m_exception_port;
374    m_exception_port = NULL;
375
376    err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX);
377    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
378        err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread);
379
380    err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX);
381    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
382        err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread);
383
384    // Deallocate our exception port that we used to track our child process
385    mach_port_t task_self = mach_task_self ();
386    err = ::mach_port_deallocate (task_self, exception_port);
387    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
388        err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port);
389    exception_port = NULL;
390
391    return err.Error();
392}
393
394
395void *
396MachTask::ExceptionThread (void *arg)
397{
398    if (arg == NULL)
399        return NULL;
400
401    MachTask *mach_task = (MachTask*) arg;
402    MachProcess *mach_proc = mach_task->Process();
403    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg);
404
405    // We keep a count of the number of consecutive exceptions received so
406    // we know to grab all exceptions without a timeout. We do this to get a
407    // bunch of related exceptions on our exception port so we can process
408    // then together. When we have multiple threads, we can get an exception
409    // per thread and they will come in consecutively. The main loop in this
410    // thread can stop periodically if needed to service things related to this
411    // process.
412    // flag set in the options, so we will wait forever for an exception on
413    // our exception port. After we get one exception, we then will use the
414    // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
415    // exceptions for our process. After we have received the last pending
416    // exception, we will get a timeout which enables us to then notify
417    // our main thread that we have an exception bundle avaiable. We then wait
418    // for the main thread to tell this exception thread to start trying to get
419    // exceptions messages again and we start again with a mach_msg read with
420    // infinite timeout.
421    uint32_t num_exceptions_received = 0;
422    DNBError err;
423    task_t task = mach_task->TaskPort();
424    mach_msg_timeout_t periodic_timeout = 0;
425
426#if defined (__arm__)
427    mach_msg_timeout_t watchdog_elapsed = 0;
428    mach_msg_timeout_t watchdog_timeout = 60 * 1000;
429    pid_t pid = mach_proc->ProcessID();
430    CFReleaser<SBSWatchdogAssertionRef> watchdog;
431
432    if (mach_proc->ProcessUsingSpringBoard())
433    {
434        // Request a renewal for every 60 seconds if we attached using SpringBoard
435        watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60));
436        DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get());
437
438        if (watchdog.get())
439        {
440            ::SBSWatchdogAssertionRenew (watchdog.get());
441
442            CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get());
443            DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval);
444            if (watchdogRenewalInterval > 0.0)
445            {
446                watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000;
447                if (watchdog_timeout > 3000)
448                    watchdog_timeout -= 1000;   // Give us a second to renew our timeout
449                else if (watchdog_timeout > 1000)
450                    watchdog_timeout -= 250;    // Give us a quarter of a second to renew our timeout
451            }
452        }
453        if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout)
454            periodic_timeout = watchdog_timeout;
455    }
456#endif  // #if defined (__arm__)
457
458    while (mach_task->ExceptionPortIsValid())
459    {
460        ::pthread_testcancel ();
461
462        MachException::Message exception_message;
463
464
465        if (num_exceptions_received > 0)
466        {
467            // No timeout, just receive as many exceptions as we can since we already have one and we want
468            // to get all currently available exceptions for this task
469            err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0);
470        }
471        else if (periodic_timeout > 0)
472        {
473            // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms)
474            err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout);
475        }
476        else
477        {
478            // We don't need to parse all current exceptions or stop periodically,
479            // just wait for an exception forever.
480            err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0);
481        }
482
483        if (err.Error() == MACH_RCV_INTERRUPTED)
484        {
485            // If we have no task port we should exit this thread
486            if (!mach_task->ExceptionPortIsValid())
487            {
488                DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled...");
489                break;
490            }
491
492            // Make sure our task is still valid
493            if (MachTask::IsValid(task))
494            {
495                // Task is still ok
496                DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing...");
497                continue;
498            }
499            else
500            {
501                DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
502                mach_proc->SetState(eStateExited);
503                // Our task has died, exit the thread.
504                break;
505            }
506        }
507        else if (err.Error() == MACH_RCV_TIMED_OUT)
508        {
509            if (num_exceptions_received > 0)
510            {
511                // We were receiving all current exceptions with a timeout of zero
512                // it is time to go back to our normal looping mode
513                num_exceptions_received = 0;
514
515                // Notify our main thread we have a complete exception message
516                // bundle available.
517                mach_proc->ExceptionMessageBundleComplete();
518
519                // in case we use a timeout value when getting exceptions...
520                // Make sure our task is still valid
521                if (MachTask::IsValid(task))
522                {
523                    // Task is still ok
524                    DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing...");
525                    continue;
526                }
527                else
528                {
529                    DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
530                    mach_proc->SetState(eStateExited);
531                    // Our task has died, exit the thread.
532                    break;
533                }
534                continue;
535            }
536
537#if defined (__arm__)
538            if (watchdog.get())
539            {
540                watchdog_elapsed += periodic_timeout;
541                if (watchdog_elapsed >= watchdog_timeout)
542                {
543                    DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get());
544                    ::SBSWatchdogAssertionRenew (watchdog.get());
545                    watchdog_elapsed = 0;
546                }
547            }
548#endif
549        }
550        else if (err.Error() != KERN_SUCCESS)
551        {
552            DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now...");
553            // TODO: notify of error?
554        }
555        else
556        {
557            if (exception_message.CatchExceptionRaise())
558            {
559                ++num_exceptions_received;
560                mach_proc->ExceptionMessageReceived(exception_message);
561            }
562        }
563    }
564
565#if defined (__arm__)
566    if (watchdog.get())
567    {
568        // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we
569        // all are up and running on systems that support it. The SBS framework has a #define
570        // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now
571        // so it should still build either way.
572        DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get());
573        ::SBSWatchdogAssertionRelease (watchdog.get());
574    }
575#endif  // #if defined (__arm__)
576
577    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg);
578    return NULL;
579}
580
581
582// So the TASK_DYLD_INFO used to just return the address of the all image infos
583// as a single member called "all_image_info". Then someone decided it would be
584// a good idea to rename this first member to "all_image_info_addr" and add a
585// size member called "all_image_info_size". This of course can not be detected
586// using code or #defines. So to hack around this problem, we define our own
587// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it.
588
589struct hack_task_dyld_info {
590    mach_vm_address_t   all_image_info_addr;
591    mach_vm_size_t      all_image_info_size;
592};
593
594nub_addr_t
595MachTask::GetDYLDAllImageInfosAddress (DNBError& err)
596{
597    struct hack_task_dyld_info dyld_info;
598    mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
599    // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info.
600    // If it is, then make COUNT smaller to match.
601    if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)))
602        count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t));
603
604    task_t task = TaskPortForProcessID (err);
605    if (err.Success())
606    {
607        err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
608        if (err.Success())
609        {
610            // We now have the address of the all image infos structure
611            return dyld_info.all_image_info_addr;
612        }
613    }
614    return INVALID_NUB_ADDRESS;
615}
616
617
618//----------------------------------------------------------------------
619// MachTask::AllocateMemory
620//----------------------------------------------------------------------
621nub_addr_t
622MachTask::AllocateMemory (size_t size, uint32_t permissions)
623{
624    mach_vm_address_t addr;
625    task_t task = TaskPort();
626    if (task == TASK_NULL)
627        return INVALID_NUB_ADDRESS;
628
629    DNBError err;
630    err = ::mach_vm_allocate (task, &addr, size, TRUE);
631    if (err.Error() == KERN_SUCCESS)
632    {
633        // Set the protections:
634        vm_prot_t mach_prot = VM_PROT_NONE;
635        if (permissions & eMemoryPermissionsReadable)
636            mach_prot |= VM_PROT_READ;
637        if (permissions & eMemoryPermissionsWritable)
638            mach_prot |= VM_PROT_WRITE;
639        if (permissions & eMemoryPermissionsExecutable)
640            mach_prot |= VM_PROT_EXECUTE;
641
642
643        err = ::mach_vm_protect (task, addr, size, 0, mach_prot);
644        if (err.Error() == KERN_SUCCESS)
645        {
646            m_allocations.insert (std::make_pair(addr, size));
647            return addr;
648        }
649        ::mach_vm_deallocate (task, addr, size);
650    }
651    return INVALID_NUB_ADDRESS;
652}
653
654//----------------------------------------------------------------------
655// MachTask::DeallocateMemory
656//----------------------------------------------------------------------
657nub_bool_t
658MachTask::DeallocateMemory (nub_addr_t addr)
659{
660    task_t task = TaskPort();
661    if (task == TASK_NULL)
662        return false;
663
664    // We have to stash away sizes for the allocations...
665    allocation_collection::iterator pos, end = m_allocations.end();
666    for (pos = m_allocations.begin(); pos != end; pos++)
667    {
668        if ((*pos).first == addr)
669        {
670            m_allocations.erase(pos);
671#define ALWAYS_ZOMBIE_ALLOCATIONS 0
672            if (ALWAYS_ZOMBIE_ALLOCATIONS || getenv ("DEBUGSERVER_ZOMBIE_ALLOCATIONS"))
673            {
674                ::mach_vm_protect (task, (*pos).first, (*pos).second, 0, VM_PROT_NONE);
675                return true;
676            }
677            else
678                return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS;
679        }
680
681    }
682    return false;
683}
684
685static void foundStackLog(mach_stack_logging_record_t record, void *context) {
686    *((bool*)context) = true;
687}
688
689bool
690MachTask::HasMallocLoggingEnabled ()
691{
692    bool found = false;
693
694    __mach_stack_logging_enumerate_records(m_task, 0x0, foundStackLog, &found);
695    return found;
696}
697
698struct history_enumerator_impl_data
699{
700    MachMallocEvent *buffer;
701    uint32_t        *position;
702    uint32_t         count;
703};
704
705static void history_enumerator_impl(mach_stack_logging_record_t record, void* enum_obj)
706{
707    history_enumerator_impl_data *data = (history_enumerator_impl_data*)enum_obj;
708
709    if (*data->position >= data->count)
710        return;
711
712    data->buffer[*data->position].m_base_address = record.address;
713    data->buffer[*data->position].m_size = record.argument;
714    data->buffer[*data->position].m_event_id = record.stack_identifier;
715    data->buffer[*data->position].m_event_type = record.type_flags == stack_logging_type_alloc ?   eMachMallocEventTypeAlloc :
716                                                 record.type_flags == stack_logging_type_dealloc ? eMachMallocEventTypeDealloc :
717                                                                                                   eMachMallocEventTypeOther;
718    *data->position+=1;
719}
720
721bool
722MachTask::EnumerateMallocRecords (MachMallocEvent *event_buffer,
723                                  uint32_t buffer_size,
724                                  uint32_t *count)
725{
726    return EnumerateMallocRecords(0,
727                                  event_buffer,
728                                  buffer_size,
729                                  count);
730}
731
732bool
733MachTask::EnumerateMallocRecords (mach_vm_address_t address,
734                                  MachMallocEvent *event_buffer,
735                                  uint32_t buffer_size,
736                                  uint32_t *count)
737{
738    if (!event_buffer || !count)
739        return false;
740
741    if (buffer_size == 0)
742        return false;
743
744    *count = 0;
745    history_enumerator_impl_data data = { event_buffer, count, buffer_size };
746    __mach_stack_logging_enumerate_records(m_task, address, history_enumerator_impl, &data);
747    return (*count > 0);
748}
749
750bool
751MachTask::EnumerateMallocFrames (MachMallocEventId event_id,
752                                 mach_vm_address_t *function_addresses_buffer,
753                                 uint32_t buffer_size,
754                                 uint32_t *count)
755{
756    if (!function_addresses_buffer || !count)
757        return false;
758
759    if (buffer_size == 0)
760        return false;
761
762    __mach_stack_logging_frames_for_uniqued_stack(m_task, event_id, &function_addresses_buffer[0], buffer_size, count);
763    *count -= 1;
764    if (function_addresses_buffer[*count-1] < vm_page_size)
765        *count -= 1;
766    return (*count > 0);
767}
768