aboutsummaryrefslogtreecommitdiff
path: root/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp')
-rw-r--r--source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp653
1 files changed, 653 insertions, 0 deletions
diff --git a/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp b/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp
new file mode 100644
index 000000000000..63b1ae6da968
--- /dev/null
+++ b/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp
@@ -0,0 +1,653 @@
+//===-- DarwinProcessLauncher.cpp -------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+//
+// DarwinProcessLauncher.cpp
+// lldb
+//
+// Created by Todd Fiala on 8/30/16.
+//
+//
+
+#include "DarwinProcessLauncher.h"
+
+// C includes
+#include <spawn.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#ifndef _POSIX_SPAWN_DISABLE_ASLR
+#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
+#endif
+
+// LLDB includes
+#include "lldb/lldb-enumerations.h"
+
+#include "lldb/Core/Error.h"
+#include "lldb/Core/Log.h"
+#include "lldb/Core/StreamString.h"
+#include "lldb/Target/ProcessLaunchInfo.h"
+#include "lldb/Utility/PseudoTerminal.h"
+
+#include "CFBundle.h"
+#include "CFString.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_darwin;
+using namespace lldb_private::darwin_process_launcher;
+
+namespace {
+static LaunchFlavor g_launch_flavor = LaunchFlavor::Default;
+}
+
+namespace lldb_private {
+namespace darwin_process_launcher {
+
+static uint32_t GetCPUTypeForLocalProcess(::pid_t pid) {
+ int mib[CTL_MAXNAME] = {
+ 0,
+ };
+ size_t len = CTL_MAXNAME;
+ if (::sysctlnametomib("sysctl.proc_cputype", mib, &len))
+ return 0;
+
+ mib[len] = pid;
+ len++;
+
+ cpu_type_t cpu;
+ size_t cpu_len = sizeof(cpu);
+ if (::sysctl(mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0))
+ cpu = 0;
+ return cpu;
+}
+
+static bool ResolveExecutablePath(const char *path, char *resolved_path,
+ size_t resolved_path_size) {
+ if (path == NULL || path[0] == '\0')
+ return false;
+
+ char max_path[PATH_MAX];
+ std::string result;
+ CFString::GlobPath(path, result);
+
+ if (result.empty())
+ result = path;
+
+ struct stat path_stat;
+ if (::stat(path, &path_stat) == 0) {
+ if ((path_stat.st_mode & S_IFMT) == S_IFDIR) {
+ CFBundle bundle(path);
+ CFReleaser<CFURLRef> url(bundle.CopyExecutableURL());
+ if (url.get()) {
+ if (::CFURLGetFileSystemRepresentation(
+ url.get(), true, (UInt8 *)resolved_path, resolved_path_size))
+ return true;
+ }
+ }
+ }
+
+ if (realpath(path, max_path)) {
+ // Found the path relatively...
+ ::strncpy(resolved_path, max_path, resolved_path_size);
+ return strlen(resolved_path) + 1 < resolved_path_size;
+ } else {
+ // Not a relative path, check the PATH environment variable if the
+ const char *PATH = getenv("PATH");
+ if (PATH) {
+ const char *curr_path_start = PATH;
+ const char *curr_path_end;
+ while (curr_path_start && *curr_path_start) {
+ curr_path_end = strchr(curr_path_start, ':');
+ if (curr_path_end == NULL) {
+ result.assign(curr_path_start);
+ curr_path_start = NULL;
+ } else if (curr_path_end > curr_path_start) {
+ size_t len = curr_path_end - curr_path_start;
+ result.assign(curr_path_start, len);
+ curr_path_start += len + 1;
+ } else
+ break;
+
+ result += '/';
+ result += path;
+ struct stat s;
+ if (stat(result.c_str(), &s) == 0) {
+ ::strncpy(resolved_path, result.c_str(), resolved_path_size);
+ return result.size() + 1 < resolved_path_size;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// TODO check if we have a general purpose fork and exec. We may be
+// able to get rid of this entirely.
+static Error ForkChildForPTraceDebugging(const char *path, char const *argv[],
+ char const *envp[], ::pid_t *pid,
+ int *pty_fd) {
+ Error error;
+ if (!path || !argv || !envp || !pid || !pty_fd) {
+ error.SetErrorString("invalid arguments");
+ return error;
+ }
+
+ // Use a fork that ties the child process's stdin/out/err to a pseudo
+ // terminal so we can read it in our MachProcess::STDIOThread
+ // as unbuffered io.
+ lldb_utility::PseudoTerminal pty;
+ char fork_error[256];
+ memset(fork_error, 0, sizeof(fork_error));
+ *pid = static_cast<::pid_t>(pty.Fork(fork_error, sizeof(fork_error)));
+ if (*pid < 0) {
+ //--------------------------------------------------------------
+ // Error during fork.
+ //--------------------------------------------------------------
+ *pid = static_cast<::pid_t>(LLDB_INVALID_PROCESS_ID);
+ error.SetErrorStringWithFormat("%s(): fork failed: %s", __FUNCTION__,
+ fork_error);
+ return error;
+ } else if (pid == 0) {
+ //--------------------------------------------------------------
+ // Child process
+ //--------------------------------------------------------------
+
+ // Debug this process.
+ ::ptrace(PT_TRACE_ME, 0, 0, 0);
+
+ // Get BSD signals as mach exceptions.
+ ::ptrace(PT_SIGEXC, 0, 0, 0);
+
+ // If our parent is setgid, lets make sure we don't inherit those
+ // extra powers due to nepotism.
+ if (::setgid(getgid()) == 0) {
+ // Let the child have its own process group. We need to execute
+ // this call in both the child and parent to avoid a race
+ // condition between the two processes.
+
+ // Set the child process group to match its pid.
+ ::setpgid(0, 0);
+
+ // Sleep a bit to before the exec call.
+ ::sleep(1);
+
+ // Turn this process into the given executable.
+ ::execv(path, (char *const *)argv);
+ }
+ // Exit with error code. Child process should have taken
+ // over in above exec call and if the exec fails it will
+ // exit the child process below.
+ ::exit(127);
+ } else {
+ //--------------------------------------------------------------
+ // Parent process
+ //--------------------------------------------------------------
+ // Let the child have its own process group. We need to execute
+ // this call in both the child and parent to avoid a race condition
+ // between the two processes.
+
+ // Set the child process group to match its pid
+ ::setpgid(*pid, *pid);
+ if (pty_fd) {
+ // Release our master pty file descriptor so the pty class doesn't
+ // close it and so we can continue to use it in our STDIO thread
+ *pty_fd = pty.ReleaseMasterFileDescriptor();
+ }
+ }
+ return error;
+}
+
+static Error
+CreatePosixSpawnFileAction(const FileAction &action,
+ posix_spawn_file_actions_t *file_actions) {
+ Error error;
+
+ // Log it.
+ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
+ if (log) {
+ StreamString stream;
+ stream.PutCString("converting file action for posix_spawn(): ");
+ action.Dump(stream);
+ stream.Flush();
+ log->PutCString(stream.GetString().c_str());
+ }
+
+ // Validate args.
+ if (!file_actions) {
+ error.SetErrorString("mandatory file_actions arg is null");
+ return error;
+ }
+
+ // Build the posix file action.
+ switch (action.GetAction()) {
+ case FileAction::eFileActionOpen: {
+ const int error_code = ::posix_spawn_file_actions_addopen(
+ file_actions, action.GetFD(), action.GetPath(),
+ action.GetActionArgument(), 0);
+ if (error_code != 0) {
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+ break;
+ }
+
+ case FileAction::eFileActionClose: {
+ const int error_code =
+ ::posix_spawn_file_actions_addclose(file_actions, action.GetFD());
+ if (error_code != 0) {
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+ break;
+ }
+
+ case FileAction::eFileActionDuplicate: {
+ const int error_code = ::posix_spawn_file_actions_adddup2(
+ file_actions, action.GetFD(), action.GetActionArgument());
+ if (error_code != 0) {
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+ break;
+ }
+
+ case FileAction::eFileActionNone:
+ default:
+ if (log)
+ log->Printf("%s(): unsupported file action %u", __FUNCTION__,
+ action.GetAction());
+ break;
+ }
+
+ return error;
+}
+
+static Error PosixSpawnChildForPTraceDebugging(const char *path,
+ ProcessLaunchInfo &launch_info,
+ ::pid_t *pid,
+ cpu_type_t *actual_cpu_type) {
+ Error error;
+ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+ if (!pid) {
+ error.SetErrorStringWithFormat("%s(): pid arg cannot be null",
+ __FUNCTION__);
+ return error;
+ }
+
+ posix_spawnattr_t attr;
+ short flags;
+ if (log) {
+ StreamString stream;
+ stream.Printf("%s(path='%s',...)\n", __FUNCTION__, path);
+ launch_info.Dump(stream, nullptr);
+ stream.Flush();
+ log->PutCString(stream.GetString().c_str());
+ }
+
+ int error_code;
+ if ((error_code = ::posix_spawnattr_init(&attr)) != 0) {
+ if (log)
+ log->Printf("::posix_spawnattr_init(&attr) failed");
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+
+ // Ensure we clean up the spawnattr structure however we exit this
+ // function.
+ std::unique_ptr<posix_spawnattr_t, int (*)(posix_spawnattr_t *)> spawnattr_up(
+ &attr, ::posix_spawnattr_destroy);
+
+ flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETSIGDEF |
+ POSIX_SPAWN_SETSIGMASK;
+ if (launch_info.GetFlags().Test(eLaunchFlagDisableASLR))
+ flags |= _POSIX_SPAWN_DISABLE_ASLR;
+
+ sigset_t no_signals;
+ sigset_t all_signals;
+ sigemptyset(&no_signals);
+ sigfillset(&all_signals);
+ ::posix_spawnattr_setsigmask(&attr, &no_signals);
+ ::posix_spawnattr_setsigdefault(&attr, &all_signals);
+
+ if ((error_code = ::posix_spawnattr_setflags(&attr, flags)) != 0) {
+ if (log)
+ log->Printf("::posix_spawnattr_setflags(&attr, "
+ "POSIX_SPAWN_START_SUSPENDED%s) failed: %s",
+ flags & _POSIX_SPAWN_DISABLE_ASLR
+ ? " | _POSIX_SPAWN_DISABLE_ASLR"
+ : "",
+ strerror(error_code));
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+
+#if !defined(__arm__)
+
+ // We don't need to do this for ARM, and we really shouldn't now that we
+ // have multiple CPU subtypes and no posix_spawnattr call that allows us
+ // to set which CPU subtype to launch...
+ cpu_type_t desired_cpu_type = launch_info.GetArchitecture().GetMachOCPUType();
+ if (desired_cpu_type != LLDB_INVALID_CPUTYPE) {
+ size_t ocount = 0;
+ error_code =
+ ::posix_spawnattr_setbinpref_np(&attr, 1, &desired_cpu_type, &ocount);
+ if (error_code != 0) {
+ if (log)
+ log->Printf("::posix_spawnattr_setbinpref_np(&attr, 1, "
+ "cpu_type = 0x%8.8x, count => %llu): %s",
+ desired_cpu_type, (uint64_t)ocount, strerror(error_code));
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+ if (ocount != 1) {
+ error.SetErrorStringWithFormat("posix_spawnattr_setbinpref_np "
+ "did not set the expected number "
+ "of cpu_type entries: expected 1 "
+ "but was %zu",
+ ocount);
+ return error;
+ }
+ }
+#endif
+
+ posix_spawn_file_actions_t file_actions;
+ if ((error_code = ::posix_spawn_file_actions_init(&file_actions)) != 0) {
+ if (log)
+ log->Printf("::posix_spawn_file_actions_init(&file_actions) "
+ "failed: %s",
+ strerror(error_code));
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+
+ // Ensure we clean up file actions however we exit this. When the
+ // file_actions_up below goes out of scope, we'll get our file action
+ // cleanup.
+ std::unique_ptr<posix_spawn_file_actions_t,
+ int (*)(posix_spawn_file_actions_t *)>
+ file_actions_up(&file_actions, ::posix_spawn_file_actions_destroy);
+
+ // We assume the caller has setup the file actions appropriately. We
+ // are not in the business of figuring out what we really need here.
+ // lldb-server will have already called FinalizeFileActions() as well
+ // to button these up properly.
+ const size_t num_actions = launch_info.GetNumFileActions();
+ for (size_t action_index = 0; action_index < num_actions; ++action_index) {
+ const FileAction *const action =
+ launch_info.GetFileActionAtIndex(action_index);
+ if (!action)
+ continue;
+
+ error = CreatePosixSpawnFileAction(*action, &file_actions);
+ if (!error.Success()) {
+ if (log)
+ log->Printf("%s(): error converting FileAction to posix_spawn "
+ "file action: %s",
+ __FUNCTION__, error.AsCString());
+ return error;
+ }
+ }
+
+ // TODO: Verify if we can set the working directory back immediately
+ // after the posix_spawnp call without creating a race condition???
+ const char *const working_directory =
+ launch_info.GetWorkingDirectory().GetCString();
+ if (working_directory && working_directory[0])
+ ::chdir(working_directory);
+
+ auto argv = launch_info.GetArguments().GetArgumentVector();
+ auto envp = launch_info.GetEnvironmentEntries().GetArgumentVector();
+ error_code = ::posix_spawnp(pid, path, &file_actions, &attr,
+ (char *const *)argv, (char *const *)envp);
+ if (error_code != 0) {
+ if (log)
+ log->Printf("::posix_spawnp(pid => %p, path = '%s', file_actions "
+ "= %p, attr = %p, argv = %p, envp = %p) failed: %s",
+ pid, path, &file_actions, &attr, argv, envp,
+ strerror(error_code));
+ error.SetError(error_code, eErrorTypePOSIX);
+ return error;
+ }
+
+ // Validate we got a pid.
+ if (pid == LLDB_INVALID_PROCESS_ID) {
+ error.SetErrorString("posix_spawn() did not indicate a failure but it "
+ "failed to return a pid, aborting.");
+ return error;
+ }
+
+ if (actual_cpu_type) {
+ *actual_cpu_type = GetCPUTypeForLocalProcess(*pid);
+ if (log)
+ log->Printf("%s(): cpu type for launched process pid=%i: "
+ "cpu_type=0x%8.8x",
+ __FUNCTION__, *pid, *actual_cpu_type);
+ }
+
+ return error;
+}
+
+Error LaunchInferior(ProcessLaunchInfo &launch_info, int *pty_master_fd,
+ LaunchFlavor *launch_flavor) {
+ Error error;
+ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+ if (!launch_flavor) {
+ error.SetErrorString("mandatory launch_flavor field was null");
+ return error;
+ }
+
+ if (log) {
+ StreamString stream;
+ stream.Printf("NativeProcessDarwin::%s(): launching with the "
+ "following launch info:",
+ __FUNCTION__);
+ launch_info.Dump(stream, nullptr);
+ stream.Flush();
+ log->PutCString(stream.GetString().c_str());
+ }
+
+ // Retrieve the binary name given to us.
+ char given_path[PATH_MAX];
+ given_path[0] = '\0';
+ launch_info.GetExecutableFile().GetPath(given_path, sizeof(given_path));
+
+ // Determine the manner in which we'll launch.
+ *launch_flavor = g_launch_flavor;
+ if (*launch_flavor == LaunchFlavor::Default) {
+ // Our default launch method is posix spawn
+ *launch_flavor = LaunchFlavor::PosixSpawn;
+#if defined WITH_FBS
+ // Check if we have an app bundle, if so launch using BackBoard Services.
+ if (strstr(given_path, ".app")) {
+ *launch_flavor = eLaunchFlavorFBS;
+ }
+#elif defined WITH_BKS
+ // Check if we have an app bundle, if so launch using BackBoard Services.
+ if (strstr(given_path, ".app")) {
+ *launch_flavor = eLaunchFlavorBKS;
+ }
+#elif defined WITH_SPRINGBOARD
+ // Check if we have an app bundle, if so launch using SpringBoard.
+ if (strstr(given_path, ".app")) {
+ *launch_flavor = eLaunchFlavorSpringBoard;
+ }
+#endif
+ }
+
+ // Attempt to resolve the binary name to an absolute path.
+ char resolved_path[PATH_MAX];
+ resolved_path[0] = '\0';
+
+ if (log)
+ log->Printf("%s(): attempting to resolve given binary path: \"%s\"",
+ __FUNCTION__, given_path);
+
+ // If we fail to resolve the path to our executable, then just use what we
+ // were given and hope for the best
+ if (!ResolveExecutablePath(given_path, resolved_path,
+ sizeof(resolved_path))) {
+ if (log)
+ log->Printf("%s(): failed to resolve binary path, using "
+ "what was given verbatim and hoping for the best",
+ __FUNCTION__);
+ ::strncpy(resolved_path, given_path, sizeof(resolved_path));
+ } else {
+ if (log)
+ log->Printf("%s(): resolved given binary path to: \"%s\"", __FUNCTION__,
+ resolved_path);
+ }
+
+ char launch_err_str[PATH_MAX];
+ launch_err_str[0] = '\0';
+
+ // TODO figure out how to handle QSetProcessEvent
+ // const char *process_event = ctx.GetProcessEvent();
+
+ // Ensure the binary is there.
+ struct stat path_stat;
+ if (::stat(resolved_path, &path_stat) == -1) {
+ error.SetErrorToErrno();
+ return error;
+ }
+
+ // Fork a child process for debugging
+ // state_callback(eStateLaunching);
+
+ const auto argv = launch_info.GetArguments().GetConstArgumentVector();
+ const auto envp =
+ launch_info.GetEnvironmentEntries().GetConstArgumentVector();
+
+ switch (*launch_flavor) {
+ case LaunchFlavor::ForkExec: {
+ ::pid_t pid = LLDB_INVALID_PROCESS_ID;
+ error = ForkChildForPTraceDebugging(resolved_path, argv, envp, &pid,
+ pty_master_fd);
+ if (error.Success()) {
+ launch_info.SetProcessID(static_cast<lldb::pid_t>(pid));
+ } else {
+ // Reset any variables that might have been set during a failed
+ // launch attempt.
+ if (pty_master_fd)
+ *pty_master_fd = -1;
+
+ // We're done.
+ return error;
+ }
+ } break;
+
+#ifdef WITH_FBS
+ case LaunchFlavor::FBS: {
+ const char *app_ext = strstr(path, ".app");
+ if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) {
+ std::string app_bundle_path(path, app_ext + strlen(".app"));
+ m_flags |= eMachProcessFlagsUsingFBS;
+ if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp,
+ no_stdio, disable_aslr, event_data,
+ launch_err) != 0)
+ return m_pid; // A successful SBLaunchForDebug() returns and assigns a
+ // non-zero m_pid.
+ else
+ break; // We tried a FBS launch, but didn't succeed lets get out
+ }
+ } break;
+#endif
+
+#ifdef WITH_BKS
+ case LaunchFlavor::BKS: {
+ const char *app_ext = strstr(path, ".app");
+ if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) {
+ std::string app_bundle_path(path, app_ext + strlen(".app"));
+ m_flags |= eMachProcessFlagsUsingBKS;
+ if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp,
+ no_stdio, disable_aslr, event_data,
+ launch_err) != 0)
+ return m_pid; // A successful SBLaunchForDebug() returns and assigns a
+ // non-zero m_pid.
+ else
+ break; // We tried a BKS launch, but didn't succeed lets get out
+ }
+ } break;
+#endif
+
+#ifdef WITH_SPRINGBOARD
+ case LaunchFlavor::SpringBoard: {
+ // .../whatever.app/whatever ?
+ // Or .../com.apple.whatever.app/whatever -- be careful of ".app" in
+ // "com.apple.whatever" here
+ const char *app_ext = strstr(path, ".app/");
+ if (app_ext == NULL) {
+ // .../whatever.app ?
+ int len = strlen(path);
+ if (len > 5) {
+ if (strcmp(path + len - 4, ".app") == 0) {
+ app_ext = path + len - 4;
+ }
+ }
+ }
+ if (app_ext) {
+ std::string app_bundle_path(path, app_ext + strlen(".app"));
+ if (SBLaunchForDebug(app_bundle_path.c_str(), argv, envp, no_stdio,
+ disable_aslr, launch_err) != 0)
+ return m_pid; // A successful SBLaunchForDebug() returns and assigns a
+ // non-zero m_pid.
+ else
+ break; // We tried a springboard launch, but didn't succeed lets get out
+ }
+ } break;
+#endif
+
+ case LaunchFlavor::PosixSpawn: {
+ ::pid_t pid = LLDB_INVALID_PROCESS_ID;
+
+ // Retrieve paths for stdin/stdout/stderr.
+ cpu_type_t actual_cpu_type = 0;
+ error = PosixSpawnChildForPTraceDebugging(resolved_path, launch_info, &pid,
+ &actual_cpu_type);
+ if (error.Success()) {
+ launch_info.SetProcessID(static_cast<lldb::pid_t>(pid));
+ if (pty_master_fd)
+ *pty_master_fd = launch_info.GetPTY().ReleaseMasterFileDescriptor();
+ } else {
+ // Reset any variables that might have been set during a failed
+ // launch attempt.
+ if (pty_master_fd)
+ *pty_master_fd = -1;
+
+ // We're done.
+ return error;
+ }
+ break;
+ }
+
+ default:
+ // Invalid launch flavor.
+ error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): unknown "
+ "launch flavor %d",
+ __FUNCTION__, (int)*launch_flavor);
+ return error;
+ }
+
+ if (launch_info.GetProcessID() == LLDB_INVALID_PROCESS_ID) {
+ // If we don't have a valid process ID and no one has set the error,
+ // then return a generic error.
+ if (error.Success())
+ error.SetErrorStringWithFormat("%s(): failed to launch, no reason "
+ "specified",
+ __FUNCTION__);
+ }
+
+ // We're done with the launch side of the operation.
+ return error;
+}
+}
+} // namespaces