summaryrefslogtreecommitdiffstats
path: root/tools/idevicedebug.c
diff options
context:
space:
mode:
authorGravatar Martin Szulecki2014-06-21 01:57:01 +0200
committerGravatar Martin Szulecki2014-09-19 15:49:07 +0200
commit9732d275d00bb1200d2b6180d94814a1a7fb7696 (patch)
tree75a04e7edd9bae1beffb5fc60e4e564f6d4b8652 /tools/idevicedebug.c
parent85f3680622da0eb3529e042793d4fbf9b0d41110 (diff)
downloadlibimobiledevice-9732d275d00bb1200d2b6180d94814a1a7fb7696.tar.gz
libimobiledevice-9732d275d00bb1200d2b6180d94814a1a7fb7696.tar.bz2
Add new "idevicedebug" tool to interact with debugserver on a device
Diffstat (limited to 'tools/idevicedebug.c')
-rw-r--r--tools/idevicedebug.c518
1 files changed, 518 insertions, 0 deletions
diff --git a/tools/idevicedebug.c b/tools/idevicedebug.c
new file mode 100644
index 0000000..784503a
--- /dev/null
+++ b/tools/idevicedebug.c
@@ -0,0 +1,518 @@
1/*
2 * idevicedebug.c
3 * Interact with the debugserver service of a device.
4 *
5 * Copyright (c) 2014 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#include <signal.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <time.h>
27#include <unistd.h>
28#include <libgen.h>
29
30#include <libimobiledevice/installation_proxy.h>
31#include <libimobiledevice/libimobiledevice.h>
32#include <libimobiledevice/debugserver.h>
33#include <plist/plist.h>
34#include "common/debug.h"
35
36enum cmd_mode {
37 CMD_NONE = 0,
38 CMD_RUN
39};
40
41static int quit_flag = 0;
42
43static void on_signal(int sig)
44{
45 fprintf(stderr, "Exiting...\n");
46 quit_flag++;
47}
48
49static instproxy_error_t instproxy_client_get_object_by_key_from_info_directionary_for_bundle_identifier(instproxy_client_t client, const char* appid, const char* key, plist_t* node)
50{
51 if (!client || !appid || !key)
52 return INSTPROXY_E_INVALID_ARG;
53
54 plist_t apps = NULL;
55
56 // create client options for any application types
57 plist_t client_opts = instproxy_client_options_new();
58 instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL);
59
60 // only return attributes we need
61 plist_t return_attributes = plist_new_array();
62 plist_array_append_item(return_attributes, plist_new_string("CFBundleIdentifier"));
63 plist_array_append_item(return_attributes, plist_new_string("CFBundleExecutable"));
64 plist_array_append_item(return_attributes, plist_new_string(key));
65 instproxy_client_options_add(client_opts, "ReturnAttributes", return_attributes, NULL);
66 plist_free(return_attributes);
67 return_attributes = NULL;
68
69 // query device for list of apps
70 instproxy_error_t ierr = instproxy_browse(client, client_opts, &apps);
71 instproxy_client_options_free(client_opts);
72 if (ierr != INSTPROXY_E_SUCCESS) {
73 return ierr;
74 }
75
76 plist_t app_found = NULL;
77 uint32_t i;
78 for (i = 0; i < plist_array_get_size(apps); i++) {
79 char *appid_str = NULL;
80 plist_t app_info = plist_array_get_item(apps, i);
81 plist_t idp = plist_dict_get_item(app_info, "CFBundleIdentifier");
82 if (idp) {
83 plist_get_string_val(idp, &appid_str);
84 }
85 if (appid_str && strcmp(appid, appid_str) == 0) {
86 app_found = app_info;
87 }
88 free(appid_str);
89 if (app_found) {
90 break;
91 }
92 }
93
94 if (!app_found) {
95 if (apps)
96 plist_free(apps);
97 *node = NULL;
98 return INSTPROXY_E_OP_FAILED;
99 }
100
101 plist_t object = plist_dict_get_item(app_found, key);
102 if (object) {
103 *node = plist_copy(object);
104 } else {
105 debug_info("key %s not found", key);
106 return INSTPROXY_E_OP_FAILED;
107 }
108
109 plist_free(apps);
110
111 return INSTPROXY_E_SUCCESS;
112}
113
114static debugserver_error_t debugserver_client_handle_response(debugserver_client_t client, char** response, int send_reply)
115{
116 debugserver_error_t dres = DEBUGSERVER_E_SUCCESS;
117 debugserver_command_t command = NULL;
118 char* o = NULL;
119 char* r = *response;
120
121 if (r[0] == 'O') {
122 /* stdout/stderr */
123 debugserver_decode_string(r + 1, strlen(r) - 1, &o);
124 printf("%s", o);
125 fflush(stdout);
126 if (o != NULL) {
127 free(o);
128 o = NULL;
129 }
130
131 free(*response);
132 *response = NULL;
133
134 if (!send_reply)
135 return dres;
136
137 /* send reply */
138 debugserver_command_new("OK", 0, NULL, &command);
139 dres = debugserver_client_send_command(client, command, response);
140 debug_info("result: %d", dres);
141 debugserver_command_free(command);
142 command = NULL;
143 } else if (r[0] == 'T') {
144 /* thread stopped information */
145 debug_info("Thread stopped. Details:\n%s", r + 1);
146
147 free(*response);
148 *response = NULL;
149
150 if (!send_reply)
151 return dres;
152
153 dres = DEBUGSERVER_E_UNKNOWN_ERROR;
154 } else if (r[0] == 'E' || r[0] == 'W') {
155 printf("%s: %s\n", (r[0] == 'E' ? "ERROR": "WARNING") , r + 1);
156
157 free(*response);
158 *response = NULL;
159
160 if (!send_reply)
161 return dres;
162
163 /* send reply */
164 debugserver_command_new("OK", 0, NULL, &command);
165 dres = debugserver_client_send_command(client, command, response);
166 debug_info("result: %d", dres);
167 debugserver_command_free(command);
168 command = NULL;
169 } else if (r && strlen(r) == 0) {
170 if (!send_reply)
171 return dres;
172
173 free(*response);
174 *response = NULL;
175
176 /* no command */
177 debugserver_command_new("OK", 0, NULL, &command);
178 dres = debugserver_client_send_command(client, command, response);
179 debug_info("result: %d", dres);
180 debugserver_command_free(command);
181 command = NULL;
182 } else {
183 debug_info("ERROR: unhandled response", r);
184 }
185
186 return dres;
187}
188
189static void print_usage(int argc, char **argv)
190{
191 char *name = NULL;
192 name = strrchr(argv[0], '/');
193 printf("Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
194 printf("Interact with the debugserver service of a device.\n\n");
195 printf(" Where COMMAND is one of:\n");
196 printf(" run BUNDLEID [ARGS...]\trun app with BUNDLEID and optional ARGS on device.\n");
197 printf("\n");
198 printf(" The following OPTIONS are accepted:\n");
199 printf(" -e, --env NAME=VALUE\tset environment variable NAME to VALUE\n");
200 printf(" -u, --udid UDID\ttarget specific device by its 40-digit device UDID\n");
201 printf(" -d, --debug\t\tenable communication debugging\n");
202 printf(" -h, --help\t\tprints usage information\n");
203 printf("\n");
204}
205
206int main(int argc, char *argv[])
207{
208 int res = -1;
209 idevice_t device = NULL;
210 idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
211 instproxy_client_t instproxy_client = NULL;
212 debugserver_client_t debugserver_client = NULL;
213 int i;
214 int debug_level = 0;
215 int cmd = CMD_NONE;
216 const char* udid = NULL;
217 const char* bundle_identifier = NULL;
218 char* path = NULL;
219 char* working_directory = NULL;
220 char **newlist = NULL;
221 char** environment = NULL;
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 /* map signals */
228 signal(SIGINT, on_signal);
229 signal(SIGTERM, on_signal);
230#ifndef WIN32
231 signal(SIGQUIT, on_signal);
232 signal(SIGPIPE, SIG_IGN);
233#endif
234
235 /* parse command line arguments */
236 for (i = 1; i < argc; i++) {
237 if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
238 debug_level++;
239 idevice_set_debug_level(debug_level);
240 continue;
241 } else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--udid")) {
242 i++;
243 if (!argv[i] || (strlen(argv[i]) != 40)) {
244 print_usage(argc, argv);
245 res = 0;
246 goto cleanup;
247 }
248 udid = argv[i];
249 continue;
250 } else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--env")) {
251 i++;
252 if (!argv[i] || (strlen(argv[i]) <= 1) || strchr(argv[i], '=') == NULL) {
253 print_usage(argc, argv);
254 res = 0;
255 goto cleanup;
256 }
257 /* add environment variable */
258 if (!newlist)
259 newlist = malloc((environment_count + 1) * sizeof(char*));
260 else
261 newlist = realloc(environment, (environment_count + 1) * sizeof(char*));
262 newlist[environment_count++] = strdup(argv[i]);
263 environment = newlist;
264 continue;
265 } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
266 print_usage(argc, argv);
267 res = 0;
268 goto cleanup;
269 } else if (!strcmp(argv[i], "run")) {
270 cmd = CMD_RUN;
271
272 i++;
273 if (!argv[i]) {
274 /* make sure at least the bundle identifier was provided */
275 printf("Please supply the bundle identifier of the app to run.\n");
276 print_usage(argc, argv);
277 res = 0;
278 goto cleanup;
279 }
280 /* read bundle identifier */
281 bundle_identifier = argv[i];
282 break;
283 } else {
284 print_usage(argc, argv);
285 res = 0;
286 goto cleanup;
287 }
288 }
289
290 if (environment) {
291 newlist = realloc(environment, (environment_count + 1) * sizeof(char*));
292 newlist[environment_count] = NULL;
293 environment = newlist;
294 }
295
296 /* verify options */
297 if (cmd == CMD_NONE) {
298 print_usage(argc, argv);
299 goto cleanup;
300 }
301
302 /* connect to the device */
303 ret = idevice_new(&device, udid);
304 if (ret != IDEVICE_E_SUCCESS) {
305 if (udid) {
306 printf("No device found with udid %s, is it plugged in?\n", udid);
307 } else {
308 printf("No device found, is it plugged in?\n");
309 }
310 goto cleanup;
311 }
312
313 switch (cmd) {
314 case CMD_RUN:
315 default:
316 /* get the path to the app and it's working directory */
317 if (instproxy_client_start_service(device, &instproxy_client, "idevicerun") != INSTPROXY_E_SUCCESS) {
318 fprintf(stderr, "Could not start installation proxy service.\n");
319 goto cleanup;
320 }
321 plist_t container = NULL;
322 instproxy_client_get_object_by_key_from_info_directionary_for_bundle_identifier(instproxy_client, bundle_identifier, "Container", &container);
323 instproxy_client_get_path_for_bundle_identifier(instproxy_client, bundle_identifier, &path);
324 instproxy_client_free(instproxy_client);
325 instproxy_client = NULL;
326 if (container && plist_get_node_type(container) == PLIST_STRING) {
327 plist_get_string_val(container, &working_directory);
328 debug_info("working_directory: %s\n", working_directory);
329 } else {
330 fprintf(stderr, "Could not determine container path for bundle identifier %s.\n", bundle_identifier);
331 goto cleanup;
332 }
333
334 /* start and connect to debugserver */
335 if (debugserver_client_start_service(device, &debugserver_client, "idevicerun") != LOCKDOWN_E_SUCCESS) {
336 fprintf(stderr,
337 "Could not start com.apple.debugserver!\n"
338 "Please make sure to mount the developer disk image first:\n"
339 " 1) Get the iOS version from `ideviceinfo -k ProductVersion`.\n"
340 " 2) Find the matching iPhoneOS DeveloperDiskImage.dmg files.\n"
341 " 3) Run `ideviceimagemounter` with the above path.\n");
342 goto cleanup;
343 }
344
345 /* enable logging for the session in debug mode */
346 if (debug_level) {
347 debug_info("Setting logging bitmask...");
348 debugserver_command_new("QSetLogging:bitmask=LOG_ALL|LOG_RNB_REMOTE|LOG_RNB_PACKETS", 0, NULL, &command);
349 dres = debugserver_client_send_command(debugserver_client, command, &response);
350 debugserver_command_free(command);
351 command = NULL;
352 if (response) {
353 if (strncmp(response, "OK", 2)) {
354 debugserver_client_handle_response(debugserver_client, &response, 0);
355 goto cleanup;
356 }
357 free(response);
358 response = NULL;
359 }
360 }
361
362 /* set maximum packet size */
363 debug_info("Setting maximum packet size...");
364 const char* packet_size[2] = {"1024", NULL};
365 debugserver_command_new("QSetMaxPacketSize:", 1, packet_size, &command);
366 dres = debugserver_client_send_command(debugserver_client, command, &response);
367 debugserver_command_free(command);
368 command = NULL;
369 if (response) {
370 if (strncmp(response, "OK", 2)) {
371 debugserver_client_handle_response(debugserver_client, &response, 0);
372 goto cleanup;
373 }
374 free(response);
375 response = NULL;
376 }
377
378 /* set working directory */
379 debug_info("Setting working directory...");
380 const char* working_dir[2] = {working_directory, NULL};
381 debugserver_command_new("QSetWorkingDir:", 1, working_dir, &command);
382 dres = debugserver_client_send_command(debugserver_client, command, &response);
383 debugserver_command_free(command);
384 command = NULL;
385 if (response) {
386 if (strncmp(response, "OK", 2)) {
387 debugserver_client_handle_response(debugserver_client, &response, 0);
388 goto cleanup;
389 }
390 free(response);
391 response = NULL;
392 }
393
394 /* set environment */
395 if (environment) {
396 debug_info("Setting environment...");
397 int environment_index = 0;
398 for (environment_index = 0; environment_index < environment_count; environment_index++) {
399 debug_info("setting environment variable: %s", environment[environment_index]);
400 debugserver_client_set_environment_hex_encoded(debugserver_client, environment[environment_index], NULL);
401 environment_index++;
402 }
403 }
404
405 /* set arguments and run app */
406 debug_info("Setting argv...");
407 int app_argc = (argc - i + 2);
408 debug_info("app_argc: %d", app_argc);
409 char **app_argv = (char**)malloc(sizeof(char*) * app_argc);
410 app_argv[0] = path;
411 debug_info("app_argv[%d] = %s", 0, app_argv[0]);
412 app_argc = 1;
413 while (i < argc && argv && argv[i]) {
414 debug_info("app_argv[%d] = argv[%d]: %s", app_argc, i, argv[i]);
415 app_argv[app_argc++] = argv[i];
416 i++;
417 }
418 app_argv[app_argc] = NULL;
419 debugserver_client_set_argv(debugserver_client, app_argc, app_argv, NULL);
420 free(app_argv);
421
422 /* check if launch succeeded */
423 debug_info("Checking if launch succeeded...");
424 debugserver_command_new("qLaunchSuccess", 0, NULL, &command);
425 dres = debugserver_client_send_command(debugserver_client, command, &response);
426 debugserver_command_free(command);
427 command = NULL;
428 if (response) {
429 if (strncmp(response, "OK", 2)) {
430 debugserver_client_handle_response(debugserver_client, &response, 0);
431 goto cleanup;
432 }
433 free(response);
434 response = NULL;
435 }
436
437 /* set thread */
438 debug_info("Setting thread...");
439 debugserver_command_new("Hc0", 0, NULL, &command);
440 dres = debugserver_client_send_command(debugserver_client, command, &response);
441 debugserver_command_free(command);
442 command = NULL;
443 if (response) {
444 if (strncmp(response, "OK", 2)) {
445 debugserver_client_handle_response(debugserver_client, &response, 0);
446 goto cleanup;
447 }
448 free(response);
449 response = NULL;
450 }
451
452 /* continue running process */
453 debug_info("Continue running process...");
454 debugserver_command_new("c", 0, NULL, &command);
455 dres = debugserver_client_send_command(debugserver_client, command, &response);
456 debugserver_command_free(command);
457 command = NULL;
458
459 /* main loop which is parsing/handling packets during the run */
460 debug_info("Entering run loop...");
461 while (!quit_flag) {
462 if (dres != DEBUGSERVER_E_SUCCESS) {
463 debug_info("failed to receive response");
464 break;
465 }
466
467 if (response) {
468 debug_info("response: %s", response);
469 dres = debugserver_client_handle_response(debugserver_client, &response, 1);
470 }
471
472 sleep(1);
473 }
474
475 /* kill process after we finished */
476 debug_info("Killing process...");
477 debugserver_command_new("k", 0, NULL, &command);
478 dres = debugserver_client_send_command(debugserver_client, command, &response);
479 debugserver_command_free(command);
480 command = NULL;
481 if (response) {
482 if (strncmp(response, "OK", 2)) {
483 debugserver_client_handle_response(debugserver_client, &response, 0);
484 goto cleanup;
485 }
486 free(response);
487 response = NULL;
488 }
489
490 res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1;
491 break;
492 }
493
494cleanup:
495 /* cleanup the house */
496 if (environment) {
497 int environment_index = 0;
498 for (environment_index = 0; environment_index < environment_count; environment_index++) {
499 free(environment[environment_index]);
500 environment_index++;
501 }
502 free(environment);
503 }
504
505 if (path)
506 free(path);
507
508 if (response)
509 free(response);
510
511 if (debugserver_client)
512 debugserver_client_free(debugserver_client);
513
514 if (device)
515 idevice_free(device);
516
517 return res;
518}