1061da546Spatrick //===-- darwin-debug.cpp ----------------------------------------*- C++ -*-===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick
9061da546Spatrick // Darwin launch helper
10061da546Spatrick //
11061da546Spatrick // This program was written to allow programs to be launched in a new
12061da546Spatrick // Terminal.app window and have the application be stopped for debugging
13061da546Spatrick // at the program entry point.
14061da546Spatrick //
15061da546Spatrick // Although it uses posix_spawn(), it uses Darwin specific posix spawn
16061da546Spatrick // attribute flags to accomplish its task. It uses an "exec only" flag
17061da546Spatrick // which avoids forking this process, and it uses a "stop at entry"
18061da546Spatrick // flag to stop the program at the entry point.
19061da546Spatrick //
20061da546Spatrick // Since it uses darwin specific flags this code should not be compiled
21061da546Spatrick // on other systems.
22061da546Spatrick #if defined(__APPLE__)
23061da546Spatrick
24*be691f3bSpatrick #include <climits>
25061da546Spatrick #include <crt_externs.h>
26*be691f3bSpatrick #include <csignal>
27*be691f3bSpatrick #include <cstdio>
28*be691f3bSpatrick #include <cstdlib>
29*be691f3bSpatrick #include <cstring>
30061da546Spatrick #include <getopt.h>
31061da546Spatrick #include <mach/machine.h>
32061da546Spatrick #include <spawn.h>
33061da546Spatrick #include <sys/socket.h>
34061da546Spatrick #include <sys/stat.h>
35061da546Spatrick #include <sys/types.h>
36061da546Spatrick #include <sys/un.h>
37061da546Spatrick
38061da546Spatrick #include <string>
39061da546Spatrick
40061da546Spatrick #ifndef _POSIX_SPAWN_DISABLE_ASLR
41061da546Spatrick #define _POSIX_SPAWN_DISABLE_ASLR 0x0100
42061da546Spatrick #endif
43061da546Spatrick
44061da546Spatrick #define streq(a, b) strcmp(a, b) == 0
45061da546Spatrick
46061da546Spatrick static struct option g_long_options[] = {
47061da546Spatrick {"arch", required_argument, NULL, 'a'},
48061da546Spatrick {"disable-aslr", no_argument, NULL, 'd'},
49061da546Spatrick {"no-env", no_argument, NULL, 'e'},
50061da546Spatrick {"help", no_argument, NULL, 'h'},
51061da546Spatrick {"setsid", no_argument, NULL, 's'},
52061da546Spatrick {"unix-socket", required_argument, NULL, 'u'},
53061da546Spatrick {"working-dir", required_argument, NULL, 'w'},
54061da546Spatrick {"env", required_argument, NULL, 'E'},
55061da546Spatrick {NULL, 0, NULL, 0}};
56061da546Spatrick
usage()57061da546Spatrick static void usage() {
58061da546Spatrick puts("NAME\n"
59061da546Spatrick " darwin-debug -- posix spawn a process that is stopped at the entry "
60061da546Spatrick "point\n"
61061da546Spatrick " for debugging.\n"
62061da546Spatrick "\n"
63061da546Spatrick "SYNOPSIS\n"
64061da546Spatrick " darwin-debug --unix-socket=<SOCKET> [--arch=<ARCH>] "
65061da546Spatrick "[--working-dir=<PATH>] [--disable-aslr] [--no-env] [--setsid] [--help] "
66061da546Spatrick "-- <PROGRAM> [<PROGRAM-ARG> <PROGRAM-ARG> ....]\n"
67061da546Spatrick "\n"
68061da546Spatrick "DESCRIPTION\n"
69061da546Spatrick " darwin-debug will exec itself into a child process <PROGRAM> that "
70061da546Spatrick "is\n"
71061da546Spatrick " halted for debugging. It does this by using posix_spawn() along "
72061da546Spatrick "with\n"
73061da546Spatrick " darwin specific posix_spawn flags that allows exec only (no fork), "
74061da546Spatrick "and\n"
75061da546Spatrick " stop at the program entry point. Any program arguments "
76061da546Spatrick "<PROGRAM-ARG> are\n"
77061da546Spatrick " passed on to the exec as the arguments for the new process. The "
78061da546Spatrick "current\n"
79061da546Spatrick " environment will be passed to the new process unless the "
80061da546Spatrick "\"--no-env\"\n"
81061da546Spatrick " option is used. A unix socket must be supplied using the\n"
82061da546Spatrick " --unix-socket=<SOCKET> option so the calling program can handshake "
83061da546Spatrick "with\n"
84061da546Spatrick " this process and get its process id.\n"
85061da546Spatrick "\n"
86061da546Spatrick "EXAMPLE\n"
87061da546Spatrick " darwin-debug --arch=i386 -- /bin/ls -al /tmp\n");
88061da546Spatrick exit(1);
89061da546Spatrick }
90061da546Spatrick
exit_with_errno(int err,const char * prefix)91061da546Spatrick static void exit_with_errno(int err, const char *prefix) {
92061da546Spatrick if (err) {
93061da546Spatrick fprintf(stderr, "%s%s", prefix ? prefix : "", strerror(err));
94061da546Spatrick exit(err);
95061da546Spatrick }
96061da546Spatrick }
97061da546Spatrick
posix_spawn_for_debug(char * const * argv,char * const * envp,const char * working_dir,cpu_type_t cpu_type,int disable_aslr)98061da546Spatrick pid_t posix_spawn_for_debug(char *const *argv, char *const *envp,
99061da546Spatrick const char *working_dir, cpu_type_t cpu_type,
100061da546Spatrick int disable_aslr) {
101061da546Spatrick pid_t pid = 0;
102061da546Spatrick
103061da546Spatrick const char *path = argv[0];
104061da546Spatrick
105061da546Spatrick posix_spawnattr_t attr;
106061da546Spatrick
107061da546Spatrick exit_with_errno(::posix_spawnattr_init(&attr),
108061da546Spatrick "::posix_spawnattr_init (&attr) error: ");
109061da546Spatrick
110061da546Spatrick // Here we are using a darwin specific feature that allows us to exec only
111061da546Spatrick // since we want this program to turn into the program we want to debug,
112061da546Spatrick // and also have the new program start suspended (right at __dyld_start)
113061da546Spatrick // so we can debug it
114061da546Spatrick short flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETEXEC |
115061da546Spatrick POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK;
116061da546Spatrick
117061da546Spatrick // Disable ASLR if we were asked to
118061da546Spatrick if (disable_aslr)
119061da546Spatrick flags |= _POSIX_SPAWN_DISABLE_ASLR;
120061da546Spatrick
121061da546Spatrick sigset_t no_signals;
122061da546Spatrick sigset_t all_signals;
123061da546Spatrick sigemptyset(&no_signals);
124061da546Spatrick sigfillset(&all_signals);
125061da546Spatrick ::posix_spawnattr_setsigmask(&attr, &no_signals);
126061da546Spatrick ::posix_spawnattr_setsigdefault(&attr, &all_signals);
127061da546Spatrick
128061da546Spatrick // Set the flags we just made into our posix spawn attributes
129061da546Spatrick exit_with_errno(::posix_spawnattr_setflags(&attr, flags),
130061da546Spatrick "::posix_spawnattr_setflags (&attr, flags) error: ");
131061da546Spatrick
132061da546Spatrick // Another darwin specific thing here where we can select the architecture
133061da546Spatrick // of the binary we want to re-exec as.
134061da546Spatrick if (cpu_type != 0) {
135061da546Spatrick size_t ocount = 0;
136061da546Spatrick exit_with_errno(
137061da546Spatrick ::posix_spawnattr_setbinpref_np(&attr, 1, &cpu_type, &ocount),
138061da546Spatrick "posix_spawnattr_setbinpref_np () error: ");
139061da546Spatrick }
140061da546Spatrick
141061da546Spatrick // I wish there was a posix_spawn flag to change the working directory of
142061da546Spatrick // the inferior process we will spawn, but there currently isn't. If there
143061da546Spatrick // ever is a better way to do this, we should use it. I would rather not
144061da546Spatrick // manually fork, chdir in the child process, and then posix_spawn with exec
145061da546Spatrick // as the whole reason for doing posix_spawn is to not hose anything up
146061da546Spatrick // after the fork and prior to the exec...
147061da546Spatrick if (working_dir)
148061da546Spatrick ::chdir(working_dir);
149061da546Spatrick
150061da546Spatrick exit_with_errno(::posix_spawnp(&pid, path, NULL, &attr, (char *const *)argv,
151061da546Spatrick (char *const *)envp),
152061da546Spatrick "posix_spawn() error: ");
153061da546Spatrick
154061da546Spatrick // This code will only be reached if the posix_spawn exec failed...
155061da546Spatrick ::posix_spawnattr_destroy(&attr);
156061da546Spatrick
157061da546Spatrick return pid;
158061da546Spatrick }
159061da546Spatrick
main(int argc,char * const * argv,char * const * envp,const char ** apple)160061da546Spatrick int main(int argc, char *const *argv, char *const *envp, const char **apple) {
161061da546Spatrick #if defined(DEBUG_LLDB_LAUNCHER)
162061da546Spatrick const char *program_name = strrchr(apple[0], '/');
163061da546Spatrick
164061da546Spatrick if (program_name)
165061da546Spatrick program_name++; // Skip the last slash..
166061da546Spatrick else
167061da546Spatrick program_name = apple[0];
168061da546Spatrick
169061da546Spatrick printf("%s called with:\n", program_name);
170061da546Spatrick for (int i = 0; i < argc; ++i)
171061da546Spatrick printf("argv[%u] = '%s'\n", i, argv[i]);
172061da546Spatrick #endif
173061da546Spatrick
174061da546Spatrick cpu_type_t cpu_type = 0;
175061da546Spatrick bool show_usage = false;
176061da546Spatrick int ch;
177061da546Spatrick int disable_aslr = 0; // By default we disable ASLR
178061da546Spatrick bool pass_env = true;
179061da546Spatrick std::string unix_socket_name;
180061da546Spatrick std::string working_dir;
181061da546Spatrick
182061da546Spatrick #if __GLIBC__
183061da546Spatrick optind = 0;
184061da546Spatrick #else
185061da546Spatrick optreset = 1;
186061da546Spatrick optind = 1;
187061da546Spatrick #endif
188061da546Spatrick
189061da546Spatrick while ((ch = getopt_long_only(argc, argv, "a:deE:hsu:?", g_long_options,
190061da546Spatrick NULL)) != -1) {
191061da546Spatrick switch (ch) {
192061da546Spatrick case 0:
193061da546Spatrick break;
194061da546Spatrick
195061da546Spatrick case 'a': // "-a i386" or "--arch=i386"
196061da546Spatrick if (optarg) {
197061da546Spatrick if (streq(optarg, "i386"))
198061da546Spatrick cpu_type = CPU_TYPE_I386;
199061da546Spatrick else if (streq(optarg, "x86_64"))
200061da546Spatrick cpu_type = CPU_TYPE_X86_64;
201061da546Spatrick else if (streq(optarg, "x86_64h"))
202061da546Spatrick cpu_type = 0; // Don't set CPU type when we have x86_64h
203061da546Spatrick else if (strstr(optarg, "arm") == optarg)
204061da546Spatrick cpu_type = CPU_TYPE_ARM;
205061da546Spatrick else {
206061da546Spatrick ::fprintf(stderr, "error: unsupported cpu type '%s'\n", optarg);
207061da546Spatrick ::exit(1);
208061da546Spatrick }
209061da546Spatrick }
210061da546Spatrick break;
211061da546Spatrick
212061da546Spatrick case 'd':
213061da546Spatrick disable_aslr = 1;
214061da546Spatrick break;
215061da546Spatrick
216061da546Spatrick case 'e':
217061da546Spatrick pass_env = false;
218061da546Spatrick break;
219061da546Spatrick
220061da546Spatrick case 'E': {
221061da546Spatrick // Since we will exec this program into our new program, we can just set
222061da546Spatrick // environment
223061da546Spatrick // variables in this process and they will make it into the child process.
224061da546Spatrick std::string name;
225061da546Spatrick std::string value;
226061da546Spatrick const char *equal_pos = strchr(optarg, '=');
227061da546Spatrick if (equal_pos) {
228061da546Spatrick name.assign(optarg, equal_pos - optarg);
229061da546Spatrick value.assign(equal_pos + 1);
230061da546Spatrick } else {
231061da546Spatrick name = optarg;
232061da546Spatrick }
233061da546Spatrick ::setenv(name.c_str(), value.c_str(), 1);
234061da546Spatrick } break;
235061da546Spatrick
236061da546Spatrick case 's':
237061da546Spatrick // Create a new session to avoid having control-C presses kill our current
238061da546Spatrick // terminal session when this program is launched from a .command file
239061da546Spatrick ::setsid();
240061da546Spatrick break;
241061da546Spatrick
242061da546Spatrick case 'u':
243061da546Spatrick unix_socket_name.assign(optarg);
244061da546Spatrick break;
245061da546Spatrick
246061da546Spatrick case 'w': {
247061da546Spatrick struct stat working_dir_stat;
248061da546Spatrick if (stat(optarg, &working_dir_stat) == 0)
249061da546Spatrick working_dir.assign(optarg);
250061da546Spatrick else
251061da546Spatrick ::fprintf(stderr, "warning: working directory doesn't exist: '%s'\n",
252061da546Spatrick optarg);
253061da546Spatrick } break;
254061da546Spatrick
255061da546Spatrick case 'h':
256061da546Spatrick case '?':
257061da546Spatrick default:
258061da546Spatrick show_usage = true;
259061da546Spatrick break;
260061da546Spatrick }
261061da546Spatrick }
262061da546Spatrick argc -= optind;
263061da546Spatrick argv += optind;
264061da546Spatrick
265061da546Spatrick if (show_usage || argc <= 0 || unix_socket_name.empty())
266061da546Spatrick usage();
267061da546Spatrick
268061da546Spatrick #if defined(DEBUG_LLDB_LAUNCHER)
269061da546Spatrick printf("\n%s post options:\n", program_name);
270061da546Spatrick for (int i = 0; i < argc; ++i)
271061da546Spatrick printf("argv[%u] = '%s'\n", i, argv[i]);
272061da546Spatrick #endif
273061da546Spatrick
274061da546Spatrick // Open the socket that was passed in as an option
275061da546Spatrick struct sockaddr_un saddr_un;
276061da546Spatrick int s = ::socket(AF_UNIX, SOCK_STREAM, 0);
277061da546Spatrick if (s < 0) {
278061da546Spatrick perror("error: socket (AF_UNIX, SOCK_STREAM, 0)");
279061da546Spatrick exit(1);
280061da546Spatrick }
281061da546Spatrick
282061da546Spatrick saddr_un.sun_family = AF_UNIX;
283061da546Spatrick ::strncpy(saddr_un.sun_path, unix_socket_name.c_str(),
284061da546Spatrick sizeof(saddr_un.sun_path) - 1);
285061da546Spatrick saddr_un.sun_path[sizeof(saddr_un.sun_path) - 1] = '\0';
286061da546Spatrick saddr_un.sun_len = SUN_LEN(&saddr_un);
287061da546Spatrick
288061da546Spatrick if (::connect(s, (struct sockaddr *)&saddr_un, SUN_LEN(&saddr_un)) < 0) {
289061da546Spatrick perror("error: connect (socket, &saddr_un, saddr_un_len)");
290061da546Spatrick exit(1);
291061da546Spatrick }
292061da546Spatrick
293061da546Spatrick // We were able to connect to the socket, now write our PID so whomever
294061da546Spatrick // launched us will know this process's ID
295061da546Spatrick char pid_str[64];
296061da546Spatrick const int pid_str_len =
297061da546Spatrick ::snprintf(pid_str, sizeof(pid_str), "%i", ::getpid());
298061da546Spatrick const int bytes_sent = ::send(s, pid_str, pid_str_len, 0);
299061da546Spatrick
300061da546Spatrick if (pid_str_len != bytes_sent) {
301061da546Spatrick perror("error: send (s, pid_str, pid_str_len, 0)");
302061da546Spatrick exit(1);
303061da546Spatrick }
304061da546Spatrick
305061da546Spatrick // We are done with the socket
306061da546Spatrick close(s);
307061da546Spatrick
308061da546Spatrick system("clear");
309061da546Spatrick printf("Launching: '%s'\n", argv[0]);
310061da546Spatrick if (working_dir.empty()) {
311061da546Spatrick char cwd[PATH_MAX];
312061da546Spatrick const char *cwd_ptr = getcwd(cwd, sizeof(cwd));
313061da546Spatrick printf("Working directory: '%s'\n", cwd_ptr);
314061da546Spatrick } else {
315061da546Spatrick printf("Working directory: '%s'\n", working_dir.c_str());
316061da546Spatrick }
317061da546Spatrick printf("%i arguments:\n", argc);
318061da546Spatrick
319061da546Spatrick for (int i = 0; i < argc; ++i)
320061da546Spatrick printf("argv[%u] = '%s'\n", i, argv[i]);
321061da546Spatrick
322061da546Spatrick // Now we posix spawn to exec this process into the inferior that we want
323061da546Spatrick // to debug.
324061da546Spatrick posix_spawn_for_debug(
325061da546Spatrick argv,
326061da546Spatrick pass_env ? *_NSGetEnviron() : NULL, // Pass current environment as we may
327061da546Spatrick // have modified it if "--env" options
328061da546Spatrick // was used, do NOT pass "envp" here
329061da546Spatrick working_dir.empty() ? NULL : working_dir.c_str(), cpu_type, disable_aslr);
330061da546Spatrick
331061da546Spatrick return 0;
332061da546Spatrick }
333061da546Spatrick
334061da546Spatrick #endif // #if defined (__APPLE__)
335