forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			1743 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1743 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- Host.mm -------------------------------------------------*- C++ -*-===//
 | |
| //
 | |
| //                     The LLVM Compiler Infrastructure
 | |
| //
 | |
| // This file is distributed under the University of Illinois Open Source
 | |
| // License. See LICENSE.TXT for details.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "lldb/Host/Host.h"
 | |
| 
 | |
| #include <AvailabilityMacros.h>
 | |
| 
 | |
| #if !defined(MAC_OS_X_VERSION_10_7) ||                                         \
 | |
|     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
 | |
| #define NO_XPC_SERVICES 1
 | |
| #endif
 | |
| 
 | |
| #if !defined(NO_XPC_SERVICES)
 | |
| #define __XPC_PRIVATE_H__
 | |
| #include <xpc/xpc.h>
 | |
| 
 | |
| #define LaunchUsingXPCRightName "com.apple.dt.Xcode.RootDebuggingXPCService"
 | |
| 
 | |
| // These XPC messaging keys are used for communication between Host.mm and the
 | |
| // XPC service.
 | |
| #define LauncherXPCServiceAuthKey "auth-key"
 | |
| #define LauncherXPCServiceArgPrefxKey "arg"
 | |
| #define LauncherXPCServiceEnvPrefxKey "env"
 | |
| #define LauncherXPCServiceCPUTypeKey "cpuType"
 | |
| #define LauncherXPCServicePosixspawnFlagsKey "posixspawnFlags"
 | |
| #define LauncherXPCServiceStdInPathKeyKey "stdInPath"
 | |
| #define LauncherXPCServiceStdOutPathKeyKey "stdOutPath"
 | |
| #define LauncherXPCServiceStdErrPathKeyKey "stdErrPath"
 | |
| #define LauncherXPCServiceChildPIDKey "childPID"
 | |
| #define LauncherXPCServiceErrorTypeKey "errorType"
 | |
| #define LauncherXPCServiceCodeTypeKey "errorCode"
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #include "llvm/Support/Host.h"
 | |
| 
 | |
| #include <asl.h>
 | |
| #include <crt_externs.h>
 | |
| #include <grp.h>
 | |
| #include <libproc.h>
 | |
| #include <pwd.h>
 | |
| #include <spawn.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <sys/proc.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/sysctl.h>
 | |
| #include <sys/types.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include "lldb/Core/ArchSpec.h"
 | |
| #include "lldb/Core/Communication.h"
 | |
| #include "lldb/Core/Module.h"
 | |
| #include "lldb/Core/ModuleSpec.h"
 | |
| #include "lldb/Host/ConnectionFileDescriptor.h"
 | |
| #include "lldb/Host/HostInfo.h"
 | |
| #include "lldb/Host/ThreadLauncher.h"
 | |
| #include "lldb/Target/Platform.h"
 | |
| #include "lldb/Target/Process.h"
 | |
| #include "lldb/Utility/CleanUp.h"
 | |
| #include "lldb/Utility/DataBufferHeap.h"
 | |
| #include "lldb/Utility/DataExtractor.h"
 | |
| #include "lldb/Utility/Endian.h"
 | |
| #include "lldb/Utility/FileSpec.h"
 | |
| #include "lldb/Utility/Log.h"
 | |
| #include "lldb/Utility/NameMatches.h"
 | |
| #include "lldb/Utility/StreamString.h"
 | |
| #include "lldb/Utility/StructuredData.h"
 | |
| 
 | |
| #include "llvm/Support/FileSystem.h"
 | |
| #include "llvm/Support/Errno.h"
 | |
| 
 | |
| #include "cfcpp/CFCBundle.h"
 | |
| #include "cfcpp/CFCMutableArray.h"
 | |
| #include "cfcpp/CFCMutableDictionary.h"
 | |
| #include "cfcpp/CFCReleaser.h"
 | |
| #include "cfcpp/CFCString.h"
 | |
| 
 | |
| #include <objc/objc-auto.h>
 | |
| 
 | |
| #include <CoreFoundation/CoreFoundation.h>
 | |
| #include <Foundation/Foundation.h>
 | |
| 
 | |
| #ifndef _POSIX_SPAWN_DISABLE_ASLR
 | |
| #define _POSIX_SPAWN_DISABLE_ASLR 0x0100
 | |
| #endif
 | |
| 
 | |
| extern "C" {
 | |
| int __pthread_chdir(const char *path);
 | |
| int __pthread_fchdir(int fildes);
 | |
| }
 | |
| 
 | |
| using namespace lldb;
 | |
| using namespace lldb_private;
 | |
| 
 | |
