1 //===-- Launcher.cpp --------------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 //---------------------------------------------------------------------- 10 // Darwin launch helper 11 // 12 // This program was written to allow programs to be launched in a new 13 // Terminal.app window and have the application be stopped for debugging 14 // at the program entry point. 15 // 16 // Although it uses posix_spawn(), it uses Darwin specific posix spawn 17 // attribute flags to accomplish its task. It uses an "exec only" flag 18 // which avoids forking this process, and it uses a "stop at entry" 19 // flag to stop the program at the entry point. 20 // 21 // Since it uses darwin specific flags this code should not be compiled 22 // on other systems. 23 //---------------------------------------------------------------------- 24 #if defined(__APPLE__) 25 26 #include <crt_externs.h> 27 #include <getopt.h> 28 #include <limits.h> 29 #include <mach/machine.h> 30 #include <signal.h> 31 #include <spawn.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <sys/socket.h> 36 #include <sys/stat.h> 37 #include <sys/types.h> 38 #include <sys/un.h> 39 40 #include <string> 41 42 #ifndef _POSIX_SPAWN_DISABLE_ASLR 43 #define _POSIX_SPAWN_DISABLE_ASLR 0x0100 44 #endif 45 46 #define streq(a, b) strcmp(a, b) == 0 47 48 static struct option g_long_options[] = { 49 {"arch", required_argument, NULL, 'a'}, 50 {"disable-aslr", no_argument, NULL, 'd'}, 51 {"no-env", no_argument, NULL, 'e'}, 52 {"help", no_argument, NULL, 'h'}, 53 {"setsid", no_argument, NULL, 's'}, 54 {"unix-socket", required_argument, NULL, 'u'}, 55 {"working-dir", required_argument, NULL, 'w'}, 56 {"env", required_argument, NULL, 'E'}, 57 {NULL, 0, NULL, 0}}; 58 59 static void usage() { 60 puts("NAME\n" 61 " darwin-debug -- posix spawn a process that is stopped at the entry " 62 "point\n" 63 " for debugging.\n" 64 "\n" 65 "SYNOPSIS\n" 66 " darwin-debug --unix-socket=<SOCKET> [--arch=<ARCH>] " 67 "[--working-dir=<PATH>] [--disable-aslr] [--no-env] [--setsid] [--help] " 68 "-- <PROGRAM> [<PROGRAM-ARG> <PROGRAM-ARG> ....]\n" 69 "\n" 70 "DESCRIPTION\n" 71 " darwin-debug will exec itself into a child process <PROGRAM> that " 72 "is\n" 73 " halted for debugging. It does this by using posix_spawn() along " 74 "with\n" 75 " darwin specific posix_spawn flags that allows exec only (no fork), " 76 "and\n" 77 " stop at the program entry point. Any program arguments " 78 "<PROGRAM-ARG> are\n" 79 " passed on to the exec as the arguments for the new process. The " 80 "current\n" 81 " environment will be passed to the new process unless the " 82 "\"--no-env\"\n" 83 " option is used. A unix socket must be supplied using the\n" 84 " --unix-socket=<SOCKET> option so the calling program can handshake " 85 "with\n" 86 " this process and get its process id.\n" 87 "\n" 88 "EXAMPLE\n" 89 " darwin-debug --arch=i386 -- /bin/ls -al /tmp\n"); 90 exit(1); 91 } 92 93 static void exit_with_errno(int err, const char *prefix) { 94 if (err) { 95 fprintf(stderr, "%s%s", prefix ? prefix : "", strerror(err)); 96 exit(err); 97 } 98 } 99 100 pid_t posix_spawn_for_debug(char *const *argv, char *const *envp, 101 const char *working_dir, cpu_type_t cpu_type, 102 int disable_aslr) { 103 pid_t pid = 0; 104 105 const char *path = argv[0]; 106 107 posix_spawnattr_t attr; 108 109 exit_with_errno(::posix_spawnattr_init(&attr), 110 "::posix_spawnattr_init (&attr) error: "); 111 112 // Here we are using a darwin specific feature that allows us to exec only 113 // since we want this program to turn into the program we want to debug, 114 // and also have the new program start suspended (right at __dyld_start) 115 // so we can debug it 116 short flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETEXEC | 117 POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK; 118 119 // Disable ASLR if we were asked to 120 if (disable_aslr) 121 flags |= _POSIX_SPAWN_DISABLE_ASLR; 122 123 sigset_t no_signals; 124 sigset_t all_signals; 125 sigemptyset(&no_signals); 126 sigfillset(&all_signals); 127 ::posix_spawnattr_setsigmask(&attr, &no_signals); 128 ::posix_spawnattr_setsigdefault(&attr, &all_signals); 129 130 // Set the flags we just made into our posix spawn attributes 131 exit_with_errno(::posix_spawnattr_setflags(&attr, flags), 132 "::posix_spawnattr_setflags (&attr, flags) error: "); 133 134 // Another darwin specific thing here where we can select the architecture 135 // of the binary we want to re-exec as. 136 if (cpu_type != 0) { 137 size_t ocount = 0; 138 exit_with_errno( 139 ::posix_spawnattr_setbinpref_np(&attr, 1, &cpu_type, &ocount), 140 "posix_spawnattr_setbinpref_np () error: "); 141 } 142 143 // I wish there was a posix_spawn flag to change the working directory of 144 // the inferior process we will spawn, but there currently isn't. If there 145 // ever is a better way to do this, we should use it. I would rather not 146 // manually fork, chdir in the child process, and then posix_spawn with exec 147 // as the whole reason for doing posix_spawn is to not hose anything up 148 // after the fork and prior to the exec... 149 if (working_dir) 150 ::chdir(working_dir); 151 152 exit_with_errno(::posix_spawnp(&pid, path, NULL, &attr, (char *const *)argv, 153 (char *const *)envp), 154 "posix_spawn() error: "); 155 156 // This code will only be reached if the posix_spawn exec failed... 157 ::posix_spawnattr_destroy(&attr); 158 159 return pid; 160 } 161 162 int main(int argc, char *const *argv, char *const *envp, const char **apple) { 163 #if defined(DEBUG_LLDB_LAUNCHER) 164 const char *program_name = strrchr(apple[0], '/'); 165 166 if (program_name) 167 program_name++; // Skip the last slash.. 168 else 169 program_name = apple[0]; 170 171 printf("%s called with:\n", program_name); 172 for (int i = 0; i < argc; ++i) 173 printf("argv[%u] = '%s'\n", i, argv[i]); 174 #endif 175 176 cpu_type_t cpu_type = 0; 177 bool show_usage = false; 178 int ch; 179 int disable_aslr = 0; // By default we disable ASLR 180 bool pass_env = true; 181 std::string unix_socket_name; 182 std::string working_dir; 183 184 #if __GLIBC__ 185 optind = 0; 186 #else 187 optreset = 1; 188 optind = 1; 189 #endif 190 191 while ((ch = getopt_long_only(argc, argv, "a:deE:hsu:?", g_long_options, 192 NULL)) != -1) { 193 switch (ch) { 194 case 0: 195 break; 196 197 case 'a': // "-a i386" or "--arch=i386" 198 if (optarg) { 199 if (streq(optarg, "i386")) 200 cpu_type = CPU_TYPE_I386; 201 else if (streq(optarg, "x86_64")) 202 cpu_type = CPU_TYPE_X86_64; 203 else if (streq(optarg, "x86_64h")) 204 cpu_type = 0; // Don't set CPU type when we have x86_64h 205 else if (strstr(optarg, "arm") == optarg) 206 cpu_type = CPU_TYPE_ARM; 207 else { 208 ::fprintf(stderr, "error: unsupported cpu type '%s'\n", optarg); 209 ::exit(1); 210 } 211 } 212 break; 213 214 case 'd': 215 disable_aslr = 1; 216 break; 217 218 case 'e': 219 pass_env = false; 220 break; 221 222 case 'E': { 223 // Since we will exec this program into our new program, we can just set 224 // environment 225 // variables in this process and they will make it into the child process. 226 std::string name; 227 std::string value; 228 const char *equal_pos = strchr(optarg, '='); 229 if (equal_pos) { 230 name.assign(optarg, equal_pos - optarg); 231 value.assign(equal_pos + 1); 232 } else { 233 name = optarg; 234 } 235 ::setenv(name.c_str(), value.c_str(), 1); 236 } break; 237 238 case 's': 239 // Create a new session to avoid having control-C presses kill our current 240 // terminal session when this program is launched from a .command file 241 ::setsid(); 242 break; 243 244 case 'u': 245 unix_socket_name.assign(optarg); 246 break; 247 248 case 'w': { 249 struct stat working_dir_stat; 250 if (stat(optarg, &working_dir_stat) == 0) 251 working_dir.assign(optarg); 252 else 253 ::fprintf(stderr, "warning: working directory doesn't exist: '%s'\n", 254 optarg); 255 } break; 256 257 case 'h': 258 case '?': 259 default: 260 show_usage = true; 261 break; 262 } 263 } 264 argc -= optind; 265 argv += optind; 266 267 if (show_usage || argc <= 0 || unix_socket_name.empty()) 268 usage(); 269 270 #if defined(DEBUG_LLDB_LAUNCHER) 271 printf("\n%s post options:\n", program_name); 272 for (int i = 0; i < argc; ++i) 273 printf("argv[%u] = '%s'\n", i, argv[i]); 274 #endif 275 276 // Open the socket that was passed in as an option 277 struct sockaddr_un saddr_un; 278 int s = ::socket(AF_UNIX, SOCK_STREAM, 0); 279 if (s < 0) { 280 perror("error: socket (AF_UNIX, SOCK_STREAM, 0)"); 281 exit(1); 282 } 283 284 saddr_un.sun_family = AF_UNIX; 285 ::strncpy(saddr_un.sun_path, unix_socket_name.c_str(), 286 sizeof(saddr_un.sun_path) - 1); 287 saddr_un.sun_path[sizeof(saddr_un.sun_path) - 1] = '\0'; 288 saddr_un.sun_len = SUN_LEN(&saddr_un); 289 290 if (::connect(s, (struct sockaddr *)&saddr_un, SUN_LEN(&saddr_un)) < 0) { 291 perror("error: connect (socket, &saddr_un, saddr_un_len)"); 292 exit(1); 293 } 294 295 // We were able to connect to the socket, now write our PID so whomever 296 // launched us will know this process's ID 297 char pid_str[64]; 298 const int pid_str_len = 299 ::snprintf(pid_str, sizeof(pid_str), "%i", ::getpid()); 300 const int bytes_sent = ::send(s, pid_str, pid_str_len, 0); 301 302 if (pid_str_len != bytes_sent) { 303 perror("error: send (s, pid_str, pid_str_len, 0)"); 304 exit(1); 305 } 306 307 // We are done with the socket 308 close(s); 309 310 system("clear"); 311 printf("Launching: '%s'\n", argv[0]); 312 if (working_dir.empty()) { 313 char cwd[PATH_MAX]; 314 const char *cwd_ptr = getcwd(cwd, sizeof(cwd)); 315 printf("Working directory: '%s'\n", cwd_ptr); 316 } else { 317 printf("Working directory: '%s'\n", working_dir.c_str()); 318 } 319 printf("%i arguments:\n", argc); 320 321 for (int i = 0; i < argc; ++i) 322 printf("argv[%u] = '%s'\n", i, argv[i]); 323 324 // Now we posix spawn to exec this process into the inferior that we want 325 // to debug. 326 posix_spawn_for_debug( 327 argv, 328 pass_env ? *_NSGetEnviron() : NULL, // Pass current environment as we may 329 // have modified it if "--env" options 330 // was used, do NOT pass "envp" here 331 working_dir.empty() ? NULL : working_dir.c_str(), cpu_type, disable_aslr); 332 333 return 0; 334 } 335 336 #endif // #if defined (__APPLE__) 337