summaryrefslogtreecommitdiffstats
path: root/tools/idevicedebug.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/idevicedebug.c')
-rw-r--r--tools/idevicedebug.c625
1 files changed, 625 insertions, 0 deletions
diff --git a/tools/idevicedebug.c b/tools/idevicedebug.c
new file mode 100644
index 0000000..36c594e
--- /dev/null
+++ b/tools/idevicedebug.c
@@ -0,0 +1,625 @@
1/*
2 * idevicedebug.c
3 * Interact with the debugserver service of a device.
4 *
5 * Copyright (c) 2014-2015 Martin Szulecki All Rights Reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#define TOOL_NAME "idevicedebug"
27
28#include <signal.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33#include <unistd.h>
34#include <libgen.h>
35#include <getopt.h>
36
37#ifdef WIN32
38#include <windows.h>
39#define sleep(x) Sleep(x*1000)
40#endif
41
42#include <libimobiledevice/installation_proxy.h>
43#include <libimobiledevice/libimobiledevice.h>
44#include <libimobiledevice/debugserver.h>
45#include <plist/plist.h>
46#include "common/debug.h"
47
48static int debug_level = 0;
49
50#define log_debug(...) if (debug_level > 0) { printf(__VA_ARGS__); fputc('\n', stdout); }
51
52enum cmd_mode {
53 CMD_NONE = 0,
54 CMD_RUN,
55 CMD_KILL
56};
57
58static int quit_flag = 0;
59
60static void on_signal(int sig)
61{
62 fprintf(stderr, "Exiting...\n");
63 quit_flag++;
64}
65
66static int cancel_receive()
67{
68 return quit_flag;
69}
70
71static instproxy_error_t instproxy_client_get_object_by_key_from_info_dictionary_for_bundle_identifier(instproxy_client_t client, const char* appid, const char* key, plist_t* node)
72{
73 if (!client || !appid || !key)
74 return INSTPROXY_E_INVALID_ARG;
75
76 plist_t apps = NULL;
77
78 // create client options for any application types
79 plist_t client_opts = instproxy_client_options_new();
80 instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL);
81
82 // only return attributes we need
83 instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", key, NULL);
84
85 // only query for specific appid
86 const char* appids[] = {appid, NULL};
87
88 // query device for list of apps
89 instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps);
90
91 instproxy_client_options_free(client_opts);
92
93 if (ierr != INSTPROXY_E_SUCCESS) {
94 return ierr;
95 }
96
97 plist_t app_found = plist_access_path(apps, 1, appid);
98 if (!app_found) {
99 if (apps)
100 plist_free(apps);
101 *node = NULL;
102 return INSTPROXY_E_OP_FAILED;
103 }
104
105 plist_t object = plist_dict_get_item(app_found, key);
106 if (object) {
107 *node = plist_copy(object);
108 } else {
109 log_debug("key %s not found", key);
110 return INSTPROXY_E_OP_FAILED;
111 }
112
113 plist_free(apps);
114
115 return INSTPROXY_E_SUCCESS;
116}
117
118static debugserver_error_t debugserver_client_handle_response(debugserver_client_t client, char** response, int* exit_status)
119{
120 debugserver_error_t dres = DEBUGSERVER_E_SUCCESS;
121 char* o = NULL;
122 char* r = *response;
123
124 /* Documentation of response codes can be found here:
125 https://github.com/llvm/llvm-project/blob/4fe839ef3a51e0ea2e72ea2f8e209790489407a2/lldb/docs/lldb-gdb-remote.txt#L1269
126 */
127
128 if (r[0] == 'O') {
129 /* stdout/stderr */
130 debugserver_decode_string(r + 1, strlen(r) - 1, &o);
131 printf("%s", o);
132 fflush(stdout);
133 } else if (r[0] == 'T') {
134 /* thread stopped information */
135 log_debug("Thread stopped. Details:\n%s", r + 1);
136 if (exit_status != NULL) {
137 /* "Thread stopped" seems to happen when assert() fails.
138 Use bash convention where signals cause an exit
139 status of 128 + signal
140 */
141 *exit_status = 128 + SIGABRT;
142 }
143 /* Break out of the loop. */
144 dres = DEBUGSERVER_E_UNKNOWN_ERROR;
145 } else if (r[0] == 'E') {
146 printf("ERROR: %s\n", r + 1);
147 } else if (r[0] == 'W' || r[0] == 'X') {
148 /* process exited */
149 debugserver_decode_string(r + 1, strlen(r) - 1, &o);
150 if (o != NULL) {
151 printf("Exit %s: %u\n", (r[0] == 'W' ? "status" : "due to signal"), o[0]);
152 if (exit_status != NULL) {
153 /* Use bash convention where signals cause an
154 exit status of 128 + signal
155 */
156 *exit_status = o[0] + (r[0] == 'W' ? 0 : 128);
157 }
158 } else {
159 log_debug("Unable to decode exit status from %s", r);
160 dres = DEBUGSERVER_E_UNKNOWN_ERROR;
161 }
162 } else if (r && strlen(r) == 0) {
163 log_debug("empty response");
164 } else {
165 log_debug("ERROR: unhandled response '%s'", r);
166 }
167
168 if (o != NULL) {
169 free(o);
170 o = NULL;
171 }
172
173 free(*response);
174 *response = NULL;
175 return dres;
176}
177
178static void print_usage(int argc, char **argv, int is_error)
179{
180 char *name = strrchr(argv[0], '/');
181 fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
182 fprintf(is_error ? stderr : stdout,
183 "\n"
184 "Interact with the debugserver service of a device.\n"
185 "\n"
186 "Where COMMAND is one of:\n"
187 " run BUNDLEID [ARGS...] run app with BUNDLEID and optional ARGS on device.\n"
188 " kill BUNDLEID kill app with BUNDLEID\n"
189 "\n"
190 "The following OPTIONS are accepted:\n"
191 " -u, --udid UDID target specific device by UDID\n"
192 " -n, --network connect to network device\n"
193 " --detach detach from app after launch, keeping it running\n"
194 " -e, --env NAME=VALUE set environment variable NAME to VALUE\n"
195 " -d, --debug enable communication debugging\n"
196 " -h, --help prints usage information\n"
197 " -v, --version prints version information\n"
198 "\n"
199 "Homepage: <" PACKAGE_URL ">\n"
200 "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
201 );
202}
203
204int main(int argc, char *argv[])
205{
206 int res = -1;
207 idevice_t device = NULL;
208 idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
209 instproxy_client_t instproxy_client = NULL;
210 debugserver_client_t debugserver_client = NULL;
211 int i;
212 int cmd = CMD_NONE;
213 const char* udid = NULL;
214 int use_network = 0;
215 int detach_after_start = 0;
216 const char* bundle_identifier = NULL;
217 char* path = NULL;
218 char* working_directory = NULL;
219 char **newlist = NULL;
220 char** environment = NULL;
221 int environment_index = 0;
222 int environment_count = 0;
223 char* response = NULL;
224 debugserver_command_t command = NULL;
225 debugserver_error_t dres = DEBUGSERVER_E_UNKNOWN_ERROR;
226
227 int c = 0;
228 const struct option longopts[] = {
229 { "debug", no_argument, NULL, 'd' },
230 { "help", no_argument, NULL, 'h' },
231 { "udid", required_argument, NULL, 'u' },
232 { "network", no_argument, NULL, 'n' },
233 { "detach", no_argument, NULL, 1 },
234 { "env", required_argument, NULL, 'e' },
235 { "version", no_argument, NULL, 'v' },
236 { NULL, 0, NULL, 0 }
237 };
238
239 /* map signals */
240 signal(SIGINT, on_signal);
241 signal(SIGTERM, on_signal);
242#ifndef WIN32
243 signal(SIGQUIT, on_signal);
244 signal(SIGPIPE, SIG_IGN);
245#endif
246
247 while ((c = getopt_long(argc, argv, "dhu:ne:v", longopts, NULL)) != -1) {
248 switch (c) {
249 case 'd':
250 debug_level++;
251 if (debug_level > 1) {
252 idevice_set_debug_level(debug_level-1);
253 }
254 break;
255 case 'u':
256 if (!*optarg) {
257 fprintf(stderr, "ERROR: UDID must not be empty!\n");
258 print_usage(argc, argv, 1);
259 return 2;
260 }
261 udid = optarg;
262 break;
263 case 'n':
264 use_network = 1;
265 break;
266 case 1:
267 detach_after_start = 1;
268 break;
269 case 'e':
270 if (!*optarg || strchr(optarg, '=') == NULL) {
271 fprintf(stderr, "ERROR: environment variables need to be specified as -e KEY=VALUE\n");
272 print_usage(argc, argv, 1);
273 res = 2;
274 goto cleanup;
275 }
276 /* add environment variable */
277 if (!newlist)
278 newlist = malloc((environment_count + 1) * sizeof(char*));
279 else
280 newlist = realloc(environment, (environment_count + 1) * sizeof(char*));
281 newlist[environment_count++] = strdup(optarg);
282 environment = newlist;
283 break;
284 case 'h':
285 print_usage(argc, argv, 0);
286 res = 0;
287 goto cleanup;
288 break;
289 case 'v':
290 printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
291 res = 0;
292 goto cleanup;
293 break;
294 default:
295 print_usage(argc, argv, 1);
296 res = 2;
297 goto cleanup;
298 break;
299 }
300 }
301 argc -= optind;
302 argv += optind;
303
304 if (argc < 1) {
305 fprintf(stderr, "ERROR: Missing command.\n");
306 print_usage(argc+optind, argv-optind, 1);
307 return 2;
308 }
309
310 if (!strcmp(argv[0], "run")) {
311 cmd = CMD_RUN;
312 if (argc < 2) {
313 /* make sure at least the bundle identifier was provided */
314 fprintf(stderr, "ERROR: Please supply the bundle identifier of the app to run.\n");
315 print_usage(argc+optind, argv-optind, 1);
316 res = 2;
317 goto cleanup;
318 }
319 /* read bundle identifier */
320 bundle_identifier = argv[1];
321 i = 1;
322 } else if (!strcmp(argv[0], "kill")) {
323 cmd = CMD_KILL;
324 if (argc < 2) {
325 /* make sure at least the bundle identifier was provided */
326 fprintf(stderr, "ERROR: Please supply the bundle identifier of the app to run.\n");
327 print_usage(argc+optind, argv-optind, 1);
328 res = 2;
329 goto cleanup;
330 }
331 /* read bundle identifier */
332 bundle_identifier = argv[1];
333 i = 1;
334 }
335
336 /* verify options */
337 if (cmd == CMD_NONE) {
338 fprintf(stderr, "ERROR: Unsupported command specified.\n");
339 print_usage(argc+optind, argv-optind, 1);
340 res = 2;
341 goto cleanup;
342 }
343
344 if (environment) {
345 newlist = realloc(environment, (environment_count + 1) * sizeof(char*));
346 newlist[environment_count] = NULL;
347 environment = newlist;
348 }
349
350 /* connect to the device */
351 ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
352 if (ret != IDEVICE_E_SUCCESS) {
353 if (udid) {
354 printf("No device found with udid %s.\n", udid);
355 } else {
356 printf("No device found.\n");
357 }
358 goto cleanup;
359 }
360
361 /* get the path to the app and it's working directory */
362 if (instproxy_client_start_service(device, &instproxy_client, TOOL_NAME) != INSTPROXY_E_SUCCESS) {
363 fprintf(stderr, "Could not start installation proxy service.\n");
364 goto cleanup;
365 }
366
367 instproxy_client_get_path_for_bundle_identifier(instproxy_client, bundle_identifier, &path);
368 if (!path) {
369 fprintf(stderr, "Invalid bundle identifier: %s\n", bundle_identifier);
370 goto cleanup;
371 }
372
373 plist_t container = NULL;
374 instproxy_client_get_object_by_key_from_info_dictionary_for_bundle_identifier(instproxy_client, bundle_identifier, "Container", &container);
375 instproxy_client_free(instproxy_client);
376 instproxy_client = NULL;
377
378 if (container && (plist_get_node_type(container) == PLIST_STRING)) {
379 plist_get_string_val(container, &working_directory);
380 log_debug("working_directory: %s\n", working_directory);
381 plist_free(container);
382 } else {
383 plist_free(container);
384 fprintf(stderr, "Could not determine container path for bundle identifier %s.\n", bundle_identifier);
385 goto cleanup;
386 }
387
388 /* start and connect to debugserver */
389 if (debugserver_client_start_service(device, &debugserver_client, TOOL_NAME) != DEBUGSERVER_E_SUCCESS) {
390 fprintf(stderr,
391 "Could not start com.apple.debugserver!\n"
392 "Please make sure to mount the developer disk image first:\n"
393 " 1) Get the iOS version from `ideviceinfo -k ProductVersion`.\n"
394 " 2) Find the matching iPhoneOS DeveloperDiskImage.dmg files.\n"
395 " 3) Run `ideviceimagemounter` with the above path.\n");
396 goto cleanup;
397 }
398
399 /* set receive params */
400 if (debugserver_client_set_receive_params(debugserver_client, cancel_receive, 250) != DEBUGSERVER_E_SUCCESS) {
401 fprintf(stderr, "Error in debugserver_client_set_receive_params\n");
402 goto cleanup;
403 }
404
405 /* enable logging for the session in debug mode */
406 if (debug_level) {
407 log_debug("Setting logging bitmask...");
408 debugserver_command_new("QSetLogging:bitmask=LOG_ALL|LOG_RNB_REMOTE|LOG_RNB_PACKETS;", 0, NULL, &command);
409 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
410 debugserver_command_free(command);
411 command = NULL;
412 if (response) {
413 if (strncmp(response, "OK", 2) != 0) {
414 debugserver_client_handle_response(debugserver_client, &response, NULL);
415 goto cleanup;
416 }
417 free(response);
418 response = NULL;
419 }
420 }
421
422 /* set maximum packet size */
423 log_debug("Setting maximum packet size...");
424 char* packet_size[2] = { (char*)"1024", NULL};
425 debugserver_command_new("QSetMaxPacketSize:", 1, packet_size, &command);
426 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
427 debugserver_command_free(command);
428 command = NULL;
429 if (response) {
430 if (strncmp(response, "OK", 2) != 0) {
431 debugserver_client_handle_response(debugserver_client, &response, NULL);
432 goto cleanup;
433 }
434 free(response);
435 response = NULL;
436 }
437
438 /* set working directory */
439 log_debug("Setting working directory...");
440 char* working_dir[2] = {working_directory, NULL};
441 debugserver_command_new("QSetWorkingDir:", 1, working_dir, &command);
442 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
443 debugserver_command_free(command);
444 command = NULL;
445 if (response) {
446 if (strncmp(response, "OK", 2) != 0) {
447 debugserver_client_handle_response(debugserver_client, &response, NULL);
448 goto cleanup;
449 }
450 free(response);
451 response = NULL;
452 }
453
454 /* set environment */
455 if (environment) {
456 log_debug("Setting environment...");
457 for (environment_index = 0; environment_index < environment_count; environment_index++) {
458 log_debug("setting environment variable: %s", environment[environment_index]);
459 debugserver_client_set_environment_hex_encoded(debugserver_client, environment[environment_index], NULL);
460 }
461 }
462
463 /* set arguments and run app */
464 log_debug("Setting argv...");
465 i++; /* i is the offset of the bundle identifier, thus skip it */
466 int app_argc = (argc - i + 2);
467 char **app_argv = (char**)malloc(sizeof(char*) * app_argc);
468 app_argv[0] = path;
469 log_debug("app_argv[%d] = %s", 0, app_argv[0]);
470 app_argc = 1;
471 while (i < argc && argv && argv[i]) {
472 log_debug("app_argv[%d] = %s", app_argc, argv[i]);
473 app_argv[app_argc++] = argv[i];
474 i++;
475 }
476 app_argv[app_argc] = NULL;
477 debugserver_client_set_argv(debugserver_client, app_argc, app_argv, NULL);
478 free(app_argv);
479
480 /* check if launch succeeded */
481 log_debug("Checking if launch succeeded...");
482 debugserver_command_new("qLaunchSuccess", 0, NULL, &command);
483 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
484 debugserver_command_free(command);
485 command = NULL;
486 if (response) {
487 if (strncmp(response, "OK", 2) != 0) {
488 debugserver_client_handle_response(debugserver_client, &response, NULL);
489 goto cleanup;
490 }
491 free(response);
492 response = NULL;
493 }
494
495 if (cmd == CMD_KILL) {
496 debugserver_command_new("k", 0, NULL, &command);
497 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
498 debugserver_command_free(command);
499 command = NULL;
500 goto cleanup;
501 } else
502 if (cmd == CMD_RUN) {
503 if (detach_after_start) {
504 log_debug("Detaching from app");
505 debugserver_command_new("D", 0, NULL, &command);
506 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
507 debugserver_command_free(command);
508 command = NULL;
509
510 res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1;
511 goto cleanup;
512 }
513
514 /* set thread */
515 log_debug("Setting thread...");
516 debugserver_command_new("Hc0", 0, NULL, &command);
517 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
518 debugserver_command_free(command);
519 command = NULL;
520 if (response) {
521 if (strncmp(response, "OK", 2) != 0) {
522 debugserver_client_handle_response(debugserver_client, &response, NULL);
523 goto cleanup;
524 }
525 free(response);
526 response = NULL;
527 }
528
529 /* continue running process */
530 log_debug("Continue running process...");
531 debugserver_command_new("c", 0, NULL, &command);
532 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
533 debugserver_command_free(command);
534 command = NULL;
535 log_debug("Continue response: %s", response);
536
537 /* main loop which is parsing/handling packets during the run */
538 log_debug("Entering run loop...");
539 while (!quit_flag) {
540 if (dres != DEBUGSERVER_E_SUCCESS) {
541 log_debug("failed to receive response; error %d", dres);
542 break;
543 }
544
545 if (response) {
546 log_debug("response: %s", response);
547 if (strncmp(response, "OK", 2) != 0) {
548 dres = debugserver_client_handle_response(debugserver_client, &response, &res);
549 if (dres != DEBUGSERVER_E_SUCCESS) {
550 log_debug("failed to process response; error %d; %s", dres, response);
551 break;
552 }
553 }
554 }
555 if (res >= 0) {
556 goto cleanup;
557 }
558
559 dres = debugserver_client_receive_response(debugserver_client, &response, NULL);
560 }
561
562 /* ignore quit_flag after this point */
563 if (debugserver_client_set_receive_params(debugserver_client, NULL, 5000) != DEBUGSERVER_E_SUCCESS) {
564 fprintf(stderr, "Error in debugserver_client_set_receive_params\n");
565 goto cleanup;
566 }
567
568 /* interrupt execution */
569 debugserver_command_new("\x03", 0, NULL, &command);
570 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
571 debugserver_command_free(command);
572 command = NULL;
573 if (response) {
574 if (strncmp(response, "OK", 2) != 0) {
575 debugserver_client_handle_response(debugserver_client, &response, NULL);
576 }
577 free(response);
578 response = NULL;
579 }
580
581 /* kill process after we finished */
582 log_debug("Killing process...");
583 debugserver_command_new("k", 0, NULL, &command);
584 dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
585 debugserver_command_free(command);
586 command = NULL;
587 if (response) {
588 if (strncmp(response, "OK", 2) != 0) {
589 debugserver_client_handle_response(debugserver_client, &response, NULL);
590 }
591 free(response);
592 response = NULL;
593 }
594
595 if (res < 0) {
596 res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1;
597 }
598 }
599
600cleanup:
601 /* cleanup the house */
602 if (environment) {
603 for (environment_index = 0; environment_index < environment_count; environment_index++) {
604 free(environment[environment_index]);
605 }
606 free(environment);
607 }
608
609 if (working_directory)
610 free(working_directory);
611
612 if (path)
613 free(path);
614
615 if (response)
616 free(response);
617
618 if (debugserver_client)
619 debugserver_client_free(debugserver_client);
620
621 if (device)
622 idevice_free(device);
623
624 return res;
625}