ProcessMacOSX.cpp   [plain text]


//===-- ProcessMacOSX.cpp ---------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// C Includes
#include <errno.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <spawn.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <unistd.h>

// C++ Includes
#include <algorithm>
#include <map>

// Other libraries and framework includes

#include "lldb/Breakpoint/WatchpointLocation.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/State.h"
#include "lldb/Core/Timer.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/TimeValue.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/TargetList.h"
#include "lldb/Utility/PseudoTerminal.h"

#if defined (__arm__)

#include <CoreFoundation/CoreFoundation.h>
#include <SpringBoardServices/SpringBoardServer.h>
#include <SpringBoardServices/SBSWatchdogAssertion.h>

#endif  // #if defined (__arm__)

// Project includes
#include "ProcessMacOSX.h"
#include "ProcessMacOSXLog.h"
#include "ThreadMacOSX.h"


#if 0
#define DEBUG_LOG(fmt, ...) printf(fmt, ## __VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif

#ifndef MACH_PROCESS_USE_POSIX_SPAWN
#define MACH_PROCESS_USE_POSIX_SPAWN 1
#endif

#ifndef _POSIX_SPAWN_DISABLE_ASLR
#define _POSIX_SPAWN_DISABLE_ASLR       0x0100
#endif

#if defined (__arm__)

static bool
IsSBProcess (lldb::pid_t pid)
{
    bool opt_runningApps = true;
    bool opt_debuggable = false;

    CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable));
    if (sbsAppIDs.get() != NULL)
    {
        CFIndex count = ::CFArrayGetCount (sbsAppIDs.get());
        CFIndex i = 0;
        for (i = 0; i < count; i++)
        {
            CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i);

            // Get the process id for the app (if there is one)
            lldb::pid_t sbs_pid = LLDB_INVALID_PROCESS_ID;
            if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &sbs_pid) == TRUE)
            {
                if (sbs_pid == pid)
                    return true;
            }
        }
    }
    return false;
}


#endif  // #if defined (__arm__)

using namespace lldb;
using namespace lldb_private;
//
//void *
//ProcessMacOSX::WaitForChildProcessToExit (void *pid_ptr)
//{
//    const lldb::pid_t pid = *((lldb::user_id_t *)pid_ptr);
//
//    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD));
//
//    if (log)
//        log->Printf ("ProcessMacOSX::%s (arg = %p) thread starting...", __FUNCTION__, pid_ptr);
//
//    int status = -1;
//
//    while (1)
//    {
//        if (log)
//            log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0)...", pid, &status);
//
//        lldb::pid_t return_pid = ::waitpid (pid, &status, 0);
//
//        if (return_pid < 0)
//        {
//            if (log)
//                log->Printf("%s ::waitpid (pid = %i, stat_loc = %p, options = 0) => errno = %i, status = 0x%8.8x", pid, &status, errno, status);
//            break;
//        }
//
//        bool set_exit_status = false;
//        if (WIFSTOPPED(status))
//        {
//            if (log)
//                log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (STOPPED)", pid, &status, return_pid, status);
//        }
//        else if (WIFEXITED(status))
//        {
//            set_exit_status = true;
//            if (log)
//                log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (EXITED)", pid, &status, return_pid, status);
//        }
//        else if (WIFSIGNALED(status))
//        {
//            set_exit_status = true;
//            if (log)
//                log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (SIGNALED)", pid, &status, return_pid, status);
//        }
//        else
//        {
//            if (log)
//                log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x", pid, &status, return_pid, status);
//        }
//
//        if (set_exit_status)
//        {
//            // Try and deliver the news to the process if it is still around
//            TargetSP target_sp(TargetList::SharedList().FindTargetWithProcessID (return_pid));
//            if (target_sp.get())
//            {
//                ProcessMacOSX *process = dynamic_cast<ProcessMacOSX*>(target_sp->GetProcess().get());
//                if (process)
//                {
//                    process->SetExitStatus (status);
//                    if (log)
//                        log->Printf("Setting exit status of %i to 0x%8.8x", pid, status);
//                    process->Task().ShutDownExceptionThread();
//                }
//            }
//            // Break out of the loop and return.
//            break;
//        }
//    }
//
//    if (log)
//        log->Printf ("ProcessMacOSX::%s (arg = %p) thread exiting...", __FUNCTION__, pid_ptr);
//
//    return NULL;
//}
//

const char *
ProcessMacOSX::GetPluginNameStatic()
{
    return "macosx-user";
}

const char *
ProcessMacOSX::GetPluginDescriptionStatic()
{
    return "Native MacOSX user process debugging plug-in.";
}

void
ProcessMacOSX::Terminate()
{
    PluginManager::UnregisterPlugin (ProcessMacOSX::CreateInstance);
}


Process*
ProcessMacOSX::CreateInstance (Target &target, Listener &listener)
{
    ProcessMacOSX::Initialize();

    return new ProcessMacOSX (target, listener);
}

bool
ProcessMacOSX::CanDebug(Target &target)
{
    // For now we are just making sure the file exists for a given module
    ModuleSP exe_module_sp(target.GetExecutableModule());
    if (exe_module_sp.get())
        return exe_module_sp->GetFileSpec().Exists();
    return false;
}

//----------------------------------------------------------------------
// ProcessMacOSX constructor
//----------------------------------------------------------------------
ProcessMacOSX::ProcessMacOSX(Target& target, Listener &listener) :
    Process (target, listener),
    m_stdio_ours (false),
    m_child_stdin (-1),
    m_child_stdout (-1),
    m_child_stderr (-1),
    m_task (this),
    m_flags (eFlagsNone),
    m_stdio_thread (LLDB_INVALID_HOST_THREAD),
    m_monitor_thread (LLDB_INVALID_HOST_THREAD),
    m_stdio_mutex (Mutex::eMutexTypeRecursive),
    m_stdout_data (),
    m_exception_messages (),
    m_exception_messages_mutex (Mutex::eMutexTypeRecursive),
    m_arch_spec ()
{
}

//----------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------
ProcessMacOSX::~ProcessMacOSX()
{
//  m_mach_process.UnregisterNotificationCallbacks (this);
    Clear();
    
}

//----------------------------------------------------------------------
// PluginInterface
//----------------------------------------------------------------------
const char *
ProcessMacOSX::GetPluginName()
{
    return "Process debugging plug-in for MacOSX";
}

const char *
ProcessMacOSX::GetShortPluginName()
{
    return GetPluginNameStatic();
}

uint32_t
ProcessMacOSX::GetPluginVersion()
{
    return 1;
}

//----------------------------------------------------------------------
// Process Control
//----------------------------------------------------------------------
Error
ProcessMacOSX::DoLaunch
(
    Module* module,
    char const *argv[],
    char const *envp[],
    uint32_t flags,
    const char *stdin_path,
    const char *stdout_path,
    const char *stderr_path,
    const char *working_dir
)
{
//  ::LogSetBitMask (PD_LOG_DEFAULT);
//  ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD);
//  ::LogSetLogFile ("/dev/stdout");

    Error error;
    ObjectFile * object_file = module->GetObjectFile();
    if (object_file)
    {
        ArchSpec arch_spec(module->GetArchitecture());

        // Set our user ID to our process ID.
        SetID (LaunchForDebug(argv[0], argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, eLaunchDefault, flags, error));
    }
    else
    {
        // Set our user ID to an invalid process ID.
        SetID (LLDB_INVALID_PROCESS_ID);
        error.SetErrorToGenericError ();
        error.SetErrorStringWithFormat("Failed to get object file from '%s' for arch %s.\n", 
                                       module->GetFileSpec().GetFilename().AsCString(), 
                                       module->GetArchitecture().GetArchitectureName());
    }

    // Return the process ID we have
    return error;
}

