1//===-- MachProcess.cpp -----------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9//  Created by Greg Clayton on 6/15/07.
10//
11//===----------------------------------------------------------------------===//
12
13#include "DNB.h"
14#include "MacOSX/CFUtils.h"
15#include "SysSignal.h"
16#include <dlfcn.h>
17#include <inttypes.h>
18#include <mach-o/loader.h>
19#include <mach/mach.h>
20#include <mach/task.h>
21#include <pthread.h>
22#include <signal.h>
23#include <spawn.h>
24#include <sys/fcntl.h>
25#include <sys/ptrace.h>
26#include <sys/stat.h>
27#include <sys/sysctl.h>
28#include <sys/types.h>
29#include <unistd.h>
30#include <uuid/uuid.h>
31
32#include <algorithm>
33#include <map>
34
35#import <Foundation/Foundation.h>
36
37#include "DNBDataRef.h"
38#include "DNBLog.h"
39#include "DNBThreadResumeActions.h"
40#include "DNBTimer.h"
41#include "MachProcess.h"
42#include "PseudoTerminal.h"
43
44#include "CFBundle.h"
45#include "CFString.h"
46
47#ifndef PLATFORM_BRIDGEOS
48#define PLATFORM_BRIDGEOS 5
49#endif
50
51#ifndef PLATFORM_MACCATALYST
52#define PLATFORM_MACCATALYST 6
53#endif
54
55#ifndef PLATFORM_IOSSIMULATOR
56#define PLATFORM_IOSSIMULATOR 7
57#endif
58
59#ifndef PLATFORM_TVOSSIMULATOR
60#define PLATFORM_TVOSSIMULATOR 8
61#endif
62
63#ifndef PLATFORM_WATCHOSSIMULATOR
64#define PLATFORM_WATCHOSSIMULATOR 9
65#endif
66
67#ifndef PLATFORM_DRIVERKIT
68#define PLATFORM_DRIVERKIT 10
69#endif
70
71#ifdef WITH_SPRINGBOARD
72
73#include <CoreFoundation/CoreFoundation.h>
74#include <SpringBoardServices/SBSWatchdogAssertion.h>
75#include <SpringBoardServices/SpringBoardServer.h>
76
77#endif // WITH_SPRINGBOARD
78
79#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) || defined(WITH_FBS)
80// This returns a CFRetained pointer to the Bundle ID for app_bundle_path,
81// or NULL if there was some problem getting the bundle id.
82static CFStringRef CopyBundleIDForPath(const char *app_bundle_path,
83                                       DNBError &err_str);
84#endif
85
86#if defined(WITH_BKS) || defined(WITH_FBS)
87#import <Foundation/Foundation.h>
88static const int OPEN_APPLICATION_TIMEOUT_ERROR = 111;
89typedef void (*SetErrorFunction)(NSInteger, DNBError &);
90typedef bool (*CallOpenApplicationFunction)(NSString *bundleIDNSStr,
91                                            NSDictionary *options,
92                                            DNBError &error, pid_t *return_pid);
93// This function runs the BKSSystemService (or FBSSystemService) method
94// openApplication:options:clientPort:withResult,
95// messaging the app passed in bundleIDNSStr.
96// The function should be run inside of an NSAutoReleasePool.
97//
98// It will use the "options" dictionary passed in, and fill the error passed in
99// if there is an error.
100// If return_pid is not NULL, we'll fetch the pid that was made for the
101// bundleID.
102// If bundleIDNSStr is NULL, then the system application will be messaged.
103
104template <typename OpenFlavor, typename ErrorFlavor,
105          ErrorFlavor no_error_enum_value, SetErrorFunction error_function>
106static bool CallBoardSystemServiceOpenApplication(NSString *bundleIDNSStr,
107                                                  NSDictionary *options,
108                                                  DNBError &error,
109                                                  pid_t *return_pid) {
110  // Now make our systemService:
111  OpenFlavor *system_service = [[OpenFlavor alloc] init];
112
113  if (bundleIDNSStr == nil) {
114    bundleIDNSStr = [system_service systemApplicationBundleIdentifier];
115    if (bundleIDNSStr == nil) {
116      // Okay, no system app...
117      error.SetErrorString("No system application to message.");
118      return false;
119    }
120  }
121
122  mach_port_t client_port = [system_service createClientPort];
123  __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
124  __block ErrorFlavor open_app_error = no_error_enum_value;
125  bool wants_pid = (return_pid != NULL);
126  __block pid_t pid_in_block;
127
128  const char *cstr = [bundleIDNSStr UTF8String];
129  if (!cstr)
130    cstr = "<Unknown Bundle ID>";
131
132  NSString *description = [options description];
133  DNBLog("About to launch process for bundle ID: %s - options:\n%s", cstr,
134    [description UTF8String]);
135  [system_service
136      openApplication:bundleIDNSStr
137              options:options
138           clientPort:client_port
139           withResult:^(NSError *bks_error) {
140             // The system service will cleanup the client port we created for
141             // us.
142             if (bks_error)
143               open_app_error = (ErrorFlavor)[bks_error code];
144
145             if (open_app_error == no_error_enum_value) {
146               if (wants_pid) {
147                 pid_in_block =
148                     [system_service pidForApplication:bundleIDNSStr];
149                 DNBLog(
150                     "In completion handler, got pid for bundle id, pid: %d.",
151                     pid_in_block);
152                 DNBLogThreadedIf(
153                     LOG_PROCESS,
154                     "In completion handler, got pid for bundle id, pid: %d.",
155                     pid_in_block);
156               } else
157                 DNBLogThreadedIf(LOG_PROCESS,
158                                  "In completion handler: success.");
159             } else {
160               const char *error_str =
161                   [(NSString *)[bks_error localizedDescription] UTF8String];
162               DNBLogThreadedIf(LOG_PROCESS, "In completion handler for send "
163                                             "event, got error \"%s\"(%ld).",
164                                error_str ? error_str : "<unknown error>",
165                                open_app_error);
166               // REMOVE ME
167               DNBLogError("In completion handler for send event, got error "
168                           "\"%s\"(%ld).",
169                           error_str ? error_str : "<unknown error>",
170                           open_app_error);
171             }
172
173             [system_service release];
174             dispatch_semaphore_signal(semaphore);
175           }
176
177  ];
178
179  const uint32_t timeout_secs = 30;
180
181  dispatch_time_t timeout =
182      dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
183
184  long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
185
186  dispatch_release(semaphore);
187
188  if (!success) {
189    DNBLogError("timed out trying to send openApplication to %s.", cstr);
190    error.SetError(OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
191    error.SetErrorString("timed out trying to launch app");
192  } else if (open_app_error != no_error_enum_value) {
193    error_function(open_app_error, error);
194    DNBLogError("unable to launch the application with CFBundleIdentifier '%s' "
195                "bks_error = %u",
196                cstr, open_app_error);
197    success = false;
198  } else if (wants_pid) {
199    *return_pid = pid_in_block;
200    DNBLogThreadedIf(
201        LOG_PROCESS,
202        "Out of completion handler, pid from block %d and passing out: %d",
203        pid_in_block, *return_pid);
204  }
205
206  return success;
207}
208#endif
209
210#if defined(WITH_BKS) || defined(WITH_FBS)
211static void SplitEventData(const char *data, std::vector<std::string> &elements)
212{
213  elements.clear();
214  if (!data)
215    return;
216
217  const char *start = data;
218
219  while (*start != '\0') {
220    const char *token = strchr(start, ':');
221    if (!token) {
222      elements.push_back(std::string(start));
223      return;
224    }
225    if (token != start)
226      elements.push_back(std::string(start, token - start));
227    start = ++token;
228  }
229}
230#endif
231
232#ifdef WITH_BKS
233#import <Foundation/Foundation.h>
234extern "C" {
235#import <BackBoardServices/BKSOpenApplicationConstants_Private.h>
236#import <BackBoardServices/BKSSystemService_LaunchServices.h>
237#import <BackBoardServices/BackBoardServices.h>
238}
239
240static bool IsBKSProcess(nub_process_t pid) {
241  BKSApplicationStateMonitor *state_monitor =
242      [[BKSApplicationStateMonitor alloc] init];
243  BKSApplicationState app_state =
244      [state_monitor mostElevatedApplicationStateForPID:pid];
245  return app_state != BKSApplicationStateUnknown;
246}
247
248static void SetBKSError(NSInteger error_code, DNBError &error) {
249  error.SetError(error_code, DNBError::BackBoard);
250  NSString *err_nsstr = ::BKSOpenApplicationErrorCodeToString(
251      (BKSOpenApplicationErrorCode)error_code);
252  const char *err_str = NULL;
253  if (err_nsstr == NULL)
254    err_str = "unknown BKS error";
255  else {
256    err_str = [err_nsstr UTF8String];
257    if (err_str == NULL)
258      err_str = "unknown BKS error";
259  }
260  error.SetErrorString(err_str);
261}
262
263static bool BKSAddEventDataToOptions(NSMutableDictionary *options,
264                                     const char *event_data,
265                                     DNBError &option_error) {
266  std::vector<std::string> values;
267  SplitEventData(event_data, values);
268  bool found_one = false;
269  for (std::string value : values)
270  {
271      if (value.compare("BackgroundContentFetching") == 0) {
272        DNBLog("Setting ActivateForEvent key in options dictionary.");
273        NSDictionary *event_details = [NSDictionary dictionary];
274        NSDictionary *event_dictionary = [NSDictionary
275            dictionaryWithObject:event_details
276                          forKey:
277                              BKSActivateForEventOptionTypeBackgroundContentFetching];
278        [options setObject:event_dictionary
279                    forKey:BKSOpenApplicationOptionKeyActivateForEvent];
280        found_one = true;
281      } else if (value.compare("ActivateSuspended") == 0) {
282        DNBLog("Setting ActivateSuspended key in options dictionary.");
283        [options setObject:@YES forKey: BKSOpenApplicationOptionKeyActivateSuspended];
284        found_one = true;
285      } else {
286        DNBLogError("Unrecognized event type: %s.  Ignoring.", value.c_str());
287        option_error.SetErrorString("Unrecognized event data");
288      }
289  }
290  return found_one;
291}
292
293static NSMutableDictionary *BKSCreateOptionsDictionary(
294    const char *app_bundle_path, NSMutableArray *launch_argv,
295    NSMutableDictionary *launch_envp, NSString *stdio_path, bool disable_aslr,
296    const char *event_data) {
297  NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
298  if (launch_argv != nil)
299    [debug_options setObject:launch_argv forKey:BKSDebugOptionKeyArguments];
300  if (launch_envp != nil)
301    [debug_options setObject:launch_envp forKey:BKSDebugOptionKeyEnvironment];
302
303  [debug_options setObject:stdio_path forKey:BKSDebugOptionKeyStandardOutPath];
304  [debug_options setObject:stdio_path
305                    forKey:BKSDebugOptionKeyStandardErrorPath];
306  [debug_options setObject:[NSNumber numberWithBool:YES]
307                    forKey:BKSDebugOptionKeyWaitForDebugger];
308  if (disable_aslr)
309    [debug_options setObject:[NSNumber numberWithBool:YES]
310                      forKey:BKSDebugOptionKeyDisableASLR];
311
312  // That will go in the overall dictionary:
313
314  NSMutableDictionary *options = [NSMutableDictionary dictionary];
315  [options setObject:debug_options
316              forKey:BKSOpenApplicationOptionKeyDebuggingOptions];
317  // And there are some other options at the top level in this dictionary:
318  [options setObject:[NSNumber numberWithBool:YES]
319              forKey:BKSOpenApplicationOptionKeyUnlockDevice];
320
321  DNBError error;
322  BKSAddEventDataToOptions(options, event_data, error);
323
324  return options;
325}
326
327static CallOpenApplicationFunction BKSCallOpenApplicationFunction =
328    CallBoardSystemServiceOpenApplication<
329        BKSSystemService, BKSOpenApplicationErrorCode,
330        BKSOpenApplicationErrorCodeNone, SetBKSError>;
331#endif // WITH_BKS
332
333#ifdef WITH_FBS
334#import <Foundation/Foundation.h>
335extern "C" {
336#import <FrontBoardServices/FBSOpenApplicationConstants_Private.h>
337#import <FrontBoardServices/FBSSystemService_LaunchServices.h>
338#import <FrontBoardServices/FrontBoardServices.h>
339#import <MobileCoreServices/LSResourceProxy.h>
340#import <MobileCoreServices/MobileCoreServices.h>
341}
342
343#ifdef WITH_BKS
344static bool IsFBSProcess(nub_process_t pid) {
345  BKSApplicationStateMonitor *state_monitor =
346      [[BKSApplicationStateMonitor alloc] init];
347  BKSApplicationState app_state =
348      [state_monitor mostElevatedApplicationStateForPID:pid];
349  return app_state != BKSApplicationStateUnknown;
350}
351#else
352static bool IsFBSProcess(nub_process_t pid) {
353  // FIXME: What is the FBS equivalent of BKSApplicationStateMonitor
354  return false;
355}
356#endif
357
358static void SetFBSError(NSInteger error_code, DNBError &error) {
359  error.SetError((DNBError::ValueType)error_code, DNBError::FrontBoard);
360  NSString *err_nsstr = ::FBSOpenApplicationErrorCodeToString(
361      (FBSOpenApplicationErrorCode)error_code);
362  const char *err_str = NULL;
363  if (err_nsstr == NULL)
364    err_str = "unknown FBS error";
365  else {
366    err_str = [err_nsstr UTF8String];
367    if (err_str == NULL)
368      err_str = "unknown FBS error";
369  }
370  error.SetErrorString(err_str);
371}
372
373static bool FBSAddEventDataToOptions(NSMutableDictionary *options,
374                                     const char *event_data,
375                                     DNBError &option_error) {
376  std::vector<std::string> values;
377  SplitEventData(event_data, values);
378  bool found_one = false;
379  for (std::string value : values)
380  {
381      if (value.compare("BackgroundContentFetching") == 0) {
382        DNBLog("Setting ActivateForEvent key in options dictionary.");
383        NSDictionary *event_details = [NSDictionary dictionary];
384        NSDictionary *event_dictionary = [NSDictionary
385            dictionaryWithObject:event_details
386                          forKey:
387                              FBSActivateForEventOptionTypeBackgroundContentFetching];
388        [options setObject:event_dictionary
389                    forKey:FBSOpenApplicationOptionKeyActivateForEvent];
390        found_one = true;
391      } else if (value.compare("ActivateSuspended") == 0) {
392        DNBLog("Setting ActivateSuspended key in options dictionary.");
393        [options setObject:@YES forKey: FBSOpenApplicationOptionKeyActivateSuspended];
394        found_one = true;
395      } else {
396        DNBLogError("Unrecognized event type: %s.  Ignoring.", value.c_str());
397        option_error.SetErrorString("Unrecognized event data.");
398      }
399  }
400  return found_one;
401}
402
403static NSMutableDictionary *
404FBSCreateOptionsDictionary(const char *app_bundle_path,
405                           NSMutableArray *launch_argv,
406                           NSDictionary *launch_envp, NSString *stdio_path,
407                           bool disable_aslr, const char *event_data) {
408  NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
409
410  if (launch_argv != nil)
411    [debug_options setObject:launch_argv forKey:FBSDebugOptionKeyArguments];
412  if (launch_envp != nil)
413    [debug_options setObject:launch_envp forKey:FBSDebugOptionKeyEnvironment];
414
415  [debug_options setObject:stdio_path forKey:FBSDebugOptionKeyStandardOutPath];
416  [debug_options setObject:stdio_path
417                    forKey:FBSDebugOptionKeyStandardErrorPath];
418  [debug_options setObject:[NSNumber numberWithBool:YES]
419                    forKey:FBSDebugOptionKeyWaitForDebugger];
420  if (disable_aslr)
421    [debug_options setObject:[NSNumber numberWithBool:YES]
422                      forKey:FBSDebugOptionKeyDisableASLR];
423
424  // That will go in the overall dictionary:
425
426  NSMutableDictionary *options = [NSMutableDictionary dictionary];
427  [options setObject:debug_options
428              forKey:FBSOpenApplicationOptionKeyDebuggingOptions];
429  // And there are some other options at the top level in this dictionary:
430  [options setObject:[NSNumber numberWithBool:YES]
431              forKey:FBSOpenApplicationOptionKeyUnlockDevice];
432
433  // We have to get the "sequence ID & UUID" for this app bundle path and send
434  // them to FBS:
435
436  NSURL *app_bundle_url =
437      [NSURL fileURLWithPath:[NSString stringWithUTF8String:app_bundle_path]
438                 isDirectory:YES];
439  LSApplicationProxy *app_proxy =
440      [LSApplicationProxy applicationProxyForBundleURL:app_bundle_url];
441  if (app_proxy) {
442    DNBLog("Sending AppProxy info: sequence no: %lu, GUID: %s.",
443           app_proxy.sequenceNumber,
444           [app_proxy.cacheGUID.UUIDString UTF8String]);
445    [options
446        setObject:[NSNumber numberWithUnsignedInteger:app_proxy.sequenceNumber]
447           forKey:FBSOpenApplicationOptionKeyLSSequenceNumber];
448    [options setObject:app_proxy.cacheGUID.UUIDString
449                forKey:FBSOpenApplicationOptionKeyLSCacheGUID];
450  }
451
452  DNBError error;
453  FBSAddEventDataToOptions(options, event_data, error);
454
455  return options;
456}
457static CallOpenApplicationFunction FBSCallOpenApplicationFunction =
458    CallBoardSystemServiceOpenApplication<
459        FBSSystemService, FBSOpenApplicationErrorCode,
460        FBSOpenApplicationErrorCodeNone, SetFBSError>;
461#endif // WITH_FBS
462
463#if 0
464#define DEBUG_LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
465#else
466#define DEBUG_LOG(fmt, ...)
467#endif
468
469#ifndef MACH_PROCESS_USE_POSIX_SPAWN
470#define MACH_PROCESS_USE_POSIX_SPAWN 1
471#endif
472
473#ifndef _POSIX_SPAWN_DISABLE_ASLR
474#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
475#endif
476
477MachProcess::MachProcess()
478    : m_pid(0), m_cpu_type(0), m_child_stdin(-1), m_child_stdout(-1),
479      m_child_stderr(-1), m_path(), m_args(), m_task(this),
480      m_flags(eMachProcessFlagsNone), m_stdio_thread(0),
481      m_stdio_mutex(PTHREAD_MUTEX_RECURSIVE), m_stdout_data(),
482      m_profile_enabled(false), m_profile_interval_usec(0), m_profile_thread(0),
483      m_profile_data_mutex(PTHREAD_MUTEX_RECURSIVE), m_profile_data(),
484      m_thread_actions(), m_exception_messages(),
485      m_exception_messages_mutex(PTHREAD_MUTEX_RECURSIVE), m_thread_list(),
486      m_activities(), m_state(eStateUnloaded),
487      m_state_mutex(PTHREAD_MUTEX_RECURSIVE), m_events(0, kAllEventsMask),
488      m_private_events(0, kAllEventsMask), m_breakpoints(), m_watchpoints(),
489      m_name_to_addr_callback(NULL), m_name_to_addr_baton(NULL),
490      m_image_infos_callback(NULL), m_image_infos_baton(NULL),
491      m_sent_interrupt_signo(0), m_auto_resume_signo(0), m_did_exec(false),
492      m_dyld_process_info_create(nullptr),
493      m_dyld_process_info_for_each_image(nullptr),
494      m_dyld_process_info_release(nullptr),
495      m_dyld_process_info_get_cache(nullptr) {
496  m_dyld_process_info_create =
497      (void *(*)(task_t task, uint64_t timestamp, kern_return_t * kernelError))
498          dlsym(RTLD_DEFAULT, "_dyld_process_info_create");
499  m_dyld_process_info_for_each_image =
500      (void (*)(void *info, void (^)(uint64_t machHeaderAddress,
501                                     const uuid_t uuid, const char *path)))
502          dlsym(RTLD_DEFAULT, "_dyld_process_info_for_each_image");
503  m_dyld_process_info_release =
504      (void (*)(void *info))dlsym(RTLD_DEFAULT, "_dyld_process_info_release");
505  m_dyld_process_info_get_cache = (void (*)(void *info, void *cacheInfo))dlsym(
506      RTLD_DEFAULT, "_dyld_process_info_get_cache");
507  m_dyld_process_info_get_platform = (uint32_t (*)(void *info))dlsym(
508      RTLD_DEFAULT, "_dyld_process_info_get_platform");
509
510  DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
511}
512
513MachProcess::~MachProcess() {
514  DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
515  Clear();
516}
517
518pid_t MachProcess::SetProcessID(pid_t pid) {
519  // Free any previous process specific data or resources
520  Clear();
521  // Set the current PID appropriately
522  if (pid == 0)
523    m_pid = ::getpid();
524  else
525    m_pid = pid;
526  return m_pid; // Return actually PID in case a zero pid was passed in
527}
528
529nub_state_t MachProcess::GetState() {
530  // If any other threads access this we will need a mutex for it
531  PTHREAD_MUTEX_LOCKER(locker, m_state_mutex);
532  return m_state;
533}
534
535const char *MachProcess::ThreadGetName(nub_thread_t tid) {
536  return m_thread_list.GetName(tid);
537}
538
539nub_state_t MachProcess::ThreadGetState(nub_thread_t tid) {
540  return m_thread_list.GetState(tid);
541}
542
543nub_size_t MachProcess::GetNumThreads() const {
544  return m_thread_list.NumThreads();
545}
546
547nub_thread_t MachProcess::GetThreadAtIndex(nub_size_t thread_idx) const {
548  return m_thread_list.ThreadIDAtIndex(thread_idx);
549}
550
551nub_thread_t
552MachProcess::GetThreadIDForMachPortNumber(thread_t mach_port_number) const {
553  return m_thread_list.GetThreadIDByMachPortNumber(mach_port_number);
554}
555
556nub_bool_t MachProcess::SyncThreadState(nub_thread_t tid) {
557  MachThreadSP thread_sp(m_thread_list.GetThreadByID(tid));
558  if (!thread_sp)
559    return false;
560  kern_return_t kret = ::thread_abort_safely(thread_sp->MachPortNumber());
561  DNBLogThreadedIf(LOG_THREAD, "thread = 0x%8.8" PRIx32
562                               " calling thread_abort_safely (tid) => %u "
563                               "(GetGPRState() for stop_count = %u)",
564                   thread_sp->MachPortNumber(), kret,
565                   thread_sp->Process()->StopCount());
566
567  if (kret == KERN_SUCCESS)
568    return true;
569  else
570    return false;
571}
572
573ThreadInfo::QoS MachProcess::GetRequestedQoS(nub_thread_t tid, nub_addr_t tsd,
574                                             uint64_t dti_qos_class_index) {
575  return m_thread_list.GetRequestedQoS(tid, tsd, dti_qos_class_index);
576}
577
578nub_addr_t MachProcess::GetPThreadT(nub_thread_t tid) {
579  return m_thread_list.GetPThreadT(tid);
580}
581
582nub_addr_t MachProcess::GetDispatchQueueT(nub_thread_t tid) {
583  return m_thread_list.GetDispatchQueueT(tid);
584}
585
586nub_addr_t MachProcess::GetTSDAddressForThread(
587    nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset,
588    uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size) {
589  return m_thread_list.GetTSDAddressForThread(
590      tid, plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset,
591      plo_pthread_tsd_entry_size);
592}
593
594
595const char *MachProcess::GetDeploymentInfo(const struct load_command& lc,
596                                           uint64_t load_command_address,
597                                           uint32_t& major_version,
598                                           uint32_t& minor_version,
599                                           uint32_t& patch_version) {
600  uint32_t cmd = lc.cmd & ~LC_REQ_DYLD;
601  bool lc_cmd_known =
602    cmd == LC_VERSION_MIN_IPHONEOS || cmd == LC_VERSION_MIN_MACOSX ||
603    cmd == LC_VERSION_MIN_TVOS || cmd == LC_VERSION_MIN_WATCHOS;
604
605  if (lc_cmd_known) {
606    struct version_min_command vers_cmd;
607    if (ReadMemory(load_command_address, sizeof(struct version_min_command),
608                   &vers_cmd) != sizeof(struct version_min_command)) {
609      return nullptr;
610    }
611    major_version = vers_cmd.sdk >> 16;
612    minor_version = (vers_cmd.sdk >> 8) & 0xffu;
613    patch_version = vers_cmd.sdk & 0xffu;
614
615    switch (cmd) {
616    case LC_VERSION_MIN_IPHONEOS:
617      return "ios";
618    case LC_VERSION_MIN_MACOSX:
619      return "macosx";
620    case LC_VERSION_MIN_TVOS:
621      return "tvos";
622    case LC_VERSION_MIN_WATCHOS:
623      return "watchos";
624    default:
625      return nullptr;
626    }
627  }
628#if defined (LC_BUILD_VERSION)
629  if (cmd == LC_BUILD_VERSION) {
630    struct build_version_command build_vers;
631    if (ReadMemory(load_command_address, sizeof(struct build_version_command),
632                   &build_vers) != sizeof(struct build_version_command)) {
633      return nullptr;
634    }
635    major_version = build_vers.sdk >> 16;;
636    minor_version = (build_vers.sdk >> 8) & 0xffu;
637    patch_version = build_vers.sdk & 0xffu;
638
639    switch (build_vers.platform) {
640    case PLATFORM_MACOS:
641      return "macosx";
642    case PLATFORM_MACCATALYST:
643      return "maccatalyst";
644    case PLATFORM_IOS:
645    case PLATFORM_IOSSIMULATOR:
646      return "ios";
647    case PLATFORM_TVOS:
648    case PLATFORM_TVOSSIMULATOR:
649      return "tvos";
650    case PLATFORM_WATCHOS:
651    case PLATFORM_WATCHOSSIMULATOR:
652      return "watchos";
653    case PLATFORM_BRIDGEOS:
654      return "bridgeos";
655    case PLATFORM_DRIVERKIT:
656      return "driverkit";
657    }
658  }
659#endif
660  return nullptr;
661}
662
663// Given an address, read the mach-o header and load commands out of memory to
664// fill in
665// the mach_o_information "inf" object.
666//
667// Returns false if there was an error in reading this mach-o file header/load
668// commands.
669
670bool MachProcess::GetMachOInformationFromMemory(
671    uint32_t dyld_platform, nub_addr_t mach_o_header_addr, int wordsize,
672    struct mach_o_information &inf) {
673  uint64_t load_cmds_p;
674  if (wordsize == 4) {
675    struct mach_header header;
676    if (ReadMemory(mach_o_header_addr, sizeof(struct mach_header), &header) !=
677        sizeof(struct mach_header)) {
678      return false;
679    }
680    load_cmds_p = mach_o_header_addr + sizeof(struct mach_header);
681    inf.mach_header.magic = header.magic;
682    inf.mach_header.cputype = header.cputype;
683    // high byte of cpusubtype is used for "capability bits", v.
684    // CPU_SUBTYPE_MASK, CPU_SUBTYPE_LIB64 in machine.h
685    inf.mach_header.cpusubtype = header.cpusubtype & 0x00ffffff;
686    inf.mach_header.filetype = header.filetype;
687    inf.mach_header.ncmds = header.ncmds;
688    inf.mach_header.sizeofcmds = header.sizeofcmds;
689    inf.mach_header.flags = header.flags;
690  } else {
691    struct mach_header_64 header;
692    if (ReadMemory(mach_o_header_addr, sizeof(struct mach_header_64),
693                   &header) != sizeof(struct mach_header_64)) {
694      return false;
695    }
696    load_cmds_p = mach_o_header_addr + sizeof(struct mach_header_64);
697    inf.mach_header.magic = header.magic;
698    inf.mach_header.cputype = header.cputype;
699    // high byte of cpusubtype is used for "capability bits", v.
700    // CPU_SUBTYPE_MASK, CPU_SUBTYPE_LIB64 in machine.h
701    inf.mach_header.cpusubtype = header.cpusubtype & 0x00ffffff;
702    inf.mach_header.filetype = header.filetype;
703    inf.mach_header.ncmds = header.ncmds;
704    inf.mach_header.sizeofcmds = header.sizeofcmds;
705    inf.mach_header.flags = header.flags;
706  }
707  for (uint32_t j = 0; j < inf.mach_header.ncmds; j++) {
708    struct load_command lc;
709    if (ReadMemory(load_cmds_p, sizeof(struct load_command), &lc) !=
710        sizeof(struct load_command)) {
711      return false;
712    }
713    if (lc.cmd == LC_SEGMENT) {
714      struct segment_command seg;
715      if (ReadMemory(load_cmds_p, sizeof(struct segment_command), &seg) !=
716          sizeof(struct segment_command)) {
717        return false;
718      }
719      struct mach_o_segment this_seg;
720      char name[17];
721      ::memset(name, 0, sizeof(name));
722      memcpy(name, seg.segname, sizeof(seg.segname));
723      this_seg.name = name;
724      this_seg.vmaddr = seg.vmaddr;
725      this_seg.vmsize = seg.vmsize;
726      this_seg.fileoff = seg.fileoff;
727      this_seg.filesize = seg.filesize;
728      this_seg.maxprot = seg.maxprot;
729      this_seg.initprot = seg.initprot;
730      this_seg.nsects = seg.nsects;
731      this_seg.flags = seg.flags;
732      inf.segments.push_back(this_seg);
733    }
734    if (lc.cmd == LC_SEGMENT_64) {
735      struct segment_command_64 seg;
736      if (ReadMemory(load_cmds_p, sizeof(struct segment_command_64), &seg) !=
737          sizeof(struct segment_command_64)) {
738        return false;
739      }
740      struct mach_o_segment this_seg;
741      char name[17];
742      ::memset(name, 0, sizeof(name));
743      memcpy(name, seg.segname, sizeof(seg.segname));
744      this_seg.name = name;
745      this_seg.vmaddr = seg.vmaddr;
746      this_seg.vmsize = seg.vmsize;
747      this_seg.fileoff = seg.fileoff;
748      this_seg.filesize = seg.filesize;
749      this_seg.maxprot = seg.maxprot;
750      this_seg.initprot = seg.initprot;
751      this_seg.nsects = seg.nsects;
752      this_seg.flags = seg.flags;
753      inf.segments.push_back(this_seg);
754    }
755    if (lc.cmd == LC_UUID) {
756      struct uuid_command uuidcmd;
757      if (ReadMemory(load_cmds_p, sizeof(struct uuid_command), &uuidcmd) ==
758          sizeof(struct uuid_command))
759        uuid_copy(inf.uuid, uuidcmd.uuid);
760    }
761
762    uint32_t major_version, minor_version, patch_version;
763    if (const char *lc_platform = GetDeploymentInfo(
764            lc, load_cmds_p, major_version, minor_version, patch_version)) {
765      // APPLE INTERNAL: macCatalyst support
766      // This handles two special cases:
767      // 1. Zippered frameworks have two deployment info load commands.
768      //    Make sure to select the requested one.
769      // 2. The xctest binary is a pure macOS binary but is launched with
770      //    DYLD_FORCE_PLATFORM=6.
771      if (dyld_platform == PLATFORM_MACCATALYST &&
772          inf.mach_header.filetype == MH_EXECUTE &&
773          inf.min_version_os_name.empty() &&
774          (strcmp("macosx", lc_platform) == 0)) {
775        // DYLD says this *is* a macCatalyst process. If we haven't
776        // parsed any load commands, transform a macOS load command
777        // into a generic macCatalyst load command. It will be
778        // overwritten by a more specific one if there is one.  This
779        // is only done for the main executable. It is perfectly fine
780        // for a macCatalyst binary to link against a macOS-only framework.
781        inf.min_version_os_name = "maccatalyst";
782        inf.min_version_os_version = GetMacCatalystVersionString();
783      } else if (dyld_platform != PLATFORM_MACCATALYST &&
784                 inf.min_version_os_name == "macosx") {
785        // This is a zippered binary and the process is not running as
786        // PLATFORM_MACCATALYST. Stick with the macosx load command
787        // that we've already processed, ignore this one, which is
788        // presumed to be a PLATFORM_MACCATALYST one.
789      } else {
790        inf.min_version_os_name = lc_platform;
791        inf.min_version_os_version = "";
792        inf.min_version_os_version += std::to_string(major_version);
793        inf.min_version_os_version += ".";
794        inf.min_version_os_version += std::to_string(minor_version);
795        if (patch_version != 0) {
796          inf.min_version_os_version += ".";
797          inf.min_version_os_version += std::to_string(patch_version);
798        }
799      }
800    }
801
802    load_cmds_p += lc.cmdsize;
803  }
804  return true;
805}
806
807// Given completely filled in array of binary_image_information structures,
808// create a JSONGenerator object
809// with all the details we want to send to lldb.
810JSONGenerator::ObjectSP MachProcess::FormatDynamicLibrariesIntoJSON(
811    const std::vector<struct binary_image_information> &image_infos) {
812
813  JSONGenerator::ArraySP image_infos_array_sp(new JSONGenerator::Array());
814
815  const size_t image_count = image_infos.size();
816
817  for (size_t i = 0; i < image_count; i++) {
818    JSONGenerator::DictionarySP image_info_dict_sp(
819        new JSONGenerator::Dictionary());
820    image_info_dict_sp->AddIntegerItem("load_address",
821                                       image_infos[i].load_address);
822    image_info_dict_sp->AddIntegerItem("mod_date", image_infos[i].mod_date);
823    image_info_dict_sp->AddStringItem("pathname", image_infos[i].filename);
824
825    uuid_string_t uuidstr;
826    uuid_unparse_upper(image_infos[i].macho_info.uuid, uuidstr);
827    image_info_dict_sp->AddStringItem("uuid", uuidstr);
828
829    if (!image_infos[i].macho_info.min_version_os_name.empty() &&
830        !image_infos[i].macho_info.min_version_os_version.empty()) {
831      image_info_dict_sp->AddStringItem(
832          "min_version_os_name", image_infos[i].macho_info.min_version_os_name);
833      image_info_dict_sp->AddStringItem(
834          "min_version_os_sdk",
835          image_infos[i].macho_info.min_version_os_version);
836    }
837
838    JSONGenerator::DictionarySP mach_header_dict_sp(
839        new JSONGenerator::Dictionary());
840    mach_header_dict_sp->AddIntegerItem(
841        "magic", image_infos[i].macho_info.mach_header.magic);
842    mach_header_dict_sp->AddIntegerItem(
843        "cputype", (uint32_t)image_infos[i].macho_info.mach_header.cputype);
844    mach_header_dict_sp->AddIntegerItem(
845        "cpusubtype",
846        (uint32_t)image_infos[i].macho_info.mach_header.cpusubtype);
847    mach_header_dict_sp->AddIntegerItem(
848        "filetype", image_infos[i].macho_info.mach_header.filetype);
849    mach_header_dict_sp->AddIntegerItem ("flags",
850                         image_infos[i].macho_info.mach_header.flags);
851
852    //          DynamicLoaderMacOSX doesn't currently need these fields, so
853    //          don't send them.
854    //            mach_header_dict_sp->AddIntegerItem ("ncmds",
855    //            image_infos[i].macho_info.mach_header.ncmds);
856    //            mach_header_dict_sp->AddIntegerItem ("sizeofcmds",
857    //            image_infos[i].macho_info.mach_header.sizeofcmds);
858    image_info_dict_sp->AddItem("mach_header", mach_header_dict_sp);
859
860    JSONGenerator::ArraySP segments_sp(new JSONGenerator::Array());
861    for (size_t j = 0; j < image_infos[i].macho_info.segments.size(); j++) {
862      JSONGenerator::DictionarySP segment_sp(new JSONGenerator::Dictionary());
863      segment_sp->AddStringItem("name",
864                                image_infos[i].macho_info.segments[j].name);
865      segment_sp->AddIntegerItem("vmaddr",
866                                 image_infos[i].macho_info.segments[j].vmaddr);
867      segment_sp->AddIntegerItem("vmsize",
868                                 image_infos[i].macho_info.segments[j].vmsize);
869      segment_sp->AddIntegerItem("fileoff",
870                                 image_infos[i].macho_info.segments[j].fileoff);
871      segment_sp->AddIntegerItem(
872          "filesize", image_infos[i].macho_info.segments[j].filesize);
873      segment_sp->AddIntegerItem("maxprot",
874                                 image_infos[i].macho_info.segments[j].maxprot);
875
876      //              DynamicLoaderMacOSX doesn't currently need these fields,
877      //              so don't send them.
878      //                segment_sp->AddIntegerItem ("initprot",
879      //                image_infos[i].macho_info.segments[j].initprot);
880      //                segment_sp->AddIntegerItem ("nsects",
881      //                image_infos[i].macho_info.segments[j].nsects);
882      //                segment_sp->AddIntegerItem ("flags",
883      //                image_infos[i].macho_info.segments[j].flags);
884      segments_sp->AddItem(segment_sp);
885    }
886    image_info_dict_sp->AddItem("segments", segments_sp);
887
888    image_infos_array_sp->AddItem(image_info_dict_sp);
889  }
890
891  JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary());
892  ;
893  reply_sp->AddItem("images", image_infos_array_sp);
894
895  return reply_sp;
896}
897
898// Get the shared library information using the old (pre-macOS 10.12, pre-iOS
899// 10, pre-tvOS 10, pre-watchOS 3)
900// code path.  We'll be given the address of an array of structures in the form
901// {void* load_addr, void* mod_date, void* pathname}
902//
903// In macOS 10.12 etc and newer, we'll use SPI calls into dyld to gather this
904// information.
905JSONGenerator::ObjectSP MachProcess::GetLoadedDynamicLibrariesInfos(
906    nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count) {
907  JSONGenerator::DictionarySP reply_sp;
908
909  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
910  struct kinfo_proc processInfo;
911  size_t bufsize = sizeof(processInfo);
912  if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize,
913             NULL, 0) == 0 &&
914      bufsize > 0) {
915    uint32_t pointer_size = 4;
916    if (processInfo.kp_proc.p_flag & P_LP64)
917      pointer_size = 8;
918
919    std::vector<struct binary_image_information> image_infos;
920    size_t image_infos_size = image_count * 3 * pointer_size;
921
922    uint8_t *image_info_buf = (uint8_t *)malloc(image_infos_size);
923    if (image_info_buf == NULL) {
924      return reply_sp;
925    }
926    if (ReadMemory(image_list_address, image_infos_size, image_info_buf) !=
927        image_infos_size) {
928      return reply_sp;
929    }
930
931    ////  First the image_infos array with (load addr, pathname, mod date)
932    ///tuples
933
934    for (size_t i = 0; i < image_count; i++) {
935      struct binary_image_information info;
936      nub_addr_t pathname_address;
937      if (pointer_size == 4) {
938        uint32_t load_address_32;
939        uint32_t pathname_address_32;
940        uint32_t mod_date_32;
941        ::memcpy(&load_address_32, image_info_buf + (i * 3 * pointer_size), 4);
942        ::memcpy(&pathname_address_32,
943                 image_info_buf + (i * 3 * pointer_size) + pointer_size, 4);
944        ::memcpy(&mod_date_32, image_info_buf + (i * 3 * pointer_size) +
945                                   pointer_size + pointer_size,
946                 4);
947        info.load_address = load_address_32;
948        info.mod_date = mod_date_32;
949        pathname_address = pathname_address_32;
950      } else {
951        uint64_t load_address_64;
952        uint64_t pathname_address_64;
953        uint64_t mod_date_64;
954        ::memcpy(&load_address_64, image_info_buf + (i * 3 * pointer_size), 8);
955        ::memcpy(&pathname_address_64,
956                 image_info_buf + (i * 3 * pointer_size) + pointer_size, 8);
957        ::memcpy(&mod_date_64, image_info_buf + (i * 3 * pointer_size) +
958                                   pointer_size + pointer_size,
959                 8);
960        info.load_address = load_address_64;
961        info.mod_date = mod_date_64;
962        pathname_address = pathname_address_64;
963      }
964      char strbuf[17];
965      info.filename = "";
966      uint64_t pathname_ptr = pathname_address;
967      bool still_reading = true;
968      while (still_reading &&
969             ReadMemory(pathname_ptr, sizeof(strbuf) - 1, strbuf) ==
970                 sizeof(strbuf) - 1) {
971        strbuf[sizeof(strbuf) - 1] = '\0';
972        info.filename += strbuf;
973        pathname_ptr += sizeof(strbuf) - 1;
974        // Stop if we found nul byte indicating the end of the string
975        for (size_t i = 0; i < sizeof(strbuf) - 1; i++) {
976          if (strbuf[i] == '\0') {
977            still_reading = false;
978            break;
979          }
980        }
981      }
982      uuid_clear(info.macho_info.uuid);
983      image_infos.push_back(info);
984    }
985    if (image_infos.size() == 0) {
986      return reply_sp;
987    }
988
989    free(image_info_buf);
990
991    ////  Second, read the mach header / load commands for all the dylibs
992
993    for (size_t i = 0; i < image_count; i++) {
994      // The SPI to provide platform is not available on older systems.
995      uint32_t platform = 0;
996      if (!GetMachOInformationFromMemory(platform,
997                                         image_infos[i].load_address,
998                                         pointer_size,
999                                         image_infos[i].macho_info)) {
1000        return reply_sp;
1001      }
1002    }
1003
1004    ////  Third, format all of the above in the JSONGenerator object.
1005
1006    return FormatDynamicLibrariesIntoJSON(image_infos);
1007  }
1008
1009  return reply_sp;
1010}
1011
1012// From dyld SPI header dyld_process_info.h
1013typedef void *dyld_process_info;
1014struct dyld_process_cache_info {
1015  uuid_t cacheUUID;          // UUID of cache used by process
1016  uint64_t cacheBaseAddress; // load address of dyld shared cache
1017  bool noCache;              // process is running without a dyld cache
1018  bool privateCache; // process is using a private copy of its dyld cache
1019};
1020
1021// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer
1022// to get
1023// the load address, uuid, and filenames of all the libraries.
1024// This only fills in those three fields in the 'struct
1025// binary_image_information' - call
1026// GetMachOInformationFromMemory to fill in the mach-o header/load command
1027// details.
1028uint32_t MachProcess::GetAllLoadedBinariesViaDYLDSPI(
1029    std::vector<struct binary_image_information> &image_infos) {
1030  uint32_t platform = 0;
1031  kern_return_t kern_ret;
1032  if (m_dyld_process_info_create) {
1033    dyld_process_info info =
1034        m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret);
1035    if (info) {
1036      m_dyld_process_info_for_each_image(
1037          info,
1038          ^(uint64_t mach_header_addr, const uuid_t uuid, const char *path) {
1039            struct binary_image_information image;
1040            image.filename = path;
1041            uuid_copy(image.macho_info.uuid, uuid);
1042            image.load_address = mach_header_addr;
1043            image_infos.push_back(image);
1044          });
1045      if (m_dyld_process_info_get_platform)
1046        platform = m_dyld_process_info_get_platform(info);
1047      m_dyld_process_info_release(info);
1048    }
1049  }
1050  return platform;
1051}
1052
1053// Fetch information about all shared libraries using the dyld SPIs that exist
1054// in
1055// macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer.
1056JSONGenerator::ObjectSP
1057MachProcess::GetAllLoadedLibrariesInfos(nub_process_t pid) {
1058  JSONGenerator::DictionarySP reply_sp;
1059
1060  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
1061  struct kinfo_proc processInfo;
1062  size_t bufsize = sizeof(processInfo);
1063  if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize,
1064             NULL, 0) == 0 &&
1065      bufsize > 0) {
1066    uint32_t pointer_size = 4;
1067    if (processInfo.kp_proc.p_flag & P_LP64)
1068      pointer_size = 8;
1069
1070    std::vector<struct binary_image_information> image_infos;
1071    uint32_t platform = GetAllLoadedBinariesViaDYLDSPI(image_infos);
1072    const size_t image_count = image_infos.size();
1073    for (size_t i = 0; i < image_count; i++) {
1074      GetMachOInformationFromMemory(platform,
1075                                    image_infos[i].load_address, pointer_size,
1076                                    image_infos[i].macho_info);
1077    }
1078    return FormatDynamicLibrariesIntoJSON(image_infos);
1079  }
1080  return reply_sp;
1081}
1082
1083// Fetch information about the shared libraries at the given load addresses
1084// using the
1085// dyld SPIs that exist in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer.
1086JSONGenerator::ObjectSP MachProcess::GetLibrariesInfoForAddresses(
1087    nub_process_t pid, std::vector<uint64_t> &macho_addresses) {
1088  JSONGenerator::DictionarySP reply_sp;
1089
1090  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
1091  struct kinfo_proc processInfo;
1092  size_t bufsize = sizeof(processInfo);
1093  if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize,
1094             NULL, 0) == 0 &&
1095      bufsize > 0) {
1096    uint32_t pointer_size = 4;
1097    if (processInfo.kp_proc.p_flag & P_LP64)
1098      pointer_size = 8;
1099
1100    std::vector<struct binary_image_information> all_image_infos;
1101    uint32_t platform = GetAllLoadedBinariesViaDYLDSPI(all_image_infos);
1102
1103    std::vector<struct binary_image_information> image_infos;
1104    const size_t macho_addresses_count = macho_addresses.size();
1105    const size_t all_image_infos_count = all_image_infos.size();
1106    for (size_t i = 0; i < macho_addresses_count; i++) {
1107      for (size_t j = 0; j < all_image_infos_count; j++) {
1108        if (all_image_infos[j].load_address == macho_addresses[i]) {
1109          image_infos.push_back(all_image_infos[j]);
1110        }
1111      }
1112    }
1113
1114    const size_t image_infos_count = image_infos.size();
1115    for (size_t i = 0; i < image_infos_count; i++) {
1116      GetMachOInformationFromMemory(platform,
1117                                    image_infos[i].load_address, pointer_size,
1118                                    image_infos[i].macho_info);
1119    }
1120    return FormatDynamicLibrariesIntoJSON(image_infos);
1121  }
1122  return reply_sp;
1123}
1124
1125// From dyld's internal podyld_process_info.h:
1126
1127JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) {
1128  JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary());
1129  ;
1130  kern_return_t kern_ret;
1131  if (m_dyld_process_info_create && m_dyld_process_info_get_cache) {
1132    dyld_process_info info =
1133        m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret);
1134    if (info) {
1135      struct dyld_process_cache_info shared_cache_info;
1136      m_dyld_process_info_get_cache(info, &shared_cache_info);
1137
1138      reply_sp->AddIntegerItem("shared_cache_base_address",
1139                               shared_cache_info.cacheBaseAddress);
1140
1141      uuid_string_t uuidstr;
1142      uuid_unparse_upper(shared_cache_info.cacheUUID, uuidstr);
1143      reply_sp->AddStringItem("shared_cache_uuid", uuidstr);
1144
1145      reply_sp->AddBooleanItem("no_shared_cache", shared_cache_info.noCache);
1146      reply_sp->AddBooleanItem("shared_cache_private_cache",
1147                               shared_cache_info.privateCache);
1148
1149      m_dyld_process_info_release(info);
1150    }
1151  }
1152  return reply_sp;
1153}
1154
1155nub_thread_t MachProcess::GetCurrentThread() {
1156  return m_thread_list.CurrentThreadID();
1157}
1158
1159nub_thread_t MachProcess::GetCurrentThreadMachPort() {
1160  return m_thread_list.GetMachPortNumberByThreadID(
1161      m_thread_list.CurrentThreadID());
1162}
1163
1164nub_thread_t MachProcess::SetCurrentThread(nub_thread_t tid) {
1165  return m_thread_list.SetCurrentThread(tid);
1166}
1167
1168bool MachProcess::GetThreadStoppedReason(nub_thread_t tid,
1169                                         struct DNBThreadStopInfo *stop_info) {
1170  if (m_thread_list.GetThreadStoppedReason(tid, stop_info)) {
1171    if (m_did_exec)
1172      stop_info->reason = eStopTypeExec;
1173    return true;
1174  }
1175  return false;
1176}
1177
1178void MachProcess::DumpThreadStoppedReason(nub_thread_t tid) const {
1179  return m_thread_list.DumpThreadStoppedReason(tid);
1180}
1181
1182const char *MachProcess::GetThreadInfo(nub_thread_t tid) const {
1183  return m_thread_list.GetThreadInfo(tid);
1184}
1185
1186uint32_t MachProcess::GetCPUType() {
1187  if (m_cpu_type == 0 && m_pid != 0)
1188    m_cpu_type = MachProcess::GetCPUTypeForLocalProcess(m_pid);
1189  return m_cpu_type;
1190}
1191
1192const DNBRegisterSetInfo *
1193MachProcess::GetRegisterSetInfo(nub_thread_t tid,
1194                                nub_size_t *num_reg_sets) const {
1195  MachThreadSP thread_sp(m_thread_list.GetThreadByID(tid));
1196  if (thread_sp) {
1197    DNBArchProtocol *arch = thread_sp->GetArchProtocol();
1198    if (arch)
1199      return arch->GetRegisterSetInfo(num_reg_sets);
1200  }
1201  *num_reg_sets = 0;
1202  return NULL;
1203}
1204
1205bool MachProcess::GetRegisterValue(nub_thread_t tid, uint32_t set, uint32_t reg,
1206                                   DNBRegisterValue *value) const {
1207  return m_thread_list.GetRegisterValue(tid, set, reg, value);
1208}
1209
1210bool MachProcess::SetRegisterValue(nub_thread_t tid, uint32_t set, uint32_t reg,
1211                                   const DNBRegisterValue *value) const {
1212  return m_thread_list.SetRegisterValue(tid, set, reg, value);
1213}
1214
1215void MachProcess::SetState(nub_state_t new_state) {
1216  // If any other threads access this we will need a mutex for it
1217  uint32_t event_mask = 0;
1218
1219  // Scope for mutex locker
1220  {
1221    PTHREAD_MUTEX_LOCKER(locker, m_state_mutex);
1222    const nub_state_t old_state = m_state;
1223
1224    if (old_state == eStateExited) {
1225      DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SetState(%s) ignoring new "
1226                                    "state since current state is exited",
1227                       DNBStateAsString(new_state));
1228    } else if (old_state == new_state) {
1229      DNBLogThreadedIf(
1230          LOG_PROCESS,
1231          "MachProcess::SetState(%s) ignoring redundant state change...",
1232          DNBStateAsString(new_state));
1233    } else {
1234      if (NUB_STATE_IS_STOPPED(new_state))
1235        event_mask = eEventProcessStoppedStateChanged;
1236      else
1237        event_mask = eEventProcessRunningStateChanged;
1238
1239      DNBLogThreadedIf(
1240          LOG_PROCESS, "MachProcess::SetState(%s) upating state (previous "
1241                       "state was %s), event_mask = 0x%8.8x",
1242          DNBStateAsString(new_state), DNBStateAsString(old_state), event_mask);
1243
1244      m_state = new_state;
1245      if (new_state == eStateStopped)
1246        m_stop_count++;
1247    }
1248  }
1249
1250  if (event_mask != 0) {
1251    m_events.SetEvents(event_mask);
1252    m_private_events.SetEvents(event_mask);
1253    if (event_mask == eEventProcessStoppedStateChanged)
1254      m_private_events.ResetEvents(eEventProcessRunningStateChanged);
1255    else
1256      m_private_events.ResetEvents(eEventProcessStoppedStateChanged);
1257
1258    // Wait for the event bit to reset if a reset ACK is requested
1259    m_events.WaitForResetAck(event_mask);
1260  }
1261}
1262
1263void MachProcess::Clear(bool detaching) {
1264  // Clear any cached thread list while the pid and task are still valid
1265
1266  m_task.Clear();
1267  // Now clear out all member variables
1268  m_pid = INVALID_NUB_PROCESS;
1269  if (!detaching)
1270    CloseChildFileDescriptors();
1271
1272  m_path.clear();
1273  m_args.clear();
1274  SetState(eStateUnloaded);
1275  m_flags = eMachProcessFlagsNone;
1276  m_stop_count = 0;
1277  m_thread_list.Clear();
1278  {
1279    PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
1280    m_exception_messages.clear();
1281  }
1282  m_activities.Clear();
1283  if (m_profile_thread) {
1284    pthread_join(m_profile_thread, NULL);
1285    m_profile_thread = NULL;
1286  }
1287}
1288
1289bool MachProcess::StartSTDIOThread() {
1290  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__);
1291  // Create the thread that watches for the child STDIO
1292  return ::pthread_create(&m_stdio_thread, NULL, MachProcess::STDIOThread,
1293                          this) == 0;
1294}
1295
1296void MachProcess::SetEnableAsyncProfiling(bool enable, uint64_t interval_usec,
1297                                          DNBProfileDataScanType scan_type) {
1298  m_profile_enabled = enable;
1299  m_profile_interval_usec = static_cast<useconds_t>(interval_usec);
1300  m_profile_scan_type = scan_type;
1301
1302  if (m_profile_enabled && (m_profile_thread == NULL)) {
1303    StartProfileThread();
1304  } else if (!m_profile_enabled && m_profile_thread) {
1305    pthread_join(m_profile_thread, NULL);
1306    m_profile_thread = NULL;
1307  }
1308}
1309
1310bool MachProcess::StartProfileThread() {
1311  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__);
1312  // Create the thread that profiles the inferior and reports back if enabled
1313  return ::pthread_create(&m_profile_thread, NULL, MachProcess::ProfileThread,
1314                          this) == 0;
1315}
1316
1317nub_addr_t MachProcess::LookupSymbol(const char *name, const char *shlib) {
1318  if (m_name_to_addr_callback != NULL && name && name[0])
1319    return m_name_to_addr_callback(ProcessID(), name, shlib,
1320                                   m_name_to_addr_baton);
1321  return INVALID_NUB_ADDRESS;
1322}
1323
1324bool MachProcess::Resume(const DNBThreadResumeActions &thread_actions) {
1325  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Resume ()");
1326  nub_state_t state = GetState();
1327
1328  if (CanResume(state)) {
1329    m_thread_actions = thread_actions;
1330    PrivateResume();
1331    return true;
1332  } else if (state == eStateRunning) {
1333    DNBLog("Resume() - task 0x%x is already running, ignoring...",
1334           m_task.TaskPort());
1335    return true;
1336  }
1337  DNBLog("Resume() - task 0x%x has state %s, can't continue...",
1338         m_task.TaskPort(), DNBStateAsString(state));
1339  return false;
1340}
1341
1342bool MachProcess::Kill(const struct timespec *timeout_abstime) {
1343  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill ()");
1344  nub_state_t state = DoSIGSTOP(true, false, NULL);
1345  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() state = %s",
1346                   DNBStateAsString(state));
1347  errno = 0;
1348  DNBLog("Sending ptrace PT_KILL to terminate inferior process.");
1349  ::ptrace(PT_KILL, m_pid, 0, 0);
1350  DNBError err;
1351  err.SetErrorToErrno();
1352  if (DNBLogCheckLogBit(LOG_PROCESS) || err.Fail()) {
1353    err.LogThreaded("MachProcess::Kill() DoSIGSTOP() ::ptrace "
1354            "(PT_KILL, pid=%u, 0, 0) => 0x%8.8x (%s)",
1355            m_pid, err.Status(), err.AsString());
1356  }
1357  m_thread_actions = DNBThreadResumeActions(eStateRunning, 0);
1358  PrivateResume();
1359
1360  // Try and reap the process without touching our m_events since
1361  // we want the code above this to still get the eStateExited event
1362  const uint32_t reap_timeout_usec =
1363      1000000; // Wait 1 second and try to reap the process
1364  const uint32_t reap_interval_usec = 10000; //
1365  uint32_t reap_time_elapsed;
1366  for (reap_time_elapsed = 0; reap_time_elapsed < reap_timeout_usec;
1367       reap_time_elapsed += reap_interval_usec) {
1368    if (GetState() == eStateExited)
1369      break;
1370    usleep(reap_interval_usec);
1371  }
1372  DNBLog("Waited %u ms for process to be reaped (state = %s)",
1373         reap_time_elapsed / 1000, DNBStateAsString(GetState()));
1374  return true;
1375}
1376
1377bool MachProcess::Interrupt() {
1378  nub_state_t state = GetState();
1379  if (IsRunning(state)) {
1380    if (m_sent_interrupt_signo == 0) {
1381      m_sent_interrupt_signo = SIGSTOP;
1382      if (Signal(m_sent_interrupt_signo)) {
1383        DNBLogThreadedIf(
1384            LOG_PROCESS,
1385            "MachProcess::Interrupt() - sent %i signal to interrupt process",
1386            m_sent_interrupt_signo);
1387        return true;
1388      } else {
1389        m_sent_interrupt_signo = 0;
1390        DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - failed to "
1391                                      "send %i signal to interrupt process",
1392                         m_sent_interrupt_signo);
1393      }
1394    } else {
1395      DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - previously "
1396                                    "sent an interrupt signal %i that hasn't "
1397                                    "been received yet, interrupt aborted",
1398                       m_sent_interrupt_signo);
1399    }
1400  } else {
1401    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - process already "
1402                                  "stopped, no interrupt sent");
1403  }
1404  return false;
1405}
1406
1407bool MachProcess::Signal(int signal, const struct timespec *timeout_abstime) {
1408  DNBLogThreadedIf(LOG_PROCESS,
1409                   "MachProcess::Signal (signal = %d, timeout = %p)", signal,
1410                   reinterpret_cast<const void *>(timeout_abstime));
1411  nub_state_t state = GetState();
1412  if (::kill(ProcessID(), signal) == 0) {
1413    // If we were running and we have a timeout, wait for the signal to stop
1414    if (IsRunning(state) && timeout_abstime) {
1415      DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout "
1416                                    "= %p) waiting for signal to stop "
1417                                    "process...",
1418                       signal, reinterpret_cast<const void *>(timeout_abstime));
1419      m_private_events.WaitForSetEvents(eEventProcessStoppedStateChanged,
1420                                        timeout_abstime);
1421      state = GetState();
1422      DNBLogThreadedIf(
1423          LOG_PROCESS,
1424          "MachProcess::Signal (signal = %d, timeout = %p) state = %s", signal,
1425          reinterpret_cast<const void *>(timeout_abstime),
1426          DNBStateAsString(state));
1427      return !IsRunning(state);
1428    }
1429    DNBLogThreadedIf(
1430        LOG_PROCESS,
1431        "MachProcess::Signal (signal = %d, timeout = %p) not waiting...",
1432        signal, reinterpret_cast<const void *>(timeout_abstime));
1433    return true;
1434  }
1435  DNBError err(errno, DNBError::POSIX);
1436  err.LogThreadedIfError("kill (pid = %d, signo = %i)", ProcessID(), signal);
1437  return false;
1438}
1439
1440bool MachProcess::SendEvent(const char *event, DNBError &send_err) {
1441  DNBLogThreadedIf(LOG_PROCESS,
1442                   "MachProcess::SendEvent (event = %s) to pid: %d", event,
1443                   m_pid);
1444  if (m_pid == INVALID_NUB_PROCESS)
1445    return false;
1446// FIXME: Shouldn't we use the launch flavor we were started with?
1447#if defined(WITH_FBS) || defined(WITH_BKS)
1448  return BoardServiceSendEvent(event, send_err);
1449#endif
1450  return true;
1451}
1452
1453nub_state_t MachProcess::DoSIGSTOP(bool clear_bps_and_wps, bool allow_running,
1454                                   uint32_t *thread_idx_ptr) {
1455  nub_state_t state = GetState();
1456  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s",
1457                   DNBStateAsString(state));
1458
1459  if (!IsRunning(state)) {
1460    if (clear_bps_and_wps) {
1461      DisableAllBreakpoints(true);
1462      DisableAllWatchpoints(true);
1463      clear_bps_and_wps = false;
1464    }
1465
1466    // If we already have a thread stopped due to a SIGSTOP, we don't have
1467    // to do anything...
1468    uint32_t thread_idx =
1469        m_thread_list.GetThreadIndexForThreadStoppedWithSignal(SIGSTOP);
1470    if (thread_idx_ptr)
1471      *thread_idx_ptr = thread_idx;
1472    if (thread_idx != UINT32_MAX)
1473      return GetState();
1474
1475    // No threads were stopped with a SIGSTOP, we need to run and halt the
1476    // process with a signal
1477    DNBLogThreadedIf(LOG_PROCESS,
1478                     "MachProcess::DoSIGSTOP() state = %s -- resuming process",
1479                     DNBStateAsString(state));
1480    if (allow_running)
1481      m_thread_actions = DNBThreadResumeActions(eStateRunning, 0);
1482    else
1483      m_thread_actions = DNBThreadResumeActions(eStateSuspended, 0);
1484
1485    PrivateResume();
1486
1487    // Reset the event that says we were indeed running
1488    m_events.ResetEvents(eEventProcessRunningStateChanged);
1489    state = GetState();
1490  }
1491
1492  // We need to be stopped in order to be able to detach, so we need
1493  // to send ourselves a SIGSTOP
1494
1495  DNBLogThreadedIf(LOG_PROCESS,
1496                   "MachProcess::DoSIGSTOP() state = %s -- sending SIGSTOP",
1497                   DNBStateAsString(state));
1498  struct timespec sigstop_timeout;
1499  DNBTimer::OffsetTimeOfDay(&sigstop_timeout, 2, 0);
1500  Signal(SIGSTOP, &sigstop_timeout);
1501  if (clear_bps_and_wps) {
1502    DisableAllBreakpoints(true);
1503    DisableAllWatchpoints(true);
1504    // clear_bps_and_wps = false;
1505  }
1506  uint32_t thread_idx =
1507      m_thread_list.GetThreadIndexForThreadStoppedWithSignal(SIGSTOP);
1508  if (thread_idx_ptr)
1509    *thread_idx_ptr = thread_idx;
1510  return GetState();
1511}
1512
1513bool MachProcess::Detach() {
1514  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach()");
1515
1516  uint32_t thread_idx = UINT32_MAX;
1517  nub_state_t state = DoSIGSTOP(true, true, &thread_idx);
1518  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach() DoSIGSTOP() returned %s",
1519                   DNBStateAsString(state));
1520
1521  {
1522    m_thread_actions.Clear();
1523    m_activities.Clear();
1524    DNBThreadResumeAction thread_action;
1525    thread_action.tid = m_thread_list.ThreadIDAtIndex(thread_idx);
1526    thread_action.state = eStateRunning;
1527    thread_action.signal = -1;
1528    thread_action.addr = INVALID_NUB_ADDRESS;
1529
1530    m_thread_actions.Append(thread_action);
1531    m_thread_actions.SetDefaultThreadActionIfNeeded(eStateRunning, 0);
1532
1533    PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
1534
1535    ReplyToAllExceptions();
1536  }
1537
1538  m_task.ShutDownExcecptionThread();
1539
1540  // Detach from our process
1541  errno = 0;
1542  nub_process_t pid = m_pid;
1543  int ret = ::ptrace(PT_DETACH, pid, (caddr_t)1, 0);
1544  DNBError err(errno, DNBError::POSIX);
1545  if (DNBLogCheckLogBit(LOG_PROCESS) || err.Fail() || (ret != 0))
1546    err.LogThreaded("::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid);
1547
1548  // Resume our task
1549  m_task.Resume();
1550
1551  // NULL our task out as we have already restored all exception ports
1552  m_task.Clear();
1553
1554  // Clear out any notion of the process we once were
1555  const bool detaching = true;
1556  Clear(detaching);
1557
1558  SetState(eStateDetached);
1559
1560  return true;
1561}
1562
1563//----------------------------------------------------------------------
1564// ReadMemory from the MachProcess level will always remove any software
1565// breakpoints from the memory buffer before returning. If you wish to
1566// read memory and see those traps, read from the MachTask
1567// (m_task.ReadMemory()) as that version will give you what is actually
1568// in inferior memory.
1569//----------------------------------------------------------------------
1570nub_size_t MachProcess::ReadMemory(nub_addr_t addr, nub_size_t size,
1571                                   void *buf) {
1572  // We need to remove any current software traps (enabled software
1573  // breakpoints) that we may have placed in our tasks memory.
1574
1575  // First just read the memory as is
1576  nub_size_t bytes_read = m_task.ReadMemory(addr, size, buf);
1577
1578  // Then place any opcodes that fall into this range back into the buffer
1579  // before we return this to callers.
1580  if (bytes_read > 0)
1581    m_breakpoints.RemoveTrapsFromBuffer(addr, bytes_read, buf);
1582  return bytes_read;
1583}
1584
1585//----------------------------------------------------------------------
1586// WriteMemory from the MachProcess level will always write memory around
1587// any software breakpoints. Any software breakpoints will have their
1588// opcodes modified if they are enabled. Any memory that doesn't overlap
1589// with software breakpoints will be written to. If you wish to write to
1590// inferior memory without this interference, then write to the MachTask
1591// (m_task.WriteMemory()) as that version will always modify inferior
1592// memory.
1593//----------------------------------------------------------------------
1594nub_size_t MachProcess::WriteMemory(nub_addr_t addr, nub_size_t size,
1595                                    const void *buf) {
1596  // We need to write any data that would go where any current software traps
1597  // (enabled software breakpoints) any software traps (breakpoints) that we
1598  // may have placed in our tasks memory.
1599
1600  std::vector<DNBBreakpoint *> bps;
1601
1602  const size_t num_bps =
1603      m_breakpoints.FindBreakpointsThatOverlapRange(addr, size, bps);
1604  if (num_bps == 0)
1605    return m_task.WriteMemory(addr, size, buf);
1606
1607  nub_size_t bytes_written = 0;
1608  nub_addr_t intersect_addr;
1609  nub_size_t intersect_size;
1610  nub_size_t opcode_offset;
1611  const uint8_t *ubuf = (const uint8_t *)buf;
1612
1613  for (size_t i = 0; i < num_bps; ++i) {
1614    DNBBreakpoint *bp = bps[i];
1615
1616    const bool intersects = bp->IntersectsRange(
1617        addr, size, &intersect_addr, &intersect_size, &opcode_offset);
1618    UNUSED_IF_ASSERT_DISABLED(intersects);
1619    assert(intersects);
1620    assert(addr <= intersect_addr && intersect_addr < addr + size);
1621    assert(addr < intersect_addr + intersect_size &&
1622           intersect_addr + intersect_size <= addr + size);
1623    assert(opcode_offset + intersect_size <= bp->ByteSize());
1624
1625    // Check for bytes before this breakpoint
1626    const nub_addr_t curr_addr = addr + bytes_written;
1627    if (intersect_addr > curr_addr) {
1628      // There are some bytes before this breakpoint that we need to
1629      // just write to memory
1630      nub_size_t curr_size = intersect_addr - curr_addr;
1631      nub_size_t curr_bytes_written =
1632          m_task.WriteMemory(curr_addr, curr_size, ubuf + bytes_written);
1633      bytes_written += curr_bytes_written;
1634      if (curr_bytes_written != curr_size) {
1635        // We weren't able to write all of the requested bytes, we
1636        // are done looping and will return the number of bytes that
1637        // we have written so far.
1638        break;
1639      }
1640    }
1641
1642    // Now write any bytes that would cover up any software breakpoints
1643    // directly into the breakpoint opcode buffer
1644    ::memcpy(bp->SavedOpcodeBytes() + opcode_offset, ubuf + bytes_written,
1645             intersect_size);
1646    bytes_written += intersect_size;
1647  }
1648
1649  // Write any remaining bytes after the last breakpoint if we have any left
1650  if (bytes_written < size)
1651    bytes_written += m_task.WriteMemory(
1652        addr + bytes_written, size - bytes_written, ubuf + bytes_written);
1653
1654  return bytes_written;
1655}
1656
1657void MachProcess::ReplyToAllExceptions() {
1658  PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
1659  if (!m_exception_messages.empty()) {
1660    MachException::Message::iterator pos;
1661    MachException::Message::iterator begin = m_exception_messages.begin();
1662    MachException::Message::iterator end = m_exception_messages.end();
1663    for (pos = begin; pos != end; ++pos) {
1664      DNBLogThreadedIf(LOG_EXCEPTIONS, "Replying to exception %u...",
1665                       (uint32_t)std::distance(begin, pos));
1666      int thread_reply_signal = 0;
1667
1668      nub_thread_t tid =
1669          m_thread_list.GetThreadIDByMachPortNumber(pos->state.thread_port);
1670      const DNBThreadResumeAction *action = NULL;
1671      if (tid != INVALID_NUB_THREAD) {
1672        action = m_thread_actions.GetActionForThread(tid, false);
1673      }
1674
1675      if (action) {
1676        thread_reply_signal = action->signal;
1677        if (thread_reply_signal)
1678          m_thread_actions.SetSignalHandledForThread(tid);
1679      }
1680
1681      DNBError err(pos->Reply(this, thread_reply_signal));
1682      if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
1683        err.LogThreadedIfError("Error replying to exception");
1684    }
1685
1686    // Erase all exception message as we should have used and replied
1687    // to them all already.
1688    m_exception_messages.clear();
1689  }
1690}
1691void MachProcess::PrivateResume() {
1692  PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
1693
1694  m_auto_resume_signo = m_sent_interrupt_signo;
1695  if (m_auto_resume_signo)
1696    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::PrivateResume() - task 0x%x "
1697                                  "resuming (with unhandled interrupt signal "
1698                                  "%i)...",
1699                     m_task.TaskPort(), m_auto_resume_signo);
1700  else
1701    DNBLogThreadedIf(LOG_PROCESS,
1702                     "MachProcess::PrivateResume() - task 0x%x resuming...",
1703                     m_task.TaskPort());
1704
1705  ReplyToAllExceptions();
1706  //    bool stepOverBreakInstruction = step;
1707
1708  // Let the thread prepare to resume and see if any threads want us to
1709  // step over a breakpoint instruction (ProcessWillResume will modify
1710  // the value of stepOverBreakInstruction).
1711  m_thread_list.ProcessWillResume(this, m_thread_actions);
1712
1713  // Set our state accordingly
1714  if (m_thread_actions.NumActionsWithState(eStateStepping))
1715    SetState(eStateStepping);
1716  else
1717    SetState(eStateRunning);
1718
1719  // Now resume our task.
1720  m_task.Resume();
1721}
1722
1723DNBBreakpoint *MachProcess::CreateBreakpoint(nub_addr_t addr, nub_size_t length,
1724                                             bool hardware) {
1725  DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = "
1726                                    "0x%8.8llx, length = %llu, hardware = %i)",
1727                   (uint64_t)addr, (uint64_t)length, hardware);
1728
1729  DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
1730  if (bp)
1731    bp->Retain();
1732  else
1733    bp = m_breakpoints.Add(addr, length, hardware);
1734
1735  if (EnableBreakpoint(addr)) {
1736    DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = "
1737                                      "0x%8.8llx, length = %llu) => %p",
1738                     (uint64_t)addr, (uint64_t)length,
1739                     reinterpret_cast<void *>(bp));
1740    return bp;
1741  } else if (bp->Release() == 0) {
1742    m_breakpoints.Remove(addr);
1743  }
1744  // We failed to enable the breakpoint
1745  return NULL;
1746}
1747
1748DNBBreakpoint *MachProcess::CreateWatchpoint(nub_addr_t addr, nub_size_t length,
1749                                             uint32_t watch_flags,
1750                                             bool hardware) {
1751  DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = "
1752                                    "0x%8.8llx, length = %llu, flags = "
1753                                    "0x%8.8x, hardware = %i)",
1754                   (uint64_t)addr, (uint64_t)length, watch_flags, hardware);
1755
1756  DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
1757  // since the Z packets only send an address, we can only have one watchpoint
1758  // at
1759  // an address. If there is already one, we must refuse to create another
1760  // watchpoint
1761  if (wp)
1762    return NULL;
1763
1764  wp = m_watchpoints.Add(addr, length, hardware);
1765  wp->SetIsWatchpoint(watch_flags);
1766
1767  if (EnableWatchpoint(addr)) {
1768    DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = "
1769                                      "0x%8.8llx, length = %llu) => %p",
1770                     (uint64_t)addr, (uint64_t)length,
1771                     reinterpret_cast<void *>(wp));
1772    return wp;
1773  } else {
1774    DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = "
1775                                      "0x%8.8llx, length = %llu) => FAILED",
1776                     (uint64_t)addr, (uint64_t)length);
1777    m_watchpoints.Remove(addr);
1778  }
1779  // We failed to enable the watchpoint
1780  return NULL;
1781}
1782
1783void MachProcess::DisableAllBreakpoints(bool remove) {
1784  DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::%s (remove = %d )",
1785                   __FUNCTION__, remove);
1786
1787  m_breakpoints.DisableAllBreakpoints(this);
1788
1789  if (remove)
1790    m_breakpoints.RemoveDisabled();
1791}
1792
1793void MachProcess::DisableAllWatchpoints(bool remove) {
1794  DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::%s (remove = %d )",
1795                   __FUNCTION__, remove);
1796
1797  m_watchpoints.DisableAllWatchpoints(this);
1798
1799  if (remove)
1800    m_watchpoints.RemoveDisabled();
1801}
1802
1803bool MachProcess::DisableBreakpoint(nub_addr_t addr, bool remove) {
1804  DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
1805  if (bp) {
1806    // After "exec" we might end up with a bunch of breakpoints that were
1807    // disabled
1808    // manually, just ignore them
1809    if (!bp->IsEnabled()) {
1810      // Breakpoint might have been disabled by an exec
1811      if (remove && bp->Release() == 0) {
1812        m_thread_list.NotifyBreakpointChanged(bp);
1813        m_breakpoints.Remove(addr);
1814      }
1815      return true;
1816    }
1817
1818    // We have multiple references to this breakpoint, decrement the ref count
1819    // and if it isn't zero, then return true;
1820    if (remove && bp->Release() > 0)
1821      return true;
1822
1823    DNBLogThreadedIf(
1824        LOG_BREAKPOINTS | LOG_VERBOSE,
1825        "MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d )",
1826        (uint64_t)addr, remove);
1827
1828    if (bp->IsHardware()) {
1829      bool hw_disable_result = m_thread_list.DisableHardwareBreakpoint(bp);
1830
1831      if (hw_disable_result) {
1832        bp->SetEnabled(false);
1833        // Let the thread list know that a breakpoint has been modified
1834        if (remove) {
1835          m_thread_list.NotifyBreakpointChanged(bp);
1836          m_breakpoints.Remove(addr);
1837        }
1838        DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::DisableBreakpoint ( "
1839                                          "addr = 0x%8.8llx, remove = %d ) "
1840                                          "(hardware) => success",
1841                         (uint64_t)addr, remove);
1842        return true;
1843      }
1844
1845      return false;
1846    }
1847
1848    const nub_size_t break_op_size = bp->ByteSize();
1849    assert(break_op_size > 0);
1850    const uint8_t *const break_op =
1851        DNBArchProtocol::GetBreakpointOpcode(bp->ByteSize());
1852    if (break_op_size > 0) {
1853      // Clear a software breakpoint instruction
1854      uint8_t curr_break_op[break_op_size];
1855      bool break_op_found = false;
1856
1857      // Read the breakpoint opcode
1858      if (m_task.ReadMemory(addr, break_op_size, curr_break_op) ==
1859          break_op_size) {
1860        bool verify = false;
1861        if (bp->IsEnabled()) {
1862          // Make sure a breakpoint opcode exists at this address
1863          if (memcmp(curr_break_op, break_op, break_op_size) == 0) {
1864            break_op_found = true;
1865            // We found a valid breakpoint opcode at this address, now restore
1866            // the saved opcode.
1867            if (m_task.WriteMemory(addr, break_op_size,
1868                                   bp->SavedOpcodeBytes()) == break_op_size) {
1869              verify = true;
1870            } else {
1871              DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, "
1872                          "remove = %d ) memory write failed when restoring "
1873                          "original opcode",
1874                          (uint64_t)addr, remove);
1875            }
1876          } else {
1877            DNBLogWarning("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, "
1878                          "remove = %d ) expected a breakpoint opcode but "
1879                          "didn't find one.",
1880                          (uint64_t)addr, remove);
1881            // Set verify to true and so we can check if the original opcode has
1882            // already been restored
1883            verify = true;
1884          }
1885        } else {
1886          DNBLogThreadedIf(LOG_BREAKPOINTS | LOG_VERBOSE,
1887                           "MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, "
1888                           "remove = %d ) is not enabled",
1889                           (uint64_t)addr, remove);
1890          // Set verify to true and so we can check if the original opcode is
1891          // there
1892          verify = true;
1893        }
1894
1895        if (verify) {
1896          uint8_t verify_opcode[break_op_size];
1897          // Verify that our original opcode made it back to the inferior
1898          if (m_task.ReadMemory(addr, break_op_size, verify_opcode) ==
1899              break_op_size) {
1900            // compare the memory we just read with the original opcode
1901            if (memcmp(bp->SavedOpcodeBytes(), verify_opcode, break_op_size) ==
1902                0) {
1903              // SUCCESS
1904              bp->SetEnabled(false);
1905              // Let the thread list know that a breakpoint has been modified
1906              if (remove && bp->Release() == 0) {
1907                m_thread_list.NotifyBreakpointChanged(bp);
1908                m_breakpoints.Remove(addr);
1909              }
1910              DNBLogThreadedIf(LOG_BREAKPOINTS,
1911                               "MachProcess::DisableBreakpoint ( addr = "
1912                               "0x%8.8llx, remove = %d ) => success",
1913                               (uint64_t)addr, remove);
1914              return true;
1915            } else {
1916              if (break_op_found)
1917                DNBLogError("MachProcess::DisableBreakpoint ( addr = "
1918                            "0x%8.8llx, remove = %d ) : failed to restore "
1919                            "original opcode",
1920                            (uint64_t)addr, remove);
1921              else
1922                DNBLogError("MachProcess::DisableBreakpoint ( addr = "
1923                            "0x%8.8llx, remove = %d ) : opcode changed",
1924                            (uint64_t)addr, remove);
1925            }
1926          } else {
1927            DNBLogWarning("MachProcess::DisableBreakpoint: unable to disable "
1928                          "breakpoint 0x%8.8llx",
1929                          (uint64_t)addr);
1930          }
1931        }
1932      } else {
1933        DNBLogWarning("MachProcess::DisableBreakpoint: unable to read memory "
1934                      "at 0x%8.8llx",
1935                      (uint64_t)addr);
1936      }
1937    }
1938  } else {
1939    DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = "
1940                "%d ) invalid breakpoint address",
1941                (uint64_t)addr, remove);
1942  }
1943  return false;
1944}
1945
1946bool MachProcess::DisableWatchpoint(nub_addr_t addr, bool remove) {
1947  DNBLogThreadedIf(LOG_WATCHPOINTS,
1948                   "MachProcess::%s(addr = 0x%8.8llx, remove = %d)",
1949                   __FUNCTION__, (uint64_t)addr, remove);
1950  DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
1951  if (wp) {
1952    // If we have multiple references to a watchpoint, removing the watchpoint
1953    // shouldn't clear it
1954    if (remove && wp->Release() > 0)
1955      return true;
1956
1957    nub_addr_t addr = wp->Address();
1958    DNBLogThreadedIf(
1959        LOG_WATCHPOINTS,
1960        "MachProcess::DisableWatchpoint ( addr = 0x%8.8llx, remove = %d )",
1961        (uint64_t)addr, remove);
1962
1963    if (wp->IsHardware()) {
1964      bool hw_disable_result = m_thread_list.DisableHardwareWatchpoint(wp);
1965
1966      if (hw_disable_result) {
1967        wp->SetEnabled(false);
1968        if (remove)
1969          m_watchpoints.Remove(addr);
1970        DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::Disablewatchpoint ( "
1971                                          "addr = 0x%8.8llx, remove = %d ) "
1972                                          "(hardware) => success",
1973                         (uint64_t)addr, remove);
1974        return true;
1975      }
1976    }
1977
1978    // TODO: clear software watchpoints if we implement them
1979  } else {
1980    DNBLogError("MachProcess::DisableWatchpoint ( addr = 0x%8.8llx, remove = "
1981                "%d ) invalid watchpoint ID",
1982                (uint64_t)addr, remove);
1983  }
1984  return false;
1985}
1986
1987uint32_t MachProcess::GetNumSupportedHardwareWatchpoints() const {
1988  return m_thread_list.NumSupportedHardwareWatchpoints();
1989}
1990
1991bool MachProcess::EnableBreakpoint(nub_addr_t addr) {
1992  DNBLogThreadedIf(LOG_BREAKPOINTS,
1993                   "MachProcess::EnableBreakpoint ( addr = 0x%8.8llx )",
1994                   (uint64_t)addr);
1995  DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
1996  if (bp) {
1997    if (bp->IsEnabled()) {
1998      DNBLogWarning("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
1999                    "breakpoint already enabled.",
2000                    (uint64_t)addr);
2001      return true;
2002    } else {
2003      if (bp->HardwarePreferred()) {
2004        bp->SetHardwareIndex(m_thread_list.EnableHardwareBreakpoint(bp));
2005        if (bp->IsHardware()) {
2006          bp->SetEnabled(true);
2007          return true;
2008        }
2009      }
2010
2011      const nub_size_t break_op_size = bp->ByteSize();
2012      assert(break_op_size != 0);
2013      const uint8_t *const break_op =
2014          DNBArchProtocol::GetBreakpointOpcode(break_op_size);
2015      if (break_op_size > 0) {
2016        // Save the original opcode by reading it
2017        if (m_task.ReadMemory(addr, break_op_size, bp->SavedOpcodeBytes()) ==
2018            break_op_size) {
2019          // Write a software breakpoint in place of the original opcode
2020          if (m_task.WriteMemory(addr, break_op_size, break_op) ==
2021              break_op_size) {
2022            uint8_t verify_break_op[4];
2023            if (m_task.ReadMemory(addr, break_op_size, verify_break_op) ==
2024                break_op_size) {
2025              if (memcmp(break_op, verify_break_op, break_op_size) == 0) {
2026                bp->SetEnabled(true);
2027                // Let the thread list know that a breakpoint has been modified
2028                m_thread_list.NotifyBreakpointChanged(bp);
2029                DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::"
2030                                                  "EnableBreakpoint ( addr = "
2031                                                  "0x%8.8llx ) : SUCCESS.",
2032                                 (uint64_t)addr);
2033                return true;
2034              } else {
2035                DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx "
2036                            "): breakpoint opcode verification failed.",
2037                            (uint64_t)addr);
2038              }
2039            } else {
2040              DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
2041                          "unable to read memory to verify breakpoint opcode.",
2042                          (uint64_t)addr);
2043            }
2044          } else {
2045            DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
2046                        "unable to write breakpoint opcode to memory.",
2047                        (uint64_t)addr);
2048          }
2049        } else {
2050          DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
2051                      "unable to read memory at breakpoint address.",
2052                      (uint64_t)addr);
2053        }
2054      } else {
2055        DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ) no "
2056                    "software breakpoint opcode for current architecture.",
2057                    (uint64_t)addr);
2058      }
2059    }
2060  }
2061  return false;
2062}
2063
2064bool MachProcess::EnableWatchpoint(nub_addr_t addr) {
2065  DNBLogThreadedIf(LOG_WATCHPOINTS,
2066                   "MachProcess::EnableWatchpoint(addr = 0x%8.8llx)",
2067                   (uint64_t)addr);
2068  DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
2069  if (wp) {
2070    nub_addr_t addr = wp->Address();
2071    if (wp->IsEnabled()) {
2072      DNBLogWarning("MachProcess::EnableWatchpoint(addr = 0x%8.8llx): "
2073                    "watchpoint already enabled.",
2074                    (uint64_t)addr);
2075      return true;
2076    } else {
2077      // Currently only try and set hardware watchpoints.
2078      wp->SetHardwareIndex(m_thread_list.EnableHardwareWatchpoint(wp));
2079      if (wp->IsHardware()) {
2080        wp->SetEnabled(true);
2081        return true;
2082      }
2083      // TODO: Add software watchpoints by doing page protection tricks.
2084    }
2085  }
2086  return false;
2087}
2088
2089// Called by the exception thread when an exception has been received from
2090// our process. The exception message is completely filled and the exception
2091// data has already been copied.
2092void MachProcess::ExceptionMessageReceived(
2093    const MachException::Message &exceptionMessage) {
2094  PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
2095
2096  if (m_exception_messages.empty())
2097    m_task.Suspend();
2098
2099  DNBLogThreadedIf(LOG_EXCEPTIONS, "MachProcess::ExceptionMessageReceived ( )");
2100
2101  // Use a locker to automatically unlock our mutex in case of exceptions
2102  // Add the exception to our internal exception stack
2103  m_exception_messages.push_back(exceptionMessage);
2104}
2105
2106task_t MachProcess::ExceptionMessageBundleComplete() {
2107  // We have a complete bundle of exceptions for our child process.
2108  PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
2109  DNBLogThreadedIf(LOG_EXCEPTIONS, "%s: %llu exception messages.",
2110                   __PRETTY_FUNCTION__, (uint64_t)m_exception_messages.size());
2111  bool auto_resume = false;
2112  if (!m_exception_messages.empty()) {
2113    m_did_exec = false;
2114    // First check for any SIGTRAP and make sure we didn't exec
2115    const task_t task = m_task.TaskPort();
2116    size_t i;
2117    if (m_pid != 0) {
2118      bool received_interrupt = false;
2119      uint32_t num_task_exceptions = 0;
2120      for (i = 0; i < m_exception_messages.size(); ++i) {
2121        if (m_exception_messages[i].state.task_port == task) {
2122          ++num_task_exceptions;
2123          const int signo = m_exception_messages[i].state.SoftSignal();
2124          if (signo == SIGTRAP) {
2125            // SIGTRAP could mean that we exec'ed. We need to check the
2126            // dyld all_image_infos.infoArray to see if it is NULL and if
2127            // so, say that we exec'ed.
2128            const nub_addr_t aii_addr = GetDYLDAllImageInfosAddress();
2129            if (aii_addr != INVALID_NUB_ADDRESS) {
2130              const nub_addr_t info_array_count_addr = aii_addr + 4;
2131              uint32_t info_array_count = 0;
2132              if (m_task.ReadMemory(info_array_count_addr, 4,
2133                                    &info_array_count) == 4) {
2134                if (info_array_count == 0) {
2135                  m_did_exec = true;
2136                  // Force the task port to update itself in case the task port
2137                  // changed after exec
2138                  DNBError err;
2139                  const task_t old_task = m_task.TaskPort();
2140                  const task_t new_task =
2141                      m_task.TaskPortForProcessID(err, true);
2142                  if (old_task != new_task)
2143                    DNBLogThreadedIf(
2144                        LOG_PROCESS,
2145                        "exec: task changed from 0x%4.4x to 0x%4.4x", old_task,
2146                        new_task);
2147                }
2148              } else {
2149                DNBLog("error: failed to read all_image_infos.infoArrayCount "
2150                       "from 0x%8.8llx",
2151                       (uint64_t)info_array_count_addr);
2152              }
2153            }
2154            break;
2155          } else if (m_sent_interrupt_signo != 0 &&
2156                     signo == m_sent_interrupt_signo) {
2157            received_interrupt = true;
2158          }
2159        }
2160      }
2161
2162      if (m_did_exec) {
2163        cpu_type_t process_cpu_type =
2164            MachProcess::GetCPUTypeForLocalProcess(m_pid);
2165        if (m_cpu_type != process_cpu_type) {
2166          DNBLog("arch changed from 0x%8.8x to 0x%8.8x", m_cpu_type,
2167                 process_cpu_type);
2168          m_cpu_type = process_cpu_type;
2169          DNBArchProtocol::SetArchitecture(process_cpu_type);
2170        }
2171        m_thread_list.Clear();
2172        m_activities.Clear();
2173        m_breakpoints.DisableAll();
2174      }
2175
2176      if (m_sent_interrupt_signo != 0) {
2177        if (received_interrupt) {
2178          DNBLogThreadedIf(LOG_PROCESS,
2179                           "MachProcess::ExceptionMessageBundleComplete(): "
2180                           "process successfully interrupted with signal %i",
2181                           m_sent_interrupt_signo);
2182
2183          // Mark that we received the interrupt signal
2184          m_sent_interrupt_signo = 0;
2185          // Not check if we had a case where:
2186          // 1 - We called MachProcess::Interrupt() but we stopped for another
2187          // reason
2188          // 2 - We called MachProcess::Resume() (but still haven't gotten the
2189          // interrupt signal)
2190          // 3 - We are now incorrectly stopped because we are handling the
2191          // interrupt signal we missed
2192          // 4 - We might need to resume if we stopped only with the interrupt
2193          // signal that we never handled
2194          if (m_auto_resume_signo != 0) {
2195            // Only auto_resume if we stopped with _only_ the interrupt signal
2196            if (num_task_exceptions == 1) {
2197              auto_resume = true;
2198              DNBLogThreadedIf(LOG_PROCESS, "MachProcess::"
2199                                            "ExceptionMessageBundleComplete(): "
2200                                            "auto resuming due to unhandled "
2201                                            "interrupt signal %i",
2202                               m_auto_resume_signo);
2203            }
2204            m_auto_resume_signo = 0;
2205          }
2206        } else {
2207          DNBLogThreadedIf(LOG_PROCESS, "MachProcess::"
2208                                        "ExceptionMessageBundleComplete(): "
2209                                        "didn't get signal %i after "
2210                                        "MachProcess::Interrupt()",
2211                           m_sent_interrupt_signo);
2212        }
2213      }
2214    }
2215
2216    // Let all threads recover from stopping and do any clean up based
2217    // on the previous thread state (if any).
2218    m_thread_list.ProcessDidStop(this);
2219    m_activities.Clear();
2220
2221    // Let each thread know of any exceptions
2222    for (i = 0; i < m_exception_messages.size(); ++i) {
2223      // Let the thread list figure use the MachProcess to forward all
2224      // exceptions
2225      // on down to each thread.
2226      if (m_exception_messages[i].state.task_port == task)
2227        m_thread_list.NotifyException(m_exception_messages[i].state);
2228      if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
2229        m_exception_messages[i].Dump();
2230    }
2231
2232    if (DNBLogCheckLogBit(LOG_THREAD))
2233      m_thread_list.Dump();
2234
2235    bool step_more = false;
2236    if (m_thread_list.ShouldStop(step_more) && !auto_resume) {
2237      // Wait for the eEventProcessRunningStateChanged event to be reset
2238      // before changing state to stopped to avoid race condition with
2239      // very fast start/stops
2240      struct timespec timeout;
2241      // DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000);   // Wait for 250
2242      // ms
2243      DNBTimer::OffsetTimeOfDay(&timeout, 1, 0); // Wait for 250 ms
2244      m_events.WaitForEventsToReset(eEventProcessRunningStateChanged, &timeout);
2245      SetState(eStateStopped);
2246    } else {
2247      // Resume without checking our current state.
2248      PrivateResume();
2249    }
2250  } else {
2251    DNBLogThreadedIf(
2252        LOG_EXCEPTIONS, "%s empty exception messages bundle (%llu exceptions).",
2253        __PRETTY_FUNCTION__, (uint64_t)m_exception_messages.size());
2254  }
2255  return m_task.TaskPort();
2256}
2257
2258nub_size_t
2259MachProcess::CopyImageInfos(struct DNBExecutableImageInfo **image_infos,
2260                            bool only_changed) {
2261  if (m_image_infos_callback != NULL)
2262    return m_image_infos_callback(ProcessID(), image_infos, only_changed,
2263                                  m_image_infos_baton);
2264  return 0;
2265}
2266
2267void MachProcess::SharedLibrariesUpdated() {
2268  uint32_t event_bits = eEventSharedLibsStateChange;
2269  // Set the shared library event bit to let clients know of shared library
2270  // changes
2271  m_events.SetEvents(event_bits);
2272  // Wait for the event bit to reset if a reset ACK is requested
2273  m_events.WaitForResetAck(event_bits);
2274}
2275
2276void MachProcess::SetExitInfo(const char *info) {
2277  if (info && info[0]) {
2278    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s(\"%s\")", __FUNCTION__,
2279                     info);
2280    m_exit_info.assign(info);
2281  } else {
2282    DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s(NULL)", __FUNCTION__);
2283    m_exit_info.clear();
2284  }
2285}
2286
2287void MachProcess::AppendSTDOUT(char *s, size_t len) {
2288  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (<%llu> %s) ...", __FUNCTION__,
2289                   (uint64_t)len, s);
2290  PTHREAD_MUTEX_LOCKER(locker, m_stdio_mutex);
2291  m_stdout_data.append(s, len);
2292  m_events.SetEvents(eEventStdioAvailable);
2293
2294  // Wait for the event bit to reset if a reset ACK is requested
2295  m_events.WaitForResetAck(eEventStdioAvailable);
2296}
2297
2298size_t MachProcess::GetAvailableSTDOUT(char *buf, size_t buf_size) {
2299  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__,
2300                   reinterpret_cast<void *>(buf), (uint64_t)buf_size);
2301  PTHREAD_MUTEX_LOCKER(locker, m_stdio_mutex);
2302  size_t bytes_available = m_stdout_data.size();
2303  if (bytes_available > 0) {
2304    if (bytes_available > buf_size) {
2305      memcpy(buf, m_stdout_data.data(), buf_size);
2306      m_stdout_data.erase(0, buf_size);
2307      bytes_available = buf_size;
2308    } else {
2309      memcpy(buf, m_stdout_data.data(), bytes_available);
2310      m_stdout_data.clear();
2311    }
2312  }
2313  return bytes_available;
2314}
2315
2316nub_addr_t MachProcess::GetDYLDAllImageInfosAddress() {
2317  DNBError err;
2318  return m_task.GetDYLDAllImageInfosAddress(err);
2319}
2320
2321size_t MachProcess::GetAvailableSTDERR(char *buf, size_t buf_size) { return 0; }
2322
2323void *MachProcess::STDIOThread(void *arg) {
2324  MachProcess *proc = (MachProcess *)arg;
2325  DNBLogThreadedIf(LOG_PROCESS,
2326                   "MachProcess::%s ( arg = %p ) thread starting...",
2327                   __FUNCTION__, arg);
2328
2329#if defined(__APPLE__)
2330  pthread_setname_np("stdio monitoring thread");
2331#endif
2332
2333  // We start use a base and more options so we can control if we
2334  // are currently using a timeout on the mach_msg. We do this to get a
2335  // bunch of related exceptions on our exception port so we can process
2336  // then together. When we have multiple threads, we can get an exception
2337  // per thread and they will come in consecutively. The main thread loop
2338  // will start by calling mach_msg to without having the MACH_RCV_TIMEOUT
2339  // flag set in the options, so we will wait forever for an exception on
2340  // our exception port. After we get one exception, we then will use the
2341  // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
2342  // exceptions for our process. After we have received the last pending
2343  // exception, we will get a timeout which enables us to then notify
2344  // our main thread that we have an exception bundle available. We then wait
2345  // for the main thread to tell this exception thread to start trying to get
2346  // exceptions messages again and we start again with a mach_msg read with
2347  // infinite timeout.
2348  DNBError err;
2349  int stdout_fd = proc->GetStdoutFileDescriptor();
2350  int stderr_fd = proc->GetStderrFileDescriptor();
2351  if (stdout_fd == stderr_fd)
2352    stderr_fd = -1;
2353
2354  while (stdout_fd >= 0 || stderr_fd >= 0) {
2355    ::pthread_testcancel();
2356
2357    fd_set read_fds;
2358    FD_ZERO(&read_fds);
2359    if (stdout_fd >= 0)
2360      FD_SET(stdout_fd, &read_fds);
2361    if (stderr_fd >= 0)
2362      FD_SET(stderr_fd, &read_fds);
2363    int nfds = std::max<int>(stdout_fd, stderr_fd) + 1;
2364
2365    int num_set_fds = select(nfds, &read_fds, NULL, NULL, NULL);
2366    DNBLogThreadedIf(LOG_PROCESS,
2367                     "select (nfds, &read_fds, NULL, NULL, NULL) => %d",
2368                     num_set_fds);
2369
2370    if (num_set_fds < 0) {
2371      int select_errno = errno;
2372      if (DNBLogCheckLogBit(LOG_PROCESS)) {
2373        err.SetError(select_errno, DNBError::POSIX);
2374        err.LogThreadedIfError(
2375            "select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
2376      }
2377
2378      switch (select_errno) {
2379      case EAGAIN: // The kernel was (perhaps temporarily) unable to allocate
2380                   // the requested number of file descriptors, or we have
2381                   // non-blocking IO
2382        break;
2383      case EBADF: // One of the descriptor sets specified an invalid descriptor.
2384        return NULL;
2385        break;
2386      case EINTR:  // A signal was delivered before the time limit expired and
2387                   // before any of the selected events occurred.
2388      case EINVAL: // The specified time limit is invalid. One of its components
2389                   // is negative or too large.
2390      default:     // Other unknown error
2391        break;
2392      }
2393    } else if (num_set_fds == 0) {
2394    } else {
2395      char s[1024];
2396      s[sizeof(s) - 1] = '\0'; // Ensure we have NULL termination
2397      ssize_t bytes_read = 0;
2398      if (stdout_fd >= 0 && FD_ISSET(stdout_fd, &read_fds)) {
2399        do {
2400          bytes_read = ::read(stdout_fd, s, sizeof(s) - 1);
2401          if (bytes_read < 0) {
2402            int read_errno = errno;
2403            DNBLogThreadedIf(LOG_PROCESS,
2404                             "read (stdout_fd, ) => %zd   errno: %d (%s)",
2405                             bytes_read, read_errno, strerror(read_errno));
2406          } else if (bytes_read == 0) {
2407            // EOF...
2408            DNBLogThreadedIf(
2409                LOG_PROCESS,
2410                "read (stdout_fd, ) => %zd  (reached EOF for child STDOUT)",
2411                bytes_read);
2412            stdout_fd = -1;
2413          } else if (bytes_read > 0) {
2414            proc->AppendSTDOUT(s, bytes_read);
2415          }
2416
2417        } while (bytes_read > 0);
2418      }
2419
2420      if (stderr_fd >= 0 && FD_ISSET(stderr_fd, &read_fds)) {
2421        do {
2422          bytes_read = ::read(stderr_fd, s, sizeof(s) - 1);
2423          if (bytes_read < 0) {
2424            int read_errno = errno;
2425            DNBLogThreadedIf(LOG_PROCESS,
2426                             "read (stderr_fd, ) => %zd   errno: %d (%s)",
2427                             bytes_read, read_errno, strerror(read_errno));
2428          } else if (bytes_read == 0) {
2429            // EOF...
2430            DNBLogThreadedIf(
2431                LOG_PROCESS,
2432                "read (stderr_fd, ) => %zd  (reached EOF for child STDERR)",
2433                bytes_read);
2434            stderr_fd = -1;
2435          } else if (bytes_read > 0) {
2436            proc->AppendSTDOUT(s, bytes_read);
2437          }
2438
2439        } while (bytes_read > 0);
2440      }
2441    }
2442  }
2443  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%p): thread exiting...",
2444                   __FUNCTION__, arg);
2445  return NULL;
2446}
2447
2448void MachProcess::SignalAsyncProfileData(const char *info) {
2449  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%s) ...", __FUNCTION__, info);
2450  PTHREAD_MUTEX_LOCKER(locker, m_profile_data_mutex);
2451  m_profile_data.push_back(info);
2452  m_events.SetEvents(eEventProfileDataAvailable);
2453
2454  // Wait for the event bit to reset if a reset ACK is requested
2455  m_events.WaitForResetAck(eEventProfileDataAvailable);
2456}
2457
2458size_t MachProcess::GetAsyncProfileData(char *buf, size_t buf_size) {
2459  DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__,
2460                   reinterpret_cast<void *>(buf), (uint64_t)buf_size);
2461  PTHREAD_MUTEX_LOCKER(locker, m_profile_data_mutex);
2462  if (m_profile_data.empty())
2463    return 0;
2464
2465  size_t bytes_available = m_profile_data.front().size();
2466  if (bytes_available > 0) {
2467    if (bytes_available > buf_size) {
2468      memcpy(buf, m_profile_data.front().data(), buf_size);
2469      m_profile_data.front().erase(0, buf_size);
2470      bytes_available = buf_size;
2471    } else {
2472      memcpy(buf, m_profile_data.front().data(), bytes_available);
2473      m_profile_data.erase(m_profile_data.begin());
2474    }
2475  }
2476  return bytes_available;
2477}
2478
2479void *MachProcess::ProfileThread(void *arg) {
2480  MachProcess *proc = (MachProcess *)arg;
2481  DNBLogThreadedIf(LOG_PROCESS,
2482                   "MachProcess::%s ( arg = %p ) thread starting...",
2483                   __FUNCTION__, arg);
2484
2485#if defined(__APPLE__)
2486  pthread_setname_np("performance profiling thread");
2487#endif
2488
2489  while (proc->IsProfilingEnabled()) {
2490    nub_state_t state = proc->GetState();
2491    if (state == eStateRunning) {
2492      std::string data =
2493          proc->Task().GetProfileData(proc->GetProfileScanType());
2494      if (!data.empty()) {
2495        proc->SignalAsyncProfileData(data.c_str());
2496      }
2497    } else if ((state == eStateUnloaded) || (state == eStateDetached) ||
2498               (state == eStateUnloaded)) {
2499      // Done. Get out of this thread.
2500      break;
2501    }
2502
2503    // A simple way to set up the profile interval. We can also use select() or
2504    // dispatch timer source if necessary.
2505    usleep(proc->ProfileInterval());
2506  }
2507  return NULL;
2508}
2509
2510pid_t MachProcess::AttachForDebug(pid_t pid, char *err_str, size_t err_len) {
2511  // Clear out and clean up from any current state
2512  Clear();
2513  if (pid != 0) {
2514    DNBError err;
2515    // Make sure the process exists...
2516    if (::getpgid(pid) < 0) {
2517      err.SetErrorToErrno();
2518      const char *err_cstr = err.AsString();
2519      ::snprintf(err_str, err_len, "%s",
2520                 err_cstr ? err_cstr : "No such process");
2521      DNBLogError ("MachProcess::AttachForDebug pid %d does not exist", pid);
2522      return INVALID_NUB_PROCESS;
2523    }
2524
2525    SetState(eStateAttaching);
2526    m_pid = pid;
2527    if (!m_task.StartExceptionThread(err)) {
2528      const char *err_cstr = err.AsString();
2529      ::snprintf(err_str, err_len, "%s",
2530                 err_cstr ? err_cstr : "unable to start the exception thread");
2531      DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid);
2532      DNBLogError ("MachProcess::AttachForDebug failed to start exception thread: %s", err_str);
2533      m_pid = INVALID_NUB_PROCESS;
2534      return INVALID_NUB_PROCESS;
2535    }
2536
2537    errno = 0;
2538    if (::ptrace(PT_ATTACHEXC, pid, 0, 0)) {
2539      err.SetError(errno);
2540      DNBLogError ("MachProcess::AttachForDebug failed to ptrace(PT_ATTACHEXC): %s", err.AsString());
2541    } else {
2542      err.Clear();
2543    }
2544
2545    if (err.Success()) {
2546      m_flags |= eMachProcessFlagsAttached;
2547      // Sleep a bit to let the exception get received and set our process
2548      // status
2549      // to stopped.
2550      ::usleep(250000);
2551      DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", pid);
2552      return m_pid;
2553    } else {
2554      ::snprintf(err_str, err_len, "%s", err.AsString());
2555      DNBLogError ("MachProcess::AttachForDebug error: failed to attach to pid %d", pid);
2556
2557      struct kinfo_proc kinfo;
2558      int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
2559      size_t len = sizeof(struct kinfo_proc);
2560      if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), &kinfo, &len, NULL, 0) == 0 && len > 0) {
2561        if (kinfo.kp_proc.p_flag & P_TRACED) {
2562          ::snprintf(err_str, err_len, "%s - process %d is already being debugged", err.AsString(), pid);
2563          DNBLogError ("MachProcess::AttachForDebug pid %d is already being debugged", pid);
2564        }
2565      }
2566    }
2567  }
2568  return INVALID_NUB_PROCESS;
2569}
2570
2571Genealogy::ThreadActivitySP
2572MachProcess::GetGenealogyInfoForThread(nub_thread_t tid, bool &timed_out) {
2573  return m_activities.GetGenealogyInfoForThread(m_pid, tid, m_thread_list,
2574                                                m_task.TaskPort(), timed_out);
2575}
2576
2577Genealogy::ProcessExecutableInfoSP
2578MachProcess::GetGenealogyImageInfo(size_t idx) {
2579  return m_activities.GetProcessExecutableInfosAtIndex(idx);
2580}
2581
2582bool MachProcess::GetOSVersionNumbers(uint64_t *major, uint64_t *minor,
2583                                      uint64_t *patch) {
2584#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) &&                  \
2585    (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101000)
2586  return false;
2587#else
2588  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2589
2590  NSOperatingSystemVersion vers =
2591      [[NSProcessInfo processInfo] operatingSystemVersion];
2592  if (major)
2593    *major = vers.majorVersion;
2594  if (minor)
2595    *minor = vers.minorVersion;
2596  if (patch)
2597    *patch = vers.patchVersion;
2598
2599  [pool drain];
2600
2601  return true;
2602#endif
2603}
2604
2605std::string MachProcess::GetMacCatalystVersionString() {
2606  @autoreleasepool {
2607    NSDictionary *version_info =
2608      [NSDictionary dictionaryWithContentsOfFile:
2609       @"/System/Library/CoreServices/SystemVersion.plist"];
2610    NSString *version_value = [version_info objectForKey: @"iOSSupportVersion"];
2611    if (const char *version_str = [version_value UTF8String])
2612      return version_str;
2613  }
2614  return {};
2615}
2616
2617// Do the process specific setup for attach.  If this returns NULL, then there's
2618// no
2619// platform specific stuff to be done to wait for the attach.  If you get
2620// non-null,
2621// pass that token to the CheckForProcess method, and then to
2622// CleanupAfterAttach.
2623
2624//  Call PrepareForAttach before attaching to a process that has not yet
2625//  launched
2626// This returns a token that can be passed to CheckForProcess, and to
2627// CleanupAfterAttach.
2628// You should call CleanupAfterAttach to free the token, and do whatever other
2629// cleanup seems good.
2630
2631const void *MachProcess::PrepareForAttach(const char *path,
2632                                          nub_launch_flavor_t launch_flavor,
2633                                          bool waitfor, DNBError &attach_err) {
2634#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) || defined(WITH_FBS)
2635  // Tell SpringBoard to halt the next launch of this application on startup.
2636
2637  if (!waitfor)
2638    return NULL;
2639
2640  const char *app_ext = strstr(path, ".app");
2641  const bool is_app =
2642      app_ext != NULL && (app_ext[4] == '\0' || app_ext[4] == '/');
2643  if (!is_app) {
2644    DNBLogThreadedIf(
2645        LOG_PROCESS,
2646        "MachProcess::PrepareForAttach(): path '%s' doesn't contain .app, "
2647        "we can't tell springboard to wait for launch...",
2648        path);
2649    return NULL;
2650  }
2651
2652#if defined(WITH_FBS)
2653  if (launch_flavor == eLaunchFlavorDefault)
2654    launch_flavor = eLaunchFlavorFBS;
2655  if (launch_flavor != eLaunchFlavorFBS)
2656    return NULL;
2657#elif defined(WITH_BKS)
2658  if (launch_flavor == eLaunchFlavorDefault)
2659    launch_flavor = eLaunchFlavorBKS;
2660  if (launch_flavor != eLaunchFlavorBKS)
2661    return NULL;
2662#elif defined(WITH_SPRINGBOARD)
2663  if (launch_flavor == eLaunchFlavorDefault)
2664    launch_flavor = eLaunchFlavorSpringBoard;
2665  if (launch_flavor != eLaunchFlavorSpringBoard)
2666    return NULL;
2667#endif
2668
2669  std::string app_bundle_path(path, app_ext + strlen(".app"));
2670
2671  CFStringRef bundleIDCFStr =
2672      CopyBundleIDForPath(app_bundle_path.c_str(), attach_err);
2673  std::string bundleIDStr;
2674  CFString::UTF8(bundleIDCFStr, bundleIDStr);
2675  DNBLogThreadedIf(LOG_PROCESS,
2676                   "CopyBundleIDForPath (%s, err_str) returned @\"%s\"",
2677                   app_bundle_path.c_str(), bundleIDStr.c_str());
2678
2679  if (bundleIDCFStr == NULL) {
2680    return NULL;
2681  }
2682
2683#if defined(WITH_FBS)
2684  if (launch_flavor == eLaunchFlavorFBS) {
2685    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2686
2687    NSString *stdio_path = nil;
2688    NSFileManager *file_manager = [NSFileManager defaultManager];
2689    const char *null_path = "/dev/null";
2690    stdio_path =
2691        [file_manager stringWithFileSystemRepresentation:null_path
2692                                                  length:strlen(null_path)];
2693
2694    NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
2695    NSMutableDictionary *options = [NSMutableDictionary dictionary];
2696
2697    DNBLogThreadedIf(LOG_PROCESS, "Calling BKSSystemService openApplication: "
2698                                  "@\"%s\",options include stdio path: \"%s\", "
2699                                  "BKSDebugOptionKeyDebugOnNextLaunch & "
2700                                  "BKSDebugOptionKeyWaitForDebugger )",
2701                     bundleIDStr.c_str(), null_path);
2702
2703    [debug_options setObject:stdio_path
2704                      forKey:FBSDebugOptionKeyStandardOutPath];
2705    [debug_options setObject:stdio_path
2706                      forKey:FBSDebugOptionKeyStandardErrorPath];
2707    [debug_options setObject:[NSNumber numberWithBool:YES]
2708                      forKey:FBSDebugOptionKeyWaitForDebugger];
2709    [debug_options setObject:[NSNumber numberWithBool:YES]
2710                      forKey:FBSDebugOptionKeyDebugOnNextLaunch];
2711
2712    [options setObject:debug_options
2713                forKey:FBSOpenApplicationOptionKeyDebuggingOptions];
2714
2715    FBSSystemService *system_service = [[FBSSystemService alloc] init];
2716
2717    mach_port_t client_port = [system_service createClientPort];
2718    __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2719    __block FBSOpenApplicationErrorCode attach_error_code =
2720        FBSOpenApplicationErrorCodeNone;
2721
2722    NSString *bundleIDNSStr = (NSString *)bundleIDCFStr;
2723
2724    [system_service openApplication:bundleIDNSStr
2725                            options:options
2726                         clientPort:client_port
2727                         withResult:^(NSError *error) {
2728                           // The system service will cleanup the client port we
2729                           // created for us.
2730                           if (error)
2731                             attach_error_code =
2732                                 (FBSOpenApplicationErrorCode)[error code];
2733
2734                           [system_service release];
2735                           dispatch_semaphore_signal(semaphore);
2736                         }];
2737
2738    const uint32_t timeout_secs = 9;
2739
2740    dispatch_time_t timeout =
2741        dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
2742
2743    long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
2744
2745    if (!success) {
2746      DNBLogError("timed out trying to launch %s.", bundleIDStr.c_str());
2747      attach_err.SetErrorString(
2748          "debugserver timed out waiting for openApplication to complete.");
2749      attach_err.SetError(OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
2750    } else if (attach_error_code != FBSOpenApplicationErrorCodeNone) {
2751      SetFBSError(attach_error_code, attach_err);
2752      DNBLogError("unable to launch the application with CFBundleIdentifier "
2753                  "'%s' bks_error = %ld",
2754                  bundleIDStr.c_str(), (NSInteger)attach_error_code);
2755    }
2756    dispatch_release(semaphore);
2757    [pool drain];
2758  }
2759#endif
2760#if defined(WITH_BKS)
2761  if (launch_flavor == eLaunchFlavorBKS) {
2762    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2763
2764    NSString *stdio_path = nil;
2765    NSFileManager *file_manager = [NSFileManager defaultManager];
2766    const char *null_path = "/dev/null";
2767    stdio_path =
2768        [file_manager stringWithFileSystemRepresentation:null_path
2769                                                  length:strlen(null_path)];
2770
2771    NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
2772    NSMutableDictionary *options = [NSMutableDictionary dictionary];
2773
2774    DNBLogThreadedIf(LOG_PROCESS, "Calling BKSSystemService openApplication: "
2775                                  "@\"%s\",options include stdio path: \"%s\", "
2776                                  "BKSDebugOptionKeyDebugOnNextLaunch & "
2777                                  "BKSDebugOptionKeyWaitForDebugger )",
2778                     bundleIDStr.c_str(), null_path);
2779
2780    [debug_options setObject:stdio_path
2781                      forKey:BKSDebugOptionKeyStandardOutPath];
2782    [debug_options setObject:stdio_path
2783                      forKey:BKSDebugOptionKeyStandardErrorPath];
2784    [debug_options setObject:[NSNumber numberWithBool:YES]
2785                      forKey:BKSDebugOptionKeyWaitForDebugger];
2786    [debug_options setObject:[NSNumber numberWithBool:YES]
2787                      forKey:BKSDebugOptionKeyDebugOnNextLaunch];
2788
2789    [options setObject:debug_options
2790                forKey:BKSOpenApplicationOptionKeyDebuggingOptions];
2791
2792    BKSSystemService *system_service = [[BKSSystemService alloc] init];
2793
2794    mach_port_t client_port = [system_service createClientPort];
2795    __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2796    __block BKSOpenApplicationErrorCode attach_error_code =
2797        BKSOpenApplicationErrorCodeNone;
2798
2799    NSString *bundleIDNSStr = (NSString *)bundleIDCFStr;
2800
2801    [system_service openApplication:bundleIDNSStr
2802                            options:options
2803                         clientPort:client_port
2804                         withResult:^(NSError *error) {
2805                           // The system service will cleanup the client port we
2806                           // created for us.
2807                           if (error)
2808                             attach_error_code =
2809                                 (BKSOpenApplicationErrorCode)[error code];
2810
2811                           [system_service release];
2812                           dispatch_semaphore_signal(semaphore);
2813                         }];
2814
2815    const uint32_t timeout_secs = 9;
2816
2817    dispatch_time_t timeout =
2818        dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
2819
2820    long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
2821
2822    if (!success) {
2823      DNBLogError("timed out trying to launch %s.", bundleIDStr.c_str());
2824      attach_err.SetErrorString(
2825          "debugserver timed out waiting for openApplication to complete.");
2826      attach_err.SetError(OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
2827    } else if (attach_error_code != BKSOpenApplicationErrorCodeNone) {
2828      SetBKSError(attach_error_code, attach_err);
2829      DNBLogError("unable to launch the application with CFBundleIdentifier "
2830                  "'%s' bks_error = %ld",
2831                  bundleIDStr.c_str(), attach_error_code);
2832    }
2833    dispatch_release(semaphore);
2834    [pool drain];
2835  }
2836#endif
2837
2838#if defined(WITH_SPRINGBOARD)
2839  if (launch_flavor == eLaunchFlavorSpringBoard) {
2840    SBSApplicationLaunchError sbs_error = 0;
2841
2842    const char *stdout_err = "/dev/null";
2843    CFString stdio_path;
2844    stdio_path.SetFileSystemRepresentation(stdout_err);
2845
2846    DNBLogThreadedIf(LOG_PROCESS, "SBSLaunchApplicationForDebugging ( @\"%s\" "
2847                                  ", NULL, NULL, NULL, @\"%s\", @\"%s\", "
2848                                  "SBSApplicationDebugOnNextLaunch | "
2849                                  "SBSApplicationLaunchWaitForDebugger )",
2850                     bundleIDStr.c_str(), stdout_err, stdout_err);
2851
2852    sbs_error = SBSLaunchApplicationForDebugging(
2853        bundleIDCFStr,
2854        (CFURLRef)NULL, // openURL
2855        NULL,           // launch_argv.get(),
2856        NULL,           // launch_envp.get(),  // CFDictionaryRef environment
2857        stdio_path.get(), stdio_path.get(),
2858        SBSApplicationDebugOnNextLaunch | SBSApplicationLaunchWaitForDebugger);
2859
2860    if (sbs_error != SBSApplicationLaunchErrorSuccess) {
2861      attach_err.SetError(sbs_error, DNBError::SpringBoard);
2862      return NULL;
2863    }
2864  }
2865#endif // WITH_SPRINGBOARD
2866
2867  DNBLogThreadedIf(LOG_PROCESS, "Successfully set DebugOnNextLaunch.");
2868  return bundleIDCFStr;
2869#else // !(defined (WITH_SPRINGBOARD) || defined (WITH_BKS) || defined
2870      // (WITH_FBS))
2871  return NULL;
2872#endif
2873}
2874
2875// Pass in the token you got from PrepareForAttach.  If there is a process
2876// for that token, then the pid will be returned, otherwise INVALID_NUB_PROCESS
2877// will be returned.
2878
2879nub_process_t MachProcess::CheckForProcess(const void *attach_token,
2880                                           nub_launch_flavor_t launch_flavor) {
2881  if (attach_token == NULL)
2882    return INVALID_NUB_PROCESS;
2883
2884#if defined(WITH_FBS)
2885  if (launch_flavor == eLaunchFlavorFBS) {
2886    NSString *bundleIDNSStr = (NSString *)attach_token;
2887    FBSSystemService *systemService = [[FBSSystemService alloc] init];
2888    pid_t pid = [systemService pidForApplication:bundleIDNSStr];
2889    [systemService release];
2890    if (pid == 0)
2891      return INVALID_NUB_PROCESS;
2892    else
2893      return pid;
2894  }
2895#endif
2896
2897#if defined(WITH_BKS)
2898  if (launch_flavor == eLaunchFlavorBKS) {
2899    NSString *bundleIDNSStr = (NSString *)attach_token;
2900    BKSSystemService *systemService = [[BKSSystemService alloc] init];
2901    pid_t pid = [systemService pidForApplication:bundleIDNSStr];
2902    [systemService release];
2903    if (pid == 0)
2904      return INVALID_NUB_PROCESS;
2905    else
2906      return pid;
2907  }
2908#endif
2909
2910#if defined(WITH_SPRINGBOARD)
2911  if (launch_flavor == eLaunchFlavorSpringBoard) {
2912    CFStringRef bundleIDCFStr = (CFStringRef)attach_token;
2913    Boolean got_it;
2914    nub_process_t attach_pid;
2915    got_it = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &attach_pid);
2916    if (got_it)
2917      return attach_pid;
2918    else
2919      return INVALID_NUB_PROCESS;
2920  }
2921#endif
2922  return INVALID_NUB_PROCESS;
2923}
2924
2925// Call this to clean up after you have either attached or given up on the
2926// attach.
2927// Pass true for success if you have attached, false if you have not.
2928// The token will also be freed at this point, so you can't use it after calling
2929// this method.
2930
2931void MachProcess::CleanupAfterAttach(const void *attach_token,
2932                                     nub_launch_flavor_t launch_flavor,
2933                                     bool success, DNBError &err_str) {
2934  if (attach_token == NULL)
2935    return;
2936
2937#if defined(WITH_FBS)
2938  if (launch_flavor == eLaunchFlavorFBS) {
2939    if (!success) {
2940      FBSCleanupAfterAttach(attach_token, err_str);
2941    }
2942    CFRelease((CFStringRef)attach_token);
2943  }
2944#endif
2945
2946#if defined(WITH_BKS)
2947
2948  if (launch_flavor == eLaunchFlavorBKS) {
2949    if (!success) {
2950      BKSCleanupAfterAttach(attach_token, err_str);
2951    }
2952    CFRelease((CFStringRef)attach_token);
2953  }
2954#endif
2955
2956#if defined(WITH_SPRINGBOARD)
2957  // Tell SpringBoard to cancel the debug on next launch of this application
2958  // if we failed to attach
2959  if (launch_flavor == eMachProcessFlagsUsingSpringBoard) {
2960    if (!success) {
2961      SBSApplicationLaunchError sbs_error = 0;
2962      CFStringRef bundleIDCFStr = (CFStringRef)attach_token;
2963
2964      sbs_error = SBSLaunchApplicationForDebugging(
2965          bundleIDCFStr, (CFURLRef)NULL, NULL, NULL, NULL, NULL,
2966          SBSApplicationCancelDebugOnNextLaunch);
2967
2968      if (sbs_error != SBSApplicationLaunchErrorSuccess) {
2969        err_str.SetError(sbs_error, DNBError::SpringBoard);
2970        return;
2971      }
2972    }
2973
2974    CFRelease((CFStringRef)attach_token);
2975  }
2976#endif
2977}
2978
2979pid_t MachProcess::LaunchForDebug(
2980    const char *path, char const *argv[], char const *envp[],
2981    const char *working_directory, // NULL => don't change, non-NULL => set
2982                                   // working directory for inferior to this
2983    const char *stdin_path, const char *stdout_path, const char *stderr_path,
2984    bool no_stdio, nub_launch_flavor_t launch_flavor, int disable_aslr,
2985    const char *event_data, DNBError &launch_err) {
2986  // Clear out and clean up from any current state
2987  Clear();
2988
2989  DNBLogThreadedIf(LOG_PROCESS,
2990                   "%s( path = '%s', argv = %p, envp = %p, "
2991                   "launch_flavor = %u, disable_aslr = %d )",
2992                   __FUNCTION__, path, reinterpret_cast<const void *>(argv),
2993                   reinterpret_cast<const void *>(envp), launch_flavor,
2994                   disable_aslr);
2995
2996  // Fork a child process for debugging
2997  SetState(eStateLaunching);
2998
2999  switch (launch_flavor) {
3000  case eLaunchFlavorForkExec:
3001    m_pid = MachProcess::ForkChildForPTraceDebugging(path, argv, envp, this,
3002                                                     launch_err);
3003    break;
3004#ifdef WITH_FBS
3005  case eLaunchFlavorFBS: {
3006    const char *app_ext = strstr(path, ".app");
3007    if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) {
3008      std::string app_bundle_path(path, app_ext + strlen(".app"));
3009      m_flags |= (eMachProcessFlagsUsingFBS | eMachProcessFlagsBoardCalculated);
3010      if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp,
3011                                     no_stdio, disable_aslr, event_data,
3012                                     launch_err) != 0)
3013        return m_pid; // A successful SBLaunchForDebug() returns and assigns a
3014                      // non-zero m_pid.
3015      else
3016        break; // We tried a FBS launch, but didn't succeed lets get out
3017    }
3018  } break;
3019#endif
3020#ifdef WITH_BKS
3021  case eLaunchFlavorBKS: {
3022    const char *app_ext = strstr(path, ".app");
3023    if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) {
3024      std::string app_bundle_path(path, app_ext + strlen(".app"));
3025      m_flags |= (eMachProcessFlagsUsingBKS | eMachProcessFlagsBoardCalculated);
3026      if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp,
3027                                     no_stdio, disable_aslr, event_data,
3028                                     launch_err) != 0)
3029        return m_pid; // A successful SBLaunchForDebug() returns and assigns a
3030                      // non-zero m_pid.
3031      else
3032        break; // We tried a BKS launch, but didn't succeed lets get out
3033    }
3034  } break;
3035#endif
3036#ifdef WITH_SPRINGBOARD
3037
3038  case eLaunchFlavorSpringBoard: {
3039    //  .../whatever.app/whatever ?
3040    //  Or .../com.apple.whatever.app/whatever -- be careful of ".app" in
3041    //  "com.apple.whatever" here
3042    const char *app_ext = strstr(path, ".app/");
3043    if (app_ext == NULL) {
3044      // .../whatever.app ?
3045      int len = strlen(path);
3046      if (len > 5) {
3047        if (strcmp(path + len - 4, ".app") == 0) {
3048          app_ext = path + len - 4;
3049        }
3050      }
3051    }
3052    if (app_ext) {
3053      std::string app_bundle_path(path, app_ext + strlen(".app"));
3054      if (SBLaunchForDebug(app_bundle_path.c_str(), argv, envp, no_stdio,
3055                           disable_aslr, launch_err) != 0)
3056        return m_pid; // A successful SBLaunchForDebug() returns and assigns a
3057                      // non-zero m_pid.
3058      else
3059        break; // We tried a springboard launch, but didn't succeed lets get out
3060    }
3061  } break;
3062
3063#endif
3064
3065  case eLaunchFlavorPosixSpawn:
3066    m_pid = MachProcess::PosixSpawnChildForPTraceDebugging(
3067        path, DNBArchProtocol::GetArchitecture(), argv, envp, working_directory,
3068        stdin_path, stdout_path, stderr_path, no_stdio, this, disable_aslr,
3069        launch_err);
3070    break;
3071
3072  default:
3073    // Invalid  launch
3074    launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
3075    return INVALID_NUB_PROCESS;
3076  }
3077
3078  if (m_pid == INVALID_NUB_PROCESS) {
3079    // If we don't have a valid process ID and no one has set the error,
3080    // then return a generic error
3081    if (launch_err.Success())
3082      launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
3083  } else {
3084    m_path = path;
3085    size_t i;
3086    char const *arg;
3087    for (i = 0; (arg = argv[i]) != NULL; i++)
3088      m_args.push_back(arg);
3089
3090    m_task.StartExceptionThread(launch_err);
3091    if (launch_err.Fail()) {
3092      if (launch_err.AsString() == NULL)
3093        launch_err.SetErrorString("unable to start the exception thread");
3094      DNBLog("Could not get inferior's Mach exception port, sending ptrace "
3095             "PT_KILL and exiting.");
3096      ::ptrace(PT_KILL, m_pid, 0, 0);
3097      m_pid = INVALID_NUB_PROCESS;
3098      return INVALID_NUB_PROCESS;
3099    }
3100
3101    StartSTDIOThread();
3102
3103    if (launch_flavor == eLaunchFlavorPosixSpawn) {
3104
3105      SetState(eStateAttaching);
3106      errno = 0;
3107      int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
3108      if (err == 0) {
3109        m_flags |= eMachProcessFlagsAttached;
3110        DNBLogThreadedIf(LOG_PROCESS, "successfully spawned pid %d", m_pid);
3111        launch_err.Clear();
3112      } else {
3113        SetState(eStateExited);
3114        DNBError ptrace_err(errno, DNBError::POSIX);
3115        DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to spawned pid "
3116                                      "%d (err = %i, errno = %i (%s))",
3117                         m_pid, err, ptrace_err.Status(),
3118                         ptrace_err.AsString());
3119        launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
3120      }
3121    } else {
3122      launch_err.Clear();
3123    }
3124  }
3125  return m_pid;
3126}
3127
3128pid_t MachProcess::PosixSpawnChildForPTraceDebugging(
3129    const char *path, cpu_type_t cpu_type, char const *argv[],
3130    char const *envp[], const char *working_directory, const char *stdin_path,
3131    const char *stdout_path, const char *stderr_path, bool no_stdio,
3132    MachProcess *process, int disable_aslr, DNBError &err) {
3133  posix_spawnattr_t attr;
3134  short flags;
3135  DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv=%p, envp=%p, "
3136                                "working_dir=%s, stdin=%s, stdout=%s "
3137                                "stderr=%s, no-stdio=%i)",
3138                   __FUNCTION__, path, reinterpret_cast<const void *>(argv),
3139                   reinterpret_cast<const void *>(envp), working_directory,
3140                   stdin_path, stdout_path, stderr_path, no_stdio);
3141
3142  err.SetError(::posix_spawnattr_init(&attr), DNBError::POSIX);
3143  if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3144    err.LogThreaded("::posix_spawnattr_init ( &attr )");
3145  if (err.Fail())
3146    return INVALID_NUB_PROCESS;
3147
3148  flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETSIGDEF |
3149          POSIX_SPAWN_SETSIGMASK;
3150  if (disable_aslr)
3151    flags |= _POSIX_SPAWN_DISABLE_ASLR;
3152
3153  sigset_t no_signals;
3154  sigset_t all_signals;
3155  sigemptyset(&no_signals);
3156  sigfillset(&all_signals);
3157  ::posix_spawnattr_setsigmask(&attr, &no_signals);
3158  ::posix_spawnattr_setsigdefault(&attr, &all_signals);
3159
3160  err.SetError(::posix_spawnattr_setflags(&attr, flags), DNBError::POSIX);
3161  if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3162    err.LogThreaded(
3163        "::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED%s )",
3164        flags & _POSIX_SPAWN_DISABLE_ASLR ? " | _POSIX_SPAWN_DISABLE_ASLR"
3165                                          : "");
3166  if (err.Fail())
3167    return INVALID_NUB_PROCESS;
3168
3169// Don't do this on SnowLeopard, _sometimes_ the TASK_BASIC_INFO will fail
3170// and we will fail to continue with our process...
3171
3172// On SnowLeopard we should set "DYLD_NO_PIE" in the inferior environment....
3173
3174#if !defined(__arm__)
3175
3176  // We don't need to do this for ARM, and we really shouldn't now that we
3177  // have multiple CPU subtypes and no posix_spawnattr call that allows us
3178  // to set which CPU subtype to launch...
3179  if (cpu_type != 0) {
3180    size_t ocount = 0;
3181    err.SetError(::posix_spawnattr_setbinpref_np(&attr, 1, &cpu_type, &ocount),
3182                 DNBError::POSIX);
3183    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3184      err.LogThreaded("::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = "
3185                      "0x%8.8x, count => %llu )",
3186                      cpu_type, (uint64_t)ocount);
3187
3188    if (err.Fail() != 0 || ocount != 1)
3189      return INVALID_NUB_PROCESS;
3190  }
3191#endif
3192
3193  PseudoTerminal pty;
3194
3195  posix_spawn_file_actions_t file_actions;
3196  err.SetError(::posix_spawn_file_actions_init(&file_actions), DNBError::POSIX);
3197  int file_actions_valid = err.Success();
3198  if (!file_actions_valid || DNBLogCheckLogBit(LOG_PROCESS))
3199    err.LogThreaded("::posix_spawn_file_actions_init ( &file_actions )");
3200  int pty_error = -1;
3201  pid_t pid = INVALID_NUB_PROCESS;
3202  if (file_actions_valid) {
3203    if (stdin_path == NULL && stdout_path == NULL && stderr_path == NULL &&
3204        !no_stdio) {
3205      pty_error = pty.OpenFirstAvailableMaster(O_RDWR | O_NOCTTY);
3206      if (pty_error == PseudoTerminal::success) {
3207        stdin_path = stdout_path = stderr_path = pty.SlaveName();
3208      }
3209    }
3210
3211    // if no_stdio or std paths not supplied, then route to "/dev/null".
3212    if (no_stdio || stdin_path == NULL || stdin_path[0] == '\0')
3213      stdin_path = "/dev/null";
3214    if (no_stdio || stdout_path == NULL || stdout_path[0] == '\0')
3215      stdout_path = "/dev/null";
3216    if (no_stdio || stderr_path == NULL || stderr_path[0] == '\0')
3217      stderr_path = "/dev/null";
3218
3219    err.SetError(::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO,
3220                                                    stdin_path,
3221                                                    O_RDONLY | O_NOCTTY, 0),
3222                 DNBError::POSIX);
3223    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3224      err.LogThreaded("::posix_spawn_file_actions_addopen (&file_actions, "
3225                      "filedes=STDIN_FILENO, path='%s')",
3226                      stdin_path);
3227
3228    err.SetError(::posix_spawn_file_actions_addopen(
3229                     &file_actions, STDOUT_FILENO, stdout_path,
3230                     O_WRONLY | O_NOCTTY | O_CREAT, 0640),
3231                 DNBError::POSIX);
3232    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3233      err.LogThreaded("::posix_spawn_file_actions_addopen (&file_actions, "
3234                      "filedes=STDOUT_FILENO, path='%s')",
3235                      stdout_path);
3236
3237    err.SetError(::posix_spawn_file_actions_addopen(
3238                     &file_actions, STDERR_FILENO, stderr_path,
3239                     O_WRONLY | O_NOCTTY | O_CREAT, 0640),
3240                 DNBError::POSIX);
3241    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3242      err.LogThreaded("::posix_spawn_file_actions_addopen (&file_actions, "
3243                      "filedes=STDERR_FILENO, path='%s')",
3244                      stderr_path);
3245
3246    // TODO: Verify if we can set the working directory back immediately
3247    // after the posix_spawnp call without creating a race condition???
3248    if (working_directory)
3249      ::chdir(working_directory);
3250
3251    err.SetError(::posix_spawnp(&pid, path, &file_actions, &attr,
3252                                const_cast<char *const *>(argv),
3253                                const_cast<char *const *>(envp)),
3254                 DNBError::POSIX);
3255    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3256      err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = "
3257                      "%p, attr = %p, argv = %p, envp = %p )",
3258                      pid, path, &file_actions, &attr, argv, envp);
3259  } else {
3260    // TODO: Verify if we can set the working directory back immediately
3261    // after the posix_spawnp call without creating a race condition???
3262    if (working_directory)
3263      ::chdir(working_directory);
3264
3265    err.SetError(::posix_spawnp(&pid, path, NULL, &attr,
3266                                const_cast<char *const *>(argv),
3267                                const_cast<char *const *>(envp)),
3268                 DNBError::POSIX);
3269    if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3270      err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = "
3271                      "%p, attr = %p, argv = %p, envp = %p )",
3272                      pid, path, NULL, &attr, argv, envp);
3273  }
3274
3275  // We have seen some cases where posix_spawnp was returning a valid
3276  // looking pid even when an error was returned, so clear it out
3277  if (err.Fail())
3278    pid = INVALID_NUB_PROCESS;
3279
3280  if (pty_error == 0) {
3281    if (process != NULL) {
3282      int master_fd = pty.ReleaseMasterFD();
3283      process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
3284    }
3285  }
3286  ::posix_spawnattr_destroy(&attr);
3287
3288  if (pid != INVALID_NUB_PROCESS) {
3289    cpu_type_t pid_cpu_type = MachProcess::GetCPUTypeForLocalProcess(pid);
3290    DNBLogThreadedIf(LOG_PROCESS,
3291                     "MachProcess::%s ( ) pid=%i, cpu_type=0x%8.8x",
3292                     __FUNCTION__, pid, pid_cpu_type);
3293    if (pid_cpu_type)
3294      DNBArchProtocol::SetArchitecture(pid_cpu_type);
3295  }
3296
3297  if (file_actions_valid) {
3298    DNBError err2;
3299    err2.SetError(::posix_spawn_file_actions_destroy(&file_actions),
3300                  DNBError::POSIX);
3301    if (err2.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
3302      err2.LogThreaded("::posix_spawn_file_actions_destroy ( &file_actions )");
3303  }
3304
3305  return pid;
3306}
3307
3308uint32_t MachProcess::GetCPUTypeForLocalProcess(pid_t pid) {
3309  int mib[CTL_MAXNAME] = {
3310      0,
3311  };
3312  size_t len = CTL_MAXNAME;
3313  if (::sysctlnametomib("sysctl.proc_cputype", mib, &len))
3314    return 0;
3315
3316  mib[len] = pid;
3317  len++;
3318
3319  cpu_type_t cpu;
3320  size_t cpu_len = sizeof(cpu);
3321  if (::sysctl(mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0))
3322    cpu = 0;
3323  return cpu;
3324}
3325
3326pid_t MachProcess::ForkChildForPTraceDebugging(const char *path,
3327                                               char const *argv[],
3328                                               char const *envp[],
3329                                               MachProcess *process,
3330                                               DNBError &launch_err) {
3331  PseudoTerminal::Status pty_error = PseudoTerminal::success;
3332
3333  // Use a fork that ties the child process's stdin/out/err to a pseudo
3334  // terminal so we can read it in our MachProcess::STDIOThread
3335  // as unbuffered io.
3336  PseudoTerminal pty;
3337  pid_t pid = pty.Fork(pty_error);
3338
3339  if (pid < 0) {
3340    //--------------------------------------------------------------
3341    // Status during fork.
3342    //--------------------------------------------------------------
3343    return pid;
3344  } else if (pid == 0) {
3345    //--------------------------------------------------------------
3346    // Child process
3347    //--------------------------------------------------------------
3348    ::ptrace(PT_TRACE_ME, 0, 0, 0); // Debug this process
3349    ::ptrace(PT_SIGEXC, 0, 0, 0);   // Get BSD signals as mach exceptions
3350
3351    // If our parent is setgid, lets make sure we don't inherit those
3352    // extra powers due to nepotism.
3353    if (::setgid(getgid()) == 0) {
3354
3355      // Let the child have its own process group. We need to execute
3356      // this call in both the child and parent to avoid a race condition
3357      // between the two processes.
3358      ::setpgid(0, 0); // Set the child process group to match its pid
3359
3360      // Sleep a bit to before the exec call
3361      ::sleep(1);
3362
3363      // Turn this process into
3364      ::execv(path, const_cast<char *const *>(argv));
3365    }
3366    // Exit with error code. Child process should have taken
3367    // over in above exec call and if the exec fails it will
3368    // exit the child process below.
3369    ::exit(127);
3370  } else {
3371    //--------------------------------------------------------------
3372    // Parent process
3373    //--------------------------------------------------------------
3374    // Let the child have its own process group. We need to execute
3375    // this call in both the child and parent to avoid a race condition
3376    // between the two processes.
3377    ::setpgid(pid, pid); // Set the child process group to match its pid
3378
3379    if (process != NULL) {
3380      // Release our master pty file descriptor so the pty class doesn't
3381      // close it and so we can continue to use it in our STDIO thread
3382      int master_fd = pty.ReleaseMasterFD();
3383      process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
3384    }
3385  }
3386  return pid;
3387}
3388
3389#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) || defined(WITH_FBS)
3390// This returns a CFRetained pointer to the Bundle ID for app_bundle_path,
3391// or NULL if there was some problem getting the bundle id.
3392static CFStringRef CopyBundleIDForPath(const char *app_bundle_path,
3393                                       DNBError &err_str) {
3394  CFBundle bundle(app_bundle_path);
3395  CFStringRef bundleIDCFStr = bundle.GetIdentifier();
3396  std::string bundleID;
3397  if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL) {
3398    struct stat app_bundle_stat;
3399    char err_msg[PATH_MAX];
3400
3401    if (::stat(app_bundle_path, &app_bundle_stat) < 0) {
3402      err_str.SetError(errno, DNBError::POSIX);
3403      snprintf(err_msg, sizeof(err_msg), "%s: \"%s\"", err_str.AsString(),
3404               app_bundle_path);
3405      err_str.SetErrorString(err_msg);
3406      DNBLogThreadedIf(LOG_PROCESS, "%s() error: %s", __FUNCTION__, err_msg);
3407    } else {
3408      err_str.SetError(-1, DNBError::Generic);
3409      snprintf(err_msg, sizeof(err_msg),
3410               "failed to extract CFBundleIdentifier from %s", app_bundle_path);
3411      err_str.SetErrorString(err_msg);
3412      DNBLogThreadedIf(
3413          LOG_PROCESS,
3414          "%s() error: failed to extract CFBundleIdentifier from '%s'",
3415          __FUNCTION__, app_bundle_path);
3416    }
3417    return NULL;
3418  }
3419
3420  DNBLogThreadedIf(LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s",
3421                   __FUNCTION__, bundleID.c_str());
3422  CFRetain(bundleIDCFStr);
3423
3424  return bundleIDCFStr;
3425}
3426#endif // #if defined (WITH_SPRINGBOARD) || defined (WITH_BKS) || defined
3427       // (WITH_FBS)
3428#ifdef WITH_SPRINGBOARD
3429
3430pid_t MachProcess::SBLaunchForDebug(const char *path, char const *argv[],
3431                                    char const *envp[], bool no_stdio,
3432                                    bool disable_aslr, DNBError &launch_err) {
3433  // Clear out and clean up from any current state
3434  Clear();
3435
3436  DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
3437
3438  // Fork a child process for debugging
3439  SetState(eStateLaunching);
3440  m_pid = MachProcess::SBForkChildForPTraceDebugging(path, argv, envp, no_stdio,
3441                                                     this, launch_err);
3442  if (m_pid != 0) {
3443    m_path = path;
3444    size_t i;
3445    char const *arg;
3446    for (i = 0; (arg = argv[i]) != NULL; i++)
3447      m_args.push_back(arg);
3448    m_task.StartExceptionThread(launch_err);
3449
3450    if (launch_err.Fail()) {
3451      if (launch_err.AsString() == NULL)
3452        launch_err.SetErrorString("unable to start the exception thread");
3453      DNBLog("Could not get inferior's Mach exception port, sending ptrace "
3454             "PT_KILL and exiting.");
3455      ::ptrace(PT_KILL, m_pid, 0, 0);
3456      m_pid = INVALID_NUB_PROCESS;
3457      return INVALID_NUB_PROCESS;
3458    }
3459
3460    StartSTDIOThread();
3461    SetState(eStateAttaching);
3462    int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
3463    if (err == 0) {
3464      m_flags |= eMachProcessFlagsAttached;
3465      DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid);
3466    } else {
3467      SetState(eStateExited);
3468      DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
3469    }
3470  }
3471  return m_pid;
3472}
3473
3474#include <servers/bootstrap.h>
3475
3476pid_t MachProcess::SBForkChildForPTraceDebugging(
3477    const char *app_bundle_path, char const *argv[], char const *envp[],
3478    bool no_stdio, MachProcess *process, DNBError &launch_err) {
3479  DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__,
3480                   app_bundle_path, process);
3481  CFAllocatorRef alloc = kCFAllocatorDefault;
3482
3483  if (argv[0] == NULL)
3484    return INVALID_NUB_PROCESS;
3485
3486  size_t argc = 0;
3487  // Count the number of arguments
3488  while (argv[argc] != NULL)
3489    argc++;
3490
3491  // Enumerate the arguments
3492  size_t first_launch_arg_idx = 1;
3493  CFReleaser<CFMutableArrayRef> launch_argv;
3494
3495  if (argv[first_launch_arg_idx]) {
3496    size_t launch_argc = argc > 0 ? argc - 1 : 0;
3497    launch_argv.reset(
3498        ::CFArrayCreateMutable(alloc, launch_argc, &kCFTypeArrayCallBacks));
3499    size_t i;
3500    char const *arg;
3501    CFString launch_arg;
3502    for (i = first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL);
3503         i++) {
3504      launch_arg.reset(
3505          ::CFStringCreateWithCString(alloc, arg, kCFStringEncodingUTF8));
3506      if (launch_arg.get() != NULL)
3507        CFArrayAppendValue(launch_argv.get(), launch_arg.get());
3508      else
3509        break;
3510    }
3511  }
3512
3513  // Next fill in the arguments dictionary.  Note, the envp array is of the form
3514  // Variable=value but SpringBoard wants a CF dictionary.  So we have to
3515  // convert
3516  // this here.
3517
3518  CFReleaser<CFMutableDictionaryRef> launch_envp;
3519
3520  if (envp[0]) {
3521    launch_envp.reset(
3522        ::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks,
3523                                    &kCFTypeDictionaryValueCallBacks));
3524    const char *value;
3525    int name_len;
3526    CFString name_string, value_string;
3527
3528    for (int i = 0; envp[i] != NULL; i++) {
3529      value = strstr(envp[i], "=");
3530
3531      // If the name field is empty or there's no =, skip it.  Somebody's
3532      // messing with us.
3533      if (value == NULL || value == envp[i])
3534        continue;
3535
3536      name_len = value - envp[i];
3537
3538      // Now move value over the "="
3539      value++;
3540
3541      name_string.reset(
3542          ::CFStringCreateWithBytes(alloc, (const UInt8 *)envp[i], name_len,
3543                                    kCFStringEncodingUTF8, false));
3544      value_string.reset(
3545          ::CFStringCreateWithCString(alloc, value, kCFStringEncodingUTF8));
3546      CFDictionarySetValue(launch_envp.get(), name_string.get(),
3547                           value_string.get());
3548    }
3549  }
3550
3551  CFString stdio_path;
3552
3553  PseudoTerminal pty;
3554  if (!no_stdio) {
3555    PseudoTerminal::Status pty_err =
3556        pty.OpenFirstAvailableMaster(O_RDWR | O_NOCTTY);
3557    if (pty_err == PseudoTerminal::success) {
3558      const char *slave_name = pty.SlaveName();
3559      DNBLogThreadedIf(LOG_PROCESS,
3560                       "%s() successfully opened master pty, slave is %s",
3561                       __FUNCTION__, slave_name);
3562      if (slave_name && slave_name[0]) {
3563        ::chmod(slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
3564        stdio_path.SetFileSystemRepresentation(slave_name);
3565      }
3566    }
3567  }
3568
3569  if (stdio_path.get() == NULL) {
3570    stdio_path.SetFileSystemRepresentation("/dev/null");
3571  }
3572
3573  CFStringRef bundleIDCFStr = CopyBundleIDForPath(app_bundle_path, launch_err);
3574  if (bundleIDCFStr == NULL)
3575    return INVALID_NUB_PROCESS;
3576
3577  // This is just for logging:
3578  std::string bundleID;
3579  CFString::UTF8(bundleIDCFStr, bundleID);
3580
3581  DNBLogThreadedIf(LOG_PROCESS, "%s() serialized launch arg array",
3582                   __FUNCTION__);
3583
3584  // Find SpringBoard
3585  SBSApplicationLaunchError sbs_error = 0;
3586  sbs_error = SBSLaunchApplicationForDebugging(
3587      bundleIDCFStr,
3588      (CFURLRef)NULL, // openURL
3589      launch_argv.get(),
3590      launch_envp.get(), // CFDictionaryRef environment
3591      stdio_path.get(), stdio_path.get(),
3592      SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice);
3593
3594  launch_err.SetError(sbs_error, DNBError::SpringBoard);
3595
3596  if (sbs_error == SBSApplicationLaunchErrorSuccess) {
3597    static const useconds_t pid_poll_interval = 200000;
3598    static const useconds_t pid_poll_timeout = 30000000;
3599
3600    useconds_t pid_poll_total = 0;
3601
3602    nub_process_t pid = INVALID_NUB_PROCESS;
3603    Boolean pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
3604    // Poll until the process is running, as long as we are getting valid
3605    // responses and the timeout hasn't expired
3606    // A return PID of 0 means the process is not running, which may be because
3607    // it hasn't been (asynchronously) started
3608    // yet, or that it died very quickly (if you weren't using waitForDebugger).
3609    while (!pid_found && pid_poll_total < pid_poll_timeout) {
3610      usleep(pid_poll_interval);
3611      pid_poll_total += pid_poll_interval;
3612      DNBLogThreadedIf(LOG_PROCESS,
3613                       "%s() polling Springboard for pid for %s...",
3614                       __FUNCTION__, bundleID.c_str());
3615      pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
3616    }
3617
3618    CFRelease(bundleIDCFStr);
3619    if (pid_found) {
3620      if (process != NULL) {
3621        // Release our master pty file descriptor so the pty class doesn't
3622        // close it and so we can continue to use it in our STDIO thread
3623        int master_fd = pty.ReleaseMasterFD();
3624        process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
3625      }
3626      DNBLogThreadedIf(LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid);
3627    } else {
3628      DNBLogError("failed to lookup the process ID for CFBundleIdentifier %s.",
3629                  bundleID.c_str());
3630    }
3631    return pid;
3632  }
3633
3634  DNBLogError("unable to launch the application with CFBundleIdentifier '%s' "
3635              "sbs_error = %u",
3636              bundleID.c_str(), sbs_error);
3637  return INVALID_NUB_PROCESS;
3638}
3639
3640#endif // #ifdef WITH_SPRINGBOARD
3641
3642#if defined(WITH_BKS) || defined(WITH_FBS)
3643pid_t MachProcess::BoardServiceLaunchForDebug(
3644    const char *path, char const *argv[], char const *envp[], bool no_stdio,
3645    bool disable_aslr, const char *event_data, DNBError &launch_err) {
3646  DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
3647
3648  // Fork a child process for debugging
3649  SetState(eStateLaunching);
3650  m_pid = BoardServiceForkChildForPTraceDebugging(
3651      path, argv, envp, no_stdio, disable_aslr, event_data, launch_err);
3652  if (m_pid != 0) {
3653    m_path = path;
3654    size_t i;
3655    char const *arg;
3656    for (i = 0; (arg = argv[i]) != NULL; i++)
3657      m_args.push_back(arg);
3658    m_task.StartExceptionThread(launch_err);
3659
3660    if (launch_err.Fail()) {
3661      if (launch_err.AsString() == NULL)
3662        launch_err.SetErrorString("unable to start the exception thread");
3663      DNBLog("Could not get inferior's Mach exception port, sending ptrace "
3664             "PT_KILL and exiting.");
3665      ::ptrace(PT_KILL, m_pid, 0, 0);
3666      m_pid = INVALID_NUB_PROCESS;
3667      return INVALID_NUB_PROCESS;
3668    }
3669
3670    StartSTDIOThread();
3671    SetState(eStateAttaching);
3672    int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
3673    if (err == 0) {
3674      m_flags |= eMachProcessFlagsAttached;
3675      DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid);
3676    } else {
3677      SetState(eStateExited);
3678      DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
3679    }
3680  }
3681  return m_pid;
3682}
3683
3684pid_t MachProcess::BoardServiceForkChildForPTraceDebugging(
3685    const char *app_bundle_path, char const *argv[], char const *envp[],
3686    bool no_stdio, bool disable_aslr, const char *event_data,
3687    DNBError &launch_err) {
3688  if (argv[0] == NULL)
3689    return INVALID_NUB_PROCESS;
3690
3691  DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__,
3692                   app_bundle_path, this);
3693
3694  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3695
3696  size_t argc = 0;
3697  // Count the number of arguments
3698  while (argv[argc] != NULL)
3699    argc++;
3700
3701  // Enumerate the arguments
3702  size_t first_launch_arg_idx = 1;
3703
3704  NSMutableArray *launch_argv = nil;
3705
3706  if (argv[first_launch_arg_idx]) {
3707    size_t launch_argc = argc > 0 ? argc - 1 : 0;
3708    launch_argv = [NSMutableArray arrayWithCapacity:launch_argc];
3709    size_t i;
3710    char const *arg;
3711    NSString *launch_arg;
3712    for (i = first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL);
3713         i++) {
3714      launch_arg = [NSString stringWithUTF8String:arg];
3715      // FIXME: Should we silently eat an argument that we can't convert into a
3716      // UTF8 string?
3717      if (launch_arg != nil)
3718        [launch_argv addObject:launch_arg];
3719      else
3720        break;
3721    }
3722  }
3723
3724  NSMutableDictionary *launch_envp = nil;
3725  if (envp[0]) {
3726    launch_envp = [[NSMutableDictionary alloc] init];
3727    const char *value;
3728    int name_len;
3729    NSString *name_string, *value_string;
3730
3731    for (int i = 0; envp[i] != NULL; i++) {
3732      value = strstr(envp[i], "=");
3733
3734      // If the name field is empty or there's no =, skip it.  Somebody's
3735      // messing with us.
3736      if (value == NULL || value == envp[i])
3737        continue;
3738
3739      name_len = value - envp[i];
3740
3741      // Now move value over the "="
3742      value++;
3743      name_string = [[NSString alloc] initWithBytes:envp[i]
3744                                             length:name_len
3745                                           encoding:NSUTF8StringEncoding];
3746      value_string = [NSString stringWithUTF8String:value];
3747      [launch_envp setObject:value_string forKey:name_string];
3748    }
3749  }
3750
3751  NSString *stdio_path = nil;
3752  NSFileManager *file_manager = [NSFileManager defaultManager];
3753
3754  PseudoTerminal pty;
3755  if (!no_stdio) {
3756    PseudoTerminal::Status pty_err =
3757        pty.OpenFirstAvailableMaster(O_RDWR | O_NOCTTY);
3758    if (pty_err == PseudoTerminal::success) {
3759      const char *slave_name = pty.SlaveName();
3760      DNBLogThreadedIf(LOG_PROCESS,
3761                       "%s() successfully opened master pty, slave is %s",
3762                       __FUNCTION__, slave_name);
3763      if (slave_name && slave_name[0]) {
3764        ::chmod(slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
3765        stdio_path = [file_manager
3766            stringWithFileSystemRepresentation:slave_name
3767                                        length:strlen(slave_name)];
3768      }
3769    }
3770  }
3771
3772  if (stdio_path == nil) {
3773    const char *null_path = "/dev/null";
3774    stdio_path =
3775        [file_manager stringWithFileSystemRepresentation:null_path
3776                                                  length:strlen(null_path)];
3777  }
3778
3779  CFStringRef bundleIDCFStr = CopyBundleIDForPath(app_bundle_path, launch_err);
3780  if (bundleIDCFStr == NULL) {
3781    [pool drain];
3782    return INVALID_NUB_PROCESS;
3783  }
3784
3785  // Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use
3786  // toll-free bridging here:
3787  NSString *bundleIDNSStr = (NSString *)bundleIDCFStr;
3788
3789  // Okay, now let's assemble all these goodies into the BackBoardServices
3790  // options mega-dictionary:
3791
3792  NSMutableDictionary *options = nullptr;
3793  pid_t return_pid = INVALID_NUB_PROCESS;
3794  bool success = false;
3795
3796#ifdef WITH_BKS
3797  if (ProcessUsingBackBoard()) {
3798    options =
3799        BKSCreateOptionsDictionary(app_bundle_path, launch_argv, launch_envp,
3800                                   stdio_path, disable_aslr, event_data);
3801    success = BKSCallOpenApplicationFunction(bundleIDNSStr, options, launch_err,
3802                                             &return_pid);
3803  }
3804#endif
3805#ifdef WITH_FBS
3806  if (ProcessUsingFrontBoard()) {
3807    options =
3808        FBSCreateOptionsDictionary(app_bundle_path, launch_argv, launch_envp,
3809                                   stdio_path, disable_aslr, event_data);
3810    success = FBSCallOpenApplicationFunction(bundleIDNSStr, options, launch_err,
3811                                             &return_pid);
3812  }
3813#endif
3814
3815  if (success) {
3816    int master_fd = pty.ReleaseMasterFD();
3817    SetChildFileDescriptors(master_fd, master_fd, master_fd);
3818    CFString::UTF8(bundleIDCFStr, m_bundle_id);
3819  }
3820
3821  [pool drain];
3822
3823  return return_pid;
3824}
3825
3826bool MachProcess::BoardServiceSendEvent(const char *event_data,
3827                                        DNBError &send_err) {
3828  bool return_value = true;
3829
3830  if (event_data == NULL || *event_data == '\0') {
3831    DNBLogError("SendEvent called with NULL event data.");
3832    send_err.SetErrorString("SendEvent called with empty event data");
3833    return false;
3834  }
3835
3836  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3837
3838  if (strcmp(event_data, "BackgroundApplication") == 0) {
3839// This is an event I cooked up.  What you actually do is foreground the system
3840// app, so:
3841#ifdef WITH_BKS
3842    if (ProcessUsingBackBoard()) {
3843      return_value = BKSCallOpenApplicationFunction(nil, nil, send_err, NULL);
3844    }
3845#endif
3846#ifdef WITH_FBS
3847    if (ProcessUsingFrontBoard()) {
3848      return_value = FBSCallOpenApplicationFunction(nil, nil, send_err, NULL);
3849    }
3850#endif
3851    if (!return_value) {
3852      DNBLogError("Failed to background application, error: %s.",
3853                  send_err.AsString());
3854    }
3855  } else {
3856    if (m_bundle_id.empty()) {
3857      // See if we can figure out the bundle ID for this PID:
3858
3859      DNBLogError(
3860          "Tried to send event \"%s\" to a process that has no bundle ID.",
3861          event_data);
3862      return false;
3863    }
3864
3865    NSString *bundleIDNSStr =
3866        [NSString stringWithUTF8String:m_bundle_id.c_str()];
3867
3868    NSMutableDictionary *options = [NSMutableDictionary dictionary];
3869
3870#ifdef WITH_BKS
3871    if (ProcessUsingBackBoard()) {
3872      if (!BKSAddEventDataToOptions(options, event_data, send_err)) {
3873        [pool drain];
3874        return false;
3875      }
3876      return_value = BKSCallOpenApplicationFunction(bundleIDNSStr, options,
3877                                                    send_err, NULL);
3878      DNBLogThreadedIf(LOG_PROCESS,
3879                       "Called BKSCallOpenApplicationFunction to send event.");
3880    }
3881#endif
3882#ifdef WITH_FBS
3883    if (ProcessUsingFrontBoard()) {
3884      if (!FBSAddEventDataToOptions(options, event_data, send_err)) {
3885        [pool drain];
3886        return false;
3887      }
3888      return_value = FBSCallOpenApplicationFunction(bundleIDNSStr, options,
3889                                                    send_err, NULL);
3890      DNBLogThreadedIf(LOG_PROCESS,
3891                       "Called FBSCallOpenApplicationFunction to send event.");
3892    }
3893#endif
3894
3895    if (!return_value) {
3896      DNBLogError("Failed to send event: %s, error: %s.", event_data,
3897                  send_err.AsString());
3898    }
3899  }
3900
3901  [pool drain];
3902  return return_value;
3903}
3904#endif // defined(WITH_BKS) || defined (WITH_FBS)
3905
3906#ifdef WITH_BKS
3907void MachProcess::BKSCleanupAfterAttach(const void *attach_token,
3908                                        DNBError &err_str) {
3909  bool success;
3910
3911  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3912
3913  // Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use
3914  // toll-free bridging here:
3915  NSString *bundleIDNSStr = (NSString *)attach_token;
3916
3917  // Okay, now let's assemble all these goodies into the BackBoardServices
3918  // options mega-dictionary:
3919
3920  // First we have the debug sub-dictionary:
3921  NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
3922  [debug_options setObject:[NSNumber numberWithBool:YES]
3923                    forKey:BKSDebugOptionKeyCancelDebugOnNextLaunch];
3924
3925  // That will go in the overall dictionary:
3926
3927  NSMutableDictionary *options = [NSMutableDictionary dictionary];
3928  [options setObject:debug_options
3929              forKey:BKSOpenApplicationOptionKeyDebuggingOptions];
3930
3931  success =
3932      BKSCallOpenApplicationFunction(bundleIDNSStr, options, err_str, NULL);
3933
3934  if (!success) {
3935    DNBLogError("error trying to cancel debug on next launch for %s: %s",
3936                [bundleIDNSStr UTF8String], err_str.AsString());
3937  }
3938
3939  [pool drain];
3940}
3941#endif // WITH_BKS
3942
3943#ifdef WITH_FBS
3944void MachProcess::FBSCleanupAfterAttach(const void *attach_token,
3945                                        DNBError &err_str) {
3946  bool success;
3947
3948  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3949
3950  // Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use
3951  // toll-free bridging here:
3952  NSString *bundleIDNSStr = (NSString *)attach_token;
3953
3954  // Okay, now let's assemble all these goodies into the BackBoardServices
3955  // options mega-dictionary:
3956
3957  // First we have the debug sub-dictionary:
3958  NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
3959  [debug_options setObject:[NSNumber numberWithBool:YES]
3960                    forKey:FBSDebugOptionKeyCancelDebugOnNextLaunch];
3961
3962  // That will go in the overall dictionary:
3963
3964  NSMutableDictionary *options = [NSMutableDictionary dictionary];
3965  [options setObject:debug_options
3966              forKey:FBSOpenApplicationOptionKeyDebuggingOptions];
3967
3968  success =
3969      FBSCallOpenApplicationFunction(bundleIDNSStr, options, err_str, NULL);
3970
3971  if (!success) {
3972    DNBLogError("error trying to cancel debug on next launch for %s: %s",
3973                [bundleIDNSStr UTF8String], err_str.AsString());
3974  }
3975
3976  [pool drain];
3977}
3978#endif // WITH_FBS
3979
3980
3981void MachProcess::CalculateBoardStatus()
3982{
3983  if (m_flags & eMachProcessFlagsBoardCalculated)
3984    return;
3985  if (m_pid == 0)
3986    return;
3987
3988#if defined (WITH_FBS) || defined (WITH_BKS)
3989    bool found_app_flavor = false;
3990#endif
3991
3992#if defined(WITH_FBS)
3993    if (!found_app_flavor && IsFBSProcess(m_pid)) {
3994      found_app_flavor = true;
3995      m_flags |= eMachProcessFlagsUsingFBS;
3996    }
3997#endif
3998#if defined(WITH_BKS)
3999    if (!found_app_flavor && IsBKSProcess(m_pid)) {
4000      found_app_flavor = true;
4001      m_flags |= eMachProcessFlagsUsingBKS;
4002    }
4003#endif
4004
4005    m_flags |= eMachProcessFlagsBoardCalculated;
4006}
4007
4008bool MachProcess::ProcessUsingBackBoard() {
4009  CalculateBoardStatus();
4010  return (m_flags & eMachProcessFlagsUsingBKS) != 0;
4011}
4012
4013bool MachProcess::ProcessUsingFrontBoard() {
4014  CalculateBoardStatus();
4015  return (m_flags & eMachProcessFlagsUsingFBS) != 0;
4016}
4017