1 //===-- darwin-debug.cpp ----------------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 // Darwin launch helper
11 // This program was written to allow programs to be launched in a new
12 // Terminal.app window and have the application be stopped for debugging
13 // at the program entry point.
15 // Although it uses posix_spawn(), it uses Darwin specific posix spawn
16 // attribute flags to accomplish its task. It uses an "exec only" flag
17 // which avoids forking this process, and it uses a "stop at entry"
18 // flag to stop the program at the entry point.
20 // Since it uses darwin specific flags this code should not be compiled
22 #if defined(__APPLE__)
25 #include <crt_externs.h>
31 #include <mach/machine.h>
33 #include <sys/socket.h>
35 #include <sys/types.h>
40 #ifndef _POSIX_SPAWN_DISABLE_ASLR
41 #define _POSIX_SPAWN_DISABLE_ASLR 0x0100
44 #define streq(a, b) strcmp(a, b) == 0
46 static struct option g_long_options
[] = {
47 {"arch", required_argument
, NULL
, 'a'},
48 {"disable-aslr", no_argument
, NULL
, 'd'},
49 {"no-env", no_argument
, NULL
, 'e'},
50 {"help", no_argument
, NULL
, 'h'},
51 {"setsid", no_argument
, NULL
, 's'},
52 {"unix-socket", required_argument
, NULL
, 'u'},
53 {"working-dir", required_argument
, NULL
, 'w'},
54 {"env", required_argument
, NULL
, 'E'},
59 " darwin-debug -- posix spawn a process that is stopped at the entry "
64 " darwin-debug --unix-socket=<SOCKET> [--arch=<ARCH>] "
65 "[--working-dir=<PATH>] [--disable-aslr] [--no-env] [--setsid] [--help] "
66 "-- <PROGRAM> [<PROGRAM-ARG> <PROGRAM-ARG> ....]\n"
69 " darwin-debug will exec itself into a child process <PROGRAM> that "
71 " halted for debugging. It does this by using posix_spawn() along "
73 " darwin specific posix_spawn flags that allows exec only (no fork), "
75 " stop at the program entry point. Any program arguments "
77 " passed on to the exec as the arguments for the new process. The "
79 " environment will be passed to the new process unless the "
81 " option is used. A unix socket must be supplied using the\n"
82 " --unix-socket=<SOCKET> option so the calling program can handshake "
84 " this process and get its process id.\n"
87 " darwin-debug --arch=i386 -- /bin/ls -al /tmp\n");
91 static void exit_with_errno(int err
, const char *prefix
) {
93 fprintf(stderr
, "%s%s", prefix
? prefix
: "", strerror(err
));
98 pid_t
posix_spawn_for_debug(char *const *argv
, char *const *envp
,
99 const char *working_dir
, cpu_type_t cpu_type
,
103 const char *path
= argv
[0];
105 posix_spawnattr_t attr
;
107 exit_with_errno(::posix_spawnattr_init(&attr
),
108 "::posix_spawnattr_init (&attr) error: ");
110 // Here we are using a darwin specific feature that allows us to exec only
111 // since we want this program to turn into the program we want to debug,
112 // and also have the new program start suspended (right at __dyld_start)
113 // so we can debug it
114 short flags
= POSIX_SPAWN_START_SUSPENDED
| POSIX_SPAWN_SETEXEC
|
115 POSIX_SPAWN_SETSIGDEF
| POSIX_SPAWN_SETSIGMASK
;
117 // Disable ASLR if we were asked to
119 flags
|= _POSIX_SPAWN_DISABLE_ASLR
;
122 sigset_t all_signals
;
123 sigemptyset(&no_signals
);
124 sigfillset(&all_signals
);
125 ::posix_spawnattr_setsigmask(&attr
, &no_signals
);
126 ::posix_spawnattr_setsigdefault(&attr
, &all_signals
);
128 // Set the flags we just made into our posix spawn attributes
129 exit_with_errno(::posix_spawnattr_setflags(&attr
, flags
),
130 "::posix_spawnattr_setflags (&attr, flags) error: ");
132 // Another darwin specific thing here where we can select the architecture
133 // of the binary we want to re-exec as.
137 ::posix_spawnattr_setbinpref_np(&attr
, 1, &cpu_type
, &ocount
),
138 "posix_spawnattr_setbinpref_np () error: ");
141 // I wish there was a posix_spawn flag to change the working directory of
142 // the inferior process we will spawn, but there currently isn't. If there
143 // ever is a better way to do this, we should use it. I would rather not
144 // manually fork, chdir in the child process, and then posix_spawn with exec
145 // as the whole reason for doing posix_spawn is to not hose anything up
146 // after the fork and prior to the exec...
148 ::chdir(working_dir
);
150 exit_with_errno(::posix_spawnp(&pid
, path
, NULL
, &attr
, (char *const *)argv
,
151 (char *const *)envp
),
152 "posix_spawn() error: ");
154 // This code will only be reached if the posix_spawn exec failed...
155 ::posix_spawnattr_destroy(&attr
);
160 int main(int argc
, char *const *argv
, char *const *envp
, const char **apple
) {
161 #if defined(DEBUG_LLDB_LAUNCHER)
162 const char *program_name
= strrchr(apple
[0], '/');
165 program_name
++; // Skip the last slash..
167 program_name
= apple
[0];
169 printf("%s called with:\n", program_name
);
170 for (int i
= 0; i
< argc
; ++i
)
171 printf("argv[%u] = '%s'\n", i
, argv
[i
]);
174 cpu_type_t cpu_type
= 0;
175 bool show_usage
= false;
177 int disable_aslr
= 0; // By default we disable ASLR
178 bool pass_env
= true;
179 std::string unix_socket_name
;
180 std::string working_dir
;
189 while ((ch
= getopt_long_only(argc
, argv
, "a:deE:hsu:?", g_long_options
,
195 case 'a': // "-a i386" or "--arch=i386"
197 if (streq(optarg
, "i386"))
198 cpu_type
= CPU_TYPE_I386
;
199 else if (streq(optarg
, "x86_64"))
200 cpu_type
= CPU_TYPE_X86_64
;
201 else if (streq(optarg
, "x86_64h"))
202 cpu_type
= 0; // Don't set CPU type when we have x86_64h
203 else if (strstr(optarg
, "arm") == optarg
)
204 cpu_type
= CPU_TYPE_ARM
;
206 ::fprintf(stderr
, "error: unsupported cpu type '%s'\n", optarg
);
221 // Since we will exec this program into our new program, we can just set
223 // variables in this process and they will make it into the child process.
226 const char *equal_pos
= strchr(optarg
, '=');
228 name
.assign(optarg
, equal_pos
- optarg
);
229 value
.assign(equal_pos
+ 1);
233 ::setenv(name
.c_str(), value
.c_str(), 1);
237 // Create a new session to avoid having control-C presses kill our current
238 // terminal session when this program is launched from a .command file
243 unix_socket_name
.assign(optarg
);
247 struct stat working_dir_stat
;
248 if (stat(optarg
, &working_dir_stat
) == 0)
249 working_dir
.assign(optarg
);
251 ::fprintf(stderr
, "warning: working directory doesn't exist: '%s'\n",
265 if (show_usage
|| argc
<= 0 || unix_socket_name
.empty())
268 #if defined(DEBUG_LLDB_LAUNCHER)
269 printf("\n%s post options:\n", program_name
);
270 for (int i
= 0; i
< argc
; ++i
)
271 printf("argv[%u] = '%s'\n", i
, argv
[i
]);
274 // Open the socket that was passed in as an option
275 struct sockaddr_un saddr_un
;
276 int s
= ::socket(AF_UNIX
, SOCK_STREAM
, 0);
278 perror("error: socket (AF_UNIX, SOCK_STREAM, 0)");
282 saddr_un
.sun_family
= AF_UNIX
;
283 ::strncpy(saddr_un
.sun_path
, unix_socket_name
.c_str(),
284 sizeof(saddr_un
.sun_path
) - 1);
285 saddr_un
.sun_path
[sizeof(saddr_un
.sun_path
) - 1] = '\0';
286 saddr_un
.sun_len
= SUN_LEN(&saddr_un
);
288 if (::connect(s
, (struct sockaddr
*)&saddr_un
, SUN_LEN(&saddr_un
)) < 0) {
289 perror("error: connect (socket, &saddr_un, saddr_un_len)");
293 // We were able to connect to the socket, now write our PID so whomever
294 // launched us will know this process's ID
296 const int pid_str_len
=
297 ::snprintf(pid_str
, sizeof(pid_str
), "%i", ::getpid());
298 const int bytes_sent
= ::send(s
, pid_str
, pid_str_len
, 0);
300 if (pid_str_len
!= bytes_sent
) {
301 perror("error: send (s, pid_str, pid_str_len, 0)");
305 // We are done with the socket
309 printf("Launching: '%s'\n", argv
[0]);
310 if (working_dir
.empty()) {
312 const char *cwd_ptr
= getcwd(cwd
, sizeof(cwd
));
313 printf("Working directory: '%s'\n", cwd_ptr
);
315 printf("Working directory: '%s'\n", working_dir
.c_str());
317 printf("%i arguments:\n", argc
);
319 for (int i
= 0; i
< argc
; ++i
)
320 printf("argv[%u] = '%s'\n", i
, argv
[i
]);
322 // Now we posix spawn to exec this process into the inferior that we want
324 posix_spawn_for_debug(
326 pass_env
? *_NSGetEnviron() : NULL
, // Pass current environment as we may
327 // have modified it if "--env" options
328 // was used, do NOT pass "envp" here
329 working_dir
.empty() ? NULL
: working_dir
.c_str(), cpu_type
, disable_aslr
);
334 #endif // #if defined (__APPLE__)