Error
ProcessMacOSX::DoAttachToProcessWithID (lldb::pid_t attach_pid)
{
    Error error;

    // Clear out and clean up from any current state
    Clear();
    // HACK: require arch be set correctly at the target level until we can
    // figure out a good way to determine the arch of what we are attaching to
    m_arch_spec = m_target.GetArchitecture();

    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
    if (attach_pid != LLDB_INVALID_PROCESS_ID)
    {
        SetID(attach_pid);
        // Let ourselves know we are going to be using SBS if the correct flag bit is set...
#if defined (__arm__)
        if (IsSBProcess(pid))
            m_flags |= eFlagsUsingSBS;
#endif

        if (Task().GetTaskPortForProcessID(error) == TASK_NULL)
        {
            if (log)
                log->Printf ("error attaching to pid %i: %s", GetID(), error.AsCString());

        }
        else
        {
            Task().StartExceptionThread(error);

            if (error.Success())
            {
                errno = 0;
                if (::ptrace (PT_ATTACHEXC, GetID(), 0, 0) == 0)
                {
                    m_flags.Set (eFlagsAttached);
                    // Sleep a bit to let the exception get received and set our process status
                    // to stopped.
                    ::usleep(250000);

                    if (log)
                        log->Printf ("successfully attached to pid %d", GetID());
                    return error;
                }
                else
                {
                    error.SetErrorToErrno();
                    if (log)
                        log->Printf ("error: failed to attach to pid %d", GetID());
                }
            }
            else
            {
                if (log)
                    log->Printf ("error: failed to start exception thread for pid %d: %s", GetID(), error.AsCString());
            }

        }
    }
    SetID (LLDB_INVALID_PROCESS_ID);
    if (error.Success())
        error.SetErrorStringWithFormat ("failed to attach to pid %d", attach_pid);
    return error;
}

Error
ProcessMacOSX::WillLaunchOrAttach ()
{
    return Error();
}


Error
ProcessMacOSX::WillLaunch (Module* module)
{
    return WillLaunchOrAttach ();
}

void
ProcessMacOSX::DidLaunchOrAttach ()
{
    if (GetID() != LLDB_INVALID_PROCESS_ID)
    {
        Module * exe_module = GetTarget().GetExecutableModule ().get();
        assert (exe_module);

        // Install a signal handler so we can catch when our child process
        // dies and set the exit status correctly.

        m_monitor_thread = Host::StartMonitoringChildProcess (Process::SetProcessExitStatus, NULL, GetID(), false);
    }
}

void
ProcessMacOSX::DidLaunch ()
{
    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::DidLaunch()");
    DidLaunchOrAttach ();
}

void
ProcessMacOSX::DidAttach ()
{
    DidLaunchOrAttach ();
}

Error
ProcessMacOSX::WillAttachToProcessWithID (lldb::pid_t pid)
{
    return WillLaunchOrAttach ();
}

Error
ProcessMacOSX::WillAttachToProcessWithName (const char *process_name, bool wait_for_launch) 
{
    return WillLaunchOrAttach ();
}


Error
ProcessMacOSX::DoResume ()
{
    Error error;
    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::Resume()");
    const StateType state = m_private_state.GetValue();

    if (CanResume(state))
    {
        error = PrivateResume(LLDB_INVALID_THREAD_ID);
    }
    else if (state == eStateRunning)
    {
        ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x is running, ignoring...", Task().GetTaskPort());
    }
    else
    {
        error.SetErrorStringWithFormat("task 0x%x can't continue, ignoring...", Task().GetTaskPort());
        ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x can't continue, ignoring...", Task().GetTaskPort());
    }
    return error;
}

uint32_t
ProcessMacOSX::UpdateThreadListIfNeeded ()
{
    // locker will keep a mutex locked until it goes out of scope
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD));
    if (log && log->GetMask().Test(PD_LOG_VERBOSE))
        log->Printf ("ProcessMacOSX::%s (pid = %4.4x)", __FUNCTION__, GetID());

    const uint32_t stop_id = GetStopID();
    if (m_thread_list.GetSize(false) == 0 || stop_id != m_thread_list.GetStopID())
    {
        // Update the thread list's stop id immediately so we don't recurse into this function.
        thread_array_t thread_list = NULL;
        mach_msg_type_number_t thread_list_count = 0;
        task_t task = Task().GetTaskPort();
        Error err(::task_threads (task, &thread_list, &thread_list_count), eErrorTypeMachKernel);

        if (log || err.Fail())
            err.PutToLog(log.get(), "::task_threads ( task = 0x%4.4x, thread_list => %p, thread_list_count => %u )", task, thread_list, thread_list_count);

        if (err.GetError() == KERN_SUCCESS && thread_list_count > 0)
        {
            ThreadList curr_thread_list (this);
            curr_thread_list.SetStopID(stop_id);

            size_t idx;
            // Iterator through the current thread list and see which threads
            // we already have in our list (keep them), which ones we don't
            // (add them), and which ones are not around anymore (remove them).
            for (idx = 0; idx < thread_list_count; ++idx)
            {
                const lldb::tid_t tid = thread_list[idx];
                ThreadSP thread_sp(GetThreadList().FindThreadByID (tid, false));
                if (thread_sp.get() == NULL)
                    thread_sp.reset (new ThreadMacOSX (*this, tid));
                curr_thread_list.AddThread(thread_sp);
            }

            m_thread_list = curr_thread_list;

            // Free the vm memory given to us by ::task_threads()
            vm_size_t thread_list_size = (vm_size_t) (thread_list_count * sizeof (lldb::tid_t));
            ::vm_deallocate (::mach_task_self(),
                             (vm_address_t)thread_list,
                             thread_list_size);
        }
    }
    return GetThreadList().GetSize(false);
}


void
ProcessMacOSX::RefreshStateAfterStop ()
{
    // If we are attaching, let our dynamic loader plug-in know so it can get
    // an initial list of shared libraries.

    // We must be attaching if we don't already have a valid architecture
    if (!m_arch_spec.IsValid())
    {
        Module *exe_module = GetTarget().GetExecutableModule().get();
        if (exe_module)
            m_arch_spec = exe_module->GetArchitecture();
    }
    // Discover new threads:
    UpdateThreadListIfNeeded ();

    // Let all threads recover from stopping and do any clean up based
    // on the previous thread state (if any).
    m_thread_list.RefreshStateAfterStop();

   // Let each thread know of any exceptions
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS));
    task_t task = Task().GetTaskPort();
    size_t i;
    for (i=0; i<m_exception_messages.size(); ++i)
    {
        // Let the thread list figure use the ProcessMacOSX to forward all exceptions
        // on down to each thread.
        if (m_exception_messages[i].state.task_port == task)
        {
            ThreadSP thread_sp(m_thread_list.FindThreadByID(m_exception_messages[i].state.thread_port));
            if (thread_sp.get())
            {
                ThreadMacOSX *macosx_thread = (ThreadMacOSX *)thread_sp.get();
                macosx_thread->NotifyException (m_exception_messages[i].state);
            }
        }
        if (log)
            m_exception_messages[i].PutToLog(log.get());
    }

}

Error
ProcessMacOSX::DoHalt (bool &caused_stop)
{
    caused_stop = true;
    return Signal (SIGSTOP);
}

Error
ProcessMacOSX::WillDetach ()
{
    Error error;
    const StateType state = m_private_state.GetValue();

    if (IsRunning(state))
    {
        error.SetErrorToGenericError();
        error.SetErrorString("Process must be stopped in order to detach.");
    }
    return error;
}

