summaryrefslogtreecommitdiffstats
path: root/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp')
-rw-r--r--lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp737
1 files changed, 737 insertions, 0 deletions
diff --git a/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp b/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp
new file mode 100644
index 00000000000..91abe4f26e2
--- /dev/null
+++ b/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp
@@ -0,0 +1,737 @@
+//
+// 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
+
OpenPOWER on IntegriCloud