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