Error
ProcessMacOSX::DoSIGSTOP (bool clear_all_breakpoints)
{
    Error error;
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));

    if (log)
        log->Printf ("ProcessMacOSX::DoSIGSTOP()");
    EventSP event_sp;
    TimeValue timeout_time;

    StateType state = m_private_state.GetValue();

    lldb::pid_t pid = GetID();

    if (IsRunning(state))
    {
        // If our process is running, we need to SIGSTOP it so we can detach.
        if (log)
            log->Printf ("ProcessMacOSX::DoDestroy() - kill (%i, SIGSTOP)", pid);

        // Send the SIGSTOP and wait a few seconds for it to stop

        // Pause the Private State Thread so it doesn't intercept the events we need to wait for.
        PausePrivateStateThread();
        // I don't think this is right.  Halt should just stop the process, and then whoever called halt should
        // arrange whatever they need to with the thread plans.
        
        //m_thread_list.DiscardThreadPlans();

        // First jettison all the current thread plans, since we want to make sure it
        // really just stops.

        if (::kill (pid, SIGSTOP) == 0)
            error.Clear();
        else
            error.SetErrorToErrno();

        if (error.Fail())
            error.PutToLog(log.get(), "::kill (pid = %i, SIGSTOP)", pid);

        timeout_time = TimeValue::Now();
        timeout_time.OffsetWithSeconds(2);

        state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp);

        // Resume the private state thread at this point.
        ResumePrivateStateThread();

        if (!StateIsStoppedState (state))
        {
            log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
            if (log)
                log->Printf("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP");
           return error;
        }
        if (clear_all_breakpoints)
            GetTarget().DisableAllBreakpoints();
    }
    else if (!HasExited(state))
    {
        if (clear_all_breakpoints)
            GetTarget().DisableAllBreakpoints();

//        const uint32_t num_threads = GetNumThreads();
//        for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx)
//        {
//            Thread *thread = GetThreadAtIndex(thread_idx);
//            thread->SetResumeState(eStateRunning);
//            if (thread_idx == 0)
//                thread->SetResumeSignal(SIGSTOP);
//        }

        // Our process was stopped, so resume it and then SIGSTOP it so we can
        // detach.
        // But discard all the thread plans first, so we don't keep going because we
        // are in mid-plan.

        // Pause the Private State Thread so it doesn't intercept the events we need to wait for.
        PausePrivateStateThread();

        m_thread_list.DiscardThreadPlans();

        if (::kill (pid, SIGSTOP) == 0)
            error.Clear();
        else
            error.SetErrorToErrno();

        log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
        if (log || error.Fail())
            error.PutToLog(log.get(), "ProcessMacOSX::DoSIGSTOP() ::kill (pid = %i, SIGSTOP)", pid);

        error = PrivateResume(LLDB_INVALID_THREAD_ID);

        // Wait a few seconds for our process to resume
        timeout_time = TimeValue::Now();
        timeout_time.OffsetWithSeconds(2);
        state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp);

        // Make sure the process resumed
        if (StateIsStoppedState (state))
        {
            log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
            if (log)
                log->Printf ("ProcessMacOSX::DoSIGSTOP() couldn't resume process, state = %s", StateAsCString(state));
            error.SetErrorStringWithFormat("ProcessMacOSX::DoSIGSTOP() couldn't resume process, state = %s", StateAsCString(state));
        }
        else
        {
            // Send the SIGSTOP and wait a few seconds for it to stop
            timeout_time = TimeValue::Now();
            timeout_time.OffsetWithSeconds(2);
            state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp);
            if (!StateIsStoppedState (state))
            {
                log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
                if (log)
                    log->Printf("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP");
                error.SetErrorString("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP");
            }
        }
        // Resume the private state thread at this point.
        ResumePrivateStateThread();
    }

    return error;
}

Error
ProcessMacOSX::DoDestroy ()
{
    Error error;
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
    if (log)
        log->Printf ("ProcessMacOSX::DoDestroy()");

    error = DoSIGSTOP (true);
    if (error.Success())
    {
        StopSTDIOThread(true);

        if (log)
            log->Printf ("ProcessMacOSX::DoDestroy() DoSIGSTOP succeeded");
        const StateType state = m_private_state.GetValue();
        // Scope for "locker" so we can reply to all of our exceptions (the SIGSTOP
        // exception).
        {
            Mutex::Locker locker(m_exception_messages_mutex);
            ReplyToAllExceptions();
        }
        if (log)
            log->Printf ("ProcessMacOSX::DoDestroy() replied to all exceptions");

        // Shut down the exception thread and cleanup our exception remappings
        Task().ShutDownExceptionThread();

        if (log)
            log->Printf ("ProcessMacOSX::DoDestroy() exception thread has been shutdown");

        if (!HasExited(state))
        {
            lldb::pid_t pid = GetID();

            // Detach from our process while we are stopped.
            errno = 0;

            // Detach from our process
            ::ptrace (PT_KILL, pid, 0, 0);

            error.SetErrorToErrno();

            if (log || error.Fail())
                error.PutToLog (log.get(), "::ptrace (PT_KILL, %u, 0, 0)", pid);

            // Resume our task and let the SIGKILL do its thing. The thread named
            // "ProcessMacOSX::WaitForChildProcessToExit(void*)" will catch the
            // process exiting, so we don't need to set our state to exited in this
            // function.
            Task().Resume();
        }

        // NULL our task out as we have already restored all exception ports
        Task().Clear();

        // Clear out any notion of the process we once were
        Clear();
    }
    return error;
}

//------------------------------------------------------------------
// Process Queries
//------------------------------------------------------------------

bool
ProcessMacOSX::IsAlive ()
{
    return MachTask::IsValid (Task().GetTaskPort());
}

lldb::addr_t
ProcessMacOSX::GetImageInfoAddress()
{
    return Task().GetDYLDAllImageInfosAddress();
}

//------------------------------------------------------------------
// Process Memory
//------------------------------------------------------------------

size_t
ProcessMacOSX::DoReadMemory (lldb::addr_t addr, void *buf, size_t size, Error& error)
{
    return Task().ReadMemory(addr, buf, size, error);
}

size_t
ProcessMacOSX::DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size, Error& error)
{
    return Task().WriteMemory(addr, buf, size, error);
}

lldb::addr_t
ProcessMacOSX::DoAllocateMemory (size_t size, uint32_t permissions, Error& error)
{
    return Task().AllocateMemory (size, permissions, error);
}

Error
ProcessMacOSX::DoDeallocateMemory (lldb::addr_t ptr)
{
    return Task().DeallocateMemory (ptr);
}

//------------------------------------------------------------------
// Process STDIO
//------------------------------------------------------------------

size_t
ProcessMacOSX::GetSTDOUT (char *buf, size_t buf_size, Error &error)
{
    error.Clear();
    Mutex::Locker locker(m_stdio_mutex);
    size_t bytes_available = m_stdout_data.size();
    if (bytes_available > 0)
    {
        ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size);
        if (bytes_available > buf_size)
        {
            memcpy(buf, m_stdout_data.c_str(), buf_size);
            m_stdout_data.erase(0, buf_size);
            bytes_available = buf_size;
        }
        else
        {
            memcpy(buf, m_stdout_data.c_str(), bytes_available);
            m_stdout_data.clear();

            //ResetEventBits(eBroadcastBitSTDOUT);
        }
    }
    return bytes_available;
}

size_t
ProcessMacOSX::GetSTDERR (char *buf, size_t buf_size, Error &error)
{
    error.Clear();
    return 0;
}

size_t
ProcessMacOSX::PutSTDIN (const char *buf, size_t buf_size, Error &error)
{
    if (m_child_stdin == -1)
    {
        error.SetErrorString ("Invalid child stdin handle.");
    }
    else
    {
        ssize_t bytes_written = ::write (m_child_stdin, buf, buf_size);
        if (bytes_written == -1)
            error.SetErrorToErrno();
        else
        {
            error.Clear();
            return bytes_written;
        }
    }
    return 0;
}

