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