1//===-- MachProcess.cpp -----------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10//  Created by Greg Clayton on 6/15/07.
11//
12//===----------------------------------------------------------------------===//
13
14#include "DNB.h"
15#include <inttypes.h>
16#include <mach/mach.h>
17#include <signal.h>
18#include <spawn.h>
19#include <sys/fcntl.h>
20#include <sys/types.h>
21#include <sys/ptrace.h>
22#include <sys/stat.h>
23#include <sys/sysctl.h>
24#include <unistd.h>
25#include <pthread.h>
26#include "MacOSX/CFUtils.h"
27#include "SysSignal.h"
28
29#include <algorithm>
30#include <map>
31
32#include "DNBDataRef.h"
33#include "DNBLog.h"
34#include "DNBThreadResumeActions.h"
35#include "DNBTimer.h"
36#include "MachProcess.h"
37#include "PseudoTerminal.h"
38
39#include "CFBundle.h"
40#include "CFData.h"
41#include "CFString.h"
42
43static CFStringRef CopyBundleIDForPath (const char *app_bundle_path, DNBError &err_str);
44
45#ifdef WITH_SPRINGBOARD
46
47#include <CoreFoundation/CoreFoundation.h>
48#include <SpringBoardServices/SpringBoardServer.h>
49#include <SpringBoardServices/SBSWatchdogAssertion.h>
50
51static bool
52IsSBProcess (nub_process_t pid)
53{
54    CFReleaser<CFArrayRef> appIdsForPID (::SBSCopyDisplayIdentifiersForProcessID(pid));
55    return appIdsForPID.get() != NULL;
56}
57
58#endif // WITH_SPRINGBOARD
59
60#ifdef WITH_BKS
61#import <Foundation/Foundation.h>
62extern "C"
63{
64#import <BackBoardServices/BackBoardServices.h>
65#import <BackBoardServices/BKSSystemService_LaunchServices.h>
66#import <BackBoardServices/BKSOpenApplicationConstants_Private.h>
67}
68
69static bool
70IsBKSProcess (nub_process_t pid)
71{
72    BKSApplicationStateMonitor *state_monitor = [[BKSApplicationStateMonitor alloc] init];
73    BKSApplicationState app_state = [state_monitor mostElevatedApplicationStateForPID: pid];
74    return app_state != BKSApplicationStateUnknown;
75}
76
77static void
78SetBKSError (BKSOpenApplicationErrorCode error_code, DNBError &error)
79{
80    error.SetError (error_code, DNBError::BackBoard);
81    NSString *err_nsstr = ::BKSOpenApplicationErrorCodeToString(error_code);
82    const char *err_str = NULL;
83    if (err_nsstr == NULL)
84        err_str = "unknown BKS error";
85    else
86    {
87        err_str = [err_nsstr UTF8String];
88        if (err_str == NULL)
89            err_str = "unknown BKS error";
90    }
91    error.SetErrorString(err_str);
92}
93
94static const int BKS_OPEN_APPLICATION_TIMEOUT_ERROR = 111;
95#endif // WITH_BKS
96#if 0
97#define DEBUG_LOG(fmt, ...) printf(fmt, ## __VA_ARGS__)
98#else
99#define DEBUG_LOG(fmt, ...)
100#endif
101
102#ifndef MACH_PROCESS_USE_POSIX_SPAWN
103#define MACH_PROCESS_USE_POSIX_SPAWN 1
104#endif
105
106#ifndef _POSIX_SPAWN_DISABLE_ASLR
107#define _POSIX_SPAWN_DISABLE_ASLR       0x0100
108#endif
109
110MachProcess::MachProcess() :
111    m_pid               (0),
112    m_cpu_type          (0),
113    m_child_stdin       (-1),
114    m_child_stdout      (-1),
115    m_child_stderr      (-1),
116    m_path              (),
117    m_args              (),
118    m_task              (this),
119    m_flags             (eMachProcessFlagsNone),
120    m_stdio_thread      (0),
121    m_stdio_mutex       (PTHREAD_MUTEX_RECURSIVE),
122    m_stdout_data       (),
123    m_thread_actions    (),
124    m_profile_enabled   (false),
125    m_profile_interval_usec (0),
126    m_profile_thread    (0),
127    m_profile_data_mutex(PTHREAD_MUTEX_RECURSIVE),
128    m_profile_data      (),
129    m_thread_list        (),
130    m_activities         (),
131    m_exception_messages (),
132    m_exception_messages_mutex (PTHREAD_MUTEX_RECURSIVE),
133    m_state             (eStateUnloaded),
134    m_state_mutex       (PTHREAD_MUTEX_RECURSIVE),
135    m_events            (0, kAllEventsMask),
136    m_private_events    (0, kAllEventsMask),
137    m_breakpoints       (),
138    m_watchpoints       (),
139    m_name_to_addr_callback(NULL),
140    m_name_to_addr_baton(NULL),
141    m_image_infos_callback(NULL),
142    m_image_infos_baton(NULL),
143    m_sent_interrupt_signo (0),
144    m_auto_resume_signo (0),
145    m_did_exec (false)
146{
147    DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
148}
149
150MachProcess::~MachProcess()
151{
152    DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
153    Clear();
154}
155
156pid_t
157MachProcess::SetProcessID(pid_t pid)
158{
159    // Free any previous process specific data or resources
160    Clear();
161    // Set the current PID appropriately
162    if (pid == 0)
163        m_pid = ::getpid ();
164    else
165        m_pid = pid;
166    return m_pid;    // Return actually PID in case a zero pid was passed in
167}
168
169nub_state_t
170MachProcess::GetState()
171{
172    // If any other threads access this we will need a mutex for it
173    PTHREAD_MUTEX_LOCKER(locker, m_state_mutex);
174    return m_state;
175}
176
177const char *
178MachProcess::ThreadGetName(nub_thread_t tid)
179{
180    return m_thread_list.GetName(tid);
181}
182
183nub_state_t
184MachProcess::ThreadGetState(nub_thread_t tid)
185{
186    return m_thread_list.GetState(tid);
187}
188
189
190nub_size_t
191MachProcess::GetNumThreads () const
192{
193    return m_thread_list.NumThreads();
194}
195
196nub_thread_t
197MachProcess::GetThreadAtIndex (nub_size_t thread_idx) const
198{
199    return m_thread_list.ThreadIDAtIndex(thread_idx);
200}
201
202nub_thread_t
203MachProcess::GetThreadIDForMachPortNumber (thread_t mach_port_number) const
204{
205    return m_thread_list.GetThreadIDByMachPortNumber (mach_port_number);
206}
207
208nub_bool_t
209MachProcess::SyncThreadState (nub_thread_t tid)
210{
211    MachThreadSP thread_sp(m_thread_list.GetThreadByID(tid));
212    if (!thread_sp)
213        return false;
214    kern_return_t kret = ::thread_abort_safely(thread_sp->MachPortNumber());
215    DNBLogThreadedIf (LOG_THREAD, "thread = 0x%8.8" PRIx32 " calling thread_abort_safely (tid) => %u (GetGPRState() for stop_count = %u)", thread_sp->MachPortNumber(), kret, thread_sp->Process()->StopCount());
216
217    if (kret == KERN_SUCCESS)
218        return true;
219    else
220        return false;
221
222}
223
224ThreadInfo::QoS
225MachProcess::GetRequestedQoS (nub_thread_t tid, nub_addr_t tsd, uint64_t dti_qos_class_index)
226{
227    return m_thread_list.GetRequestedQoS (tid, tsd, dti_qos_class_index);
228}
229
230nub_addr_t
231MachProcess::GetPThreadT (nub_thread_t tid)
232{
233    return m_thread_list.GetPThreadT (tid);
234}
235
236nub_addr_t
237MachProcess::GetDispatchQueueT (nub_thread_t tid)
238{
239    return m_thread_list.GetDispatchQueueT (tid);
240}
241
242nub_addr_t
243MachProcess::GetTSDAddressForThread (nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset, uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size)
244{
245    return m_thread_list.GetTSDAddressForThread (tid, plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset, plo_pthread_tsd_entry_size);
246}
247
248nub_thread_t
249MachProcess::GetCurrentThread ()
250{
251    return m_thread_list.CurrentThreadID();
252}
253
254nub_thread_t
255MachProcess::GetCurrentThreadMachPort ()
256{
257    return m_thread_list.GetMachPortNumberByThreadID(m_thread_list.CurrentThreadID());
258}
259
260nub_thread_t
261MachProcess::SetCurrentThread(nub_thread_t tid)
262{
263    return m_thread_list.SetCurrentThread(tid);
264}
265
266bool
267MachProcess::GetThreadStoppedReason(nub_thread_t tid, struct DNBThreadStopInfo *stop_info)
268{
269    if (m_thread_list.GetThreadStoppedReason(tid, stop_info))
270    {
271        if (m_did_exec)
272            stop_info->reason = eStopTypeExec;
273        return true;
274    }
275    return false;
276}
277
278void
279MachProcess::DumpThreadStoppedReason(nub_thread_t tid) const
280{
281    return m_thread_list.DumpThreadStoppedReason(tid);
282}
283
284const char *
285MachProcess::GetThreadInfo(nub_thread_t tid) const
286{
287    return m_thread_list.GetThreadInfo(tid);
288}
289
290uint32_t
291MachProcess::GetCPUType ()
292{
293    if (m_cpu_type == 0 && m_pid != 0)
294        m_cpu_type = MachProcess::GetCPUTypeForLocalProcess (m_pid);
295    return m_cpu_type;
296}
297
298const DNBRegisterSetInfo *
299MachProcess::GetRegisterSetInfo (nub_thread_t tid, nub_size_t *num_reg_sets) const
300{
301    MachThreadSP thread_sp (m_thread_list.GetThreadByID (tid));
302    if (thread_sp)
303    {
304        DNBArchProtocol *arch = thread_sp->GetArchProtocol();
305        if (arch)
306            return arch->GetRegisterSetInfo (num_reg_sets);
307    }
308    *num_reg_sets = 0;
309    return NULL;
310}
311
312bool
313MachProcess::GetRegisterValue ( nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *value ) const
314{
315    return m_thread_list.GetRegisterValue(tid, set, reg, value);
316}
317
318bool
319MachProcess::SetRegisterValue ( nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *value ) const
320{
321    return m_thread_list.SetRegisterValue(tid, set, reg, value);
322}
323
324void
325MachProcess::SetState(nub_state_t new_state)
326{
327    // If any other threads access this we will need a mutex for it
328    uint32_t event_mask = 0;
329
330    // Scope for mutex locker
331    {
332        PTHREAD_MUTEX_LOCKER(locker, m_state_mutex);
333        const nub_state_t old_state = m_state;
334
335        if (old_state == eStateExited)
336        {
337            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SetState(%s) ignoring new state since current state is exited", DNBStateAsString(new_state));
338        }
339        else if (old_state == new_state)
340        {
341            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SetState(%s) ignoring redundant state change...", DNBStateAsString(new_state));
342        }
343        else
344        {
345            if (NUB_STATE_IS_STOPPED(new_state))
346                event_mask = eEventProcessStoppedStateChanged;
347            else
348                event_mask = eEventProcessRunningStateChanged;
349
350            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SetState(%s) upating state (previous state was %s), event_mask = 0x%8.8x", DNBStateAsString(new_state), DNBStateAsString(old_state), event_mask);
351
352            m_state = new_state;
353            if (new_state == eStateStopped)
354                m_stop_count++;
355        }
356    }
357
358    if (event_mask != 0)
359    {
360        m_events.SetEvents (event_mask);
361        m_private_events.SetEvents (event_mask);
362        if (event_mask == eEventProcessStoppedStateChanged)
363            m_private_events.ResetEvents (eEventProcessRunningStateChanged);
364        else
365            m_private_events.ResetEvents (eEventProcessStoppedStateChanged);
366
367        // Wait for the event bit to reset if a reset ACK is requested
368        m_events.WaitForResetAck(event_mask);
369    }
370
371}
372
373void
374MachProcess::Clear(bool detaching)
375{
376    // Clear any cached thread list while the pid and task are still valid
377
378    m_task.Clear();
379    // Now clear out all member variables
380    m_pid = INVALID_NUB_PROCESS;
381    if (!detaching)
382        CloseChildFileDescriptors();
383
384    m_path.clear();
385    m_args.clear();
386    SetState(eStateUnloaded);
387    m_flags = eMachProcessFlagsNone;
388    m_stop_count = 0;
389    m_thread_list.Clear();
390    {
391        PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
392        m_exception_messages.clear();
393    }
394    m_activities.Clear();
395    if (m_profile_thread)
396    {
397        pthread_join(m_profile_thread, NULL);
398        m_profile_thread = NULL;
399    }
400}
401
402
403bool
404MachProcess::StartSTDIOThread()
405{
406    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__);
407    // Create the thread that watches for the child STDIO
408    return ::pthread_create (&m_stdio_thread, NULL, MachProcess::STDIOThread, this) == 0;
409}
410
411void
412MachProcess::SetEnableAsyncProfiling(bool enable, uint64_t interval_usec, DNBProfileDataScanType scan_type)
413{
414    m_profile_enabled = enable;
415    m_profile_interval_usec = interval_usec;
416    m_profile_scan_type = scan_type;
417
418    if (m_profile_enabled && (m_profile_thread == NULL))
419    {
420        StartProfileThread();
421    }
422    else if (!m_profile_enabled && m_profile_thread)
423    {
424        pthread_join(m_profile_thread, NULL);
425        m_profile_thread = NULL;
426    }
427}
428
429bool
430MachProcess::StartProfileThread()
431{
432    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__);
433    // Create the thread that profiles the inferior and reports back if enabled
434    return ::pthread_create (&m_profile_thread, NULL, MachProcess::ProfileThread, this) == 0;
435}
436
437
438nub_addr_t
439MachProcess::LookupSymbol(const char *name, const char *shlib)
440{
441    if (m_name_to_addr_callback != NULL && name && name[0])
442        return m_name_to_addr_callback(ProcessID(), name, shlib, m_name_to_addr_baton);
443    return INVALID_NUB_ADDRESS;
444}
445
446bool
447MachProcess::Resume (const DNBThreadResumeActions& thread_actions)
448{
449    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Resume ()");
450    nub_state_t state = GetState();
451
452    if (CanResume(state))
453    {
454        m_thread_actions = thread_actions;
455        PrivateResume();
456        return true;
457    }
458    else if (state == eStateRunning)
459    {
460        DNBLog("Resume() - task 0x%x is already running, ignoring...", m_task.TaskPort());
461        return true;
462    }
463    DNBLog("Resume() - task 0x%x has state %s, can't continue...", m_task.TaskPort(), DNBStateAsString(state));
464    return false;
465}
466
467bool
468MachProcess::Kill (const struct timespec *timeout_abstime)
469{
470    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill ()");
471    nub_state_t state = DoSIGSTOP(true, false, NULL);
472    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() state = %s", DNBStateAsString(state));
473    errno = 0;
474    DNBLog ("Sending ptrace PT_KILL to terminate inferior process.");
475    ::ptrace (PT_KILL, m_pid, 0, 0);
476    DNBError err;
477    err.SetErrorToErrno();
478    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() ::ptrace (PT_KILL, pid=%u, 0, 0) => 0x%8.8x (%s)", m_pid, err.Error(), err.AsString());
479    m_thread_actions = DNBThreadResumeActions (eStateRunning, 0);
480    PrivateResume ();
481
482    // Try and reap the process without touching our m_events since
483    // we want the code above this to still get the eStateExited event
484    const uint32_t reap_timeout_usec = 1000000;    // Wait 1 second and try to reap the process
485    const uint32_t reap_interval_usec = 10000;  //
486    uint32_t reap_time_elapsed;
487    for (reap_time_elapsed = 0;
488         reap_time_elapsed < reap_timeout_usec;
489         reap_time_elapsed += reap_interval_usec)
490    {
491        if (GetState() == eStateExited)
492            break;
493        usleep(reap_interval_usec);
494    }
495    DNBLog ("Waited %u ms for process to be reaped (state = %s)", reap_time_elapsed/1000, DNBStateAsString(GetState()));
496    return true;
497}
498
499bool
500MachProcess::Interrupt()
501{
502    nub_state_t state = GetState();
503    if (IsRunning(state))
504    {
505        if (m_sent_interrupt_signo == 0)
506        {
507            m_sent_interrupt_signo = SIGSTOP;
508            if (Signal (m_sent_interrupt_signo))
509            {
510                DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - sent %i signal to interrupt process", m_sent_interrupt_signo);
511            }
512            else
513            {
514                m_sent_interrupt_signo = 0;
515                DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - failed to send %i signal to interrupt process", m_sent_interrupt_signo);
516            }
517        }
518        else
519        {
520            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - previously sent an interrupt signal %i that hasn't been received yet, interrupt aborted", m_sent_interrupt_signo);
521        }
522    }
523    else
524    {
525        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - process already stopped, no interrupt sent");
526    }
527    return false;
528}
529
530bool
531MachProcess::Signal (int signal, const struct timespec *timeout_abstime)
532{
533    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p)", signal, timeout_abstime);
534    nub_state_t state = GetState();
535    if (::kill (ProcessID(), signal) == 0)
536    {
537        // If we were running and we have a timeout, wait for the signal to stop
538        if (IsRunning(state) && timeout_abstime)
539        {
540            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p) waiting for signal to stop process...", signal, timeout_abstime);
541            m_private_events.WaitForSetEvents(eEventProcessStoppedStateChanged, timeout_abstime);
542            state = GetState();
543            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p) state = %s", signal, timeout_abstime, DNBStateAsString(state));
544            return !IsRunning (state);
545        }
546        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p) not waiting...", signal, timeout_abstime);
547        return true;
548    }
549    DNBError err(errno, DNBError::POSIX);
550    err.LogThreadedIfError("kill (pid = %d, signo = %i)", ProcessID(), signal);
551    return false;
552
553}
554
555bool
556MachProcess::SendEvent (const char *event, DNBError &send_err)
557{
558    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SendEvent (event = %s) to pid: %d", event, m_pid);
559    if (m_pid == INVALID_NUB_PROCESS)
560        return false;
561#if WITH_BKS
562    return BKSSendEvent (event, send_err);
563#endif
564    return true;
565}
566
567nub_state_t
568MachProcess::DoSIGSTOP (bool clear_bps_and_wps, bool allow_running, uint32_t *thread_idx_ptr)
569{
570    nub_state_t state = GetState();
571    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s", DNBStateAsString (state));
572
573    if (!IsRunning(state))
574    {
575        if (clear_bps_and_wps)
576        {
577            DisableAllBreakpoints (true);
578            DisableAllWatchpoints (true);
579            clear_bps_and_wps = false;
580        }
581
582        // If we already have a thread stopped due to a SIGSTOP, we don't have
583        // to do anything...
584        uint32_t thread_idx = m_thread_list.GetThreadIndexForThreadStoppedWithSignal (SIGSTOP);
585        if (thread_idx_ptr)
586            *thread_idx_ptr = thread_idx;
587        if (thread_idx != UINT32_MAX)
588            return GetState();
589
590        // No threads were stopped with a SIGSTOP, we need to run and halt the
591        // process with a signal
592        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s -- resuming process", DNBStateAsString (state));
593        if (allow_running)
594            m_thread_actions = DNBThreadResumeActions (eStateRunning, 0);
595        else
596            m_thread_actions = DNBThreadResumeActions (eStateSuspended, 0);
597
598        PrivateResume ();
599
600        // Reset the event that says we were indeed running
601        m_events.ResetEvents(eEventProcessRunningStateChanged);
602        state = GetState();
603    }
604
605    // We need to be stopped in order to be able to detach, so we need
606    // to send ourselves a SIGSTOP
607
608    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s -- sending SIGSTOP", DNBStateAsString (state));
609    struct timespec sigstop_timeout;
610    DNBTimer::OffsetTimeOfDay(&sigstop_timeout, 2, 0);
611    Signal (SIGSTOP, &sigstop_timeout);
612    if (clear_bps_and_wps)
613    {
614        DisableAllBreakpoints (true);
615        DisableAllWatchpoints (true);
616        //clear_bps_and_wps = false;
617    }
618    uint32_t thread_idx = m_thread_list.GetThreadIndexForThreadStoppedWithSignal (SIGSTOP);
619    if (thread_idx_ptr)
620        *thread_idx_ptr = thread_idx;
621    return GetState();
622}
623
624bool
625MachProcess::Detach()
626{
627    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach()");
628
629    uint32_t thread_idx = UINT32_MAX;
630    nub_state_t state = DoSIGSTOP(true, true, &thread_idx);
631    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach() DoSIGSTOP() returned %s", DNBStateAsString(state));
632
633    {
634        m_thread_actions.Clear();
635        m_activities.Clear();
636        DNBThreadResumeAction thread_action;
637        thread_action.tid = m_thread_list.ThreadIDAtIndex (thread_idx);
638        thread_action.state = eStateRunning;
639        thread_action.signal = -1;
640        thread_action.addr = INVALID_NUB_ADDRESS;
641
642        m_thread_actions.Append (thread_action);
643        m_thread_actions.SetDefaultThreadActionIfNeeded (eStateRunning, 0);
644
645        PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex);
646
647        ReplyToAllExceptions ();
648
649    }
650
651    m_task.ShutDownExcecptionThread();
652
653    // Detach from our process
654    errno = 0;
655    nub_process_t pid = m_pid;
656    int ret = ::ptrace (PT_DETACH, pid, (caddr_t)1, 0);
657    DNBError err(errno, DNBError::POSIX);
658    if (DNBLogCheckLogBit(LOG_PROCESS) || err.Fail() || (ret != 0))
659        err.LogThreaded("::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid);
660
661    // Resume our task
662    m_task.Resume();
663
664    // NULL our task out as we have already retored all exception ports
665    m_task.Clear();
666
667    // Clear out any notion of the process we once were
668    const bool detaching = true;
669    Clear(detaching);
670
671    SetState(eStateDetached);
672
673    return true;
674}
675
676//----------------------------------------------------------------------
677// ReadMemory from the MachProcess level will always remove any software
678// breakpoints from the memory buffer before returning. If you wish to
679// read memory and see those traps, read from the MachTask
680// (m_task.ReadMemory()) as that version will give you what is actually
681// in inferior memory.
682//----------------------------------------------------------------------
683nub_size_t
684MachProcess::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf)
685{
686    // We need to remove any current software traps (enabled software
687    // breakpoints) that we may have placed in our tasks memory.
688
689    // First just read the memory as is
690    nub_size_t bytes_read = m_task.ReadMemory(addr, size, buf);
691
692    // Then place any opcodes that fall into this range back into the buffer
693    // before we return this to callers.
694    if (bytes_read > 0)
695        m_breakpoints.RemoveTrapsFromBuffer (addr, bytes_read, buf);
696    return bytes_read;
697}
698
699//----------------------------------------------------------------------
700// WriteMemory from the MachProcess level will always write memory around
701// any software breakpoints. Any software breakpoints will have their
702// opcodes modified if they are enabled. Any memory that doesn't overlap
703// with software breakpoints will be written to. If you wish to write to
704// inferior memory without this interference, then write to the MachTask
705// (m_task.WriteMemory()) as that version will always modify inferior
706// memory.
707//----------------------------------------------------------------------
708nub_size_t
709MachProcess::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf)
710{
711    // We need to write any data that would go where any current software traps
712    // (enabled software breakpoints) any software traps (breakpoints) that we
713    // may have placed in our tasks memory.
714
715    std::vector<DNBBreakpoint *> bps;
716
717    const size_t num_bps = m_breakpoints.FindBreakpointsThatOverlapRange(addr, size, bps);
718    if (num_bps == 0)
719        return m_task.WriteMemory(addr, size, buf);
720
721    nub_size_t bytes_written = 0;
722    nub_addr_t intersect_addr;
723    nub_size_t intersect_size;
724    nub_size_t opcode_offset;
725    const uint8_t *ubuf = (const uint8_t *)buf;
726
727    for (size_t i=0; i<num_bps; ++i)
728    {
729        DNBBreakpoint *bp = bps[i];
730
731        const bool intersects = bp->IntersectsRange(addr, size, &intersect_addr, &intersect_size, &opcode_offset);
732        assert(intersects);
733        assert(addr <= intersect_addr && intersect_addr < addr + size);
734        assert(addr < intersect_addr + intersect_size && intersect_addr + intersect_size <= addr + size);
735        assert(opcode_offset + intersect_size <= bp->ByteSize());
736
737        // Check for bytes before this breakpoint
738        const nub_addr_t curr_addr = addr + bytes_written;
739        if (intersect_addr > curr_addr)
740        {
741            // There are some bytes before this breakpoint that we need to
742            // just write to memory
743            nub_size_t curr_size = intersect_addr - curr_addr;
744            nub_size_t curr_bytes_written = m_task.WriteMemory(curr_addr, curr_size, ubuf + bytes_written);
745            bytes_written += curr_bytes_written;
746            if (curr_bytes_written != curr_size)
747            {
748                // We weren't able to write all of the requested bytes, we
749                // are done looping and will return the number of bytes that
750                // we have written so far.
751                break;
752            }
753        }
754
755        // Now write any bytes that would cover up any software breakpoints
756        // directly into the breakpoint opcode buffer
757        ::memcpy(bp->SavedOpcodeBytes() + opcode_offset, ubuf + bytes_written, intersect_size);
758        bytes_written += intersect_size;
759    }
760
761    // Write any remaining bytes after the last breakpoint if we have any left
762    if (bytes_written < size)
763        bytes_written += m_task.WriteMemory(addr + bytes_written, size - bytes_written, ubuf + bytes_written);
764
765    return bytes_written;
766}
767
768void
769MachProcess::ReplyToAllExceptions ()
770{
771    PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
772    if (m_exception_messages.empty() == false)
773    {
774        MachException::Message::iterator pos;
775        MachException::Message::iterator begin = m_exception_messages.begin();
776        MachException::Message::iterator end = m_exception_messages.end();
777        for (pos = begin; pos != end; ++pos)
778        {
779            DNBLogThreadedIf(LOG_EXCEPTIONS, "Replying to exception %u...", (uint32_t)std::distance(begin, pos));
780            int thread_reply_signal = 0;
781
782            nub_thread_t tid = m_thread_list.GetThreadIDByMachPortNumber (pos->state.thread_port);
783            const DNBThreadResumeAction *action = NULL;
784            if (tid != INVALID_NUB_THREAD)
785            {
786                action = m_thread_actions.GetActionForThread (tid, false);
787            }
788
789            if (action)
790            {
791                thread_reply_signal = action->signal;
792                if (thread_reply_signal)
793                    m_thread_actions.SetSignalHandledForThread (tid);
794            }
795
796            DNBError err (pos->Reply(this, thread_reply_signal));
797            if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
798                err.LogThreadedIfError("Error replying to exception");
799        }
800
801        // Erase all exception message as we should have used and replied
802        // to them all already.
803        m_exception_messages.clear();
804    }
805}
806void
807MachProcess::PrivateResume ()
808{
809    PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex);
810
811    m_auto_resume_signo = m_sent_interrupt_signo;
812    if (m_auto_resume_signo)
813        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::PrivateResume() - task 0x%x resuming (with unhandled interrupt signal %i)...", m_task.TaskPort(), m_auto_resume_signo);
814    else
815        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::PrivateResume() - task 0x%x resuming...", m_task.TaskPort());
816
817    ReplyToAllExceptions ();
818//    bool stepOverBreakInstruction = step;
819
820    // Let the thread prepare to resume and see if any threads want us to
821    // step over a breakpoint instruction (ProcessWillResume will modify
822    // the value of stepOverBreakInstruction).
823    m_thread_list.ProcessWillResume (this, m_thread_actions);
824
825    // Set our state accordingly
826    if (m_thread_actions.NumActionsWithState(eStateStepping))
827        SetState (eStateStepping);
828    else
829        SetState (eStateRunning);
830
831    // Now resume our task.
832    m_task.Resume();
833}
834
835DNBBreakpoint *
836MachProcess::CreateBreakpoint(nub_addr_t addr, nub_size_t length, bool hardware)
837{
838    DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = 0x%8.8llx, length = %llu, hardware = %i)", (uint64_t)addr, (uint64_t)length, hardware);
839
840    DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
841    if (bp)
842        bp->Retain();
843    else
844        bp =  m_breakpoints.Add(addr, length, hardware);
845
846    if (EnableBreakpoint(addr))
847    {
848        DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = 0x%8.8llx, length = %llu) => %p", (uint64_t)addr, (uint64_t)length, bp);
849        return bp;
850    }
851    else if (bp->Release() == 0)
852    {
853        m_breakpoints.Remove(addr);
854    }
855    // We failed to enable the breakpoint
856    return NULL;
857}
858
859DNBBreakpoint *
860MachProcess::CreateWatchpoint(nub_addr_t addr, nub_size_t length, uint32_t watch_flags, bool hardware)
861{
862    DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = 0x%8.8llx, length = %llu, flags = 0x%8.8x, hardware = %i)", (uint64_t)addr, (uint64_t)length, watch_flags, hardware);
863
864    DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
865    // since the Z packets only send an address, we can only have one watchpoint at
866    // an address. If there is already one, we must refuse to create another watchpoint
867    if (wp)
868        return NULL;
869
870    wp = m_watchpoints.Add(addr, length, hardware);
871    wp->SetIsWatchpoint(watch_flags);
872
873    if (EnableWatchpoint(addr))
874    {
875        DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = 0x%8.8llx, length = %llu) => %p", (uint64_t)addr, (uint64_t)length, wp);
876        return wp;
877    }
878    else
879    {
880        DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = 0x%8.8llx, length = %llu) => FAILED", (uint64_t)addr, (uint64_t)length);
881        m_watchpoints.Remove(addr);
882    }
883    // We failed to enable the watchpoint
884    return NULL;
885}
886
887void
888MachProcess::DisableAllBreakpoints (bool remove)
889{
890    DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::%s (remove = %d )", __FUNCTION__, remove);
891
892    m_breakpoints.DisableAllBreakpoints (this);
893
894    if (remove)
895        m_breakpoints.RemoveDisabled();
896}
897
898void
899MachProcess::DisableAllWatchpoints(bool remove)
900{
901    DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::%s (remove = %d )", __FUNCTION__, remove);
902
903    m_watchpoints.DisableAllWatchpoints(this);
904
905    if (remove)
906        m_watchpoints.RemoveDisabled();
907}
908
909bool
910MachProcess::DisableBreakpoint(nub_addr_t addr, bool remove)
911{
912    DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
913    if (bp)
914    {
915        // After "exec" we might end up with a bunch of breakpoints that were disabled
916        // manually, just ignore them
917        if (!bp->IsEnabled())
918        {
919            // Breakpoint might have been disabled by an exec
920            if (remove && bp->Release() == 0)
921            {
922                m_thread_list.NotifyBreakpointChanged(bp);
923                m_breakpoints.Remove(addr);
924            }
925            return true;
926        }
927
928        // We have multiple references to this breakpoint, decrement the ref count
929        // and if it isn't zero, then return true;
930        if (remove && bp->Release() > 0)
931            return true;
932
933        DNBLogThreadedIf(LOG_BREAKPOINTS | LOG_VERBOSE, "MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d )", (uint64_t)addr, remove);
934
935        if (bp->IsHardware())
936        {
937            bool hw_disable_result = m_thread_list.DisableHardwareBreakpoint (bp);
938
939            if (hw_disable_result == true)
940            {
941                bp->SetEnabled(false);
942                // Let the thread list know that a breakpoint has been modified
943                if (remove)
944                {
945                    m_thread_list.NotifyBreakpointChanged(bp);
946                    m_breakpoints.Remove(addr);
947                }
948                DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) (hardware) => success", (uint64_t)addr, remove);
949                return true;
950            }
951
952            return false;
953        }
954
955        const nub_size_t break_op_size = bp->ByteSize();
956        assert (break_op_size > 0);
957        const uint8_t * const break_op = DNBArchProtocol::GetBreakpointOpcode (bp->ByteSize());
958        if (break_op_size > 0)
959        {
960            // Clear a software breakpoint instruction
961            uint8_t curr_break_op[break_op_size];
962            bool break_op_found = false;
963
964            // Read the breakpoint opcode
965            if (m_task.ReadMemory(addr, break_op_size, curr_break_op) == break_op_size)
966            {
967                bool verify = false;
968                if (bp->IsEnabled())
969                {
970                    // Make sure we have the a breakpoint opcode exists at this address
971                    if (memcmp(curr_break_op, break_op, break_op_size) == 0)
972                    {
973                        break_op_found = true;
974                        // We found a valid breakpoint opcode at this address, now restore
975                        // the saved opcode.
976                        if (m_task.WriteMemory(addr, break_op_size, bp->SavedOpcodeBytes()) == break_op_size)
977                        {
978                            verify = true;
979                        }
980                        else
981                        {
982                            DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) memory write failed when restoring original opcode", (uint64_t)addr, remove);
983                        }
984                    }
985                    else
986                    {
987                        DNBLogWarning("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) expected a breakpoint opcode but didn't find one.", (uint64_t)addr, remove);
988                        // Set verify to true and so we can check if the original opcode has already been restored
989                        verify = true;
990                    }
991                }
992                else
993                {
994                    DNBLogThreadedIf(LOG_BREAKPOINTS | LOG_VERBOSE, "MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) is not enabled", (uint64_t)addr, remove);
995                    // Set verify to true and so we can check if the original opcode is there
996                    verify = true;
997                }
998
999                if (verify)
1000                {
1001                    uint8_t verify_opcode[break_op_size];
1002                    // Verify that our original opcode made it back to the inferior
1003                    if (m_task.ReadMemory(addr, break_op_size, verify_opcode) == break_op_size)
1004                    {
1005                        // compare the memory we just read with the original opcode
1006                        if (memcmp(bp->SavedOpcodeBytes(), verify_opcode, break_op_size) == 0)
1007                        {
1008                            // SUCCESS
1009                            bp->SetEnabled(false);
1010                            // Let the thread list know that a breakpoint has been modified
1011                            if (remove && bp->Release() == 0)
1012                            {
1013                                m_thread_list.NotifyBreakpointChanged(bp);
1014                                m_breakpoints.Remove(addr);
1015                            }
1016                            DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) => success", (uint64_t)addr, remove);
1017                            return true;
1018                        }
1019                        else
1020                        {
1021                            if (break_op_found)
1022                                DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) : failed to restore original opcode", (uint64_t)addr, remove);
1023                            else
1024                                DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) : opcode changed", (uint64_t)addr, remove);
1025                        }
1026                    }
1027                    else
1028                    {
1029                        DNBLogWarning("MachProcess::DisableBreakpoint: unable to disable breakpoint 0x%8.8llx", (uint64_t)addr);
1030                    }
1031                }
1032            }
1033            else
1034            {
1035                DNBLogWarning("MachProcess::DisableBreakpoint: unable to read memory at 0x%8.8llx", (uint64_t)addr);
1036            }
1037        }
1038    }
1039    else
1040    {
1041        DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d ) invalid breakpoint address", (uint64_t)addr, remove);
1042    }
1043    return false;
1044}
1045
1046bool
1047MachProcess::DisableWatchpoint(nub_addr_t addr, bool remove)
1048{
1049    DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::%s(addr = 0x%8.8llx, remove = %d)", __FUNCTION__, (uint64_t)addr, remove);
1050    DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
1051    if (wp)
1052    {
1053        // If we have multiple references to a watchpoint, removing the watchpoint shouldn't clear it
1054        if (remove && wp->Release() > 0)
1055            return true;
1056
1057        nub_addr_t addr = wp->Address();
1058        DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::DisableWatchpoint ( addr = 0x%8.8llx, remove = %d )", (uint64_t)addr, remove);
1059
1060        if (wp->IsHardware())
1061        {
1062            bool hw_disable_result = m_thread_list.DisableHardwareWatchpoint (wp);
1063
1064            if (hw_disable_result == true)
1065            {
1066                wp->SetEnabled(false);
1067                if (remove)
1068                    m_watchpoints.Remove(addr);
1069                DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::Disablewatchpoint ( addr = 0x%8.8llx, remove = %d ) (hardware) => success", (uint64_t)addr, remove);
1070                return true;
1071            }
1072        }
1073
1074        // TODO: clear software watchpoints if we implement them
1075    }
1076    else
1077    {
1078        DNBLogError("MachProcess::DisableWatchpoint ( addr = 0x%8.8llx, remove = %d ) invalid watchpoint ID", (uint64_t)addr, remove);
1079    }
1080    return false;
1081}
1082
1083
1084uint32_t
1085MachProcess::GetNumSupportedHardwareWatchpoints () const
1086{
1087    return m_thread_list.NumSupportedHardwareWatchpoints();
1088}
1089
1090bool
1091MachProcess::EnableBreakpoint(nub_addr_t addr)
1092{
1093    DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::EnableBreakpoint ( addr = 0x%8.8llx )", (uint64_t)addr);
1094    DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
1095    if (bp)
1096    {
1097        if (bp->IsEnabled())
1098        {
1099            DNBLogWarning("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): breakpoint already enabled.", (uint64_t)addr);
1100            return true;
1101        }
1102        else
1103        {
1104            if (bp->HardwarePreferred())
1105            {
1106                bp->SetHardwareIndex(m_thread_list.EnableHardwareBreakpoint(bp));
1107                if (bp->IsHardware())
1108                {
1109                    bp->SetEnabled(true);
1110                    return true;
1111                }
1112            }
1113
1114            const nub_size_t break_op_size = bp->ByteSize();
1115            assert (break_op_size != 0);
1116            const uint8_t * const break_op = DNBArchProtocol::GetBreakpointOpcode (break_op_size);
1117            if (break_op_size > 0)
1118            {
1119                // Save the original opcode by reading it
1120                if (m_task.ReadMemory(addr, break_op_size, bp->SavedOpcodeBytes()) == break_op_size)
1121                {
1122                    // Write a software breakpoint in place of the original opcode
1123                    if (m_task.WriteMemory(addr, break_op_size, break_op) == break_op_size)
1124                    {
1125                        uint8_t verify_break_op[4];
1126                        if (m_task.ReadMemory(addr, break_op_size, verify_break_op) == break_op_size)
1127                        {
1128                            if (memcmp(break_op, verify_break_op, break_op_size) == 0)
1129                            {
1130                                bp->SetEnabled(true);
1131                                // Let the thread list know that a breakpoint has been modified
1132                                m_thread_list.NotifyBreakpointChanged(bp);
1133                                DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ) : SUCCESS.", (uint64_t)addr);
1134                                return true;
1135                            }
1136                            else
1137                            {
1138                                DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): breakpoint opcode verification failed.", (uint64_t)addr);
1139                            }
1140                        }
1141                        else
1142                        {
1143                            DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): unable to read memory to verify breakpoint opcode.", (uint64_t)addr);
1144                        }
1145                    }
1146                    else
1147                    {
1148                        DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): unable to write breakpoint opcode to memory.", (uint64_t)addr);
1149                    }
1150                }
1151                else
1152                {
1153                    DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): unable to read memory at breakpoint address.", (uint64_t)addr);
1154                }
1155            }
1156            else
1157            {
1158                DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ) no software breakpoint opcode for current architecture.", (uint64_t)addr);
1159            }
1160        }
1161    }
1162    return false;
1163}
1164
1165bool
1166MachProcess::EnableWatchpoint(nub_addr_t addr)
1167{
1168    DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::EnableWatchpoint(addr = 0x%8.8llx)", (uint64_t)addr);
1169    DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
1170    if (wp)
1171    {
1172        nub_addr_t addr = wp->Address();
1173        if (wp->IsEnabled())
1174        {
1175            DNBLogWarning("MachProcess::EnableWatchpoint(addr = 0x%8.8llx): watchpoint already enabled.", (uint64_t)addr);
1176            return true;
1177        }
1178        else
1179        {
1180            // Currently only try and set hardware watchpoints.
1181            wp->SetHardwareIndex(m_thread_list.EnableHardwareWatchpoint(wp));
1182            if (wp->IsHardware())
1183            {
1184                wp->SetEnabled(true);
1185                return true;
1186            }
1187            // TODO: Add software watchpoints by doing page protection tricks.
1188        }
1189    }
1190    return false;
1191}
1192
1193// Called by the exception thread when an exception has been received from
1194// our process. The exception message is completely filled and the exception
1195// data has already been copied.
1196void
1197MachProcess::ExceptionMessageReceived (const MachException::Message& exceptionMessage)
1198{
1199    PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex);
1200
1201    if (m_exception_messages.empty())
1202        m_task.Suspend();
1203
1204    DNBLogThreadedIf(LOG_EXCEPTIONS, "MachProcess::ExceptionMessageReceived ( )");
1205
1206    // Use a locker to automatically unlock our mutex in case of exceptions
1207    // Add the exception to our internal exception stack
1208    m_exception_messages.push_back(exceptionMessage);
1209}
1210
1211task_t
1212MachProcess::ExceptionMessageBundleComplete()
1213{
1214    // We have a complete bundle of exceptions for our child process.
1215    PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex);
1216    DNBLogThreadedIf(LOG_EXCEPTIONS, "%s: %llu exception messages.", __PRETTY_FUNCTION__, (uint64_t)m_exception_messages.size());
1217    bool auto_resume = false;
1218    if (!m_exception_messages.empty())
1219    {
1220        m_did_exec = false;
1221        // First check for any SIGTRAP and make sure we didn't exec
1222        const task_t task = m_task.TaskPort();
1223        size_t i;
1224        if (m_pid != 0)
1225        {
1226            bool received_interrupt = false;
1227            uint32_t num_task_exceptions = 0;
1228            for (i=0; i<m_exception_messages.size(); ++i)
1229            {
1230                if (m_exception_messages[i].state.task_port == task)
1231                {
1232                    ++num_task_exceptions;
1233                    const int signo = m_exception_messages[i].state.SoftSignal();
1234                    if (signo == SIGTRAP)
1235                    {
1236                        // SIGTRAP could mean that we exec'ed. We need to check the
1237                        // dyld all_image_infos.infoArray to see if it is NULL and if
1238                        // so, say that we exec'ed.
1239                        const nub_addr_t aii_addr = GetDYLDAllImageInfosAddress();
1240                        if (aii_addr != INVALID_NUB_ADDRESS)
1241                        {
1242                            const nub_addr_t info_array_count_addr = aii_addr + 4;
1243                            uint32_t info_array_count = 0;
1244                            if (m_task.ReadMemory(info_array_count_addr, 4, &info_array_count) == 4)
1245                            {
1246                                if (info_array_count == 0)
1247                                {
1248                                    m_did_exec = true;
1249                                    // Force the task port to update itself in case the task port changed after exec
1250                                    DNBError err;
1251                                    const task_t old_task = m_task.TaskPort();
1252                                    const task_t new_task = m_task.TaskPortForProcessID (err, true);
1253                                    if (old_task != new_task)
1254                                        DNBLogThreadedIf(LOG_PROCESS, "exec: task changed from 0x%4.4x to 0x%4.4x", old_task, new_task);
1255                                }
1256                            }
1257                            else
1258                            {
1259                                DNBLog ("error: failed to read all_image_infos.infoArrayCount from 0x%8.8llx", (uint64_t)info_array_count_addr);
1260                            }
1261                        }
1262                        break;
1263                    }
1264                    else if (m_sent_interrupt_signo != 0 && signo == m_sent_interrupt_signo)
1265                    {
1266                        received_interrupt = true;
1267                    }
1268                }
1269            }
1270
1271            if (m_did_exec)
1272            {
1273                cpu_type_t process_cpu_type = MachProcess::GetCPUTypeForLocalProcess (m_pid);
1274                if (m_cpu_type != process_cpu_type)
1275                {
1276                    DNBLog ("arch changed from 0x%8.8x to 0x%8.8x", m_cpu_type, process_cpu_type);
1277                    m_cpu_type = process_cpu_type;
1278                    DNBArchProtocol::SetArchitecture (process_cpu_type);
1279                }
1280                m_thread_list.Clear();
1281                m_activities.Clear();
1282                m_breakpoints.DisableAll();
1283            }
1284
1285            if (m_sent_interrupt_signo != 0)
1286            {
1287                if (received_interrupt)
1288                {
1289                    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::ExceptionMessageBundleComplete(): process successfully interrupted with signal %i", m_sent_interrupt_signo);
1290
1291                    // Mark that we received the interrupt signal
1292                    m_sent_interrupt_signo = 0;
1293                    // Not check if we had a case where:
1294                    // 1 - We called MachProcess::Interrupt() but we stopped for another reason
1295                    // 2 - We called MachProcess::Resume() (but still haven't gotten the interrupt signal)
1296                    // 3 - We are now incorrectly stopped because we are handling the interrupt signal we missed
1297                    // 4 - We might need to resume if we stopped only with the interrupt signal that we never handled
1298                    if (m_auto_resume_signo != 0)
1299                    {
1300                        // Only auto_resume if we stopped with _only_ the interrupt signal
1301                        if (num_task_exceptions == 1)
1302                        {
1303                            auto_resume = true;
1304                            DNBLogThreadedIf(LOG_PROCESS, "MachProcess::ExceptionMessageBundleComplete(): auto resuming due to unhandled interrupt signal %i", m_auto_resume_signo);
1305                        }
1306                        m_auto_resume_signo = 0;
1307                    }
1308                }
1309                else
1310                {
1311                    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::ExceptionMessageBundleComplete(): didn't get signal %i after MachProcess::Interrupt()",
1312                            m_sent_interrupt_signo);
1313                }
1314            }
1315        }
1316
1317        // Let all threads recover from stopping and do any clean up based
1318        // on the previous thread state (if any).
1319        m_thread_list.ProcessDidStop(this);
1320        m_activities.Clear();
1321
1322        // Let each thread know of any exceptions
1323        for (i=0; i<m_exception_messages.size(); ++i)
1324        {
1325            // Let the thread list figure use the MachProcess to forward all exceptions
1326            // on down to each thread.
1327            if (m_exception_messages[i].state.task_port == task)
1328                m_thread_list.NotifyException(m_exception_messages[i].state);
1329            if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
1330                m_exception_messages[i].Dump();
1331        }
1332
1333        if (DNBLogCheckLogBit(LOG_THREAD))
1334            m_thread_list.Dump();
1335
1336        bool step_more = false;
1337        if (m_thread_list.ShouldStop(step_more) && auto_resume == false)
1338        {
1339            // Wait for the eEventProcessRunningStateChanged event to be reset
1340            // before changing state to stopped to avoid race condition with
1341            // very fast start/stops
1342            struct timespec timeout;
1343            //DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000);   // Wait for 250 ms
1344            DNBTimer::OffsetTimeOfDay(&timeout, 1, 0);  // Wait for 250 ms
1345            m_events.WaitForEventsToReset(eEventProcessRunningStateChanged, &timeout);
1346            SetState(eStateStopped);
1347        }
1348        else
1349        {
1350            // Resume without checking our current state.
1351            PrivateResume ();
1352        }
1353    }
1354    else
1355    {
1356        DNBLogThreadedIf(LOG_EXCEPTIONS, "%s empty exception messages bundle (%llu exceptions).", __PRETTY_FUNCTION__, (uint64_t)m_exception_messages.size());
1357    }
1358    return m_task.TaskPort();
1359}
1360
1361nub_size_t
1362MachProcess::CopyImageInfos ( struct DNBExecutableImageInfo **image_infos, bool only_changed)
1363{
1364    if (m_image_infos_callback != NULL)
1365        return m_image_infos_callback(ProcessID(), image_infos, only_changed, m_image_infos_baton);
1366    return 0;
1367}
1368
1369void
1370MachProcess::SharedLibrariesUpdated ( )
1371{
1372    uint32_t event_bits = eEventSharedLibsStateChange;
1373    // Set the shared library event bit to let clients know of shared library
1374    // changes
1375    m_events.SetEvents(event_bits);
1376    // Wait for the event bit to reset if a reset ACK is requested
1377    m_events.WaitForResetAck(event_bits);
1378}
1379
1380void
1381MachProcess::SetExitInfo (const char *info)
1382{
1383    if (info && info[0])
1384    {
1385        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s(\"%s\")", __FUNCTION__, info);
1386        m_exit_info.assign(info);
1387    }
1388    else
1389    {
1390        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s(NULL)", __FUNCTION__);
1391        m_exit_info.clear();
1392    }
1393}
1394
1395void
1396MachProcess::AppendSTDOUT (char* s, size_t len)
1397{
1398    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (<%llu> %s) ...", __FUNCTION__, (uint64_t)len, s);
1399    PTHREAD_MUTEX_LOCKER (locker, m_stdio_mutex);
1400    m_stdout_data.append(s, len);
1401    m_events.SetEvents(eEventStdioAvailable);
1402
1403    // Wait for the event bit to reset if a reset ACK is requested
1404    m_events.WaitForResetAck(eEventStdioAvailable);
1405}
1406
1407size_t
1408MachProcess::GetAvailableSTDOUT (char *buf, size_t buf_size)
1409{
1410    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__, buf, (uint64_t)buf_size);
1411    PTHREAD_MUTEX_LOCKER (locker, m_stdio_mutex);
1412    size_t bytes_available = m_stdout_data.size();
1413    if (bytes_available > 0)
1414    {
1415        if (bytes_available > buf_size)
1416        {
1417            memcpy(buf, m_stdout_data.data(), buf_size);
1418            m_stdout_data.erase(0, buf_size);
1419            bytes_available = buf_size;
1420        }
1421        else
1422        {
1423            memcpy(buf, m_stdout_data.data(), bytes_available);
1424            m_stdout_data.clear();
1425        }
1426    }
1427    return bytes_available;
1428}
1429
1430nub_addr_t
1431MachProcess::GetDYLDAllImageInfosAddress ()
1432{
1433    DNBError err;
1434    return m_task.GetDYLDAllImageInfosAddress(err);
1435}
1436
1437size_t
1438MachProcess::GetAvailableSTDERR (char *buf, size_t buf_size)
1439{
1440    return 0;
1441}
1442
1443void *
1444MachProcess::STDIOThread(void *arg)
1445{
1446    MachProcess *proc = (MachProcess*) arg;
1447    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( arg = %p ) thread starting...", __FUNCTION__, arg);
1448
1449#if defined (__APPLE__)
1450    pthread_setname_np ("stdio monitoring thread");
1451#endif
1452
1453    // We start use a base and more options so we can control if we
1454    // are currently using a timeout on the mach_msg. We do this to get a
1455    // bunch of related exceptions on our exception port so we can process
1456    // then together. When we have multiple threads, we can get an exception
1457    // per thread and they will come in consecutively. The main thread loop
1458    // will start by calling mach_msg to without having the MACH_RCV_TIMEOUT
1459    // flag set in the options, so we will wait forever for an exception on
1460    // our exception port. After we get one exception, we then will use the
1461    // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
1462    // exceptions for our process. After we have received the last pending
1463    // exception, we will get a timeout which enables us to then notify
1464    // our main thread that we have an exception bundle available. We then wait
1465    // for the main thread to tell this exception thread to start trying to get
1466    // exceptions messages again and we start again with a mach_msg read with
1467    // infinite timeout.
1468    DNBError err;
1469    int stdout_fd = proc->GetStdoutFileDescriptor();
1470    int stderr_fd = proc->GetStderrFileDescriptor();
1471    if (stdout_fd == stderr_fd)
1472        stderr_fd = -1;
1473
1474    while (stdout_fd >= 0 || stderr_fd >= 0)
1475    {
1476        ::pthread_testcancel ();
1477
1478        fd_set read_fds;
1479        FD_ZERO (&read_fds);
1480        if (stdout_fd >= 0)
1481            FD_SET (stdout_fd, &read_fds);
1482        if (stderr_fd >= 0)
1483            FD_SET (stderr_fd, &read_fds);
1484        int nfds = std::max<int>(stdout_fd, stderr_fd) + 1;
1485
1486        int num_set_fds = select (nfds, &read_fds, NULL, NULL, NULL);
1487        DNBLogThreadedIf(LOG_PROCESS, "select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
1488
1489        if (num_set_fds < 0)
1490        {
1491            int select_errno = errno;
1492            if (DNBLogCheckLogBit(LOG_PROCESS))
1493            {
1494                err.SetError (select_errno, DNBError::POSIX);
1495                err.LogThreadedIfError("select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
1496            }
1497
1498            switch (select_errno)
1499            {
1500            case EAGAIN:    // The kernel was (perhaps temporarily) unable to allocate the requested number of file descriptors, or we have non-blocking IO
1501                break;
1502            case EBADF:     // One of the descriptor sets specified an invalid descriptor.
1503                return NULL;
1504                break;
1505            case EINTR:     // A signal was delivered before the time limit expired and before any of the selected events occurred.
1506            case EINVAL:    // The specified time limit is invalid. One of its components is negative or too large.
1507            default:        // Other unknown error
1508                break;
1509            }
1510        }
1511        else if (num_set_fds == 0)
1512        {
1513        }
1514        else
1515        {
1516            char s[1024];
1517            s[sizeof(s)-1] = '\0';  // Ensure we have NULL termination
1518            int bytes_read = 0;
1519            if (stdout_fd >= 0 && FD_ISSET (stdout_fd, &read_fds))
1520            {
1521                do
1522                {
1523                    bytes_read = ::read (stdout_fd, s, sizeof(s)-1);
1524                    if (bytes_read < 0)
1525                    {
1526                        int read_errno = errno;
1527                        DNBLogThreadedIf(LOG_PROCESS, "read (stdout_fd, ) => %d   errno: %d (%s)", bytes_read, read_errno, strerror(read_errno));
1528                    }
1529                    else if (bytes_read == 0)
1530                    {
1531                        // EOF...
1532                        DNBLogThreadedIf(LOG_PROCESS, "read (stdout_fd, ) => %d  (reached EOF for child STDOUT)", bytes_read);
1533                        stdout_fd = -1;
1534                    }
1535                    else if (bytes_read > 0)
1536                    {
1537                        proc->AppendSTDOUT(s, bytes_read);
1538                    }
1539
1540                } while (bytes_read > 0);
1541            }
1542
1543            if (stderr_fd >= 0 && FD_ISSET (stderr_fd, &read_fds))
1544            {
1545                do
1546                {
1547                    bytes_read = ::read (stderr_fd, s, sizeof(s)-1);
1548                    if (bytes_read < 0)
1549                    {
1550                        int read_errno = errno;
1551                        DNBLogThreadedIf(LOG_PROCESS, "read (stderr_fd, ) => %d   errno: %d (%s)", bytes_read, read_errno, strerror(read_errno));
1552                    }
1553                    else if (bytes_read == 0)
1554                    {
1555                        // EOF...
1556                        DNBLogThreadedIf(LOG_PROCESS, "read (stderr_fd, ) => %d  (reached EOF for child STDERR)", bytes_read);
1557                        stderr_fd = -1;
1558                    }
1559                    else if (bytes_read > 0)
1560                    {
1561                        proc->AppendSTDOUT(s, bytes_read);
1562                    }
1563
1564                } while (bytes_read > 0);
1565            }
1566        }
1567    }
1568    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%p): thread exiting...", __FUNCTION__, arg);
1569    return NULL;
1570}
1571
1572
1573void
1574MachProcess::SignalAsyncProfileData (const char *info)
1575{
1576    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%s) ...", __FUNCTION__, info);
1577    PTHREAD_MUTEX_LOCKER (locker, m_profile_data_mutex);
1578    m_profile_data.push_back(info);
1579    m_events.SetEvents(eEventProfileDataAvailable);
1580
1581    // Wait for the event bit to reset if a reset ACK is requested
1582    m_events.WaitForResetAck(eEventProfileDataAvailable);
1583}
1584
1585
1586size_t
1587MachProcess::GetAsyncProfileData (char *buf, size_t buf_size)
1588{
1589    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__, buf, (uint64_t)buf_size);
1590    PTHREAD_MUTEX_LOCKER (locker, m_profile_data_mutex);
1591    if (m_profile_data.empty())
1592        return 0;
1593
1594    size_t bytes_available = m_profile_data.front().size();
1595    if (bytes_available > 0)
1596    {
1597        if (bytes_available > buf_size)
1598        {
1599            memcpy(buf, m_profile_data.front().data(), buf_size);
1600            m_profile_data.front().erase(0, buf_size);
1601            bytes_available = buf_size;
1602        }
1603        else
1604        {
1605            memcpy(buf, m_profile_data.front().data(), bytes_available);
1606            m_profile_data.erase(m_profile_data.begin());
1607        }
1608    }
1609    return bytes_available;
1610}
1611
1612
1613void *
1614MachProcess::ProfileThread(void *arg)
1615{
1616    MachProcess *proc = (MachProcess*) arg;
1617    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( arg = %p ) thread starting...", __FUNCTION__, arg);
1618
1619#if defined (__APPLE__)
1620    pthread_setname_np ("performance profiling thread");
1621#endif
1622
1623    while (proc->IsProfilingEnabled())
1624    {
1625        nub_state_t state = proc->GetState();
1626        if (state == eStateRunning)
1627        {
1628            std::string data = proc->Task().GetProfileData(proc->GetProfileScanType());
1629            if (!data.empty())
1630            {
1631                proc->SignalAsyncProfileData(data.c_str());
1632            }
1633        }
1634        else if ((state == eStateUnloaded) || (state == eStateDetached) || (state == eStateUnloaded))
1635        {
1636            // Done. Get out of this thread.
1637            break;
1638        }
1639
1640        // A simple way to set up the profile interval. We can also use select() or dispatch timer source if necessary.
1641        usleep(proc->ProfileInterval());
1642    }
1643    return NULL;
1644}
1645
1646
1647pid_t
1648MachProcess::AttachForDebug (pid_t pid, char *err_str, size_t err_len)
1649{
1650    // Clear out and clean up from any current state
1651    Clear();
1652    if (pid != 0)
1653    {
1654        DNBError err;
1655        // Make sure the process exists...
1656        if (::getpgid (pid) < 0)
1657        {
1658            err.SetErrorToErrno();
1659            const char *err_cstr = err.AsString();
1660            ::snprintf (err_str, err_len, "%s", err_cstr ? err_cstr : "No such process");
1661            return INVALID_NUB_PROCESS;
1662        }
1663
1664        SetState(eStateAttaching);
1665        m_pid = pid;
1666        // Let ourselves know we are going to be using SBS or BKS if the correct flag bit is set...
1667#if defined (WITH_BKS)
1668        if (IsBKSProcess (pid))
1669            m_flags |= eMachProcessFlagsUsingBKS;
1670#elif defined (WITH_SPRINGBOARD)
1671        if (IsSBProcess(pid))
1672            m_flags |= eMachProcessFlagsUsingSBS;
1673#endif
1674        if (!m_task.StartExceptionThread(err))
1675        {
1676            const char *err_cstr = err.AsString();
1677            ::snprintf (err_str, err_len, "%s", err_cstr ? err_cstr : "unable to start the exception thread");
1678            DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid);
1679            m_pid = INVALID_NUB_PROCESS;
1680            return INVALID_NUB_PROCESS;
1681        }
1682
1683        errno = 0;
1684        if (::ptrace (PT_ATTACHEXC, pid, 0, 0))
1685            err.SetError(errno);
1686        else
1687            err.Clear();
1688
1689        if (err.Success())
1690        {
1691            m_flags |= eMachProcessFlagsAttached;
1692            // Sleep a bit to let the exception get received and set our process status
1693            // to stopped.
1694            ::usleep(250000);
1695            DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", pid);
1696            return m_pid;
1697        }
1698        else
1699        {
1700            ::snprintf (err_str, err_len, "%s", err.AsString());
1701            DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid);
1702        }
1703    }
1704    return INVALID_NUB_PROCESS;
1705}
1706
1707Genealogy::ThreadActivitySP
1708MachProcess::GetGenealogyInfoForThread (nub_thread_t tid, bool &timed_out)
1709{
1710    return m_activities.GetGenealogyInfoForThread (m_pid, tid, m_thread_list, m_task.TaskPort(), timed_out);
1711}
1712
1713Genealogy::ProcessExecutableInfoSP
1714MachProcess::GetGenealogyImageInfo (size_t idx)
1715{
1716    return m_activities.GetProcessExecutableInfosAtIndex (idx);
1717}
1718
1719// Do the process specific setup for attach.  If this returns NULL, then there's no
1720// platform specific stuff to be done to wait for the attach.  If you get non-null,
1721// pass that token to the CheckForProcess method, and then to CleanupAfterAttach.
1722
1723//  Call PrepareForAttach before attaching to a process that has not yet launched
1724// This returns a token that can be passed to CheckForProcess, and to CleanupAfterAttach.
1725// You should call CleanupAfterAttach to free the token, and do whatever other
1726// cleanup seems good.
1727
1728const void *
1729MachProcess::PrepareForAttach (const char *path, nub_launch_flavor_t launch_flavor, bool waitfor, DNBError &attach_err)
1730{
1731#if defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
1732    // Tell SpringBoard to halt the next launch of this application on startup.
1733
1734    if (!waitfor)
1735        return NULL;
1736
1737    const char *app_ext = strstr(path, ".app");
1738    const bool is_app = app_ext != NULL && (app_ext[4] == '\0' || app_ext[4] == '/');
1739    if (!is_app)
1740    {
1741        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::PrepareForAttach(): path '%s' doesn't contain .app, "
1742                                      "we can't tell springboard to wait for launch...",
1743                                      path);
1744        return NULL;
1745    }
1746
1747#if defined (WITH_BKS)
1748    if (launch_flavor == eLaunchFlavorDefault)
1749        launch_flavor = eLaunchFlavorBKS;
1750    if (launch_flavor != eLaunchFlavorBKS)
1751        return NULL;
1752#elif defined (WITH_SPRINGBOARD)
1753    if (launch_flavor == eLaunchFlavorDefault)
1754        launch_flavor = eLaunchFlavorSpringBoard;
1755    if (launch_flavor != eLaunchFlavorSpringBoard)
1756        return NULL;
1757#endif
1758
1759    std::string app_bundle_path(path, app_ext + strlen(".app"));
1760
1761    CFStringRef bundleIDCFStr = CopyBundleIDForPath (app_bundle_path.c_str (), attach_err);
1762    std::string bundleIDStr;
1763    CFString::UTF8(bundleIDCFStr, bundleIDStr);
1764    DNBLogThreadedIf(LOG_PROCESS,
1765                     "CopyBundleIDForPath (%s, err_str) returned @\"%s\"",
1766                     app_bundle_path.c_str (),
1767                     bundleIDStr.c_str());
1768
1769    if (bundleIDCFStr == NULL)
1770    {
1771        return NULL;
1772    }
1773
1774#if defined (WITH_BKS)
1775    if (launch_flavor == eLaunchFlavorBKS)
1776    {
1777        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1778
1779        NSString *stdio_path = nil;
1780        NSFileManager *file_manager = [NSFileManager defaultManager];
1781        const char *null_path = "/dev/null";
1782        stdio_path = [file_manager stringWithFileSystemRepresentation: null_path length: strlen(null_path)];
1783
1784        NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
1785        NSMutableDictionary *options       = [NSMutableDictionary dictionary];
1786
1787        DNBLogThreadedIf(LOG_PROCESS, "Calling BKSSystemService openApplication: @\"%s\",options include stdio path: \"%s\", "
1788                                      "BKSDebugOptionKeyDebugOnNextLaunch & BKSDebugOptionKeyWaitForDebugger )",
1789                                      bundleIDStr.c_str(),
1790                                      null_path);
1791
1792        [debug_options setObject: stdio_path forKey: BKSDebugOptionKeyStandardOutPath];
1793        [debug_options setObject: stdio_path forKey: BKSDebugOptionKeyStandardErrorPath];
1794        [debug_options setObject: [NSNumber numberWithBool: YES] forKey: BKSDebugOptionKeyWaitForDebugger];
1795        [debug_options setObject: [NSNumber numberWithBool: YES] forKey: BKSDebugOptionKeyDebugOnNextLaunch];
1796
1797        [options setObject: debug_options forKey: BKSOpenApplicationOptionKeyDebuggingOptions];
1798
1799        BKSSystemService *system_service = [[BKSSystemService alloc] init];
1800
1801        mach_port_t client_port = [system_service createClientPort];
1802        __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
1803        __block BKSOpenApplicationErrorCode attach_error_code = BKSOpenApplicationErrorCodeNone;
1804
1805        NSString *bundleIDNSStr = (NSString *) bundleIDCFStr;
1806
1807        [system_service openApplication: bundleIDNSStr
1808                       options: options
1809                       clientPort: client_port
1810                       withResult: ^(NSError *error)
1811                       {
1812                            // The system service will cleanup the client port we created for us.
1813                            if (error)
1814                                attach_error_code = (BKSOpenApplicationErrorCode)[error code];
1815
1816                            [system_service release];
1817                            dispatch_semaphore_signal(semaphore);
1818                        }
1819        ];
1820
1821        const uint32_t timeout_secs = 9;
1822
1823        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
1824
1825        long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
1826
1827        if (!success)
1828        {
1829            DNBLogError("timed out trying to launch %s.", bundleIDStr.c_str());
1830            attach_err.SetErrorString("debugserver timed out waiting for openApplication to complete.");
1831            attach_err.SetError (BKS_OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
1832        }
1833        else if (attach_error_code != BKSOpenApplicationErrorCodeNone)
1834        {
1835            SetBKSError (attach_error_code, attach_err);
1836            DNBLogError("unable to launch the application with CFBundleIdentifier '%s' bks_error = %u",
1837                        bundleIDStr.c_str(),
1838                        attach_error_code);
1839        }
1840        dispatch_release(semaphore);
1841        [pool drain];
1842    }
1843#elif defined (WITH_SPRINGBOARD)
1844    if (launch_flavor == eLaunchFlavorSpringBoard)
1845    {
1846        SBSApplicationLaunchError sbs_error = 0;
1847
1848        const char *stdout_err = "/dev/null";
1849        CFString stdio_path;
1850        stdio_path.SetFileSystemRepresentation (stdout_err);
1851
1852        DNBLogThreadedIf(LOG_PROCESS, "SBSLaunchApplicationForDebugging ( @\"%s\" , NULL, NULL, NULL, @\"%s\", @\"%s\", "
1853                                      "SBSApplicationDebugOnNextLaunch | SBSApplicationLaunchWaitForDebugger )",
1854                                      bundleIDStr.c_str(),
1855                                      stdout_err,
1856                                      stdout_err);
1857
1858        sbs_error = SBSLaunchApplicationForDebugging (bundleIDCFStr,
1859                                                      (CFURLRef)NULL,         // openURL
1860                                                      NULL, // launch_argv.get(),
1861                                                      NULL, // launch_envp.get(),  // CFDictionaryRef environment
1862                                                      stdio_path.get(),
1863                                                      stdio_path.get(),
1864                                                      SBSApplicationDebugOnNextLaunch | SBSApplicationLaunchWaitForDebugger);
1865
1866        if (sbs_error != SBSApplicationLaunchErrorSuccess)
1867        {
1868            attach_err.SetError(sbs_error, DNBError::SpringBoard);
1869            return NULL;
1870        }
1871    }
1872#endif // WITH_SPRINGBOARD
1873
1874    DNBLogThreadedIf(LOG_PROCESS, "Successfully set DebugOnNextLaunch.");
1875    return bundleIDCFStr;
1876# else  // defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
1877  return NULL;
1878#endif
1879}
1880
1881// Pass in the token you got from PrepareForAttach.  If there is a process
1882// for that token, then the pid will be returned, otherwise INVALID_NUB_PROCESS
1883// will be returned.
1884
1885nub_process_t
1886MachProcess::CheckForProcess (const void *attach_token)
1887{
1888    if (attach_token == NULL)
1889        return INVALID_NUB_PROCESS;
1890
1891#if defined (WITH_BKS)
1892    NSString *bundleIDNSStr = (NSString *) attach_token;
1893    BKSSystemService *systemService = [[BKSSystemService alloc] init];
1894    pid_t pid = [systemService pidForApplication: bundleIDNSStr];
1895    [systemService release];
1896    if (pid == 0)
1897        return INVALID_NUB_PROCESS;
1898    else
1899        return pid;
1900#elif defined (WITH_SPRINGBOARD)
1901    CFStringRef bundleIDCFStr = (CFStringRef) attach_token;
1902    Boolean got_it;
1903    nub_process_t attach_pid;
1904    got_it = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &attach_pid);
1905    if (got_it)
1906        return attach_pid;
1907    else
1908        return INVALID_NUB_PROCESS;
1909#else
1910    return INVALID_NUB_PROCESS;
1911#endif
1912}
1913
1914// Call this to clean up after you have either attached or given up on the attach.
1915// Pass true for success if you have attached, false if you have not.
1916// The token will also be freed at this point, so you can't use it after calling
1917// this method.
1918
1919void
1920MachProcess::CleanupAfterAttach (const void *attach_token, bool success, DNBError &err_str)
1921{
1922    if (attach_token == NULL)
1923        return;
1924
1925#if defined (WITH_BKS)
1926
1927    if (!success)
1928    {
1929        BKSCleanupAfterAttach (attach_token, err_str);
1930    }
1931    CFRelease((CFStringRef) attach_token);
1932
1933#elif defined (WITH_SPRINGBOARD)
1934    // Tell SpringBoard to cancel the debug on next launch of this application
1935    // if we failed to attach
1936    if (!success)
1937    {
1938        SBSApplicationLaunchError sbs_error = 0;
1939        CFStringRef bundleIDCFStr = (CFStringRef) attach_token;
1940
1941        sbs_error = SBSLaunchApplicationForDebugging (bundleIDCFStr,
1942                                                      (CFURLRef)NULL,
1943                                                      NULL,
1944                                                      NULL,
1945                                                      NULL,
1946                                                      NULL,
1947                                                      SBSApplicationCancelDebugOnNextLaunch);
1948
1949        if (sbs_error != SBSApplicationLaunchErrorSuccess)
1950        {
1951            err_str.SetError(sbs_error, DNBError::SpringBoard);
1952            return;
1953        }
1954    }
1955
1956    CFRelease((CFStringRef) attach_token);
1957#endif
1958}
1959
1960pid_t
1961MachProcess::LaunchForDebug
1962(
1963    const char *path,
1964    char const *argv[],
1965    char const *envp[],
1966    const char *working_directory, // NULL => don't change, non-NULL => set working directory for inferior to this
1967    const char *stdin_path,
1968    const char *stdout_path,
1969    const char *stderr_path,
1970    bool no_stdio,
1971    nub_launch_flavor_t launch_flavor,
1972    int disable_aslr,
1973    const char *event_data,
1974    DNBError &launch_err
1975)
1976{
1977    // Clear out and clean up from any current state
1978    Clear();
1979
1980    DNBLogThreadedIf(LOG_PROCESS, "%s( path = '%s', argv = %p, envp = %p, launch_flavor = %u, disable_aslr = %d )", __FUNCTION__, path, argv, envp, launch_flavor, disable_aslr);
1981
1982    // Fork a child process for debugging
1983    SetState(eStateLaunching);
1984
1985    switch (launch_flavor)
1986    {
1987    case eLaunchFlavorForkExec:
1988        m_pid = MachProcess::ForkChildForPTraceDebugging (path, argv, envp, this, launch_err);
1989        break;
1990#ifdef WITH_BKS
1991    case eLaunchFlavorBKS:
1992        {
1993            const char *app_ext = strstr(path, ".app");
1994            if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/'))
1995            {
1996                std::string app_bundle_path(path, app_ext + strlen(".app"));
1997                if (BKSLaunchForDebug (app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, event_data, launch_err) != 0)
1998                    return m_pid; // A successful SBLaunchForDebug() returns and assigns a non-zero m_pid.
1999                else
2000                    break; // We tried a BKS launch, but didn't succeed lets get out
2001            }
2002        }
2003        // In case the executable name has a ".app" fragment which confuses our debugserver,
2004        // let's do an intentional fallthrough here...
2005        launch_flavor = eLaunchFlavorPosixSpawn;
2006#endif
2007#ifdef WITH_SPRINGBOARD
2008
2009    case eLaunchFlavorSpringBoard:
2010        {
2011            //  .../whatever.app/whatever ?
2012            //  Or .../com.apple.whatever.app/whatever -- be careful of ".app" in "com.apple.whatever" here
2013            const char *app_ext = strstr (path, ".app/");
2014            if (app_ext == NULL)
2015            {
2016                // .../whatever.app ?
2017                int len = strlen (path);
2018                if (len > 5)
2019                {
2020                    if (strcmp (path + len - 4, ".app") == 0)
2021                    {
2022                        app_ext = path + len - 4;
2023                    }
2024                }
2025            }
2026            if (app_ext)
2027            {
2028                std::string app_bundle_path(path, app_ext + strlen(".app"));
2029                if (SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, launch_err) != 0)
2030                    return m_pid; // A successful SBLaunchForDebug() returns and assigns a non-zero m_pid.
2031                else
2032                    break; // We tried a springboard launch, but didn't succeed lets get out
2033            }
2034        }
2035        // In case the executable name has a ".app" fragment which confuses our debugserver,
2036        // let's do an intentional fallthrough here...
2037        launch_flavor = eLaunchFlavorPosixSpawn;
2038
2039#endif
2040
2041    case eLaunchFlavorPosixSpawn:
2042        m_pid = MachProcess::PosixSpawnChildForPTraceDebugging (path,
2043                                                                DNBArchProtocol::GetArchitecture (),
2044                                                                argv,
2045                                                                envp,
2046                                                                working_directory,
2047                                                                stdin_path,
2048                                                                stdout_path,
2049                                                                stderr_path,
2050                                                                no_stdio,
2051                                                                this,
2052                                                                disable_aslr,
2053                                                                launch_err);
2054        break;
2055
2056    default:
2057        // Invalid  launch
2058        launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
2059        return INVALID_NUB_PROCESS;
2060    }
2061
2062    if (m_pid == INVALID_NUB_PROCESS)
2063    {
2064        // If we don't have a valid process ID and no one has set the error,
2065        // then return a generic error
2066        if (launch_err.Success())
2067            launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
2068    }
2069    else
2070    {
2071        m_path = path;
2072        size_t i;
2073        char const *arg;
2074        for (i=0; (arg = argv[i]) != NULL; i++)
2075            m_args.push_back(arg);
2076
2077        m_task.StartExceptionThread(launch_err);
2078        if (launch_err.Fail())
2079        {
2080            if (launch_err.AsString() == NULL)
2081                launch_err.SetErrorString("unable to start the exception thread");
2082            DNBLog ("Could not get inferior's Mach exception port, sending ptrace PT_KILL and exiting.");
2083            ::ptrace (PT_KILL, m_pid, 0, 0);
2084            m_pid = INVALID_NUB_PROCESS;
2085            return INVALID_NUB_PROCESS;
2086        }
2087
2088        StartSTDIOThread();
2089
2090        if (launch_flavor == eLaunchFlavorPosixSpawn)
2091        {
2092
2093            SetState (eStateAttaching);
2094            errno = 0;
2095            int err = ::ptrace (PT_ATTACHEXC, m_pid, 0, 0);
2096            if (err == 0)
2097            {
2098                m_flags |= eMachProcessFlagsAttached;
2099                DNBLogThreadedIf(LOG_PROCESS, "successfully spawned pid %d", m_pid);
2100                launch_err.Clear();
2101            }
2102            else
2103            {
2104                SetState (eStateExited);
2105                DNBError ptrace_err(errno, DNBError::POSIX);
2106                DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to spawned pid %d (err = %i, errno = %i (%s))", m_pid, err, ptrace_err.Error(), ptrace_err.AsString());
2107                launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
2108            }
2109        }
2110        else
2111        {
2112            launch_err.Clear();
2113        }
2114    }
2115    return m_pid;
2116}
2117
2118pid_t
2119MachProcess::PosixSpawnChildForPTraceDebugging
2120(
2121    const char *path,
2122    cpu_type_t cpu_type,
2123    char const *argv[],
2124    char const *envp[],
2125    const char *working_directory,
2126    const char *stdin_path,
2127    const char *stdout_path,
2128    const char *stderr_path,
2129    bool no_stdio,
2130    MachProcess* process,
2131    int disable_aslr,
2132    DNBError& err
2133)
2134{
2135    posix_spawnattr_t attr;
2136    short flags;
2137    DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv=%p, envp=%p, working_dir=%s, stdin=%s, stdout=%s stderr=%s, no-stdio=%i)",
2138                     __FUNCTION__,
2139                     path,
2140                     argv,
2141                     envp,
2142                     working_directory,
2143                     stdin_path,
2144                     stdout_path,
2145                     stderr_path,
2146                     no_stdio);
2147
2148    err.SetError( ::posix_spawnattr_init (&attr), DNBError::POSIX);
2149    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
2150        err.LogThreaded("::posix_spawnattr_init ( &attr )");
2151    if (err.Fail())
2152        return INVALID_NUB_PROCESS;
2153
2154    flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK;
2155    if (disable_aslr)
2156        flags |= _POSIX_SPAWN_DISABLE_ASLR;
2157
2158    sigset_t no_signals;
2159    sigset_t all_signals;
2160    sigemptyset (&no_signals);
2161    sigfillset (&all_signals);
2162    ::posix_spawnattr_setsigmask(&attr, &no_signals);
2163    ::posix_spawnattr_setsigdefault(&attr, &all_signals);
2164
2165    err.SetError( ::posix_spawnattr_setflags (&attr, flags), DNBError::POSIX);
2166    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
2167        err.LogThreaded("::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED%s )", flags & _POSIX_SPAWN_DISABLE_ASLR ? " | _POSIX_SPAWN_DISABLE_ASLR" : "");
2168    if (err.Fail())
2169        return INVALID_NUB_PROCESS;
2170
2171    // Don't do this on SnowLeopard, _sometimes_ the TASK_BASIC_INFO will fail
2172    // and we will fail to continue with our process...
2173
2174    // On SnowLeopard we should set "DYLD_NO_PIE" in the inferior environment....
2175
2176#if !defined(__arm__)
2177
2178    // We don't need to do this for ARM, and we really shouldn't now that we
2179    // have multiple CPU subtypes and no posix_spawnattr call that allows us
2180    // to set which CPU subtype to launch...
2181    if (cpu_type != 0)
2182    {
2183        size_t ocount = 0;
2184        err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu_type, &ocount), DNBError::POSIX);
2185        if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
2186            err.LogThreaded("::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %llu )", cpu_type, (uint64_t)ocount);
2187
2188        if (err.Fail() != 0 || ocount != 1)
2189            return INVALID_NUB_PROCESS;
2190    }
2191#endif
2192
2193    PseudoTerminal pty;
2194
2195    posix_spawn_file_actions_t file_actions;
2196    err.SetError( ::posix_spawn_file_actions_init (&file_actions), DNBError::POSIX);
2197    int file_actions_valid = err.Success();
2198    if (!file_actions_valid || DNBLogCheckLogBit(LOG_PROCESS))
2199        err.LogThreaded("::posix_spawn_file_actions_init ( &file_actions )");
2200    int pty_error = -1;
2201    pid_t pid = INVALID_NUB_PROCESS;
2202    if (file_actions_valid)
2203    {
2204        if (stdin_path == NULL && stdout_path == NULL && stderr_path == NULL && !no_stdio)
2205        {
2206            pty_error = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY);
2207            if (pty_error == PseudoTerminal::success)
2208            {
2209                stdin_path = stdout_path = stderr_path = pty.SlaveName();
2210            }
2211        }
2212
2213        // if no_stdio or std paths not supplied, then route to "/dev/null".
2214        if (no_stdio || stdin_path == NULL || stdin_path[0] == '\0')
2215            stdin_path = "/dev/null";
2216        if (no_stdio || stdout_path == NULL || stdout_path[0] == '\0')
2217            stdout_path = "/dev/null";
2218        if (no_stdio || stderr_path == NULL || stderr_path[0] == '\0')
2219            stderr_path = "/dev/null";
2220
2221        err.SetError( ::posix_spawn_file_actions_addopen (&file_actions,
2222                                                          STDIN_FILENO,
2223                                                          stdin_path,
2224                                                          O_RDONLY | O_NOCTTY,
2225                                                          0),
2226                     DNBError::POSIX);
2227        if (err.Fail() || DNBLogCheckLogBit (LOG_PROCESS))
2228            err.LogThreaded ("::posix_spawn_file_actions_addopen (&file_actions, filedes=STDIN_FILENO, path='%s')", stdin_path);
2229
2230        err.SetError( ::posix_spawn_file_actions_addopen (&file_actions,
2231                                                          STDOUT_FILENO,
2232                                                          stdout_path,
2233                                                          O_WRONLY | O_NOCTTY | O_CREAT,
2234                                                          0640),
2235                     DNBError::POSIX);
2236        if (err.Fail() || DNBLogCheckLogBit (LOG_PROCESS))
2237            err.LogThreaded ("::posix_spawn_file_actions_addopen (&file_actions, filedes=STDOUT_FILENO, path='%s')", stdout_path);
2238
2239        err.SetError( ::posix_spawn_file_actions_addopen (&file_actions,
2240                                                          STDERR_FILENO,
2241                                                          stderr_path,
2242                                                          O_WRONLY | O_NOCTTY | O_CREAT,
2243                                                          0640),
2244                     DNBError::POSIX);
2245        if (err.Fail() || DNBLogCheckLogBit (LOG_PROCESS))
2246            err.LogThreaded ("::posix_spawn_file_actions_addopen (&file_actions, filedes=STDERR_FILENO, path='%s')", stderr_path);
2247
2248        // TODO: Verify if we can set the working directory back immediately
2249        // after the posix_spawnp call without creating a race condition???
2250        if (working_directory)
2251            ::chdir (working_directory);
2252
2253        err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), DNBError::POSIX);
2254        if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
2255            err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, &file_actions, &attr, argv, envp);
2256    }
2257    else
2258    {
2259        // TODO: Verify if we can set the working directory back immediately
2260        // after the posix_spawnp call without creating a race condition???
2261        if (working_directory)
2262            ::chdir (working_directory);
2263
2264        err.SetError( ::posix_spawnp (&pid, path, NULL, &attr, (char * const*)argv, (char * const*)envp), DNBError::POSIX);
2265        if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
2266            err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, NULL, &attr, argv, envp);
2267    }
2268
2269    // We have seen some cases where posix_spawnp was returning a valid
2270    // looking pid even when an error was returned, so clear it out
2271    if (err.Fail())
2272        pid = INVALID_NUB_PROCESS;
2273
2274    if (pty_error == 0)
2275    {
2276        if (process != NULL)
2277        {
2278            int master_fd = pty.ReleaseMasterFD();
2279            process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
2280        }
2281    }
2282    ::posix_spawnattr_destroy (&attr);
2283
2284    if (pid != INVALID_NUB_PROCESS)
2285    {
2286        cpu_type_t pid_cpu_type = MachProcess::GetCPUTypeForLocalProcess (pid);
2287        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( ) pid=%i, cpu_type=0x%8.8x", __FUNCTION__, pid, pid_cpu_type);
2288        if (pid_cpu_type)
2289            DNBArchProtocol::SetArchitecture (pid_cpu_type);
2290    }
2291
2292    if (file_actions_valid)
2293    {
2294        DNBError err2;
2295        err2.SetError( ::posix_spawn_file_actions_destroy (&file_actions), DNBError::POSIX);
2296        if (err2.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
2297            err2.LogThreaded("::posix_spawn_file_actions_destroy ( &file_actions )");
2298    }
2299
2300    return pid;
2301}
2302
2303uint32_t
2304MachProcess::GetCPUTypeForLocalProcess (pid_t pid)
2305{
2306    int mib[CTL_MAXNAME]={0,};
2307    size_t len = CTL_MAXNAME;
2308    if (::sysctlnametomib("sysctl.proc_cputype", mib, &len))
2309        return 0;
2310
2311    mib[len] = pid;
2312    len++;
2313
2314    cpu_type_t cpu;
2315    size_t cpu_len = sizeof(cpu);
2316    if (::sysctl (mib, len, &cpu, &cpu_len, 0, 0))
2317        cpu = 0;
2318    return cpu;
2319}
2320
2321pid_t
2322MachProcess::ForkChildForPTraceDebugging
2323(
2324    const char *path,
2325    char const *argv[],
2326    char const *envp[],
2327    MachProcess* process,
2328    DNBError& launch_err
2329)
2330{
2331    PseudoTerminal::Error pty_error = PseudoTerminal::success;
2332
2333    // Use a fork that ties the child process's stdin/out/err to a pseudo
2334    // terminal so we can read it in our MachProcess::STDIOThread
2335    // as unbuffered io.
2336    PseudoTerminal pty;
2337    pid_t pid = pty.Fork(pty_error);
2338
2339    if (pid < 0)
2340    {
2341        //--------------------------------------------------------------
2342        // Error during fork.
2343        //--------------------------------------------------------------
2344        return pid;
2345    }
2346    else if (pid == 0)
2347    {
2348        //--------------------------------------------------------------
2349        // Child process
2350        //--------------------------------------------------------------
2351        ::ptrace (PT_TRACE_ME, 0, 0, 0);    // Debug this process
2352        ::ptrace (PT_SIGEXC, 0, 0, 0);    // Get BSD signals as mach exceptions
2353
2354        // If our parent is setgid, lets make sure we don't inherit those
2355        // extra powers due to nepotism.
2356        if (::setgid (getgid ()) == 0)
2357        {
2358
2359            // Let the child have its own process group. We need to execute
2360            // this call in both the child and parent to avoid a race condition
2361            // between the two processes.
2362            ::setpgid (0, 0);    // Set the child process group to match its pid
2363
2364            // Sleep a bit to before the exec call
2365            ::sleep (1);
2366
2367            // Turn this process into
2368            ::execv (path, (char * const *)argv);
2369        }
2370        // Exit with error code. Child process should have taken
2371        // over in above exec call and if the exec fails it will
2372        // exit the child process below.
2373        ::exit (127);
2374    }
2375    else
2376    {
2377        //--------------------------------------------------------------
2378        // Parent process
2379        //--------------------------------------------------------------
2380        // Let the child have its own process group. We need to execute
2381        // this call in both the child and parent to avoid a race condition
2382        // between the two processes.
2383        ::setpgid (pid, pid);    // Set the child process group to match its pid
2384
2385        if (process != NULL)
2386        {
2387            // Release our master pty file descriptor so the pty class doesn't
2388            // close it and so we can continue to use it in our STDIO thread
2389            int master_fd = pty.ReleaseMasterFD();
2390            process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
2391        }
2392    }
2393    return pid;
2394}
2395
2396#if defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
2397// This returns a CFRetained pointer to the Bundle ID for app_bundle_path,
2398// or NULL if there was some problem getting the bundle id.
2399static CFStringRef
2400CopyBundleIDForPath (const char *app_bundle_path, DNBError &err_str)
2401{
2402    CFBundle bundle(app_bundle_path);
2403    CFStringRef bundleIDCFStr = bundle.GetIdentifier();
2404    std::string bundleID;
2405    if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL)
2406    {
2407        struct stat app_bundle_stat;
2408        char err_msg[PATH_MAX];
2409
2410        if (::stat (app_bundle_path, &app_bundle_stat) < 0)
2411        {
2412            err_str.SetError(errno, DNBError::POSIX);
2413            snprintf(err_msg, sizeof(err_msg), "%s: \"%s\"", err_str.AsString(), app_bundle_path);
2414            err_str.SetErrorString(err_msg);
2415            DNBLogThreadedIf(LOG_PROCESS, "%s() error: %s", __FUNCTION__, err_msg);
2416        }
2417        else
2418        {
2419            err_str.SetError(-1, DNBError::Generic);
2420            snprintf(err_msg, sizeof(err_msg), "failed to extract CFBundleIdentifier from %s", app_bundle_path);
2421            err_str.SetErrorString(err_msg);
2422            DNBLogThreadedIf(LOG_PROCESS, "%s() error: failed to extract CFBundleIdentifier from '%s'", __FUNCTION__, app_bundle_path);
2423        }
2424        return NULL;
2425    }
2426
2427    DNBLogThreadedIf(LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s", __FUNCTION__, bundleID.c_str());
2428    CFRetain (bundleIDCFStr);
2429
2430    return bundleIDCFStr;
2431}
2432#endif // #if defined 9WITH_SPRINGBOARD) || defined (WITH_BKS)
2433#ifdef WITH_SPRINGBOARD
2434
2435pid_t
2436MachProcess::SBLaunchForDebug (const char *path, char const *argv[], char const *envp[], bool no_stdio, bool disable_aslr, DNBError &launch_err)
2437{
2438    // Clear out and clean up from any current state
2439    Clear();
2440
2441    DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
2442
2443    // Fork a child process for debugging
2444    SetState(eStateLaunching);
2445    m_pid = MachProcess::SBForkChildForPTraceDebugging(path, argv, envp, no_stdio, this, launch_err);
2446    if (m_pid != 0)
2447    {
2448        m_flags |= eMachProcessFlagsUsingSBS;
2449        m_path = path;
2450        size_t i;
2451        char const *arg;
2452        for (i=0; (arg = argv[i]) != NULL; i++)
2453            m_args.push_back(arg);
2454        m_task.StartExceptionThread(launch_err);
2455
2456        if (launch_err.Fail())
2457        {
2458            if (launch_err.AsString() == NULL)
2459                launch_err.SetErrorString("unable to start the exception thread");
2460            DNBLog ("Could not get inferior's Mach exception port, sending ptrace PT_KILL and exiting.");
2461            ::ptrace (PT_KILL, m_pid, 0, 0);
2462            m_pid = INVALID_NUB_PROCESS;
2463            return INVALID_NUB_PROCESS;
2464        }
2465
2466        StartSTDIOThread();
2467        SetState (eStateAttaching);
2468        int err = ::ptrace (PT_ATTACHEXC, m_pid, 0, 0);
2469        if (err == 0)
2470        {
2471            m_flags |= eMachProcessFlagsAttached;
2472            DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid);
2473        }
2474        else
2475        {
2476            SetState (eStateExited);
2477            DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
2478        }
2479    }
2480    return m_pid;
2481}
2482
2483#include <servers/bootstrap.h>
2484
2485pid_t
2486MachProcess::SBForkChildForPTraceDebugging (const char *app_bundle_path, char const *argv[], char const *envp[], bool no_stdio, MachProcess* process, DNBError &launch_err)
2487{
2488    DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, process);
2489    CFAllocatorRef alloc = kCFAllocatorDefault;
2490
2491    if (argv[0] == NULL)
2492        return INVALID_NUB_PROCESS;
2493
2494    size_t argc = 0;
2495    // Count the number of arguments
2496    while (argv[argc] != NULL)
2497        argc++;
2498
2499    // Enumerate the arguments
2500    size_t first_launch_arg_idx = 1;
2501    CFReleaser<CFMutableArrayRef> launch_argv;
2502
2503    if (argv[first_launch_arg_idx])
2504    {
2505        size_t launch_argc = argc > 0 ? argc - 1 : 0;
2506        launch_argv.reset (::CFArrayCreateMutable (alloc, launch_argc, &kCFTypeArrayCallBacks));
2507        size_t i;
2508        char const *arg;
2509        CFString launch_arg;
2510        for (i=first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL); i++)
2511        {
2512            launch_arg.reset(::CFStringCreateWithCString (alloc, arg, kCFStringEncodingUTF8));
2513            if (launch_arg.get() != NULL)
2514                CFArrayAppendValue(launch_argv.get(), launch_arg.get());
2515            else
2516                break;
2517        }
2518    }
2519
2520    // Next fill in the arguments dictionary.  Note, the envp array is of the form
2521    // Variable=value but SpringBoard wants a CF dictionary.  So we have to convert
2522    // this here.
2523
2524    CFReleaser<CFMutableDictionaryRef> launch_envp;
2525
2526    if (envp[0])
2527    {
2528        launch_envp.reset(::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
2529        const char *value;
2530        int name_len;
2531        CFString name_string, value_string;
2532
2533        for (int i = 0; envp[i] != NULL; i++)
2534        {
2535            value = strstr (envp[i], "=");
2536
2537            // If the name field is empty or there's no =, skip it.  Somebody's messing with us.
2538            if (value == NULL || value == envp[i])
2539                continue;
2540
2541            name_len = value - envp[i];
2542
2543            // Now move value over the "="
2544            value++;
2545
2546            name_string.reset(::CFStringCreateWithBytes(alloc, (const UInt8 *) envp[i], name_len, kCFStringEncodingUTF8, false));
2547            value_string.reset(::CFStringCreateWithCString(alloc, value, kCFStringEncodingUTF8));
2548            CFDictionarySetValue (launch_envp.get(), name_string.get(), value_string.get());
2549        }
2550    }
2551
2552    CFString stdio_path;
2553
2554    PseudoTerminal pty;
2555    if (!no_stdio)
2556    {
2557        PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY);
2558        if (pty_err == PseudoTerminal::success)
2559        {
2560            const char* slave_name = pty.SlaveName();
2561            DNBLogThreadedIf(LOG_PROCESS, "%s() successfully opened master pty, slave is %s", __FUNCTION__, slave_name);
2562            if (slave_name && slave_name[0])
2563            {
2564                ::chmod (slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
2565                stdio_path.SetFileSystemRepresentation (slave_name);
2566            }
2567        }
2568    }
2569
2570    if (stdio_path.get() == NULL)
2571    {
2572        stdio_path.SetFileSystemRepresentation ("/dev/null");
2573    }
2574
2575    CFStringRef bundleIDCFStr = CopyBundleIDForPath (app_bundle_path, launch_err);
2576    if (bundleIDCFStr == NULL)
2577        return INVALID_NUB_PROCESS;
2578
2579    // This is just for logging:
2580    std::string bundleID;
2581    CFString::UTF8(bundleIDCFStr, bundleID);
2582
2583    DNBLogThreadedIf(LOG_PROCESS, "%s() serialized launch arg array", __FUNCTION__);
2584
2585    // Find SpringBoard
2586    SBSApplicationLaunchError sbs_error = 0;
2587    sbs_error = SBSLaunchApplicationForDebugging (bundleIDCFStr,
2588                                                  (CFURLRef)NULL,         // openURL
2589                                                  launch_argv.get(),
2590                                                  launch_envp.get(),  // CFDictionaryRef environment
2591                                                  stdio_path.get(),
2592                                                  stdio_path.get(),
2593                                                  SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice);
2594
2595
2596    launch_err.SetError(sbs_error, DNBError::SpringBoard);
2597
2598    if (sbs_error == SBSApplicationLaunchErrorSuccess)
2599    {
2600        static const useconds_t pid_poll_interval = 200000;
2601        static const useconds_t pid_poll_timeout = 30000000;
2602
2603        useconds_t pid_poll_total = 0;
2604
2605        nub_process_t pid = INVALID_NUB_PROCESS;
2606        Boolean pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
2607        // Poll until the process is running, as long as we are getting valid responses and the timeout hasn't expired
2608        // A return PID of 0 means the process is not running, which may be because it hasn't been (asynchronously) started
2609        // yet, or that it died very quickly (if you weren't using waitForDebugger).
2610        while (!pid_found && pid_poll_total < pid_poll_timeout)
2611        {
2612            usleep (pid_poll_interval);
2613            pid_poll_total += pid_poll_interval;
2614            DNBLogThreadedIf(LOG_PROCESS, "%s() polling Springboard for pid for %s...", __FUNCTION__, bundleID.c_str());
2615            pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
2616        }
2617
2618        CFRelease (bundleIDCFStr);
2619        if (pid_found)
2620        {
2621            if (process != NULL)
2622            {
2623                // Release our master pty file descriptor so the pty class doesn't
2624                // close it and so we can continue to use it in our STDIO thread
2625                int master_fd = pty.ReleaseMasterFD();
2626                process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
2627            }
2628            DNBLogThreadedIf(LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid);
2629        }
2630        else
2631        {
2632            DNBLogError("failed to lookup the process ID for CFBundleIdentifier %s.", bundleID.c_str());
2633        }
2634        return pid;
2635    }
2636
2637    DNBLogError("unable to launch the application with CFBundleIdentifier '%s' sbs_error = %u", bundleID.c_str(), sbs_error);
2638    return INVALID_NUB_PROCESS;
2639}
2640
2641#endif // #ifdef WITH_SPRINGBOARD
2642
2643#ifdef WITH_BKS
2644
2645
2646// This function runs the BKSSystemService method openApplication:options:clientPort:withResult,
2647// messaging the app passed in bundleIDNSStr.
2648// The function should be run inside of an NSAutoReleasePool.
2649//
2650// It will use the "options" dictionary passed in, and fill the error passed in if there is an error.
2651// If return_pid is not NULL, we'll fetch the pid that was made for the bundleID.
2652// If bundleIDNSStr is NULL, then the system application will be messaged.
2653
2654static bool
2655CallBKSSystemServiceOpenApplication (NSString *bundleIDNSStr, NSDictionary *options, DNBError &error, pid_t *return_pid)
2656{
2657    // Now make our systemService:
2658    BKSSystemService *system_service = [[BKSSystemService alloc] init];
2659
2660    if (bundleIDNSStr == nil)
2661    {
2662        bundleIDNSStr = [system_service systemApplicationBundleIdentifier];
2663        if (bundleIDNSStr == nil)
2664        {
2665            // Okay, no system app...
2666            error.SetErrorString("No system application to message.");
2667            return false;
2668        }
2669    }
2670
2671    mach_port_t client_port = [system_service createClientPort];
2672    __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2673    __block BKSOpenApplicationErrorCode open_app_error = BKSOpenApplicationErrorCodeNone;
2674    bool wants_pid = (return_pid != NULL);
2675    __block pid_t pid_in_block;
2676
2677    const char *cstr = [bundleIDNSStr UTF8String];
2678    if (!cstr)
2679        cstr = "<Unknown Bundle ID>";
2680
2681    DNBLog ("About to launch process for bundle ID: %s", cstr);
2682    [system_service openApplication: bundleIDNSStr
2683                    options: options
2684                    clientPort: client_port
2685                    withResult: ^(NSError *bks_error)
2686                    {
2687                        // The system service will cleanup the client port we created for us.
2688                        if (bks_error)
2689                            open_app_error = (BKSOpenApplicationErrorCode)[bks_error code];
2690
2691                        if (open_app_error == BKSOpenApplicationErrorCodeNone)
2692                        {
2693                            if (wants_pid)
2694                            {
2695                                pid_in_block = [system_service pidForApplication: bundleIDNSStr];
2696                                DNBLog("In completion handler, got pid for bundle id, pid: %d.", pid_in_block);
2697                                DNBLogThreadedIf(LOG_PROCESS, "In completion handler, got pid for bundle id, pid: %d.", pid_in_block);
2698                            }
2699                            else
2700                                DNBLogThreadedIf (LOG_PROCESS, "In completion handler: success.");
2701                        }
2702                        else
2703                        {
2704                            const char *error_str = [[bks_error localizedDescription] UTF8String];
2705                            DNBLogThreadedIf(LOG_PROCESS, "In completion handler for send event, got error \"%s\"(%d).",
2706                                             error_str ? error_str : "<unknown error>",
2707                                             open_app_error);
2708                            // REMOVE ME
2709                            DNBLogError ("In completion handler for send event, got error \"%s\"(%d).",
2710                                             error_str ? error_str : "<unknown error>",
2711                                             open_app_error);
2712                        }
2713
2714                        [system_service release];
2715                        dispatch_semaphore_signal(semaphore);
2716                    }
2717
2718    ];
2719
2720    const uint32_t timeout_secs = 9;
2721
2722    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
2723
2724    long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
2725
2726    dispatch_release(semaphore);
2727
2728    if (!success)
2729    {
2730        DNBLogError("timed out trying to send openApplication to %s.", cstr);
2731        error.SetError (BKS_OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
2732        error.SetErrorString ("timed out trying to launch app");
2733    }
2734    else if (open_app_error != BKSOpenApplicationErrorCodeNone)
2735    {
2736        SetBKSError (open_app_error, error);
2737        DNBLogError("unable to launch the application with CFBundleIdentifier '%s' bks_error = %u", cstr, open_app_error);
2738        success = false;
2739    }
2740    else if (wants_pid)
2741    {
2742        *return_pid = pid_in_block;
2743        DNBLogThreadedIf (LOG_PROCESS, "Out of completion handler, pid from block %d and passing out: %d", pid_in_block, *return_pid);
2744    }
2745
2746
2747    return success;
2748}
2749
2750void
2751MachProcess::BKSCleanupAfterAttach (const void *attach_token, DNBError &err_str)
2752{
2753    bool success;
2754
2755    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2756
2757    // Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use toll-free bridging here:
2758    NSString *bundleIDNSStr = (NSString *) attach_token;
2759
2760    // Okay, now let's assemble all these goodies into the BackBoardServices options mega-dictionary:
2761
2762    // First we have the debug sub-dictionary:
2763    NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
2764    [debug_options setObject: [NSNumber numberWithBool: YES] forKey: BKSDebugOptionKeyCancelDebugOnNextLaunch];
2765
2766    // That will go in the overall dictionary:
2767
2768    NSMutableDictionary *options = [NSMutableDictionary dictionary];
2769    [options setObject: debug_options forKey: BKSOpenApplicationOptionKeyDebuggingOptions];
2770
2771    success = CallBKSSystemServiceOpenApplication(bundleIDNSStr, options, err_str, NULL);
2772
2773    if (!success)
2774    {
2775        DNBLogError ("error trying to cancel debug on next launch for %s: %s", [bundleIDNSStr UTF8String], err_str.AsString());
2776    }
2777
2778    [pool drain];
2779}
2780
2781bool
2782AddEventDataToOptions (NSMutableDictionary *options, const char *event_data, DNBError &option_error)
2783{
2784    if (strcmp (event_data, "BackgroundContentFetching") == 0)
2785    {
2786        DNBLog("Setting ActivateForEvent key in options dictionary.");
2787        NSDictionary *event_details = [NSDictionary dictionary];
2788        NSDictionary *event_dictionary = [NSDictionary dictionaryWithObject:event_details forKey:BKSActivateForEventOptionTypeBackgroundContentFetching];
2789        [options setObject: event_dictionary forKey: BKSOpenApplicationOptionKeyActivateForEvent];
2790        return true;
2791    }
2792    else
2793    {
2794        DNBLogError ("Unrecognized event type: %s.  Ignoring.", event_data);
2795        option_error.SetErrorString("Unrecognized event data.");
2796        return false;
2797    }
2798
2799}
2800
2801pid_t
2802MachProcess::BKSLaunchForDebug (const char *path, char const *argv[], char const *envp[], bool no_stdio, bool disable_aslr, const char *event_data, DNBError &launch_err)
2803{
2804    // Clear out and clean up from any current state
2805    Clear();
2806
2807    DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
2808
2809    // Fork a child process for debugging
2810    SetState(eStateLaunching);
2811    m_pid = BKSForkChildForPTraceDebugging(path, argv, envp, no_stdio, disable_aslr, event_data, launch_err);
2812    if (m_pid != 0)
2813    {
2814        m_flags |= eMachProcessFlagsUsingBKS;
2815        m_path = path;
2816        size_t i;
2817        char const *arg;
2818        for (i=0; (arg = argv[i]) != NULL; i++)
2819            m_args.push_back(arg);
2820        m_task.StartExceptionThread(launch_err);
2821
2822        if (launch_err.Fail())
2823        {
2824            if (launch_err.AsString() == NULL)
2825                launch_err.SetErrorString("unable to start the exception thread");
2826            DNBLog ("Could not get inferior's Mach exception port, sending ptrace PT_KILL and exiting.");
2827            ::ptrace (PT_KILL, m_pid, 0, 0);
2828            m_pid = INVALID_NUB_PROCESS;
2829            return INVALID_NUB_PROCESS;
2830        }
2831
2832        StartSTDIOThread();
2833        SetState (eStateAttaching);
2834        int err = ::ptrace (PT_ATTACHEXC, m_pid, 0, 0);
2835        if (err == 0)
2836        {
2837            m_flags |= eMachProcessFlagsAttached;
2838            DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid);
2839        }
2840        else
2841        {
2842            SetState (eStateExited);
2843            DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
2844        }
2845    }
2846    return m_pid;
2847}
2848
2849pid_t
2850MachProcess::BKSForkChildForPTraceDebugging (const char *app_bundle_path,
2851                                             char const *argv[],
2852                                             char const *envp[],
2853                                             bool no_stdio,
2854                                             bool disable_aslr,
2855                                             const char *event_data,
2856                                             DNBError &launch_err)
2857{
2858    if (argv[0] == NULL)
2859        return INVALID_NUB_PROCESS;
2860
2861    DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, this);
2862
2863    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2864
2865    size_t argc = 0;
2866    // Count the number of arguments
2867    while (argv[argc] != NULL)
2868        argc++;
2869
2870    // Enumerate the arguments
2871    size_t first_launch_arg_idx = 1;
2872
2873    NSMutableArray *launch_argv = nil;
2874
2875    if (argv[first_launch_arg_idx])
2876    {
2877        size_t launch_argc = argc > 0 ? argc - 1 : 0;
2878        launch_argv = [NSMutableArray arrayWithCapacity: launch_argc];
2879        size_t i;
2880        char const *arg;
2881        NSString *launch_arg;
2882        for (i=first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL); i++)
2883        {
2884            launch_arg = [NSString stringWithUTF8String: arg];
2885            // FIXME: Should we silently eat an argument that we can't convert into a UTF8 string?
2886            if (launch_arg != nil)
2887                [launch_argv addObject: launch_arg];
2888            else
2889                break;
2890        }
2891    }
2892
2893    NSMutableDictionary *launch_envp = nil;
2894    if (envp[0])
2895    {
2896        launch_envp = [[NSMutableDictionary alloc] init];
2897        const char *value;
2898        int name_len;
2899        NSString *name_string, *value_string;
2900
2901        for (int i = 0; envp[i] != NULL; i++)
2902        {
2903            value = strstr (envp[i], "=");
2904
2905            // If the name field is empty or there's no =, skip it.  Somebody's messing with us.
2906            if (value == NULL || value == envp[i])
2907                continue;
2908
2909            name_len = value - envp[i];
2910
2911            // Now move value over the "="
2912            value++;
2913            name_string = [[NSString alloc] initWithBytes: envp[i] length: name_len encoding: NSUTF8StringEncoding];
2914            value_string = [NSString stringWithUTF8String: value];
2915            [launch_envp setObject: value_string forKey: name_string];
2916        }
2917    }
2918
2919    NSString *stdio_path = nil;
2920    NSFileManager *file_manager = [NSFileManager defaultManager];
2921
2922    PseudoTerminal pty;
2923    if (!no_stdio)
2924    {
2925        PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY);
2926        if (pty_err == PseudoTerminal::success)
2927        {
2928            const char* slave_name = pty.SlaveName();
2929            DNBLogThreadedIf(LOG_PROCESS, "%s() successfully opened master pty, slave is %s", __FUNCTION__, slave_name);
2930            if (slave_name && slave_name[0])
2931            {
2932                ::chmod (slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
2933                stdio_path = [file_manager stringWithFileSystemRepresentation: slave_name length: strlen(slave_name)];
2934            }
2935        }
2936    }
2937
2938    if (stdio_path == nil)
2939    {
2940        const char *null_path = "/dev/null";
2941        stdio_path = [file_manager stringWithFileSystemRepresentation: null_path length: strlen(null_path)];
2942    }
2943
2944    CFStringRef bundleIDCFStr = CopyBundleIDForPath (app_bundle_path, launch_err);
2945    if (bundleIDCFStr == NULL)
2946    {
2947        [pool drain];
2948        return INVALID_NUB_PROCESS;
2949    }
2950
2951    // Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use toll-free bridging here:
2952    NSString *bundleIDNSStr = (NSString *) bundleIDCFStr;
2953
2954    // Okay, now let's assemble all these goodies into the BackBoardServices options mega-dictionary:
2955
2956    // First we have the debug sub-dictionary:
2957    NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
2958    if (launch_argv != nil)
2959        [debug_options setObject: launch_argv forKey: BKSDebugOptionKeyArguments];
2960    if (launch_envp != nil)
2961        [debug_options setObject: launch_envp forKey: BKSDebugOptionKeyEnvironment];
2962
2963    [debug_options setObject: stdio_path forKey: BKSDebugOptionKeyStandardOutPath];
2964    [debug_options setObject: stdio_path forKey: BKSDebugOptionKeyStandardErrorPath];
2965    [debug_options setObject: [NSNumber numberWithBool: YES] forKey: BKSDebugOptionKeyWaitForDebugger];
2966    if (disable_aslr)
2967        [debug_options setObject: [NSNumber numberWithBool: YES] forKey: BKSDebugOptionKeyDisableASLR];
2968
2969    // That will go in the overall dictionary:
2970
2971    NSMutableDictionary *options = [NSMutableDictionary dictionary];
2972    [options setObject: debug_options forKey: BKSOpenApplicationOptionKeyDebuggingOptions];
2973
2974    // For now we only support one kind of event: the "fetch" event, which is indicated by the fact that its data
2975    // is an empty dictionary.
2976    if (event_data != NULL && *event_data != '\0')
2977    {
2978        if (!AddEventDataToOptions(options, event_data, launch_err))
2979        {
2980            [pool drain];
2981            return INVALID_NUB_PROCESS;
2982        }
2983    }
2984
2985    // And there are some other options at the top level in this dictionary:
2986    [options setObject: [NSNumber numberWithBool: YES] forKey: BKSOpenApplicationOptionKeyUnlockDevice];
2987
2988    pid_t return_pid = INVALID_NUB_PROCESS;
2989    bool success = CallBKSSystemServiceOpenApplication(bundleIDNSStr, options, launch_err, &return_pid);
2990
2991    if (success)
2992    {
2993        int master_fd = pty.ReleaseMasterFD();
2994        SetChildFileDescriptors(master_fd, master_fd, master_fd);
2995        CFString::UTF8(bundleIDCFStr, m_bundle_id);
2996    }
2997
2998    [pool drain];
2999
3000    return return_pid;
3001}
3002
3003bool
3004MachProcess::BKSSendEvent (const char *event_data, DNBError &send_err)
3005{
3006    bool return_value = true;
3007
3008    if (event_data == NULL || *event_data == '\0')
3009    {
3010        DNBLogError ("SendEvent called with NULL event data.");
3011        send_err.SetErrorString("SendEvent called with empty event data");
3012        return false;
3013    }
3014
3015    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3016
3017    if (strcmp (event_data, "BackgroundApplication") == 0)
3018    {
3019        // This is an event I cooked up.  What you actually do is foreground the system app, so:
3020        return_value = CallBKSSystemServiceOpenApplication(nil, nil, send_err, NULL);
3021        if (!return_value)
3022        {
3023            DNBLogError ("Failed to background application, error: %s.", send_err.AsString());
3024        }
3025    }
3026    else
3027    {
3028        if (m_bundle_id.empty())
3029        {
3030            // See if we can figure out the bundle ID for this PID:
3031
3032            DNBLogError ("Tried to send event \"%s\" to a process that has no bundle ID.", event_data);
3033            return false;
3034        }
3035
3036        NSString *bundleIDNSStr = [NSString stringWithUTF8String:m_bundle_id.c_str()];
3037
3038        NSMutableDictionary *options = [NSMutableDictionary dictionary];
3039
3040        if (!AddEventDataToOptions(options, event_data, send_err))
3041        {
3042            [pool drain];
3043            return false;
3044        }
3045
3046
3047        return_value = CallBKSSystemServiceOpenApplication(bundleIDNSStr, options, send_err, NULL);
3048
3049        if (!return_value)
3050        {
3051            DNBLogError ("Failed to send event: %s, error: %s.", event_data, send_err.AsString());
3052        }
3053    }
3054
3055    [pool drain];
3056    return return_value;
3057}
3058#endif // WITH_BKS
3059