Error
ProcessMacOSX::EnableBreakpoint (BreakpointSite *bp_site)
{
    Error error;
    assert (bp_site != NULL);
    const lldb::addr_t addr = bp_site->GetLoadAddress();
    const lldb::user_id_t site_id = bp_site->GetID();

    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS));
    if (log)
        log->Printf ("ProcessMacOSX::EnableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr);

    if (bp_site->IsEnabled())
    {
        if (log)
            log->Printf ("ProcessMacOSX::EnableBreakpoint (site_id = %d) addr = 0x%8.8llx -- SUCCESS (already enabled)", site_id, (uint64_t)addr);
        return error;
    }

    if (bp_site->HardwarePreferred())
    {
        // FIXME: This code doesn't make sense.  Breakpoint sites don't really have single ThreadID's, since one site could be
        // owned by a number of Locations, each with a different Thread ID.  So either this should run over all the Locations and
        // set it for all threads owned by those locations, or set it for all threads, and let the thread specific code sort it out.
        
//        ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp_site->GetThreadID()).get();
//        if (thread)
//        {
//            bp_site->SetHardwareIndex (thread->SetHardwareBreakpoint(bp_site));
//            if (bp_site->IsHardware())
//            {
//                bp_site->SetEnabled(true);
//                return error;
//            }
//        }
    }

    // Just let lldb::Process::EnableSoftwareBreakpoint() handle everything...
    return EnableSoftwareBreakpoint (bp_site);
}

Error
ProcessMacOSX::DisableBreakpoint (BreakpointSite *bp_site)
{
    Error error;
    assert (bp_site != NULL);
    const lldb::addr_t addr = bp_site->GetLoadAddress();
    const lldb::user_id_t site_id = bp_site->GetID();

    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS));
    if (log)
        log->Printf ("ProcessMacOSX::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr);

    if (bp_site->IsHardware())
    {
        error.SetErrorString("hardware breakpoints are no supported");
        return error;
    }

    // Just let lldb::Process::EnableSoftwareBreakpoint() handle everything...
    return DisableSoftwareBreakpoint (bp_site);
}

Error
ProcessMacOSX::EnableWatchpoint (WatchpointLocation *wp)
{
    Error error;
    if (wp)
    {
        lldb::user_id_t watchID = wp->GetID();
        lldb::addr_t addr = wp->GetLoadAddress();
        LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS));
        if (log)
            log->Printf ("ProcessMacOSX::EnableWatchpoint(watchID = %d)", watchID);
        if (wp->IsEnabled())
        {
            if (log)
                log->Printf("ProcessMacOSX::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr);
            return error;
        }
        else
        {
            // Watchpoints aren't supported at present.
            error.SetErrorString("Watchpoints aren't currently supported.");
        }
    }
    return error;
}

Error
ProcessMacOSX::DisableWatchpoint (WatchpointLocation *wp)
{
    Error error;
    if (wp)
    {
        lldb::user_id_t watchID = wp->GetID();

        LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS));

        lldb::addr_t addr = wp->GetLoadAddress();
        if (log)
            log->Printf ("ProcessMacOSX::DisableWatchpoint (watchID = %d) addr = 0x%8.8llx", watchID, (uint64_t)addr);

        if (wp->IsHardware())
        {
            error.SetErrorString("Watchpoints aren't currently supported.");
        }
        // TODO: clear software watchpoints if we implement them
        error.SetErrorToGenericError();
    }
    else
    {
        error.SetErrorString("Watchpoint location argument was NULL.");
    }
    return error;
}


static ProcessMacOSX::CreateArchCalback
ArchCallbackMap(const ArchSpec& arch_spec, ProcessMacOSX::CreateArchCalback callback, bool add )
{
    // We must wrap the "g_arch_map" file static in a function to avoid
    // any global constructors so we don't get a build verification error
    typedef std::multimap<ArchSpec, ProcessMacOSX::CreateArchCalback> ArchToProtocolMap;
    static ArchToProtocolMap g_arch_map;

    if (add)
    {
        g_arch_map.insert(std::make_pair(arch_spec, callback));
        return callback;
    }
    else
    {
        ArchToProtocolMap::const_iterator pos = g_arch_map.find(arch_spec);
        if (pos != g_arch_map.end())
        {
            return pos->second;
        }
    }
    return NULL;
}

void
ProcessMacOSX::AddArchCreateCallback(const ArchSpec& arch_spec, CreateArchCalback callback)
{
    ArchCallbackMap (arch_spec, callback, true);
}

ProcessMacOSX::CreateArchCalback
ProcessMacOSX::GetArchCreateCallback()
{
    return ArchCallbackMap (m_arch_spec, NULL, false);
}

void
ProcessMacOSX::Clear()
{
    // Clear any cached thread list while the pid and task are still valid

    Task().Clear();
    // Now clear out all member variables
    CloseChildFileDescriptors();

    m_flags = eFlagsNone;
    m_thread_list.Clear();
    {
        Mutex::Locker locker(m_exception_messages_mutex);
        m_exception_messages.clear();
    }

    if (IS_VALID_LLDB_HOST_THREAD(m_monitor_thread))
    {
        Host::ThreadCancel (m_monitor_thread, NULL);
        thread_result_t thread_result;
        Host::ThreadJoin (m_monitor_thread, &thread_result, NULL);
        m_monitor_thread = LLDB_INVALID_HOST_THREAD;
    }

}

bool
ProcessMacOSX::StartSTDIOThread()
{
    if (IS_VALID_LLDB_HOST_THREAD(m_stdio_thread))
        return true;

    // If we created and own the child STDIO file handles, then we track the
    // STDIO ourselves, else we let whomever owns these file handles track
    // the IO themselves.
    if (m_stdio_ours)
    {
        ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s ( )", __FUNCTION__);
        // Create the thread that watches for the child STDIO
        m_stdio_thread = Host::ThreadCreate ("<lldb.process.process-macosx.stdio>", ProcessMacOSX::STDIOThread, this, NULL);
        return IS_VALID_LLDB_HOST_THREAD(m_stdio_thread);
    }
    return false;
}


void
ProcessMacOSX::StopSTDIOThread(bool close_child_fds)
{
    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s ( )", __FUNCTION__);
    // Stop the stdio thread
    if (IS_VALID_LLDB_HOST_THREAD(m_stdio_thread))
    {
        Host::ThreadCancel (m_stdio_thread, NULL);
        thread_result_t result = NULL;
        Host::ThreadJoin (m_stdio_thread, &result, NULL);
        if (close_child_fds)
            CloseChildFileDescriptors();
        else
        {
            // We may have given up control of these file handles, so just
            // set them to invalid values so the STDIO thread can exit when
            // we interrupt it with pthread_cancel...
            m_child_stdin = -1;
            m_child_stdout = -1;
            m_child_stderr = -1;
        }
    }
}