| bool Host::GetBundleDirectory(const FileSpec &file,
 | |
|                               FileSpec &bundle_directory) {
 | |
| #if defined(__APPLE__)
 | |
|   if (llvm::sys::fs::is_directory(file.GetPath())) {
 | |
|     char path[PATH_MAX];
 | |
|     if (file.GetPath(path, sizeof(path))) {
 | |
|       CFCBundle bundle(path);
 | |
|       if (bundle.GetPath(path, sizeof(path))) {
 | |
|         bundle_directory.SetFile(path, false);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   bundle_directory.Clear();
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool Host::ResolveExecutableInBundle(FileSpec &file) {
 | |
| #if defined(__APPLE__)
 | |
|   if (llvm::sys::fs::is_directory(file.GetPath())) {
 | |
|     char path[PATH_MAX];
 | |
|     if (file.GetPath(path, sizeof(path))) {
 | |
|       CFCBundle bundle(path);
 | |
|       CFCReleaser<CFURLRef> url(bundle.CopyExecutableURL());
 | |
|       if (url.get()) {
 | |
|         if (::CFURLGetFileSystemRepresentation(url.get(), YES, (UInt8 *)path,
 | |
|                                                sizeof(path))) {
 | |
|           file.SetFile(path, false);
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static void *AcceptPIDFromInferior(void *arg) {
 | |
|   const char *connect_url = (const char *)arg;
 | |
|   ConnectionFileDescriptor file_conn;
 | |
|   Status error;
 | |
|   if (file_conn.Connect(connect_url, &error) == eConnectionStatusSuccess) {
 | |
|     char pid_str[256];
 | |
|     ::memset(pid_str, 0, sizeof(pid_str));
 | |
|     ConnectionStatus status;
 | |
|     const size_t pid_str_len = file_conn.Read(
 | |
|         pid_str, sizeof(pid_str), std::chrono::seconds(0), status, NULL);
 | |
|     if (pid_str_len > 0) {
 | |
|       int pid = atoi(pid_str);
 | |
|       return (void *)(intptr_t)pid;
 | |
|     }
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| static bool WaitForProcessToSIGSTOP(const lldb::pid_t pid,
 | |
|                                     const int timeout_in_seconds) {
 | |
|   const int time_delta_usecs = 100000;
 | |
|   const int num_retries = timeout_in_seconds / time_delta_usecs;
 | |
|   for (int i = 0; i < num_retries; i++) {
 | |
|     struct proc_bsdinfo bsd_info;
 | |
|     int error = ::proc_pidinfo(pid, PROC_PIDTBSDINFO, (uint64_t)0, &bsd_info,
 | |
|                                PROC_PIDTBSDINFO_SIZE);
 | |
| 
 | |
|     switch (error) {
 | |
|     case EINVAL:
 | |
|     case ENOTSUP:
 | |
|     case ESRCH:
 | |
|     case EPERM:
 | |
|       return false;
 | |
| 
 | |
|     default:
 | |
|       break;
 | |
| 
 | |
|     case 0:
 | |
|       if (bsd_info.pbi_status == SSTOP)
 | |
|         return true;
 | |
|     }
 | |
|     ::usleep(time_delta_usecs);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| #if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__)
 | |
| 
 | |
| // static lldb::pid_t
 | |
| // LaunchInNewTerminalWithCommandFile
 | |
| //(
 | |
| //    const char **argv,
 | |
| //    const char **envp,
 | |
| //    const char *working_dir,
 | |
| //    const ArchSpec *arch_spec,
 | |
| //    bool stop_at_entry,
 | |
| //    bool disable_aslr
 | |
| //)
 | |
| //{
 | |
| //    if (!argv || !argv[0])
 | |
| //        return LLDB_INVALID_PROCESS_ID;
 | |
| //
 | |
| //    OSStatus error = 0;
 | |
| //
 | |
| //    FileSpec program (argv[0], false);
 | |
| //
 | |
| //
 | |
| //    std::string unix_socket_name;
 | |
| //
 | |
| //    char temp_file_path[PATH_MAX];
 | |
| //    const char *tmpdir = ::getenv ("TMPDIR");
 | |
| //    if (tmpdir == NULL)
 | |
| //        tmpdir = "/tmp/";
 | |
| //    ::snprintf (temp_file_path, sizeof(temp_file_path), "%s%s-XXXXXX", tmpdir,
 | |
| //    program.GetFilename().AsCString());
 | |
| //
 | |
| //    if (::mktemp (temp_file_path) == NULL)
 | |
| //        return LLDB_INVALID_PROCESS_ID;
 | |
| //
 | |
| //    unix_socket_name.assign (temp_file_path);
 | |
| //
 | |
| //    ::strlcat (temp_file_path, ".command", sizeof (temp_file_path));
 | |
| //
 | |
| //    StreamFile command_file;
 | |
| //    command_file.GetFile().Open (temp_file_path,
 | |
| //                                 File::eOpenOptionWrite |
 | |
| //                                 File::eOpenOptionCanCreate,
 | |
| //                                 lldb::eFilePermissionsDefault);
 | |
| //
 | |
| //    if (!command_file.GetFile().IsValid())
 | |
| //        return LLDB_INVALID_PROCESS_ID;
 | |
| //
 | |
| //    FileSpec darwin_debug_file_spec;
 | |
| //    if (!HostInfo::GetLLDBPath (ePathTypeSupportExecutableDir,
 | |
| //    darwin_debug_file_spec))
 | |
| //        return LLDB_INVALID_PROCESS_ID;
 | |
| //    darwin_debug_file_spec.GetFilename().SetCString("darwin-debug");
 | |
| //
 | |
| //    if (!darwin_debug_file_spec.Exists())
 | |
| //        return LLDB_INVALID_PROCESS_ID;
 | |
| //
 | |
| //    char launcher_path[PATH_MAX];
 | |
| //    darwin_debug_file_spec.GetPath(launcher_path, sizeof(launcher_path));
 | |
| //    command_file.Printf("\"%s\" ", launcher_path);
 | |
| //
 | |
| //    command_file.Printf("--unix-socket=%s ", unix_socket_name.c_str());
 | |
| //
 | |
| //    if (arch_spec && arch_spec->IsValid())
 | |
| //    {
 | |
| //        command_file.Printf("--arch=%s ", arch_spec->GetArchitectureName());
 | |
| //    }
 | |
| //
 | |
| //    if (disable_aslr)
 | |
| //    {
 | |
| //        command_file.PutCString("--disable-aslr ");
 | |
| //    }
 | |
| //
 | |
| //    command_file.PutCString("-- ");
 | |
| //
 | |
| //    if (argv)
 | |
| //    {
 | |
| //        for (size_t i=0; argv[i] != NULL; ++i)
 | |
| //        {
 | |
| //            command_file.Printf("\"%s\" ", argv[i]);
 | |
| //        }
 | |
| //    }
 | |
| //    command_file.PutCString("\necho Process exited with status $?\n");
 | |
| //    command_file.GetFile().Close();
 | |
| //    if (::chmod (temp_file_path, S_IRWXU | S_IRWXG) != 0)
 | |
| //        return LLDB_INVALID_PROCESS_ID;
 | |
| //
 | |
| //    CFCMutableDictionary cf_env_dict;
 | |
| //
 | |
| //    const bool can_create = true;
 | |
| //    if (envp)
 | |
| //    {
 | |
| //        for (size_t i=0; envp[i] != NULL; ++i)
 | |
| //        {
 | |
| //            const char *env_entry = envp[i];
 | |
| //            const char *equal_pos = strchr(env_entry, '=');
 | |
| //            if (equal_pos)
 | |
| //            {
 | |
| //                std::string env_key (env_entry, equal_pos);
 | |
| //                std::string env_val (equal_pos + 1);
 | |
| //                CFCString cf_env_key (env_key.c_str(), kCFStringEncodingUTF8);
 | |
| //                CFCString cf_env_val (env_val.c_str(), kCFStringEncodingUTF8);
 | |
| //                cf_env_dict.AddValue (cf_env_key.get(), cf_env_val.get(),
 | |
| //                can_create);
 | |
| //            }
 | |
| //        }
 | |
| //    }
 | |
| //
 | |
| //    LSApplicationParameters app_params;
 | |
| //    ::memset (&app_params, 0, sizeof (app_params));
 | |
| //    app_params.flags = kLSLaunchDontAddToRecents | kLSLaunchAsync;
 | |
| //    app_params.argv = NULL;
 | |
| //    app_params.environment = (CFDictionaryRef)cf_env_dict.get();
 | |
| //
 | |
| //    CFCReleaser<CFURLRef> command_file_url
 | |
| //    (::CFURLCreateFromFileSystemRepresentation (NULL,
 | |
| //                                                                                       (const UInt8 *)temp_file_path,
 | |
| //                                                                                       strlen(temp_file_path),
 | |
| //                                                                                       false));
 | |
| //
 | |
| //    CFCMutableArray urls;
 | |
| //
 | |
| //    // Terminal.app will open the ".command" file we have created
 | |
| //    // and run our process inside it which will wait at the entry point
 | |
| //    // for us to attach.
 | |
| //    urls.AppendValue(command_file_url.get());
 | |
| //
 | |
| //
 | |
| //    lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
 | |
| //
 | |
| //    Status lldb_error;
 | |
| //    // Sleep and wait a bit for debugserver to start to listen...
 | |
| //    char connect_url[128];
 | |
| //    ::snprintf (connect_url, sizeof(connect_url), "unix-accept://%s",
 | |
| //    unix_socket_name.c_str());
 | |
| //
 | |
| //    // Spawn a new thread to accept incoming connection on the connect_url
 | |
| //    // so we can grab the pid from the inferior
 | |
| //    lldb::thread_t accept_thread = Host::ThreadCreate
 | |
| //    (unix_socket_name.c_str(),
 | |
| //                                                       AcceptPIDFromInferior,
 | |
| //                                                       connect_url,
 | |
| //                                                       &lldb_error);
 | |
| //
 | |
| //    ProcessSerialNumber psn;
 | |
| //    error = LSOpenURLsWithRole(urls.get(), kLSRolesShell, NULL, &app_params,
 | |
| //    &psn, 1);
 | |
| //    if (error == noErr)
 | |
| //    {
 | |
| //        thread_result_t accept_thread_result = NULL;
 | |
| //        if (Host::ThreadJoin (accept_thread, &accept_thread_result,
 | |
| //        &lldb_error))
 | |
| //        {
 | |
| //            if (accept_thread_result)
 | |
| //            {
 | |
| //                pid = (intptr_t)accept_thread_result;
 | |
| //
 | |
| //                // Wait for process to be stopped the entry point by watching
 | |
| //                // for the process status to be set to SSTOP which indicates
 | |
| //                it it
 | |
| //                // SIGSTOP'ed at the entry point
 | |
| //                WaitForProcessToSIGSTOP (pid, 5);
 | |
| //            }
 | |
| //        }
 | |
| //    }
 | |
| //    else
 | |
| //    {
 | |
| //        Host::ThreadCancel (accept_thread, &lldb_error);
 | |
| //    }
 | |
| //
 | |
| //    return pid;
 | |
| //}
 | |
| 
 | |
| const char *applscript_in_new_tty = "tell application \"Terminal\"\n"
 | |
|                                     "   activate\n"
 | |
|                                     "	do script \"/bin/bash -c '%s';exit\"\n"
 | |
|                                     "end tell\n";
 | |
| 
 | |
| const char *applscript_in_existing_tty = "\
 | |
| set the_shell_script to \"/bin/bash -c '%s';exit\"\n\
 | |
| tell application \"Terminal\"\n\
 | |
| 	repeat with the_window in (get windows)\n\
 | |
| 		repeat with the_tab in tabs of the_window\n\
 | |
| 			set the_tty to tty in the_tab\n\
 | |
| 			if the_tty contains \"%s\" then\n\
 | |
| 				if the_tab is not busy then\n\
 | |
| 					set selected of the_tab to true\n\
 | |
| 					set frontmost of the_window to true\n\
 | |
| 					do script the_shell_script in the_tab\n\
 | |
| 					return\n\
 | |
| 				end if\n\
 | |
| 			end if\n\
 | |
| 		end repeat\n\
 | |
| 	end repeat\n\
 | |
| 	do script the_shell_script\n\
 | |
| end tell\n";
 | |
| 
 | |
| static Status
 | |
| LaunchInNewTerminalWithAppleScript(const char *exe_path,
 | |
|                                    ProcessLaunchInfo &launch_info) {
 | |
|   Status error;
 | |
|   char unix_socket_name[PATH_MAX] = "/tmp/XXXXXX";
 | |
|   if (::mktemp(unix_socket_name) == NULL) {
 | |
|     error.SetErrorString("failed to make temporary path for a unix socket");
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   StreamString command;
 | |
|   FileSpec darwin_debug_file_spec;
 | |
|   if (!HostInfo::GetLLDBPath(ePathTypeSupportExecutableDir,
 | |
|                              darwin_debug_file_spec)) {
 | |
|     error.SetErrorString("can't locate the 'darwin-debug' executable");
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   darwin_debug_file_spec.GetFilename().SetCString("darwin-debug");
 | |
| 
 | |
|   if (!darwin_debug_file_spec.Exists()) {
 | |
|     error.SetErrorStringWithFormat(
 | |
|         "the 'darwin-debug' executable doesn't exists at '%s'",
 | |
|         darwin_debug_file_spec.GetPath().c_str());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   char launcher_path[PATH_MAX];
 | |
|   darwin_debug_file_spec.GetPath(launcher_path, sizeof(launcher_path));
 | |
| 
 | |
|   const ArchSpec &arch_spec = launch_info.GetArchitecture();
 | |
|   // Only set the architecture if it is valid and if it isn't Haswell (x86_64h).
 | |
|   if (arch_spec.IsValid() &&
 | |
|       arch_spec.GetCore() != ArchSpec::eCore_x86_64_x86_64h)
 | |
|     command.Printf("arch -arch %s ", arch_spec.GetArchitectureName());
 | |
| 
 | |
|   command.Printf("'%s' --unix-socket=%s", launcher_path, unix_socket_name);
 | |
| 
 | |
|   if (arch_spec.IsValid())
 | |
|     command.Printf(" --arch=%s", arch_spec.GetArchitectureName());
 | |
| 
 | |
|   FileSpec working_dir{launch_info.GetWorkingDirectory()};
 | |
|   if (working_dir)
 | |
|     command.Printf(" --working-dir '%s'", working_dir.GetCString());
 | |
|   else {
 | |
|     char cwd[PATH_MAX];
 | |
|     if (getcwd(cwd, PATH_MAX))
 | |
|       command.Printf(" --working-dir '%s'", cwd);
 | |
|   }
 | |
| 
 | |
|   if (launch_info.GetFlags().Test(eLaunchFlagDisableASLR))
 | |
|     command.PutCString(" --disable-aslr");
 | |
| 
 | |
|   // We are launching on this host in a terminal. So compare the environment on
 | |
|   // the host
 | |
|   // to what is supplied in the launch_info. Any items that aren't in the host
 | |
|   // environment
 | |
|   // need to be sent to darwin-debug. If we send all environment entries, we
 | |
|   // might blow the
 | |
|   // max command line length, so we only send user modified entries.
 | |
|   const char **envp =
 | |
|       launch_info.GetEnvironmentEntries().GetConstArgumentVector();
 | |
| 
 | |
|   StringList host_env;
 | |
|   const size_t host_env_count = Host::GetEnvironment(host_env);
 | |
| 
 | |
|   if (envp && envp[0]) {
 | |
|     const char *env_entry;
 | |
|     for (size_t env_idx = 0; (env_entry = envp[env_idx]) != NULL; ++env_idx) {
 | |
|       bool add_entry = true;
 | |
|       for (size_t i = 0; i < host_env_count; ++i) {
 | |
|         const char *host_env_entry = host_env.GetStringAtIndex(i);
 | |
|         if (strcmp(env_entry, host_env_entry) == 0) {
 | |
|           add_entry = false;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (add_entry) {
 | |
|         command.Printf(" --env='%s'", env_entry);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   command.PutCString(" -- ");
 | |
| 
 | |
|   const char **argv = launch_info.GetArguments().GetConstArgumentVector();
 | |
|   if (argv) {
 | |
|     for (size_t i = 0; argv[i] != NULL; ++i) {
 | |
|       if (i == 0)
 | |
|         command.Printf(" '%s'", exe_path);
 | |
|       else
 | |
|         command.Printf(" '%s'", argv[i]);
 | |
|     }
 | |
|   } else {
 | |
|     command.Printf(" '%s'", exe_path);
 | |
|   }
 | |
|   command.PutCString(" ; echo Process exited with status $?");
 | |
|   if (launch_info.GetFlags().Test(lldb::eLaunchFlagCloseTTYOnExit))
 | |
|     command.PutCString(" ; exit");
 | |
| 
 | |
|   StreamString applescript_source;
 | |
| 
 | |
|   //    if (tty_name && tty_name[0])
 | |
|   //    {
 | |
|   //        applescript_source.Printf (applscript_in_existing_tty,
 | |
|   //                                   tty_command,
 | |
|   //                                   tty_name);
 | |
|   //    }
 | |
|   //    else
 | |
|   //    {
 | |
|   applescript_source.Printf(applscript_in_new_tty,
 | |
|                             command.GetString().str().c_str());
 | |
|   //    }
 | |
| 
 | |
|   // puts (script_source);
 | |
|   NSAppleScript *applescript = [[NSAppleScript alloc]
 | |
|       initWithSource:[NSString stringWithCString:applescript_source.GetString()
 | |
|                                                      .str()
 | |
|                                                      .c_str()
 | |
|                                         encoding:NSUTF8StringEncoding]];
 | |
| 
 | |
|   lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
 | |
| 
 | |
|   Status lldb_error;
 | |
|   // Sleep and wait a bit for debugserver to start to listen...
 | |
|   ConnectionFileDescriptor file_conn;
 | |
|   char connect_url[128];
 | |
|   ::snprintf(connect_url, sizeof(connect_url), "unix-accept://%s",
 | |
|              unix_socket_name);
 | |
| 
 | |
|   // Spawn a new thread to accept incoming connection on the connect_url
 | |
|   // so we can grab the pid from the inferior. We have to do this because we
 | |
|   // are sending an AppleScript that will launch a process in Terminal.app,
 | |
|   // in a shell and the shell will fork/exec a couple of times before we get
 | |
|   // to the process that we wanted to launch. So when our process actually
 | |
|   // gets launched, we will handshake with it and get the process ID for it.
 | |
|   HostThread accept_thread = ThreadLauncher::LaunchThread(
 | |
|       unix_socket_name, AcceptPIDFromInferior, connect_url, &lldb_error);
 | |
| 
 | |
|   [applescript executeAndReturnError:nil];
 | |
| 
 | |
|   thread_result_t accept_thread_result = NULL;
 | |
|   lldb_error = accept_thread.Join(&accept_thread_result);
 | |
|   if (lldb_error.Success() && accept_thread_result) {
 | |
|     pid = (intptr_t)accept_thread_result;
 | |
| 
 | |
|     // Wait for process to be stopped at the entry point by watching
 | |
|     // for the process status to be set to SSTOP which indicates it it
 | |
|     // SIGSTOP'ed at the entry point
 | |
|     WaitForProcessToSIGSTOP(pid, 5);
 | |
|   }
 | |
| 
 | |
|   llvm::sys::fs::remove(unix_socket_name);
 | |
|   [applescript release];
 | |
|   if (pid != LLDB_INVALID_PROCESS_ID)
 | |
|     launch_info.SetProcessID(pid);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| #endif // #if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__)
 | |
| 
 | |
| bool Host::OpenFileInExternalEditor(const FileSpec &file_spec,
 | |
|                                     uint32_t line_no) {
 | |
| #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
 | |
|   return false;
 | |
| #else
 | |
|   // We attach this to an 'odoc' event to specify a particular selection
 | |
|   typedef struct {
 | |
|     int16_t reserved0; // must be zero
 | |
|     int16_t fLineNumber;
 | |
|     int32_t fSelStart;
 | |
|     int32_t fSelEnd;
 | |
|     uint32_t reserved1; // must be zero
 | |
|     uint32_t reserved2; // must be zero
 | |
|   } BabelAESelInfo;
 | |
| 
 | |
|   Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_HOST));
 | |
|   char file_path[PATH_MAX];
 | |
|   file_spec.GetPath(file_path, PATH_MAX);
 | |
|   CFCString file_cfstr(file_path, kCFStringEncodingUTF8);
 | |
|   CFCReleaser<CFURLRef> file_URL(::CFURLCreateWithFileSystemPath(
 | |
|       NULL, file_cfstr.get(), kCFURLPOSIXPathStyle, false));
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf(
 | |
|         "Sending source file: \"%s\" and line: %d to external editor.\n",
 | |
|         file_path, line_no);
 | |
| 
 | |
|   long error;
 | |
|   BabelAESelInfo file_and_line_info = {
 | |
|       0,                      // reserved0
 | |
|       (int16_t)(line_no - 1), // fLineNumber (zero based line number)
 | |
|       1,                      // fSelStart
 | |
|       1024,                   // fSelEnd
 | |
|       0,                      // reserved1
 | |
|       0                       // reserved2
 | |
|   };
 | |
| 
 | |
|   AEKeyDesc file_and_line_desc;
 | |
| 
 | |
|   error = ::AECreateDesc(typeUTF8Text, &file_and_line_info,
 | |
|                          sizeof(file_and_line_info),
 | |
|                          &(file_and_line_desc.descContent));
 | |
| 
 | |
|   if (error != noErr) {
 | |
|     if (log)
 | |
|       log->Printf("Error creating AEDesc: %ld.\n", error);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   file_and_line_desc.descKey = keyAEPosition;
 | |
| 
 | |
|   static std::string g_app_name;
 | |
|   static FSRef g_app_fsref;
 | |
| 
 | |
|   LSApplicationParameters app_params;
 | |
|   ::memset(&app_params, 0, sizeof(app_params));
 | |
|   app_params.flags =
 | |
|       kLSLaunchDefaults | kLSLaunchDontAddToRecents | kLSLaunchDontSwitch;
 | |
| 
 | |
|   char *external_editor = ::getenv("LLDB_EXTERNAL_EDITOR");
 | |
| 
 | |
|   if (external_editor) {
 | |
|     if (log)
 | |
|       log->Printf("Looking for external editor \"%s\".\n", external_editor);
 | |
| 
 | |
|     if (g_app_name.empty() ||
 | |
|         strcmp(g_app_name.c_str(), external_editor) != 0) {
 | |
|       CFCString editor_name(external_editor, kCFStringEncodingUTF8);
 | |
|       error = ::LSFindApplicationForInfo(kLSUnknownCreator, NULL,
 | |
|                                          editor_name.get(), &g_app_fsref, NULL);
 | |
| 
 | |
|       // If we found the app, then store away the name so we don't have to
 | |
|       // re-look it up.
 | |
|       if (error != noErr) {
 | |
|         if (log)
 | |
|           log->Printf(
 | |
|               "Could not find External Editor application, error: %ld.\n",
 | |
|               error);
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     app_params.application = &g_app_fsref;
 | |
|   }
 | |
| 
 | |
|   ProcessSerialNumber psn;
 | |
|   CFCReleaser<CFArrayRef> file_array(
 | |
|       CFArrayCreate(NULL, (const void **)file_URL.ptr_address(false), 1, NULL));
 | |
|   error = ::LSOpenURLsWithRole(file_array.get(), kLSRolesAll,
 | |
|                                &file_and_line_desc, &app_params, &psn, 1);
 | |
| 
 | |
|   AEDisposeDesc(&(file_and_line_desc.descContent));
 | |
| 
 | |
|   if (error != noErr) {
 | |
|     if (log)
 | |
|       log->Printf("LSOpenURLsWithRole failed, error: %ld.\n", error);
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| #endif // #if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__)
 | |
| }
 | |
| 
 | |
| size_t Host::GetEnvironment(StringList &env) {
 | |
|   char **host_env = *_NSGetEnviron();
 | |
|   char *env_entry;
 | |
|   size_t i;
 | |
|   for (i = 0; (env_entry = host_env[i]) != NULL; ++i)
 | |
|     env.AppendString(env_entry);
 | |
|   return i;
 | |
| }
 | |
| 
 | |
| static bool GetMacOSXProcessCPUType(ProcessInstanceInfo &process_info) {
 | |
|   if (process_info.ProcessIDIsValid()) {
 | |
|     // Make a new mib to stay thread safe
 | |
|     int mib[CTL_MAXNAME] = {
 | |
|         0,
 | |
|     };
 | |
|     size_t mib_len = CTL_MAXNAME;
 | |
|     if (::sysctlnametomib("sysctl.proc_cputype", mib, &mib_len))
 | |
|       return false;
 | |
| 
 | |
|     mib[mib_len] = process_info.GetProcessID();
 | |
|     mib_len++;
 | |
| 
 | |
|     cpu_type_t cpu, sub = 0;
 | |
|     size_t len = sizeof(cpu);
 | |
|     if (::sysctl(mib, mib_len, &cpu, &len, 0, 0) == 0) {
 | |
|       switch (cpu) {
 | |
|       case CPU_TYPE_I386:
 | |
|         sub = CPU_SUBTYPE_I386_ALL;
 | |
|         break;
 | |
|       case CPU_TYPE_X86_64:
 | |
|         sub = CPU_SUBTYPE_X86_64_ALL;
 | |
|         break;
 | |
| 
 | |
| #if defined(CPU_TYPE_ARM64) && defined(CPU_SUBTYPE_ARM64_ALL)
 | |
|       case CPU_TYPE_ARM64:
 | |
|         sub = CPU_SUBTYPE_ARM64_ALL;
 | |
|         break;
 | |
| #endif
 | |
| 
 | |
|       case CPU_TYPE_ARM: {
 | |
|         // Note that we fetched the cpu type from the PROCESS but we can't get a
 | |
|         // cpusubtype of the
 | |
|         // process -- we can only get the host's cpu subtype.
 | |
|         uint32_t cpusubtype = 0;
 | |
|         len = sizeof(cpusubtype);
 | |
|         if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0)
 | |
|           sub = cpusubtype;
 | |
| 
 | |
|         bool host_cpu_is_64bit;
 | |
|         uint32_t is64bit_capable;
 | |
|         size_t is64bit_capable_len = sizeof(is64bit_capable);
 | |
|         if (sysctlbyname("hw.cpu64bit_capable", &is64bit_capable,
 | |
|                          &is64bit_capable_len, NULL, 0) == 0)
 | |
|           host_cpu_is_64bit = true;
 | |
|         else
 | |
|           host_cpu_is_64bit = false;
 | |
| 
 | |
|         // if the host is an armv8 device, its cpusubtype will be in
 | |
|         // CPU_SUBTYPE_ARM64 numbering
 | |
|         // and we need to rewrite it to a reasonable CPU_SUBTYPE_ARM value
 | |
|         // instead.
 | |
| 
 | |
|         if (host_cpu_is_64bit) {
 | |
|           sub = CPU_SUBTYPE_ARM_V7;
 | |
|         }
 | |
|       } break;
 | |
| 
 | |
|       default:
 | |
|         break;
 | |
|       }
 | |
|       process_info.GetArchitecture().SetArchitecture(eArchTypeMachO, cpu, sub);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   process_info.GetArchitecture().Clear();
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static bool GetMacOSXProcessArgs(const ProcessInstanceInfoMatch *match_info_ptr,
 | |
|                                  ProcessInstanceInfo &process_info) {
 | |
|   if (process_info.ProcessIDIsValid()) {
 | |
|     int proc_args_mib[3] = {CTL_KERN, KERN_PROCARGS2,
 | |
|                             (int)process_info.GetProcessID()};
 | |
| 
 | |
|     size_t arg_data_size = 0;
 | |
|     if (::sysctl(proc_args_mib, 3, nullptr, &arg_data_size, NULL, 0) ||
 | |
|         arg_data_size == 0)
 | |
|       arg_data_size = 8192;
 | |
| 
 | |
|     // Add a few bytes to the calculated length, I know we need to add at least
 | |
|     // one byte
 | |
|     // to this number otherwise we get junk back, so add 128 just in case...
 | |
|     DataBufferHeap arg_data(arg_data_size + 128, 0);
 | |
|     arg_data_size = arg_data.GetByteSize();
 | |
|     if (::sysctl(proc_args_mib, 3, arg_data.GetBytes(), &arg_data_size, NULL,
 | |
|                  0) == 0) {
 | |
|       DataExtractor data(arg_data.GetBytes(), arg_data_size,
 | |
|                          endian::InlHostByteOrder(), sizeof(void *));
 | |
|       lldb::offset_t offset = 0;
 | |
|       uint32_t argc = data.GetU32(&offset);
 | |
|       llvm::Triple &triple = process_info.GetArchitecture().GetTriple();
 | |
|       const llvm::Triple::ArchType triple_arch = triple.getArch();
 | |
|       const bool check_for_ios_simulator =
 | |
|           (triple_arch == llvm::Triple::x86 ||
 | |
|            triple_arch == llvm::Triple::x86_64);
 | |
|       const char *cstr = data.GetCStr(&offset);
 | |
|       if (cstr) {
 | |
|         process_info.GetExecutableFile().SetFile(cstr, false);
 | |
| 
 | |
|         if (match_info_ptr == NULL ||
 | |
|             NameMatches(
 | |
|                 process_info.GetExecutableFile().GetFilename().GetCString(),
 | |
|                 match_info_ptr->GetNameMatchType(),
 | |
|                 match_info_ptr->GetProcessInfo().GetName())) {
 | |
|           // Skip NULLs
 | |
|           while (1) {
 | |
|             const uint8_t *p = data.PeekData(offset, 1);
 | |
|             if ((p == NULL) || (*p != '\0'))
 | |
|               break;
 | |
|             ++offset;
 | |
|           }
 | |
|           // Now extract all arguments
 | |
|           Args &proc_args = process_info.GetArguments();
 | |
|           for (int i = 0; i < static_cast<int>(argc); ++i) {
 | |
|             cstr = data.GetCStr(&offset);
 | |
|             if (cstr)
 | |
|               proc_args.AppendArgument(llvm::StringRef(cstr));
 | |
|           }
 | |
| 
 | |
|           Args &proc_env = process_info.GetEnvironmentEntries();
 | |
|           while ((cstr = data.GetCStr(&offset))) {
 | |
|             if (cstr[0] == '\0')
 | |
|               break;
 | |
| 
 | |
|             if (check_for_ios_simulator) {
 | |
|               if (strncmp(cstr, "SIMULATOR_UDID=", strlen("SIMULATOR_UDID=")) ==
 | |
|                   0)
 | |
|                 process_info.GetArchitecture().GetTriple().setOS(
 | |
|                     llvm::Triple::IOS);
 | |
|               else
 | |
|                 process_info.GetArchitecture().GetTriple().setOS(
 | |
|                     llvm::Triple::MacOSX);
 | |
|             }
 | |
| 
 | |
|             proc_env.AppendArgument(llvm::StringRef(cstr));
 | |
|           }
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static bool GetMacOSXProcessUserAndGroup(ProcessInstanceInfo &process_info) {
 | |
|   if (process_info.ProcessIDIsValid()) {
 | |
|     int mib[4];
 | |
|     mib[0] = CTL_KERN;
 | |
|     mib[1] = KERN_PROC;
 | |
|     mib[2] = KERN_PROC_PID;
 | |
|     mib[3] = process_info.GetProcessID();
 | |
|     struct kinfo_proc proc_kinfo;
 | |
|     size_t proc_kinfo_size = sizeof(struct kinfo_proc);
 | |
| 
 | |
|     if (::sysctl(mib, 4, &proc_kinfo, &proc_kinfo_size, NULL, 0) == 0) {
 | |
|       if (proc_kinfo_size > 0) {
 | |
|         process_info.SetParentProcessID(proc_kinfo.kp_eproc.e_ppid);
 | |
|         process_info.SetUserID(proc_kinfo.kp_eproc.e_pcred.p_ruid);
 | |
|         process_info.SetGroupID(proc_kinfo.kp_eproc.e_pcred.p_rgid);
 | |
|         process_info.SetEffectiveUserID(proc_kinfo.kp_eproc.e_ucred.cr_uid);
 | |
|         if (proc_kinfo.kp_eproc.e_ucred.cr_ngroups > 0)
 | |
|           process_info.SetEffectiveGroupID(
 | |
|               proc_kinfo.kp_eproc.e_ucred.cr_groups[0]);
 | |
|         else
 | |
|           process_info.SetEffectiveGroupID(UINT32_MAX);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   process_info.SetParentProcessID(LLDB_INVALID_PROCESS_ID);
 | |
|   process_info.SetUserID(UINT32_MAX);
 | |
|   process_info.SetGroupID(UINT32_MAX);
 | |
|   process_info.SetEffectiveUserID(UINT32_MAX);
 | |
|   process_info.SetEffectiveGroupID(UINT32_MAX);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
 | |
|                              ProcessInstanceInfoList &process_infos) {
 | |
|   std::vector<struct kinfo_proc> kinfos;
 | |
| 
 | |
|   int mib[3] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};
 | |
| 
 | |
|   size_t pid_data_size = 0;
 | |
|   if (::sysctl(mib, 4, NULL, &pid_data_size, NULL, 0) != 0)
 | |
|     return 0;
 | |
| 
 | |
|   // Add a few extra in case a few more show up
 | |
|   const size_t estimated_pid_count =
 | |
|       (pid_data_size / sizeof(struct kinfo_proc)) + 10;
 | |
| 
 | |
|   kinfos.resize(estimated_pid_count);
 | |
|   pid_data_size = kinfos.size() * sizeof(struct kinfo_proc);
 | |
| 
 | |
|   if (::sysctl(mib, 4, &kinfos[0], &pid_data_size, NULL, 0) != 0)
 | |
|     return 0;
 | |
| 
 | |
|   const size_t actual_pid_count = (pid_data_size / sizeof(struct kinfo_proc));
 | |
| 
 | |
|   bool all_users = match_info.GetMatchAllUsers();
 | |
|   const lldb::pid_t our_pid = getpid();
 | |
|   const uid_t our_uid = getuid();
 | |
|   for (size_t i = 0; i < actual_pid_count; i++) {
 | |
|     const struct kinfo_proc &kinfo = kinfos[i];
 | |
| 
 | |
|     bool kinfo_user_matches = false;
 | |
|     if (all_users)
 | |
|       kinfo_user_matches = true;
 | |
|     else
 | |
|       kinfo_user_matches = kinfo.kp_eproc.e_pcred.p_ruid == our_uid;
 | |
| 
 | |
|     // Special case, if lldb is being run as root we can attach to anything.
 | |
|     if (our_uid == 0)
 | |
|       kinfo_user_matches = true;
 | |
| 
 | |
|     if (kinfo_user_matches == false || // Make sure the user is acceptable
 | |
|         static_cast<lldb::pid_t>(kinfo.kp_proc.p_pid) ==
 | |
|             our_pid ||                   // Skip this process
 | |
|         kinfo.kp_proc.p_pid == 0 ||      // Skip kernel (kernel pid is zero)
 | |
|         kinfo.kp_proc.p_stat == SZOMB || // Zombies are bad, they like brains...
 | |
|         kinfo.kp_proc.p_flag & P_TRACED ||   // Being debugged?
 | |
|         kinfo.kp_proc.p_flag & P_WEXIT ||    // Working on exiting?
 | |
|         kinfo.kp_proc.p_flag & P_TRANSLATED) // Skip translated ppc (Rosetta)
 | |
|       continue;
 | |
| 
 | |
|     ProcessInstanceInfo process_info;
 | |
|     process_info.SetProcessID(kinfo.kp_proc.p_pid);
 | |
|     process_info.SetParentProcessID(kinfo.kp_eproc.e_ppid);
 | |
|     process_info.SetUserID(kinfo.kp_eproc.e_pcred.p_ruid);
 | |
|     process_info.SetGroupID(kinfo.kp_eproc.e_pcred.p_rgid);
 | |
|     process_info.SetEffectiveUserID(kinfo.kp_eproc.e_ucred.cr_uid);
 | |
|     if (kinfo.kp_eproc.e_ucred.cr_ngroups > 0)
 | |
|       process_info.SetEffectiveGroupID(kinfo.kp_eproc.e_ucred.cr_groups[0]);
 | |
|     else
 | |
|       process_info.SetEffectiveGroupID(UINT32_MAX);
 | |
| 
 | |
|     // Make sure our info matches before we go fetch the name and cpu type
 | |
|     if (match_info.Matches(process_info)) {
 | |
|       // Get CPU type first so we can know to look for iOS simulator is we have
 | |
|       // x86 or x86_64
 | |
|       if (GetMacOSXProcessCPUType(process_info)) {
 | |
|         if (GetMacOSXProcessArgs(&match_info, process_info)) {
 | |
|           if (match_info.Matches(process_info))
 | |
|             process_infos.Append(process_info);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return process_infos.GetSize();
 | |
| }
 | |
| 
 | |
| bool Host::GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &process_info) {
 | |
|   process_info.SetProcessID(pid);
 | |
|   bool success = false;
 | |
| 
 | |
|   // Get CPU type first so we can know to look for iOS simulator is we have x86
 | |
|   // or x86_64
 | |
|   if (GetMacOSXProcessCPUType(process_info))
 | |
|     success = true;
 | |
| 
 | |
|   if (GetMacOSXProcessArgs(NULL, process_info))
 | |
|     success = true;
 | |
| 
 | |
|   if (GetMacOSXProcessUserAndGroup(process_info))
 | |
|     success = true;
 | |
| 
 | |
|   if (success)
 | |
|     return true;
 | |
| 
 | |
|   process_info.Clear();
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| #if !NO_XPC_SERVICES
 | |
| static void PackageXPCArguments(xpc_object_t message, const char *prefix,
 | |
|                                 const Args &args) {
 | |
|   size_t count = args.GetArgumentCount();
 | |
|   char buf[50]; // long enough for 'argXXX'
 | |
|   memset(buf, 0, 50);
 | |
|   sprintf(buf, "%sCount", prefix);
 | |
|   xpc_dictionary_set_int64(message, buf, count);
 | |
|   for (size_t i = 0; i < count; i++) {
 | |
|     memset(buf, 0, 50);
 | |
|     sprintf(buf, "%s%zi", prefix, i);
 | |
|     xpc_dictionary_set_string(message, buf, args.GetArgumentAtIndex(i));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  A valid authorizationRef means that
 | |
|     - there is the LaunchUsingXPCRightName rights in the /etc/authorization
 | |
|     - we have successfully copied the rights to be send over the XPC wire
 | |
|  Once obtained, it will be valid for as long as the process lives.
 | |
|  */
 | |
| static AuthorizationRef authorizationRef = NULL;
 | |
| static Status getXPCAuthorization(ProcessLaunchInfo &launch_info) {
 | |
|   Status error;
 | |
|   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST |
 | |
|                                                   LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   if ((launch_info.GetUserID() == 0) && !authorizationRef) {
 | |
|     OSStatus createStatus =
 | |
|         AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
 | |
|                             kAuthorizationFlagDefaults, &authorizationRef);
 | |
|     if (createStatus != errAuthorizationSuccess) {
 | |
|       error.SetError(1, eErrorTypeGeneric);
 | |
|       error.SetErrorString("Can't create authorizationRef.");
 | |
|       LLDB_LOG(log, "error: {0}", error);
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     OSStatus rightsStatus =
 | |
|         AuthorizationRightGet(LaunchUsingXPCRightName, NULL);
 | |
|     if (rightsStatus != errAuthorizationSuccess) {
 | |
|       // No rights in the security database, Create it with the right prompt.
 | |
|       CFStringRef prompt =
 | |
|           CFSTR("Xcode is trying to take control of a root process.");
 | |
|       CFStringRef keys[] = {CFSTR("en")};
 | |
|       CFTypeRef values[] = {prompt};
 | |
|       CFDictionaryRef promptDict = CFDictionaryCreate(
 | |
|           kCFAllocatorDefault, (const void **)keys, (const void **)values, 1,
 | |
|           &kCFCopyStringDictionaryKeyCallBacks,
 | |
|           &kCFTypeDictionaryValueCallBacks);
 | |
| 
 | |
|       CFStringRef keys1[] = {CFSTR("class"), CFSTR("group"), CFSTR("comment"),
 | |
|                              CFSTR("default-prompt"), CFSTR("shared")};
 | |
|       CFTypeRef values1[] = {CFSTR("user"), CFSTR("admin"),
 | |
|                              CFSTR(LaunchUsingXPCRightName), promptDict,
 | |
|                              kCFBooleanFalse};
 | |
|       CFDictionaryRef dict = CFDictionaryCreate(
 | |
|           kCFAllocatorDefault, (const void **)keys1, (const void **)values1, 5,
 | |
|           &kCFCopyStringDictionaryKeyCallBacks,
 | |
|           &kCFTypeDictionaryValueCallBacks);
 | |
|       rightsStatus = AuthorizationRightSet(
 | |
|           authorizationRef, LaunchUsingXPCRightName, dict, NULL, NULL, NULL);
 | |
|       CFRelease(promptDict);
 | |
|       CFRelease(dict);
 | |
|     }
 | |
| 
 | |
|     OSStatus copyRightStatus = errAuthorizationDenied;
 | |
|     if (rightsStatus == errAuthorizationSuccess) {
 | |
|       AuthorizationItem item1 = {LaunchUsingXPCRightName, 0, NULL, 0};
 | |
|       AuthorizationItem items[] = {item1};
 | |
|       AuthorizationRights requestedRights = {1, items};
 | |
|       AuthorizationFlags authorizationFlags =
 | |
|           kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
 | |
|       copyRightStatus = AuthorizationCopyRights(
 | |
|           authorizationRef, &requestedRights, kAuthorizationEmptyEnvironment,
 | |
|           authorizationFlags, NULL);
 | |
|     }
 | |
| 
 | |
|     if (copyRightStatus != errAuthorizationSuccess) {
 | |
|       // Eventually when the commandline supports running as root and the user
 | |
|       // is not
 | |
|       // logged in in the current audit session, we will need the trick in gdb
 | |
|       // where
 | |
|       // we ask the user to type in the root passwd in the terminal.
 | |
|       error.SetError(2, eErrorTypeGeneric);
 | |
|       error.SetErrorStringWithFormat(
 | |
|           "Launching as root needs root authorization.");
 | |
|       LLDB_LOG(log, "error: {0}", error);
 | |
| 
 | |
|       if (authorizationRef) {
 | |
|         AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
 | |
|         authorizationRef = NULL;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static short GetPosixspawnFlags(const ProcessLaunchInfo &launch_info) {
 | |
|   short flags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK;
 | |
| 
 | |
|   if (launch_info.GetFlags().Test(eLaunchFlagExec))
 | |
|     flags |= POSIX_SPAWN_SETEXEC; // Darwin specific posix_spawn flag
 | |
| 
 | |
|   if (launch_info.GetFlags().Test(eLaunchFlagDebug))
 | |
|     flags |= POSIX_SPAWN_START_SUSPENDED; // Darwin specific posix_spawn flag
 | |
| 
 | |
|   if (launch_info.GetFlags().Test(eLaunchFlagDisableASLR))
 | |
|     flags |= _POSIX_SPAWN_DISABLE_ASLR; // Darwin specific posix_spawn flag
 | |
| 
 | |
|   if (launch_info.GetLaunchInSeparateProcessGroup())
 | |
|     flags |= POSIX_SPAWN_SETPGROUP;
 | |
| 
 | |
| #ifdef POSIX_SPAWN_CLOEXEC_DEFAULT
 | |
| #if defined(__x86_64__) || defined(__i386__)
 | |
|   static LazyBool g_use_close_on_exec_flag = eLazyBoolCalculate;
 | |
|   if (g_use_close_on_exec_flag == eLazyBoolCalculate) {
 | |
|     g_use_close_on_exec_flag = eLazyBoolNo;
 | |
| 
 | |
|     uint32_t major, minor, update;
 | |
|     if (HostInfo::GetOSVersion(major, minor, update)) {
 | |
|       // Kernel panic if we use the POSIX_SPAWN_CLOEXEC_DEFAULT on 10.7 or
 | |
|       // earlier
 | |
|       if (major > 10 || (major == 10 && minor > 7)) {
 | |
|         // Only enable for 10.8 and later OS versions
 | |
|         g_use_close_on_exec_flag = eLazyBoolYes;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #else
 | |
|   static LazyBool g_use_close_on_exec_flag = eLazyBoolYes;
 | |
| #endif // defined(__x86_64__) || defined(__i386__)
 | |
|   // Close all files exception those with file actions if this is supported.
 | |
|   if (g_use_close_on_exec_flag == eLazyBoolYes)
 | |
|     flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
 | |
| #endif // ifdef POSIX_SPAWN_CLOEXEC_DEFAULT
 | |
|   return flags;
 | |
| }
 | |
| 
 | |
| static Status LaunchProcessXPC(const char *exe_path,
 | |
|                                ProcessLaunchInfo &launch_info,
 | |
|                                lldb::pid_t &pid) {
 | |
| #if !NO_XPC_SERVICES
 | |
|   Status error = getXPCAuthorization(launch_info);
 | |
|   if (error.Fail())
 | |
|     return error;
 | |
| 
 | |
|   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST |
 | |
|                                                   LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   uid_t requested_uid = launch_info.GetUserID();
 | |
|   const char *xpc_service = nil;
 | |
|   bool send_auth = false;
 | |
|   AuthorizationExternalForm extForm;
 | |
|   if (requested_uid == 0) {
 | |
|     if (AuthorizationMakeExternalForm(authorizationRef, &extForm) ==
 | |
|         errAuthorizationSuccess) {
 | |
|       send_auth = true;
 | |
|     } else {
 | |
|       error.SetError(3, eErrorTypeGeneric);
 | |
|       error.SetErrorStringWithFormat("Launching root via XPC needs to "
 | |
|                                      "externalize authorization reference.");
 | |
|       LLDB_LOG(log, "error: {0}", error);
 | |
|       return error;
 | |
|     }
 | |
|     xpc_service = LaunchUsingXPCRightName;
 | |
|   } else {
 | |
|     error.SetError(4, eErrorTypeGeneric);
 | |
|     error.SetErrorStringWithFormat(
 | |
|         "Launching via XPC is only currently available for root.");
 | |
|     LLDB_LOG(log, "error: {0}", error);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   xpc_connection_t conn = xpc_connection_create(xpc_service, NULL);
 | |
| 
 | |
|   xpc_connection_set_event_handler(conn, ^(xpc_object_t event) {
 | |
|     xpc_type_t type = xpc_get_type(event);
 | |
| 
 | |
|     if (type == XPC_TYPE_ERROR) {
 | |
|       if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
 | |
|         // The service has either canceled itself, crashed, or been terminated.
 | |
|         // The XPC connection is still valid and sending a message to it will
 | |
|         // re-launch the service.
 | |
|         // If the service is state-full, this is the time to initialize the new
 | |
|         // service.
 | |
|         return;
 | |
|       } else if (event == XPC_ERROR_CONNECTION_INVALID) {
 | |
|         // The service is invalid. Either the service name supplied to
 | |
|         // xpc_connection_create() is incorrect
 | |
|         // or we (this process) have canceled the service; we can do any cleanup
 | |
|         // of application state at this point.
 | |
|         // printf("Service disconnected");
 | |
|         return;
 | |
|       } else {
 | |
|         // printf("Unexpected error from service: %s",
 | |
|         // xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
 | |
|       }
 | |
| 
 | |
|     } else {
 | |
|       // printf("Received unexpected event in handler");
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   xpc_connection_set_finalizer_f(conn, xpc_finalizer_t(xpc_release));
 | |
|   xpc_connection_resume(conn);
 | |
|   xpc_object_t message = xpc_dictionary_create(nil, nil, 0);
 | |
| 
 | |
|   if (send_auth) {
 | |
|     xpc_dictionary_set_data(message, LauncherXPCServiceAuthKey, extForm.bytes,
 | |
|                             sizeof(AuthorizationExternalForm));
 | |
|   }
 | |
| 
 | |
|   PackageXPCArguments(message, LauncherXPCServiceArgPrefxKey,
 | |
|                       launch_info.GetArguments());
 | |
|   PackageXPCArguments(message, LauncherXPCServiceEnvPrefxKey,
 | |
|                       launch_info.GetEnvironmentEntries());
 | |
| 
 | |
|   // Posix spawn stuff.
 | |
|   xpc_dictionary_set_int64(message, LauncherXPCServiceCPUTypeKey,
 | |
|                            launch_info.GetArchitecture().GetMachOCPUType());
 | |
|   xpc_dictionary_set_int64(message, LauncherXPCServicePosixspawnFlagsKey,
 | |
|                            GetPosixspawnFlags(launch_info));
 | |
|   const FileAction *file_action = launch_info.GetFileActionForFD(STDIN_FILENO);
 | |
|   if (file_action && !file_action->GetPath().empty()) {
 | |
|     xpc_dictionary_set_string(message, LauncherXPCServiceStdInPathKeyKey,
 | |
|                               file_action->GetPath().str().c_str());
 | |
|   }
 | |
|   file_action = launch_info.GetFileActionForFD(STDOUT_FILENO);
 | |
|   if (file_action && !file_action->GetPath().empty()) {
 | |
|     xpc_dictionary_set_string(message, LauncherXPCServiceStdOutPathKeyKey,
 | |
|                               file_action->GetPath().str().c_str());
 | |
|   }
 | |
|   file_action = launch_info.GetFileActionForFD(STDERR_FILENO);
 | |
|   if (file_action && !file_action->GetPath().empty()) {
 | |
|     xpc_dictionary_set_string(message, LauncherXPCServiceStdErrPathKeyKey,
 | |
|                               file_action->GetPath().str().c_str());
 | |
|   }
 | |
| 
 | |
|   xpc_object_t reply =
 | |
|       xpc_connection_send_message_with_reply_sync(conn, message);
 | |
|   xpc_type_t returnType = xpc_get_type(reply);
 | |
|   if (returnType == XPC_TYPE_DICTIONARY) {
 | |
|     pid = xpc_dictionary_get_int64(reply, LauncherXPCServiceChildPIDKey);
 | |
|     if (pid == 0) {
 | |
|       int errorType =
 | |
|           xpc_dictionary_get_int64(reply, LauncherXPCServiceErrorTypeKey);
 | |
|       int errorCode =
 | |
|           xpc_dictionary_get_int64(reply, LauncherXPCServiceCodeTypeKey);
 | |
| 
 | |
|       error.SetError(errorCode, eErrorTypeGeneric);
 | |
|       error.SetErrorStringWithFormat(
 | |
|           "Problems with launching via XPC. Error type : %i, code : %i",
 | |
|           errorType, errorCode);
 | |
|       LLDB_LOG(log, "error: {0}", error);
 | |
| 
 | |
|       if (authorizationRef) {
 | |
|         AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
 | |
|         authorizationRef = NULL;
 | |
|       }
 | |
|     }
 | |
|   } else if (returnType == XPC_TYPE_ERROR) {
 | |
|     error.SetError(5, eErrorTypeGeneric);
 | |
|     error.SetErrorStringWithFormat(
 | |
|         "Problems with launching via XPC. XPC error : %s",
 | |
|         xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION));
 | |
|     LLDB_LOG(log, "error: {0}", error);
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| #else
 | |
|   Status error;
 | |
|   return error;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static bool AddPosixSpawnFileAction(void *_file_actions, const FileAction *info,
 | |
|                                     Log *log, Status &error) {
 | |
|   if (info == NULL)
 | |
|     return false;
 | |
| 
 | |
|   posix_spawn_file_actions_t *file_actions =
 | |
|       reinterpret_cast<posix_spawn_file_actions_t *>(_file_actions);
 | |
| 
 | |
|   switch (info->GetAction()) {
 | |
|   case FileAction::eFileActionNone:
 | |
|     error.Clear();
 | |
|     break;
 | |
| 
 | |
|   case FileAction::eFileActionClose:
 | |
|     if (info->GetFD() == -1)
 | |
|       error.SetErrorString(
 | |
|           "invalid fd for posix_spawn_file_actions_addclose(...)");
 | |
|     else {
 | |
|       error.SetError(
 | |
|           ::posix_spawn_file_actions_addclose(file_actions, info->GetFD()),
 | |
|           eErrorTypePOSIX);
 | |
|       if (error.Fail())
 | |
|         LLDB_LOG(log,
 | |
|                  "error: {0}, posix_spawn_file_actions_addclose "
 | |
|                  "(action={1}, fd={2})",
 | |
|                  error, file_actions, info->GetFD());
 | |
|     }
 | |
|     break;
 | |
| 
 | |
|   case FileAction::eFileActionDuplicate:
 | |
|     if (info->GetFD() == -1)
 | |
|       error.SetErrorString(
 | |
|           "invalid fd for posix_spawn_file_actions_adddup2(...)");
 | |
|     else if (info->GetActionArgument() == -1)
 | |
|       error.SetErrorString(
 | |
|           "invalid duplicate fd for posix_spawn_file_actions_adddup2(...)");
 | |
|     else {
 | |
|       error.SetError(
 | |
|           ::posix_spawn_file_actions_adddup2(file_actions, info->GetFD(),
 | |
|                                              info->GetActionArgument()),
 | |
|           eErrorTypePOSIX);
 | |
|       if (error.Fail())
 | |
|         LLDB_LOG(log,
 | |
|                  "error: {0}, posix_spawn_file_actions_adddup2 "
 | |
|                  "(action={1}, fd={2}, dup_fd={3})",
 | |
|                  error, file_actions, info->GetFD(), info->GetActionArgument());
 | |
|     }
 | |
|     break;
 | |
| 
 | |
|   case FileAction::eFileActionOpen:
 | |
|     if (info->GetFD() == -1)
 | |
|       error.SetErrorString(
 | |
|           "invalid fd in posix_spawn_file_actions_addopen(...)");
 | |
|     else {
 | |
|       int oflag = info->GetActionArgument();
 | |
| 
 | |
|       mode_t mode = 0;
 | |
| 
 | |
|       if (oflag & O_CREAT)
 | |
|         mode = 0640;
 | |
| 
 | |
|       error.SetError(::posix_spawn_file_actions_addopen(
 | |
|                          file_actions, info->GetFD(),
 | |
|                          info->GetPath().str().c_str(), oflag, mode),
 | |
|                      eErrorTypePOSIX);
 | |
|       if (error.Fail())
 | |
|         LLDB_LOG(log,
 | |
|                  "error: {0}, posix_spawn_file_actions_addopen (action={1}, "
 | |
|                  "fd={2}, path='{3}', oflag={4}, mode={5})",
 | |
|                  error, file_actions, info->GetFD(), info->GetPath(), oflag,
 | |
|                  mode);
 | |
|     }
 | |
|     break;
 | |
|   }
 | |
|   return error.Success();
 | |
| }
 | |
| 
 | |
| static Status LaunchProcessPosixSpawn(const char *exe_path,
 | |
|                                       const ProcessLaunchInfo &launch_info,
 | |
|                                       lldb::pid_t &pid) {
 | |
|   Status error;
 | |
|   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST |
 | |
|                                                   LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   posix_spawnattr_t attr;
 | |
|   error.SetError(::posix_spawnattr_init(&attr), eErrorTypePOSIX);
 | |
| 
 | |
|   if (error.Fail()) {
 | |
|     LLDB_LOG(log, "error: {0}, ::posix_spawnattr_init ( &attr )", error);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Make a quick class that will cleanup the posix spawn attributes in case
 | |
|   // we return in the middle of this function.
 | |
|   lldb_utility::CleanUp<posix_spawnattr_t *, int> posix_spawnattr_cleanup(
 | |
|       &attr, posix_spawnattr_destroy);
 | |
| 
 | |
|   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);
 | |
| 
 | |
|   short flags = GetPosixspawnFlags(launch_info);
 | |
| 
 | |
|   error.SetError(::posix_spawnattr_setflags(&attr, flags), eErrorTypePOSIX);
 | |
|   if (error.Fail()) {
 | |
|     LLDB_LOG(log,
 | |
|              "error: {0}, ::posix_spawnattr_setflags ( &attr, flags={1:x} )",
 | |
|              error, flags);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
| // posix_spawnattr_setbinpref_np appears to be an Apple extension per:
 | |
| // http://www.unix.com/man-page/OSX/3/posix_spawnattr_setbinpref_np/
 | |
| #if !defined(__arm__)
 | |
| 
 | |
|   // Don't set the binpref if a shell was provided.  After all, that's only
 | |
|   // going to affect what version of the shell
 | |
|   // is launched, not what fork of the binary is launched.  We insert "arch
 | |
|   // --arch <ARCH> as part of the shell invocation
 | |
|   // to do that job on OSX.
 | |
| 
 | |
|   if (launch_info.GetShell() == nullptr) {
 | |
|     // 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...
 | |
|     const ArchSpec &arch_spec = launch_info.GetArchitecture();
 | |
|     cpu_type_t cpu = arch_spec.GetMachOCPUType();
 | |
|     cpu_type_t sub = arch_spec.GetMachOCPUSubType();
 | |
|     if (cpu != 0 && cpu != static_cast<cpu_type_t>(UINT32_MAX) &&
 | |
|         cpu != static_cast<cpu_type_t>(LLDB_INVALID_CPUTYPE) &&
 | |
|         !(cpu == 0x01000007 && sub == 8)) // If haswell is specified, don't try
 | |
|                                           // to set the CPU type or we will fail
 | |
|     {
 | |
|       size_t ocount = 0;
 | |
|       error.SetError(::posix_spawnattr_setbinpref_np(&attr, 1, &cpu, &ocount),
 | |
|                      eErrorTypePOSIX);
 | |
|       if (error.Fail())
 | |
|         LLDB_LOG(log,
 | |
|                  "error: {0}, ::posix_spawnattr_setbinpref_np ( &attr, 1, "
 | |
|                  "cpu_type = {1:x}, count => {2} )",
 | |
|                  error, cpu, ocount);
 | |
| 
 | |
|       if (error.Fail() || ocount != 1)
 | |
|         return error;
 | |
|     }
 | |
|   }
 | |
| #endif // !defined(__arm__)
 | |
| 
 | |
|   const char *tmp_argv[2];
 | |
|   char *const *argv = const_cast<char *const *>(
 | |
|       launch_info.GetArguments().GetConstArgumentVector());
 | |
|   char *const *envp = const_cast<char *const *>(
 | |
|       launch_info.GetEnvironmentEntries().GetConstArgumentVector());
 | |
|   if (argv == NULL) {
 | |
|     // posix_spawn gets very unhappy if it doesn't have at least the program
 | |
|     // name in argv[0]. One of the side affects I have noticed is the
 | |
|     // environment
 | |
|     // variables don't make it into the child process if "argv == NULL"!!!
 | |
|     tmp_argv[0] = exe_path;
 | |
|     tmp_argv[1] = NULL;
 | |
|     argv = const_cast<char *const *>(tmp_argv);
 | |
|   }
 | |
| 
 | |
|   FileSpec working_dir{launch_info.GetWorkingDirectory()};
 | |
|   if (working_dir) {
 | |
|     // Set the working directory on this thread only
 | |
|     if (__pthread_chdir(working_dir.GetCString()) < 0) {
 | |
|       if (errno == ENOENT) {
 | |
|         error.SetErrorStringWithFormat("No such file or directory: %s",
 | |
|                                        working_dir.GetCString());
 | |
|       } else if (errno == ENOTDIR) {
 | |
|         error.SetErrorStringWithFormat("Path doesn't name a directory: %s",
 | |
|                                        working_dir.GetCString());
 | |
|       } else {
 | |
|         error.SetErrorStringWithFormat("An unknown error occurred when "
 | |
|                                        "changing directory for process "
 | |
|                                        "execution.");
 | |
|       }
 | |
|       return error;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ::pid_t result_pid = LLDB_INVALID_PROCESS_ID;
 | |
|   const size_t num_file_actions = launch_info.GetNumFileActions();
 | |
|   if (num_file_actions > 0) {
 | |
|     posix_spawn_file_actions_t file_actions;
 | |
|     error.SetError(::posix_spawn_file_actions_init(&file_actions),
 | |
|                    eErrorTypePOSIX);
 | |
|     if (error.Fail()) {
 | |
|       LLDB_LOG(log,
 | |
|                "error: {0}, ::posix_spawn_file_actions_init ( &file_actions )",
 | |
|                error);
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     // Make a quick class that will cleanup the posix spawn attributes in case
 | |
|     // we return in the middle of this function.
 | |
|     lldb_utility::CleanUp<posix_spawn_file_actions_t *, int>
 | |
|         posix_spawn_file_actions_cleanup(&file_actions,
 | |
|                                          posix_spawn_file_actions_destroy);
 | |
| 
 | |
|     for (size_t i = 0; i < num_file_actions; ++i) {
 | |
|       const FileAction *launch_file_action =
 | |
|           launch_info.GetFileActionAtIndex(i);
 | |
|       if (launch_file_action) {
 | |
|         if (!AddPosixSpawnFileAction(&file_actions, launch_file_action, log,
 | |
|                                      error))
 | |
|           return error;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     error.SetError(
 | |
|         ::posix_spawnp(&result_pid, exe_path, &file_actions, &attr, argv, envp),
 | |
|         eErrorTypePOSIX);
 | |
| 
 | |
|     if (error.Fail()) {
 | |
|       LLDB_LOG(log,
 | |
|                "error: {0}, ::posix_spawnp(pid => {1}, path = '{2}', "
 | |
|                "file_actions = {3}, "
 | |
|                "attr = {4}, argv = {5}, envp = {6} )",
 | |
|                error, result_pid, exe_path, &file_actions, &attr, argv, envp);
 | |
|       if (log) {
 | |
|         for (int ii = 0; argv[ii]; ++ii)
 | |
|           LLDB_LOG(log, "argv[{0}] = '{1}'", ii, argv[ii]);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   } else {
 | |
|     error.SetError(
 | |
|         ::posix_spawnp(&result_pid, exe_path, NULL, &attr, argv, envp),
 | |
|         eErrorTypePOSIX);
 | |
| 
 | |
|     if (error.Fail()) {
 | |
|       LLDB_LOG(log,
 | |
|                "error: {0}, ::posix_spawnp ( pid => {1}, path = '{2}', "
 | |
|                "file_actions = NULL, attr = {3}, argv = {4}, envp = {5} )",
 | |
|                error, result_pid, exe_path, &attr, argv, envp);
 | |
|       if (log) {
 | |
|         for (int ii = 0; argv[ii]; ++ii)
 | |
|           LLDB_LOG(log, "argv[{0}] = '{1}'", ii, argv[ii]);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   pid = result_pid;
 | |
| 
 | |
|   if (working_dir) {
 | |
|     // No more thread specific current working directory
 | |
|     __pthread_fchdir(-1);
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| static bool ShouldLaunchUsingXPC(ProcessLaunchInfo &launch_info) {
 | |
|   bool result = false;
 | |
| 
 | |
| #if !NO_XPC_SERVICES
 | |
|   bool launchingAsRoot = launch_info.GetUserID() == 0;
 | |
|   bool currentUserIsRoot = HostInfo::GetEffectiveUserID() == 0;
 | |
| 
 | |
|   if (launchingAsRoot && !currentUserIsRoot) {
 | |
|     // If current user is already root, we don't need XPC's help.
 | |
|     result = true;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| Status Host::LaunchProcess(ProcessLaunchInfo &launch_info) {
 | |
|   Status error;
 | |
|   char exe_path[PATH_MAX];
 | |
|   PlatformSP host_platform_sp(Platform::GetHostPlatform());
 | |
| 
 | |
|   ModuleSpec exe_module_spec(launch_info.GetExecutableFile(),
 | |
|                              launch_info.GetArchitecture());
 | |
| 
 | |
|   if (!llvm::sys::fs::is_regular_file(
 | |
|           exe_module_spec.GetFileSpec().GetPath())) {
 | |
|     lldb::ModuleSP exe_module_sp;
 | |
|     error = host_platform_sp->ResolveExecutable(exe_module_spec, exe_module_sp,
 | |
|                                                 NULL);
 | |
| 
 | |
|     if (error.Fail())
 | |
|       return error;
 | |
| 
 | |
|     if (exe_module_sp)
 | |
|       exe_module_spec.GetFileSpec() = exe_module_sp->GetFileSpec();
 | |
|   }
 | |
| 
 | |
|   if (exe_module_spec.GetFileSpec().Exists()) {
 | |
|     exe_module_spec.GetFileSpec().GetPath(exe_path, sizeof(exe_path));
 | |
|   } else {
 | |
|     launch_info.GetExecutableFile().GetPath(exe_path, sizeof(exe_path));
 | |
|     error.SetErrorStringWithFormat("executable doesn't exist: '%s'", exe_path);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   if (launch_info.GetFlags().Test(eLaunchFlagLaunchInTTY)) {
 | |
| #if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__)
 | |
|     return LaunchInNewTerminalWithAppleScript(exe_path, launch_info);
 | |
| #else
 | |
|     error.SetErrorString("launching a process in a new terminal is not "
 | |
|                          "supported on iOS devices");
 | |
|     return error;
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
 | |
| 
 | |
|   if (ShouldLaunchUsingXPC(launch_info)) {
 | |
|     error = LaunchProcessXPC(exe_path, launch_info, pid);
 | |
|   } else {
 | |
|     error = LaunchProcessPosixSpawn(exe_path, launch_info, pid);
 | |
|   }
 | |
| 
 | |
|   if (pid != LLDB_INVALID_PROCESS_ID) {
 | |
|     // If all went well, then set the process ID into the launch info
 | |
|     launch_info.SetProcessID(pid);
 | |
| 
 | |
|     // Make sure we reap any processes we spawn or we will have zombies.
 | |
|     if (!launch_info.MonitorProcess()) {
 | |
|       const bool monitor_signals = false;
 | |
|       Host::MonitorChildProcessCallback callback = nullptr;
 | |
| 
 | |
|       if (!launch_info.GetFlags().Test(lldb::eLaunchFlagDontSetExitStatus))
 | |
|         callback = Process::SetProcessExitStatus;
 | |
| 
 | |
|       StartMonitoringChildProcess(callback, pid, monitor_signals);
 | |
|     }
 | |
|   } else {
 | |
|     // Invalid process ID, something didn't go well
 | |
|     if (error.Success())
 | |
|       error.SetErrorString("process launch failed for unknown reasons");
 | |
|   }
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status Host::ShellExpandArguments(ProcessLaunchInfo &launch_info) {
 | |
|   Status error;
 | |
|   if (launch_info.GetFlags().Test(eLaunchFlagShellExpandArguments)) {
 | |
|     FileSpec expand_tool_spec;
 | |
|     if (!HostInfo::GetLLDBPath(lldb::ePathTypeSupportExecutableDir,
 | |
|                                expand_tool_spec)) {
 | |
|       error.SetErrorString(
 | |
|           "could not get support executable directory for lldb-argdumper tool");
 | |
|       return error;
 | |
|     }
 | |
|     expand_tool_spec.AppendPathComponent("lldb-argdumper");
 | |
|     if (!expand_tool_spec.Exists()) {
 | |
|       error.SetErrorStringWithFormat(
 | |
|           "could not find the lldb-argdumper tool: %s",
 | |
|           expand_tool_spec.GetPath().c_str());
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     StreamString expand_tool_spec_stream;
 | |
|     expand_tool_spec_stream.Printf("\"%s\"",
 | |
|                                    expand_tool_spec.GetPath().c_str());
 | |
| 
 | |
|     Args expand_command(expand_tool_spec_stream.GetData());
 | |
|     expand_command.AppendArguments(launch_info.GetArguments());
 | |
| 
 | |
|     int status;
 | |
|     std::string output;
 | |
|     FileSpec cwd(launch_info.GetWorkingDirectory());
 | |
|     if (!cwd.Exists()) {
 | |
|       char *wd = getcwd(nullptr, 0);
 | |
|       if (wd == nullptr) {
 | |
|         error.SetErrorStringWithFormat(
 | |
|             "cwd does not exist; cannot launch with shell argument expansion");
 | |
|         return error;
 | |
|       } else {
 | |
|         FileSpec working_dir(wd, false);
 | |
|         free(wd);
 | |
|         launch_info.SetWorkingDirectory(working_dir);
 | |
|       }
 | |
|     }
 | |
|     RunShellCommand(expand_command, cwd, &status, nullptr, &output, 10);
 | |
| 
 | |
|     if (status != 0) {
 | |
|       error.SetErrorStringWithFormat("lldb-argdumper exited with error %d",
 | |
|                                      status);
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     auto data_sp = StructuredData::ParseJSON(output);
 | |
|     if (!data_sp) {
 | |
|       error.SetErrorString("invalid JSON");
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     auto dict_sp = data_sp->GetAsDictionary();
 | |
|     if (!data_sp) {
 | |
|       error.SetErrorString("invalid JSON");
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     auto args_sp = dict_sp->GetObjectForDotSeparatedPath("arguments");
 | |
|     if (!args_sp) {
 | |
|       error.SetErrorString("invalid JSON");
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     auto args_array_sp = args_sp->GetAsArray();
 | |
|     if (!args_array_sp) {
 | |
|       error.SetErrorString("invalid JSON");
 | |
|       return error;
 | |
|     }
 | |
| 
 | |
|     launch_info.GetArguments().Clear();
 | |
| 
 | |
|     for (size_t i = 0; i < args_array_sp->GetSize(); i++) {
 | |
|       auto item_sp = args_array_sp->GetItemAtIndex(i);
 | |
|       if (!item_sp)
 | |
|         continue;
 | |
|       auto str_sp = item_sp->GetAsString();
 | |
|       if (!str_sp)
 | |
|         continue;
 | |
| 
 | |
|       launch_info.GetArguments().AppendArgument(str_sp->GetValue());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| HostThread Host::StartMonitoringChildProcess(
 | |
|     const Host::MonitorChildProcessCallback &callback, lldb::pid_t pid,
 | |
|     bool monitor_signals) {
 | |
|   unsigned long mask = DISPATCH_PROC_EXIT;
 | |
|   if (monitor_signals)
 | |
|     mask |= DISPATCH_PROC_SIGNAL;
 | |
| 
 | |
|   Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_HOST |
 | |
|                                                   LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   dispatch_source_t source = ::dispatch_source_create(
 | |
|       DISPATCH_SOURCE_TYPE_PROC, pid, mask,
 | |
|       ::dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("Host::StartMonitoringChildProcess "
 | |
|                 "(callback, pid=%i, monitor_signals=%i) "
 | |
|                 "source = %p\n",
 | |
|                 static_cast<int>(pid), monitor_signals,
 | |
|                 reinterpret_cast<void *>(source));
 | |
| 
 | |
|   if (source) {
 | |
|     Host::MonitorChildProcessCallback callback_copy = callback;
 | |
|     ::dispatch_source_set_cancel_handler(source, ^{
 | |
|       dispatch_release(source);
 | |
|     });
 | |
|     ::dispatch_source_set_event_handler(source, ^{
 | |
| 
 | |
|       int status = 0;
 | |
|       int wait_pid = 0;
 | |
|       bool cancel = false;
 | |
|       bool exited = false;
 | |
|       wait_pid = llvm::sys::RetryAfterSignal(-1, ::waitpid, pid, &status, 0);
 | |
|       if (wait_pid >= 0) {
 | |
|         int signal = 0;
 | |
|         int exit_status = 0;
 | |
|         const char *status_cstr = NULL;
 | |
|         if (WIFSTOPPED(status)) {
 | |
|           signal = WSTOPSIG(status);
 | |
|           status_cstr = "STOPPED";
 | |
|         } else if (WIFEXITED(status)) {
 | |
|           exit_status = WEXITSTATUS(status);
 | |
|           status_cstr = "EXITED";
 | |
|           exited = true;
 | |
|         } else if (WIFSIGNALED(status)) {
 | |
|           signal = WTERMSIG(status);
 | |
|           status_cstr = "SIGNALED";
 | |
|           exited = true;
 | |
|           exit_status = -1;
 | |
|         } else {
 | |
|           status_cstr = "???";
 | |
|         }
 | |
| 
 | |
|         if (log)
 | |
|           log->Printf("::waitpid (pid = %llu, &status, 0) => pid = %i, status "
 | |
|                       "= 0x%8.8x (%s), signal = %i, exit_status = %i",
 | |
|                       pid, wait_pid, status, status_cstr, signal, exit_status);
 | |
| 
 | |
|         if (callback_copy)
 | |
|           cancel = callback_copy(pid, exited, signal, exit_status);
 | |
| 
 | |
|         if (exited || cancel) {
 | |
|           ::dispatch_source_cancel(source);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     ::dispatch_resume(source);
 | |
|   }
 | |
|   return HostThread();
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| // Log to both stderr and to ASL Logging when running on MacOSX.
 | |
| //----------------------------------------------------------------------
 | |
| void Host::SystemLog(SystemLogType type, const char *format, va_list args) {
 | |
|   if (format && format[0]) {
 | |
|     static aslmsg g_aslmsg = NULL;
 | |
|     if (g_aslmsg == NULL) {
 | |
|       g_aslmsg = ::asl_new(ASL_TYPE_MSG);
 | |
|       char asl_key_sender[PATH_MAX];
 | |
|       snprintf(asl_key_sender, sizeof(asl_key_sender),
 | |
|                "com.apple.LLDB.framework");
 | |
|       ::asl_set(g_aslmsg, ASL_KEY_SENDER, asl_key_sender);
 | |
|     }
 | |
| 
 | |
|     // Copy the va_list so we can log this message twice
 | |
|     va_list copy_args;
 | |
|     va_copy(copy_args, args);
 | |
|     // Log to stderr
 | |
|     ::vfprintf(stderr, format, copy_args);
 | |
|     va_end(copy_args);
 | |
| 
 | |
|     int asl_level;
 | |
|     switch (type) {
 | |
|     case eSystemLogError:
 | |
|       asl_level = ASL_LEVEL_ERR;
 | |
|       break;
 | |
| 
 | |
|     case eSystemLogWarning:
 | |
|       asl_level = ASL_LEVEL_WARNING;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // Log to ASL
 | |
|     ::asl_vlog(NULL, g_aslmsg, asl_level, format, args);
 | |
|   }
 | |
| }
 |