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 while ((ch = getopt_long_only(argc, argv, "a:deE:hsu:?", g_long_options, NULL)) != -1) 190 { 191 switch (ch) 192 { 193 case 0: 194 break; 195 196 case 'a': // "-a i386" or "--arch=i386" 197 if (optarg) 198 { 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 (strstr (optarg, "arm") == optarg) 204 cpu_type = CPU_TYPE_ARM; 205 else 206 { 207 ::fprintf (stderr, "error: unsupported cpu type '%s'\n", optarg); 208 ::exit (1); 209 } 210 } 211 break; 212 213 case 'd': 214 disable_aslr = 1; 215 break; 216 217 case 'e': 218 pass_env = false; 219 break; 220 221 case 'E': 222 { 223 // Since we will exec this program into our new program, we can just set environment 224 // varaibles in this process and they will make it into the child process. 225 std::string name; 226 std::string value; 227 const char *equal_pos = strchr (optarg, '='); 228 if (equal_pos) 229 { 230 name.assign (optarg, equal_pos - optarg); 231 value.assign (equal_pos + 1); 232 } 233 else 234 { 235 name = optarg; 236 } 237 ::setenv (name.c_str(), value.c_str(), 1); 238 } 239 break; 240 241 case 's': 242 // Create a new session to avoid having control-C presses kill our current 243 // terminal session when this program is launched from a .command file 244 ::setsid(); 245 break; 246 247 case 'u': 248 unix_socket_name.assign (optarg); 249 break; 250 251 case 'w': 252 { 253 struct stat working_dir_stat; 254 if (stat (optarg, &working_dir_stat) == 0) 255 working_dir.assign (optarg); 256 else 257 ::fprintf(stderr, "warning: working directory doesn't exist: '%s'\n", optarg); 258 } 259 break; 260 261 case 'h': 262 case '?': 263 default: 264 show_usage = true; 265 break; 266 } 267 } 268 argc -= optind; 269 argv += optind; 270 271 if (show_usage || argc <= 0 || unix_socket_name.empty()) 272 usage(); 273 274 #if defined (DEBUG_LLDB_LAUNCHER) 275 printf ("\n%s post options:\n", program_name); 276 for (int i=0; i<argc; ++i) 277 printf ("argv[%u] = '%s'\n", i, argv[i]); 278 #endif 279 280 // Open the socket that was passed in as an option 281 struct sockaddr_un saddr_un; 282 int s = ::socket (AF_UNIX, SOCK_STREAM, 0); 283 if (s < 0) 284 { 285 perror("error: socket (AF_UNIX, SOCK_STREAM, 0)"); 286 exit(1); 287 } 288 289 saddr_un.sun_family = AF_UNIX; 290 ::strncpy(saddr_un.sun_path, unix_socket_name.c_str(), sizeof(saddr_un.sun_path) - 1); 291 saddr_un.sun_path[sizeof(saddr_un.sun_path) - 1] = '\0'; 292 saddr_un.sun_len = SUN_LEN (&saddr_un); 293 294 if (::connect (s, (struct sockaddr *)&saddr_un, SUN_LEN (&saddr_un)) < 0) 295 { 296 perror("error: connect (socket, &saddr_un, saddr_un_len)"); 297 exit(1); 298 } 299 300 // We were able to connect to the socket, now write our PID so whomever 301 // launched us will know this process's ID 302 char pid_str[64]; 303 const int pid_str_len = ::snprintf (pid_str, sizeof(pid_str), "%i", ::getpid()); 304 const int bytes_sent = ::send (s, pid_str, pid_str_len, 0); 305 306 if (pid_str_len != bytes_sent) 307 { 308 perror("error: send (s, pid_str, pid_str_len, 0)"); 309 exit (1); 310 } 311 312 // We are done with the socket 313 close (s); 314 315 system("clear"); 316 printf ("Launching: '%s'\n", argv[0]); 317 if (working_dir.empty()) 318 { 319 char cwd[PATH_MAX]; 320 const char *cwd_ptr = getcwd(cwd, sizeof(cwd)); 321 printf ("Working directory: '%s'\n", cwd_ptr); 322 } 323 else 324 { 325 printf ("Working directory: '%s'\n", working_dir.c_str()); 326 } 327 printf ("%i arguments:\n", argc); 328 329 for (int i=0; i<argc; ++i) 330 printf ("argv[%u] = '%s'\n", i, argv[i]); 331 332 // Now we posix spawn to exec this process into the inferior that we want 333 // to debug. 334 posix_spawn_for_debug (argv, 335 pass_env ? *_NSGetEnviron() : NULL, // Pass current environment as we may have modified it if "--env" options was used, do NOT pass "envp" here 336 working_dir.empty() ? NULL : working_dir.c_str(), 337 cpu_type, 338 disable_aslr); 339 340 return 0; 341 } 342 343 #endif // #if defined (__APPLE__) 344 345