void *
ProcessMacOSX::STDIOThread(void *arg)
{
    ProcessMacOSX *proc = (ProcessMacOSX*) arg;

    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
    if (log)
        log->Printf ("ProcessMacOSX::%s (arg = %p) thread starting...", __FUNCTION__, arg);

    // We start use a base and more options so we can control if we
    // are currently using a timeout on the mach_msg. 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 thread loop
    // will start by calling mach_msg to without having the MACH_RCV_TIMEOUT
    // 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 available. 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.
    Error err;
    int stdout_fd = proc->GetStdoutFileDescriptor();
    int stderr_fd = proc->GetStderrFileDescriptor();
    if (stdout_fd == stderr_fd)
        stderr_fd = -1;

    while (stdout_fd >= 0 || stderr_fd >= 0)
    {
        //::pthread_testcancel ();

        fd_set read_fds;
        FD_ZERO (&read_fds);
        if (stdout_fd >= 0)
            FD_SET (stdout_fd, &read_fds);
        if (stderr_fd >= 0)
            FD_SET (stderr_fd, &read_fds);
        int nfds = std::max<int>(stdout_fd, stderr_fd) + 1;

        int num_set_fds = select (nfds, &read_fds, NULL, NULL, NULL);
        log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
        if (log)
            log->Printf("select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);

        if (num_set_fds < 0)
        {
            int select_errno = errno;
            if (log)
            {
                err.SetError (select_errno, eErrorTypePOSIX);
                err.LogIfError(log.get(), "select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
            }

            switch (select_errno)
            {
            case EAGAIN:    // The kernel was (perhaps temporarily) unable to allocate the requested number of file descriptors, or we have non-blocking IO
                break;
            case EBADF:     // One of the descriptor sets specified an invalid descriptor.
                return NULL;
                break;
            case EINTR:     // A signal was delivered before the time limit expired and before any of the selected events occurred.
            case EINVAL:    // The specified time limit is invalid. One of its components is negative or too large.
            default:        // Other unknown error
                break;
            }
        }
        else if (num_set_fds == 0)
        {
        }
        else
        {
            char s[1024];
            s[sizeof(s)-1] = '\0';  // Ensure we have NULL termination
            int bytes_read = 0;
            if (stdout_fd >= 0 && FD_ISSET (stdout_fd, &read_fds))
            {
                do
                {
                    bytes_read = ::read (stdout_fd, s, sizeof(s)-1);
                    if (bytes_read < 0)
                    {
                        int read_errno = errno;
                        if (log)
                            log->Printf("read (stdout_fd, ) => %d   errno: %d (%s)", bytes_read, read_errno, strerror(read_errno));
                    }
                    else if (bytes_read == 0)
                    {
                        // EOF...
                        if (log)
                            log->Printf("read (stdout_fd, ) => %d  (reached EOF for child STDOUT)", bytes_read);
                        stdout_fd = -1;
                    }
                    else if (bytes_read > 0)
                    {
                        proc->AppendSTDOUT(s, bytes_read);
                    }

                } while (bytes_read > 0);
            }

            if (stderr_fd >= 0 && FD_ISSET (stderr_fd, &read_fds))
            {
                do
                {
                    bytes_read = ::read (stderr_fd, s, sizeof(s)-1);
                    if (bytes_read < 0)
                    {
                        int read_errno = errno;
                        if (log)
                            log->Printf("read (stderr_fd, ) => %d   errno: %d (%s)", bytes_read, read_errno, strerror(read_errno));
                    }
                    else if (bytes_read == 0)
                    {
                        // EOF...
                        if (log)
                            log->Printf("read (stderr_fd, ) => %d  (reached EOF for child STDERR)", bytes_read);
                        stderr_fd = -1;
                    }
                    else if (bytes_read > 0)
                    {
                        proc->AppendSTDOUT(s, bytes_read);
                    }

                } while (bytes_read > 0);
            }
        }
    }

    log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
    if (log)
        log->Printf("ProcessMacOSX::%s (%p): thread exiting...", __FUNCTION__, arg);

    return NULL;
}

Error
ProcessMacOSX::DoSignal (int signal)
{
    Error error;
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
    if (log)
        log->Printf ("ProcessMacOSX::DoSignal (signal = %d)", signal);
    if (::kill (GetID(), signal) != 0)
    {
        error.SetErrorToErrno();
        error.LogIfError(log.get(), "ProcessMacOSX::DoSignal (%d)", signal);
    }
    return error;
}


Error
ProcessMacOSX::DoDetach()
{
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
    if (log)
        log->Printf ("ProcessMacOSX::DoDetach()");

    Error error (DoSIGSTOP (true));
    if (error.Success())
    {
        CloseChildFileDescriptors ();

        // Scope for "locker" so we can reply to all of our exceptions (the SIGSTOP
        // exception).
        {
            Mutex::Locker locker(m_exception_messages_mutex);
            ReplyToAllExceptions();
        }

        // Shut down the exception thread and cleanup our exception remappings
        Task().ShutDownExceptionThread();

        lldb::pid_t pid = GetID();

        // Detach from our process while we are stopped.
        errno = 0;

        // Detach from our process
        ::ptrace (PT_DETACH, pid, (caddr_t)1, 0);

        error.SetErrorToErrno();

        if (log || error.Fail())
            error.PutToLog(log.get(), "::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid);

        // Resume our task
        Task().Resume();

        // NULL our task out as we have already restored all exception ports
        Task().Clear();

        // Clear out any notion of the process we once were
        Clear();

        SetPrivateState (eStateDetached);
    }
    return error;
}



Error
ProcessMacOSX::ReplyToAllExceptions()
{
    Error error;
    Mutex::Locker locker(m_exception_messages_mutex);
    if (m_exception_messages.empty() == false)
    {
        LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS));

        MachException::Message::iterator pos;
        MachException::Message::iterator begin = m_exception_messages.begin();
        MachException::Message::iterator end = m_exception_messages.end();
        for (pos = begin; pos != end; ++pos)
        {
            int resume_signal = -1;
            ThreadSP thread_sp = m_thread_list.FindThreadByID(pos->state.thread_port);
            if (thread_sp.get())
                resume_signal = thread_sp->GetResumeSignal();
            if (log)
                log->Printf ("Replying to exception %d, tid = 0x%4.4x, resume_signal = %i", std::distance(begin, pos), thread_sp->GetID(), resume_signal);
            Error curr_error (pos->Reply (Task().GetTaskPort(), GetID(), resume_signal));

            // Only report the first error
            if (curr_error.Fail() && error.Success())
                error = curr_error;

            error.LogIfError(log.get(), "Error replying to exception");
        }

        // Erase all exception message as we should have used and replied
        // to them all already.
        m_exception_messages.clear();
    }
    return error;
}


Error
ProcessMacOSX::PrivateResume (lldb::tid_t tid)
{
    
    Mutex::Locker locker(m_exception_messages_mutex);
    Error error (ReplyToAllExceptions());

    // Let the thread prepare to resume and see if any threads want us to
    // step over a breakpoint instruction (ProcessWillResume will modify
    // the value of stepOverBreakInstruction).
    //StateType process_state = m_thread_list.ProcessWillResume(this);

    // Set our state accordingly
    SetPrivateState (eStateRunning);

    // Now resume our task.
    error = Task().Resume();
    return error;
}

// Called by the exception thread when an exception has been received from
// our process. The exception message is completely filled and the exception
// data has already been copied.
void
ProcessMacOSX::ExceptionMessageReceived (const MachException::Message& exceptionMessage)
{
    Mutex::Locker locker(m_exception_messages_mutex);

    if (m_exception_messages.empty())
        Task().Suspend();

    ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "ProcessMacOSX::ExceptionMessageReceived ( )");

    // Use a locker to automatically unlock our mutex in case of exceptions
    // Add the exception to our internal exception stack
    m_exception_messages.push_back(exceptionMessage);
}


//bool
//ProcessMacOSX::GetProcessInfo (struct kinfo_proc* proc_info)
//{
//  int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, GetID() };
//  size_t buf_size = sizeof(struct kinfo_proc);
//
//  if (::sysctl (mib, (unsigned)(sizeof(mib)/sizeof(int)), &proc_info, &buf_size, NULL, 0) == 0)
//      return buf_size > 0;
//
//  return false;
//}
//
//
void
ProcessMacOSX::ExceptionMessageBundleComplete()
{
    // We have a complete bundle of exceptions for our child process.
    Mutex::Locker locker(m_exception_messages_mutex);
    ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s: %d exception messages.", __PRETTY_FUNCTION__, m_exception_messages.size());
    if (!m_exception_messages.empty())
    {
        SetPrivateState (eStateStopped);
    }
    else
    {
        ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s empty exception messages bundle.", __PRETTY_FUNCTION__, m_exception_messages.size());
    }
}

