Initial checkin of lldb code from internal Apple repo.

llvm-svn: 105619
diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.cpp b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp
new file mode 100644
index 0000000..68d858c
--- /dev/null
+++ b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp
@@ -0,0 +1,660 @@
+//===-- MachTask.cpp --------------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//----------------------------------------------------------------------
+//
+//  MachTask.cpp
+//  debugserver
+//
+//  Created by Greg Clayton on 12/5/08.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MachTask.h"
+
+// C Includes
+
+#include <mach-o/dyld_images.h>
+#include <mach/mach_vm.h>
+
+// C++ Includes
+// Other libraries and framework includes
+// Project includes
+#include "CFUtils.h"
+#include "DNB.h"
+#include "DNBError.h"
+#include "DNBLog.h"
+#include "MachProcess.h"
+#include "DNBDataRef.h"
+
+#if defined (__arm__)
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SpringBoardServices/SpringBoardServer.h>
+#include <SpringBoardServices/SBSWatchdogAssertion.h>
+
+#endif
+
+//----------------------------------------------------------------------
+// MachTask constructor
+//----------------------------------------------------------------------
+MachTask::MachTask(MachProcess *process) :
+    m_process (process),
+    m_task (TASK_NULL),
+    m_vm_memory (),
+    m_exception_thread (0),
+    m_exception_port (MACH_PORT_NULL)
+{
+    memset(&m_exc_port_info, 0, sizeof(m_exc_port_info));
+
+}
+
+//----------------------------------------------------------------------
+// Destructor
+//----------------------------------------------------------------------
+MachTask::~MachTask()
+{
+    Clear();
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::Suspend
+//----------------------------------------------------------------------
+kern_return_t
+MachTask::Suspend()
+{
+    DNBError err;
+    task_t task = TaskPort();
+    err = ::task_suspend (task);
+    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
+        err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task);
+    return err.Error();
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::Resume
+//----------------------------------------------------------------------
+kern_return_t
+MachTask::Resume()
+{
+    struct task_basic_info task_info;
+    task_t task = TaskPort();
+
+    DNBError err;
+    err = BasicInfo(task, &task_info);
+
+    if (err.Success())
+    {
+        integer_t i;
+        for (i=0; i<task_info.suspend_count; i++)
+        {
+            err = ::task_resume (task);
+            if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
+                err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task);
+        }
+    }
+    return err.Error();
+}
+
+//----------------------------------------------------------------------
+// MachTask::ExceptionPort
+//----------------------------------------------------------------------
+mach_port_t
+MachTask::ExceptionPort() const
+{
+    return m_exception_port;
+}
+
+//----------------------------------------------------------------------
+// MachTask::ExceptionPortIsValid
+//----------------------------------------------------------------------
+bool
+MachTask::ExceptionPortIsValid() const
+{
+    return MACH_PORT_VALID(m_exception_port);
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::Clear
+//----------------------------------------------------------------------
+void
+MachTask::Clear()
+{
+    // Do any cleanup needed for this task
+    m_task = TASK_NULL;
+    m_exception_thread = 0;
+    m_exception_port = MACH_PORT_NULL;
+
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::SaveExceptionPortInfo
+//----------------------------------------------------------------------
+kern_return_t
+MachTask::SaveExceptionPortInfo()
+{
+    return m_exc_port_info.Save(TaskPort());
+}
+
+//----------------------------------------------------------------------
+// MachTask::RestoreExceptionPortInfo
+//----------------------------------------------------------------------
+kern_return_t
+MachTask::RestoreExceptionPortInfo()
+{
+    return m_exc_port_info.Restore(TaskPort());
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::ReadMemory
+//----------------------------------------------------------------------
+nub_size_t
+MachTask::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf)
+{
+    nub_size_t n = 0;
+    task_t task = TaskPort();
+    if (task != TASK_NULL)
+    {
+        n = m_vm_memory.Read(task, addr, buf, size);
+
+        DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes read", (uint64_t)addr, size, buf, n);
+        if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8))
+        {
+            DNBDataRef data((uint8_t*)buf, n, false);
+            data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16);
+        }
+    }
+    return n;
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::WriteMemory
+//----------------------------------------------------------------------
+nub_size_t
+MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf)
+{
+    nub_size_t n = 0;
+    task_t task = TaskPort();
+    if (task != TASK_NULL)
+    {
+        n = m_vm_memory.Write(task, addr, buf, size);
+        DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes written", (uint64_t)addr, size, buf, n);
+        if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8))
+        {
+            DNBDataRef data((uint8_t*)buf, n, false);
+            data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16);
+        }
+    }
+    return n;
+}
+
+//----------------------------------------------------------------------
+// MachTask::TaskPortForProcessID
+//----------------------------------------------------------------------
+task_t
+MachTask::TaskPortForProcessID (DNBError &err)
+{
+    if (m_task == TASK_NULL && m_process != NULL)
+        m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err);
+    return m_task;
+}
+
+//----------------------------------------------------------------------
+// MachTask::TaskPortForProcessID
+//----------------------------------------------------------------------
+task_t
+MachTask::TaskPortForProcessID (pid_t pid, DNBError &err)
+{
+    task_t task = TASK_NULL;
+    if (pid != INVALID_NUB_PROCESS)
+    {
+        mach_port_t task_self = mach_task_self ();
+        err = ::task_for_pid ( task_self, pid, &task);
+        if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
+        {
+            char str[1024];
+            ::snprintf (str,
+                        sizeof(str),
+                        "::task_for_pid ( task_self, pid = %d, task => TASK_NULL (0x%4.4x) ) uid=%u, euid=%u gid=%u egid=%u (%s)",
+                        pid,
+                        task,
+                        getuid(),
+                        geteuid(),
+                        getgid(),
+                        getegid(),
+                        err.AsString() ? err.AsString() : "success");
+            if (err.Fail())
+                err.SetErrorString(str);
+            err.LogThreaded(str);
+        }
+    }
+    return task;
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::BasicInfo
+//----------------------------------------------------------------------
+kern_return_t
+MachTask::BasicInfo(struct task_basic_info *info)
+{
+    return BasicInfo (TaskPort(), info);
+}
+
+//----------------------------------------------------------------------
+// MachTask::BasicInfo
+//----------------------------------------------------------------------
+kern_return_t
+MachTask::BasicInfo(task_t task, struct task_basic_info *info)
+{
+    if (info == NULL)
+        return KERN_INVALID_ARGUMENT;
+
+    DNBError err;
+    mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
+    err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count);
+    const bool log_process = DNBLogCheckLogBit(LOG_TASK);
+    if (log_process || err.Fail())
+        err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count);
+    if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success())
+    {
+        float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
+        float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
+        DNBLogThreaded("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8x, resident_size = 0x%8.8x, user_time = %f, system_time = %f }",
+            info->suspend_count, info->virtual_size, info->resident_size, user, system);
+    }
+    return err.Error();
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::IsValid
+//
+// Returns true if a task is a valid task port for a current process.
+//----------------------------------------------------------------------
+bool
+MachTask::IsValid () const
+{
+    return MachTask::IsValid(TaskPort());
+}
+
+//----------------------------------------------------------------------
+// MachTask::IsValid
+//
+// Returns true if a task is a valid task port for a current process.
+//----------------------------------------------------------------------
+bool
+MachTask::IsValid (task_t task)
+{
+    if (task != TASK_NULL)
+    {
+        struct task_basic_info task_info;
+        return BasicInfo(task, &task_info) == KERN_SUCCESS;
+    }
+    return false;
+}
+
+
+bool
+MachTask::StartExceptionThread(DNBError &err)
+{
+    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__);
+    task_t task = TaskPortForProcessID(err);
+    if (MachTask::IsValid(task))
+    {
+        // Got the mach port for the current process
+        mach_port_t task_self = mach_task_self ();
+
+        // Allocate an exception port that we will use to track our child process
+        err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port);
+        if (err.Fail())
+            return false;
+
+        // Add the ability to send messages on the new exception port
+        err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
+        if (err.Fail())
+            return false;
+
+        // Save the original state of the exception ports for our child process
+        SaveExceptionPortInfo();
+
+        // Set the ability to get all exceptions on this port
+        err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
+        if (err.Fail())
+            return false;
+
+        // Create the exception thread
+        err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this);
+        return err.Success();
+    }
+    else
+    {
+        DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__);
+    }
+    return false;
+}
+
+kern_return_t
+MachTask::ShutDownExcecptionThread()
+{
+    DNBError err;
+
+    err = RestoreExceptionPortInfo();
+
+    // NULL our our exception port and let our exception thread exit
+    mach_port_t exception_port = m_exception_port;
+    m_exception_port = NULL;
+
+    err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX);
+    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
+        err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread);
+
+    err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX);
+    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
+        err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread);
+
+    // Deallocate our exception port that we used to track our child process
+    mach_port_t task_self = mach_task_self ();
+    err = ::mach_port_deallocate (task_self, exception_port);
+    if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
+        err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port);
+    exception_port = NULL;
+
+    return err.Error();
+}
+
+
+void *
+MachTask::ExceptionThread (void *arg)
+{
+    if (arg == NULL)
+        return NULL;
+
+    MachTask *mach_task = (MachTask*) arg;
+    MachProcess *mach_proc = mach_task->Process();
+    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg);
+
+    // We keep a count of the number of consecutive exceptions received so
+    // we know to grab all exceptions without a timeout. We do this to get a
+    // bunch of related exceptions on our exception port so we can process
+    // then together. When we have multiple threads, we can get an exception
+    // per thread and they will come in consecutively. The main loop in this
+    // thread can stop periodically if needed to service things related to this
+    // process.
+    // flag set in the options, so we will wait forever for an exception on
+    // our exception port. After we get one exception, we then will use the
+    // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
+    // exceptions for our process. After we have received the last pending
+    // exception, we will get a timeout which enables us to then notify
+    // our main thread that we have an exception bundle avaiable. We then wait
+    // for the main thread to tell this exception thread to start trying to get
+    // exceptions messages again and we start again with a mach_msg read with
+    // infinite timeout.
+    uint32_t num_exceptions_received = 0;
+    DNBError err;
+    task_t task = mach_task->TaskPort();
+    mach_msg_timeout_t periodic_timeout = 0;
+
+#if defined (__arm__)
+    mach_msg_timeout_t watchdog_elapsed = 0;
+    mach_msg_timeout_t watchdog_timeout = 60 * 1000;
+    pid_t pid = mach_proc->ProcessID();
+    CFReleaser<SBSWatchdogAssertionRef> watchdog;
+
+    if (mach_proc->ProcessUsingSpringBoard())
+    {
+        // Request a renewal for every 60 seconds if we attached using SpringBoard
+        watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60));
+        DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get());
+
+        if (watchdog.get())
+        {
+            ::SBSWatchdogAssertionRenew (watchdog.get());
+
+            CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get());
+            DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval);
+            if (watchdogRenewalInterval > 0.0)
+            {
+                watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000;
+                if (watchdog_timeout > 3000)
+                    watchdog_timeout -= 1000;   // Give us a second to renew our timeout
+                else if (watchdog_timeout > 1000)
+                    watchdog_timeout -= 250;    // Give us a quarter of a second to renew our timeout
+            }
+        }
+        if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout)
+            periodic_timeout = watchdog_timeout;
+    }
+#endif  // #if defined (__arm__)
+
+    while (mach_task->ExceptionPortIsValid())
+    {
+        ::pthread_testcancel ();
+
+        MachException::Message exception_message;
+
+
+        if (num_exceptions_received > 0)
+        {
+            // No timeout, just receive as many exceptions as we can since we already have one and we want
+            // to get all currently available exceptions for this task
+            err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0);
+        }
+        else if (periodic_timeout > 0)
+        {
+            // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms)
+            err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout);
+        }
+        else
+        {
+            // We don't need to parse all current exceptions or stop periodically,
+            // just wait for an exception forever.
+            err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0);
+        }
+
+        if (err.Error() == MACH_RCV_INTERRUPTED)
+        {
+            // If we have no task port we should exit this thread
+            if (!mach_task->ExceptionPortIsValid())
+            {
+                DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled...");
+                break;
+            }
+
+            // Make sure our task is still valid
+            if (MachTask::IsValid(task))
+            {
+                // Task is still ok
+                DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing...");
+                continue;
+            }
+            else
+            {
+                DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
+                mach_proc->SetState(eStateExited);
+                // Our task has died, exit the thread.
+                break;
+            }
+        }
+        else if (err.Error() == MACH_RCV_TIMED_OUT)
+        {
+            if (num_exceptions_received > 0)
+            {
+                // We were receiving all current exceptions with a timeout of zero
+                // it is time to go back to our normal looping mode
+                num_exceptions_received = 0;
+
+                // Notify our main thread we have a complete exception message
+                // bundle available.
+                mach_proc->ExceptionMessageBundleComplete();
+
+                // in case we use a timeout value when getting exceptions...
+                // Make sure our task is still valid
+                if (MachTask::IsValid(task))
+                {
+                    // Task is still ok
+                    DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing...");
+                    continue;
+                }
+                else
+                {
+                    DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
+                    mach_proc->SetState(eStateExited);
+                    // Our task has died, exit the thread.
+                    break;
+                }
+                continue;
+            }
+
+#if defined (__arm__)
+            if (watchdog.get())
+            {
+                watchdog_elapsed += periodic_timeout;
+                if (watchdog_elapsed >= watchdog_timeout)
+                {
+                    DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get());
+                    ::SBSWatchdogAssertionRenew (watchdog.get());
+                    watchdog_elapsed = 0;
+                }
+            }
+#endif
+        }
+        else if (err.Error() != KERN_SUCCESS)
+        {
+            DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now...");
+            // TODO: notify of error?
+        }
+        else
+        {
+            if (exception_message.CatchExceptionRaise())
+            {
+                ++num_exceptions_received;
+                mach_proc->ExceptionMessageReceived(exception_message);
+            }
+        }
+    }
+
+#if defined (__arm__)
+    if (watchdog.get())
+    {
+        // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we
+        // all are up and running on systems that support it. The SBS framework has a #define
+        // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now
+        // so it should still build either way.
+        DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get());
+        ::SBSWatchdogAssertionRelease (watchdog.get());
+    }
+#endif  // #if defined (__arm__)
+
+    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg);
+    return NULL;
+}
+
+
+// So the TASK_DYLD_INFO used to just return the address of the all image infos
+// as a single member called "all_image_info". Then someone decided it would be
+// a good idea to rename this first member to "all_image_info_addr" and add a
+// size member called "all_image_info_size". This of course can not be detected
+// using code or #defines. So to hack around this problem, we define our own
+// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it.
+
+struct hack_task_dyld_info {
+    mach_vm_address_t   all_image_info_addr;
+    mach_vm_size_t      all_image_info_size;
+};
+
+nub_addr_t
+MachTask::GetDYLDAllImageInfosAddress (DNBError& err)
+{
+    struct hack_task_dyld_info dyld_info;
+    mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
+    // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info.
+    // If it is, then make COUNT smaller to match.
+    if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)))
+        count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t));
+
+    task_t task = TaskPortForProcessID (err);
+    if (err.Success())
+    {
+        err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
+        if (err.Success())
+        {
+            // We now have the address of the all image infos structure
+            return dyld_info.all_image_info_addr;
+        }
+    }
+    return INVALID_NUB_ADDRESS;
+}
+
+
+//----------------------------------------------------------------------
+// MachTask::AllocateMemory
+//----------------------------------------------------------------------
+nub_addr_t
+MachTask::AllocateMemory (size_t size, uint32_t permissions)
+{
+    mach_vm_address_t addr;
+    task_t task = TaskPort();
+    if (task == TASK_NULL)
+        return INVALID_NUB_ADDRESS;
+
+    DNBError err;
+    err = ::mach_vm_allocate (task, &addr, size, TRUE);
+    if (err.Error() == KERN_SUCCESS)
+    {
+        // Set the protections:
+        vm_prot_t mach_prot = 0;
+        if (permissions & eMemoryPermissionsReadable)
+            mach_prot |= VM_PROT_READ;
+        if (permissions & eMemoryPermissionsWritable)
+            mach_prot |= VM_PROT_WRITE;
+        if (permissions & eMemoryPermissionsExecutable)
+            mach_prot |= VM_PROT_EXECUTE;
+
+
+        err = ::mach_vm_protect (task, addr, size, 0, mach_prot);
+        if (err.Error() == KERN_SUCCESS)
+        {
+            m_allocations.insert (std::make_pair(addr, size));
+            return addr;
+        }
+        ::mach_vm_deallocate (task, addr, size);
+    }
+    return INVALID_NUB_ADDRESS;
+}
+
+//----------------------------------------------------------------------
+// MachTask::DeallocateMemory
+//----------------------------------------------------------------------
+nub_bool_t
+MachTask::DeallocateMemory (nub_addr_t addr)
+{
+    task_t task = TaskPort();
+    if (task == TASK_NULL)
+        return false;
+
+    // We have to stash away sizes for the allocations...
+    allocation_collection::iterator pos, end = m_allocations.end();
+    for (pos = m_allocations.begin(); pos != end; pos++)
+    {
+        if ((*pos).first == addr)
+        {
+            m_allocations.erase(pos);
+            return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS;
+        }
+        
+    }
+    return false;
+}
+