bool
ProcessMacOSX::ReleaseChildFileDescriptors ( int *stdin_fileno, int *stdout_fileno, int *stderr_fileno )
{
    if (stdin_fileno)
        *stdin_fileno = m_child_stdin;
    if (stdout_fileno)
        *stdout_fileno = m_child_stdout;
    if (stderr_fileno)
        *stderr_fileno = m_child_stderr;
    // Stop the stdio thread if we have one, but don't have it close the child
    // file descriptors since we are giving control of these descriptors to the
    // caller
    bool close_child_fds = false;
    StopSTDIOThread(close_child_fds);
    return true;
}

void
ProcessMacOSX::AppendSTDOUT (const char* s, size_t len)
{
    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s (<%d> %s) ...", __FUNCTION__, len, s);
    Mutex::Locker locker(m_stdio_mutex);
    m_stdout_data.append(s, len);

    // FIXME: Make a real data object for this and put it out.
    BroadcastEventIfUnique (eBroadcastBitSTDOUT);
}

lldb::pid_t
ProcessMacOSX::LaunchForDebug
(
    const char *path,
    char const *argv[],
    char const *envp[],
    ArchSpec& arch_spec,
    const char *stdin_path,
    const char *stdout_path,
    const char *stderr_path,
    PDLaunchType launch_type,
    uint32_t flags,
    Error &launch_err)
{
    // Clear out and clean up from any current state
    Clear();

    m_arch_spec = arch_spec;

    if (launch_type == eLaunchDefault)
        launch_type = eLaunchPosixSpawn;

    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
    if (log)
        log->Printf ("%s( path = '%s', argv = %p, envp = %p, launch_type = %u, flags = %x )", __FUNCTION__, path, argv, envp, launch_type, flags);

    // Fork a child process for debugging
    SetPrivateState (eStateLaunching);
    switch (launch_type)
    {
    case eLaunchForkExec:
        SetID(ProcessMacOSX::ForkChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err));
        break;

    case eLaunchPosixSpawn:
        SetID(ProcessMacOSX::PosixSpawnChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, flags & eLaunchFlagDisableASLR ? 1 : 0, launch_err));
        break;

#if defined (__arm__)

    case eLaunchSpringBoard:
        {
            const char *app_ext = strstr(path, ".app");
            if (app_ext != NULL)
            {
                std::string app_bundle_path(path, app_ext + strlen(".app"));
                return SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, launch_err);
            }
        }
        break;

#endif

    default:
        // Invalid  launch
        launch_err.SetErrorToGenericError ();
        return LLDB_INVALID_PROCESS_ID;
    }

    lldb::pid_t pid = GetID();

    if (pid == LLDB_INVALID_PROCESS_ID)
    {
        // If we don't have a valid process ID and no one has set the error,
        // then return a generic error
        if (launch_err.Success())
            launch_err.SetErrorToGenericError ();
    }
    else
    {
        // Make sure we can get our task port before going any further
        Task().GetTaskPortForProcessID (launch_err);

        // If that goes well then kick off our exception thread
        if (launch_err.Success())
            Task().StartExceptionThread(launch_err);

        if (launch_err.Success())
        {
            //m_path = path;
//          size_t i;
//          if (argv)
//          {
//              char const *arg;
//              for (i=0; (arg = argv[i]) != NULL; i++)
//                  m_args.push_back(arg);
//          }

            StartSTDIOThread();

            if (launch_type == eLaunchPosixSpawn)
            {
                errno = 0;
                if (::ptrace (PT_ATTACHEXC, pid, 0, 0) == 0)
                    launch_err.Clear();
                else
                    launch_err.SetErrorToErrno();

                log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
                if (launch_err.Fail() || log)
                    launch_err.PutToLog(log.get(), "::ptrace (PT_ATTACHEXC, pid = %i, 0, 0 )", pid);

                if (launch_err.Success())
                    m_flags.Set (eFlagsAttached);
                else
                    SetPrivateState (eStateExited);
            }
            else
            {
                launch_err.Clear();
            }
        }
        else
        {
            // We were able to launch the process, but not get its task port
            // so now we need to make it sleep with da fishes.
            SetID(LLDB_INVALID_PROCESS_ID);
            ::ptrace (PT_KILL, pid, 0, 0 );
            ::kill (pid, SIGCONT);
            pid = LLDB_INVALID_PROCESS_ID;
        }

    }
    return pid;
}

lldb::pid_t
ProcessMacOSX::PosixSpawnChildForPTraceDebugging
(
    const char *path,
    char const *argv[],
    char const *envp[],
    ArchSpec& arch_spec,
    const char *stdin_path,
    const char *stdout_path,
    const char *stderr_path,
    ProcessMacOSX* process,
    int disable_aslr,
    Error &err
)
{
    posix_spawnattr_t attr;
    short flags;
    LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));

    Error local_err;    // Errors that don't affect the spawning.
    if (log)
        log->Printf ("%s ( path='%s', argv=%p, envp=%p, process )", __FUNCTION__, path, argv, envp);
    err.SetError( ::posix_spawnattr_init (&attr), eErrorTypePOSIX);
    if (err.Fail() || log)
        err.PutToLog(log.get(), "::posix_spawnattr_init ( &attr )");
    if (err.Fail())
        return LLDB_INVALID_PROCESS_ID;

    flags = POSIX_SPAWN_START_SUSPENDED;
    if (disable_aslr)
        flags |= _POSIX_SPAWN_DISABLE_ASLR;
    
    err.SetError( ::posix_spawnattr_setflags (&attr, flags), eErrorTypePOSIX);
    if (err.Fail() || log)
        err.PutToLog(log.get(), "::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED%s )", disable_aslr ? " | _POSIX_SPAWN_DISABLE_ASLR" : "");
    if (err.Fail())
        return LLDB_INVALID_PROCESS_ID;

#if !defined(__arm__)

    // We don't need to do this for ARM, and we really shouldn't now that we
    // have multiple CPU subtypes and no posix_spawnattr call that allows us
    // to set which CPU subtype to launch...
    cpu_type_t cpu = arch_spec.GetMachOCPUType();
    if (cpu != 0 && cpu != UINT32_MAX && cpu != LLDB_INVALID_CPUTYPE)
    {
        size_t ocount = 0;
        err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu, &ocount), eErrorTypePOSIX);
        if (err.Fail() || log)
            err.PutToLog(log.get(), "::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %zu )", cpu, ocount);

        if (err.Fail() != 0 || ocount != 1)
            return LLDB_INVALID_PROCESS_ID;
    }

#endif

    lldb_utility::PseudoTerminal pty;

    posix_spawn_file_actions_t file_actions;
    err.SetError( ::posix_spawn_file_actions_init (&file_actions), eErrorTypePOSIX);
    int file_actions_valid = err.Success();
    if (!file_actions_valid || log)
        err.PutToLog(log.get(), "::posix_spawn_file_actions_init ( &file_actions )");
    Error stdio_err;
    lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
    if (file_actions_valid)
    {
        // If the user specified any STDIO files, then use those
        if (stdin_path || stdout_path || stderr_path)
        {
            process->SetSTDIOIsOurs(false);
            if (stderr_path != NULL && stderr_path[0])
            {
                stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO,    stderr_path, O_RDWR, 0), eErrorTypePOSIX);
                if (stdio_err.Fail() || log)
                    stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", stderr_path);
            }

            if (stdin_path != NULL && stdin_path[0])
            {
                stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, stdin_path, O_RDONLY, 0), eErrorTypePOSIX);
                if (stdio_err.Fail() || log)
                    stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", stdin_path);
            }

            if (stdout_path != NULL && stdout_path[0])
            {
                stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO,    stdout_path, O_WRONLY, 0), eErrorTypePOSIX);
                if (stdio_err.Fail() || log)
                    stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", stdout_path);
            }
        }
        else
        {
            // The user did not specify any STDIO files, use a pseudo terminal.
            // Callers can then access the file handles using the
            // ProcessMacOSX::ReleaseChildFileDescriptors() function, otherwise
            // this class will spawn a thread that tracks STDIO and buffers it.
            process->SetSTDIOIsOurs(true);
            char error_str[1024];
            if (pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY, error_str, sizeof(error_str)))
            {
                const char* slave_name = pty.GetSlaveName(error_str, sizeof(error_str));
                if (slave_name == NULL)
                    slave_name = "/dev/null";
                stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO,    slave_name, O_RDWR|O_NOCTTY, 0), eErrorTypePOSIX);
                if (stdio_err.Fail() || log)
                    stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR|O_NOCTTY, mode = 0 )", slave_name);

                stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, slave_name, O_RDONLY|O_NOCTTY, 0), eErrorTypePOSIX);
                if (stdio_err.Fail() || log)
                    stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY|O_NOCTTY, mode = 0 )", slave_name);

                stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO,    slave_name, O_WRONLY|O_NOCTTY, 0), eErrorTypePOSIX);
                if (stdio_err.Fail() || log)
                    stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY|O_NOCTTY, mode = 0 )", slave_name);
            }
            else
            {
                if (error_str[0])
                    stdio_err.SetErrorString(error_str);
                else
                    stdio_err.SetErrorString("Unable to open master side of pty for inferior.");
            }

        }
        err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), eErrorTypePOSIX);
        if (err.Fail() || log)
            err.PutToLog(log.get(), "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, &file_actions, &attr, argv, envp);

        if (stdio_err.Success())
        {
            // If we have a valid process and we created the STDIO file handles,
            // then remember them on our process class so we can spawn a STDIO
            // thread and close them when we are done with them.
            if (process != NULL && process->STDIOIsOurs())
            {
                int master_fd = pty.ReleaseMasterFileDescriptor ();
                process->SetChildFileDescriptors (master_fd, master_fd, master_fd);
            }
        }
    }
    else
    {
        err.SetError( ::posix_spawnp (&pid, path, NULL, &attr, (char * const*)argv, (char * const*)envp), eErrorTypePOSIX);
        if (err.Fail() || log)
            err.PutToLog(log.get(), "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, NULL, &attr, argv, envp);
    }
    
    ::posix_spawnattr_destroy (&attr);

    // We have seen some cases where posix_spawnp was returning a valid
    // looking pid even when an error was returned, so clear it out
    if (err.Fail())
        pid = LLDB_INVALID_PROCESS_ID;

    if (file_actions_valid)
    {
        local_err.SetError( ::posix_spawn_file_actions_destroy (&file_actions), eErrorTypePOSIX);
        if (local_err.Fail() || log)
            local_err.PutToLog(log.get(), "::posix_spawn_file_actions_destroy ( &file_actions )");
    }

    return pid;
}

lldb::pid_t
ProcessMacOSX::ForkChildForPTraceDebugging
(
    const char *path,
    char const *argv[],
    char const *envp[],
    ArchSpec& arch_spec,
    const char *stdin_path,
    const char *stdout_path,
    const char *stderr_path,
    ProcessMacOSX* process,
    Error &launch_err
)
{
    lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;

    if (stdin_path || stdout_path || stderr_path)
    {
        assert(!"TODO: ForkChildForPTraceDebugging doesn't currently support fork/exec with user file handles...");
    }
    else
    {

        // Use a fork that ties the child process's stdin/out/err to a pseudo
        // terminal so we can read it in our ProcessMacOSX::STDIOThread
        // as unbuffered io.
        lldb_utility::PseudoTerminal pty;
        char error_str[1024];
        pid = pty.Fork(error_str, sizeof(error_str));

        if (pid < 0)
        {
            launch_err.SetErrorString (error_str);
            //--------------------------------------------------------------
            // Error during fork.
            //--------------------------------------------------------------
            return pid;
        }
        else if (pid == 0)
        {
            //--------------------------------------------------------------
            // Child process
            //--------------------------------------------------------------
            ::ptrace (PT_TRACE_ME, 0, 0, 0);    // Debug this process
            ::ptrace (PT_SIGEXC, 0, 0, 0);    // Get BSD signals as mach exceptions

            // If our parent is setgid, lets make sure we don't inherit those
            // extra powers due to nepotism.
            ::setgid (getgid ());

            // Let the child have its own process group. We need to execute
            // this call in both the child and parent to avoid a race condition
            // between the two processes.
            ::setpgid (0, 0);    // Set the child process group to match its pid

            // Sleep a bit to before the exec call
            ::sleep (1);

            // Turn this process into
            ::execv (path, (char * const *)argv);
            // Exit with error code. Child process should have taken
            // over in above exec call and if the exec fails it will
            // exit the child process below.
            ::exit (127);
        }
        else
        {
            //--------------------------------------------------------------
            // Parent process
            //--------------------------------------------------------------
            // Let the child have its own process group. We need to execute
            // this call in both the child and parent to avoid a race condition
            // between the two processes.
            ::setpgid (pid, pid);    // Set the child process group to match its pid

            if (process != NULL)
            {
                // Release our master pty file descriptor so the pty class doesn't
                // close it and so we can continue to use it in our STDIO thread
                int master_fd = pty.ReleaseMasterFileDescriptor ();
                process->SetChildFileDescriptors (master_fd, master_fd, master_fd);
            }
        }
    }
    return pid;
}

#if defined (__arm__)

lldb::pid_t
ProcessMacOSX::SBLaunchForDebug
(
    const char *path,
    char const *argv[],
    char const *envp[],
    ArchSpec& arch_spec,
    const char *stdin_path,
    const char *stdout_path,
    const char *stderr_path,
    Error &launch_err
)
{
    // Clear out and clean up from any current state
    Clear();

    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);

    // Fork a child process for debugging
    SetState(eStateLaunching);
    m_pid = ProcessMacOSX::SBLaunchForDebug(path, argv, envp, this, launch_err);
    if (m_pid != 0)
    {
        m_flags |= eFlagsUsingSBS;
        //m_path = path;
//        size_t i;
//        char const *arg;
//        for (i=0; (arg = argv[i]) != NULL; i++)
//            m_args.push_back(arg);
        Task().StartExceptionThread();
        StartSTDIOThread();
        int err = ptrace (PT_ATTACHEXC, m_pid, 0, 0);
        if (err == 0)
        {
            m_flags |= eFlagsAttached;
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "successfully attached to pid %d", m_pid);
        }
        else
        {
            SetState (eStateExited);
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
        }
    }
    return m_pid;
}

#include <servers/bootstrap.h>
#include "CFBundle.h"
#include "CFData.h"
#include "CFString.h"

lldb::pid_t
ProcessMacOSX::SBLaunchForDebug
(
    const char *app_bundle_path,
    char const *argv[],
    char const *envp[],
    ArchSpec& arch_spec,
    const char *stdin_path,
    const char *stdout_path,
    const char *stderr_path,
    ProcessMacOSX* process,
    Error &launch_err
)
{
    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, process);
    CFAllocatorRef alloc = kCFAllocatorDefault;
    if (argv[0] == NULL)
        return LLDB_INVALID_PROCESS_ID;

    size_t argc = 0;
    // Count the number of arguments
    while (argv[argc] != NULL)
        argc++;

    // Enumerate the arguments
    size_t first_launch_arg_idx = 1;
    CFReleaser<CFMutableArrayRef> launch_argv;

    if (argv[first_launch_arg_idx])
    {
        size_t launch_argc = argc > 0 ? argc - 1 : 0;
        launch_argv.reset (::CFArrayCreateMutable (alloc, launch_argc, &kCFTypeArrayCallBacks));
        size_t i;
        char const *arg;
        CFString launch_arg;
        for (i=first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL); i++)
        {
            launch_arg.reset(::CFStringCreateWithCString (alloc, arg, kCFStringEncodingUTF8));
            if (launch_arg.get() != NULL)
                CFArrayAppendValue(launch_argv.get(), launch_arg.get());
            else
                break;
        }
    }

    // Next fill in the arguments dictionary.  Note, the envp array is of the form
    // Variable=value but SpringBoard wants a CF dictionary.  So we have to convert
    // this here.

    CFReleaser<CFMutableDictionaryRef> launch_envp;

    if (envp[0])
    {
        launch_envp.reset(::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
        const char *value;
        int name_len;
        CFString name_string, value_string;

        for (int i = 0; envp[i] != NULL; i++)
        {
            value = strstr (envp[i], "=");

            // If the name field is empty or there's no =, skip it.  Somebody's messing with us.
            if (value == NULL || value == envp[i])
                continue;

            name_len = value - envp[i];

            // Now move value over the "="
            value++;

            name_string.reset(::CFStringCreateWithBytes(alloc, (const UInt8 *) envp[i], name_len, kCFStringEncodingUTF8, false));
            value_string.reset(::CFStringCreateWithCString(alloc, value, kCFStringEncodingUTF8));
            CFDictionarySetValue (launch_envp.get(), name_string.get(), value_string.get());
        }
    }

    CFString stdout_cf_path;
    CFString stderr_cf_path;
    PseudoTerminal pty;

    if (stdin_path || stdout_path || stderr_path)
    {
        process->SetSTDIOIsOurs(false);
        if (stdout_path)
            stdout_cf_path.SetFileSystemRepresentation (stdout_path);
        if (stderr_path)
            stderr_cf_path.SetFileSystemRepresentation (stderr_path);
    }
    else
    {
        process->SetSTDIOIsOurs(true);
        PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY);
        if (pty_err == PseudoTerminal::success)
        {
            const char* slave_name = pty.SlaveName();
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() successfully opened master pty, slave is %s", __FUNCTION__, slave_name);
            if (slave_name && slave_name[0])
            {
                ::chmod (slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
                stdout_cf_path.SetFileSystemRepresentation (slave_name);
                stderr_cf_path.(stdout_cf_path);
            }
        }
    }

    if (stdout_cf_path.get() == NULL)
        stdout_cf_path.SetFileSystemRepresentation ("/dev/null");
    if (stderr_cf_path.get() == NULL)
        stderr_cf_path.SetFileSystemRepresentation ("/dev/null");

    CFBundle bundle(app_bundle_path);
    CFStringRef bundleIDCFStr = bundle.GetIdentifier();
    std::string bundleID;
    if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL)
    {
        struct stat app_bundle_stat;
        if (::stat (app_bundle_path, &app_bundle_stat) < 0)
        {
            launch_err.SetError(errno, eErrorTypePOSIX);
            launch_err.SetErrorStringWithFormat("%s: \"%s\".\n", launch_err.AsString(), app_bundle_path);
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: %s", __FUNCTION__, launch_err.AsCString());
        }
        else
        {
            launch_err.SetError(-1, eErrorTypeGeneric);
            launch_err.SetErrorStringWithFormat("Failed to extract CFBundleIdentifier from %s.\n", app_bundle_path);
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to extract CFBundleIdentifier from '%s'", __FUNCTION__, app_bundle_path);
        }
        return LLDB_INVALID_PROCESS_ID;
    }
    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s", __FUNCTION__, bundleID.c_str());


    CFData argv_data(NULL);

    if (launch_argv.get())
    {
        if (argv_data.Serialize(launch_argv.get(), kCFPropertyListBinaryFormat_v1_0) == NULL)
        {
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to serialize launch arg array...", __FUNCTION__);
            return LLDB_INVALID_PROCESS_ID;
        }
    }

    ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() serialized launch arg array", __FUNCTION__);

    // Find SpringBoard
    SBSApplicationLaunchError sbs_error = 0;
    sbs_error = SBSLaunchApplication (  bundleIDCFStr,
                                        (CFURLRef)NULL,         // openURL
                                        launch_argv.get(),
                                        launch_envp.get(),      // CFDictionaryRef environment
                                        stdout_cf_path.get(),
                                        stderr_cf_path.get(),
                                        SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice);


    launch_err.SetError(sbs_error, eErrorTypeSpringBoard);

    if (sbs_error == SBSApplicationLaunchErrorSuccess)
    {
        static const useconds_t pid_poll_interval = 200000;
        static const useconds_t pid_poll_timeout = 30000000;

        useconds_t pid_poll_total = 0;

        lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
        Boolean pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
        // Poll until the process is running, as long as we are getting valid responses and the timeout hasn't expired
        // A return PID of 0 means the process is not running, which may be because it hasn't been (asynchronously) started
        // yet, or that it died very quickly (if you weren't using waitForDebugger).
        while (!pid_found && pid_poll_total < pid_poll_timeout)
        {
            usleep (pid_poll_interval);
            pid_poll_total += pid_poll_interval;
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() polling Springboard for pid for %s...", __FUNCTION__, bundleID.c_str());
            pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
        }

        if (pid_found)
        {
            // If we have a valid process and we created the STDIO file handles,
            // then remember them on our process class so we can spawn a STDIO
            // thread and close them when we are done with them.
            if (process != NULL && process->STDIOIsOurs())
            {
                // Release our master pty file descriptor so the pty class doesn't
                // close it and so we can continue to use it in our STDIO thread
                int master_fd = pty.ReleaseMasterFD();
                process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
            }
            ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid);
        }
        else
        {
            LogError("failed to lookup the process ID for CFBundleIdentifier %s.", bundleID.c_str());
        }
        return pid;
    }

    LogError("unable to launch the application with CFBundleIdentifier '%s' sbs_error = %u", bundleID.c_str(), sbs_error);
    return LLDB_INVALID_PROCESS_ID;
}

#endif // #if defined (__arm__)


#include "MachThreadContext_x86_64.h"
#include "MachThreadContext_i386.h"
#include "MachThreadContext_arm.h"

void
ProcessMacOSX::Initialize()
{
    static bool g_initialized = false;

    if (g_initialized == false)
    {
        g_initialized = true;

        MachThreadContext_x86_64::Initialize();
        MachThreadContext_i386::Initialize();
        MachThreadContext_arm::Initialize();
        PluginManager::RegisterPlugin (GetPluginNameStatic(),
                                       GetPluginDescriptionStatic(),
                                       CreateInstance);

        Log::Callbacks log_callbacks = {
            ProcessMacOSXLog::DisableLog,
            ProcessMacOSXLog::EnableLog,
            ProcessMacOSXLog::ListLogCategories
        };

        Log::RegisterLogChannel (ProcessMacOSX::GetPluginNameStatic(), log_callbacks);


    }
}

//uint32_t
//ProcessMacOSX::ListProcessesMatchingName (const char *name, lldb_private::StringList &matches, std::vector<lldb::pid_t> &pids)
//{
//    return Host::ListProcessesMatchingName (name, matches, pids);
//}