From 9423d1bf24d5094a6685f8e8882c57b237d91440 Mon Sep 17 00:00:00 2001 From: Niklas Therning Date: Tue, 24 Apr 2012 11:17:44 +0200 Subject: [PATCH 01/22] Fixed bug in Makefile --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bd6beb41..dfeeddf4 100644 --- a/Makefile +++ b/Makefile @@ -9,16 +9,16 @@ demo.app: demo Info.plist codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app demo: demo.c - $(IOS_CC) -arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -framework CoreFoundation -o demo demo.c + $(IOS_CC) -arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -framework CoreFoundation -g -o demo demo.c fruitstrap: fruitstrap.c gcc -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c install: all - ./fruitstrap demo.app + ./fruitstrap -b demo.app debug: all - ./fruitstrap -d demo.app + ./fruitstrap -d -b demo.app clean: rm -rf *.app demo fruitstrap \ No newline at end of file From 976f8523217c23fce80a74e929f58437666474d3 Mon Sep 17 00:00:00 2001 From: Niklas Therning Date: Tue, 24 Apr 2012 11:18:18 +0200 Subject: [PATCH 02/22] Added -u/--unbuffered option which disables buffering of stdout. --- fruitstrap.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fruitstrap.c b/fruitstrap.c index 6693f515..1f441b81 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -44,7 +44,7 @@ int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); int AMDeviceLookupApplications(AMDeviceRef device, int zero, CFDictionaryRef* result); -bool found_device = false, debug = false, verbose = false; +bool found_device = false, debug = false, verbose = false, unbuffered = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; @@ -496,7 +496,7 @@ void timeout_callback(CFRunLoopTimerRef timer, void *info) { } void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)]\n", app); + printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]\n", app); } int main(int argc, char *argv[]) { @@ -507,11 +507,12 @@ int main(int argc, char *argv[]) { { "args", required_argument, NULL, 'a' }, { "verbose", no_argument, NULL, 'v' }, { "timeout", required_argument, NULL, 't' }, + { "unbuffered", no_argument, NULL, 'u' }, { NULL, 0, NULL, 0 }, }; char ch; - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u", longopts, NULL)) != -1) { switch (ch) { case 'd': @@ -532,6 +533,9 @@ int main(int argc, char *argv[]) { case 't': timeout = atoi(optarg); break; + case 'u': + unbuffered = 1; + break; default: usage(argv[0]); return 1; @@ -543,6 +547,8 @@ int main(int argc, char *argv[]) { exit(0); } + if (unbuffered) setbuf(stdout, NULL); + printf("------ Install phase ------\n"); assert(access(app_path, F_OK) == 0); From e2bb4a21fbc5f13aad827ebcc60c86c91757e155 Mon Sep 17 00:00:00 2001 From: Niklas Therning Date: Thu, 26 Apr 2012 15:36:19 +0200 Subject: [PATCH 03/22] Changed gdb command line arguments (changed --arch to amrv7f and added -i mi -q) --- fruitstrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fruitstrap.c b/fruitstrap.c index 1f441b81..0cd72777 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -11,7 +11,7 @@ #define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" #define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" -#define GDB_SHELL "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7 -q -x " PREP_CMDS_PATH +#define GDB_SHELL "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7f -i mi -q -x " PREP_CMDS_PATH // approximation of what Xcode does: #define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ From 44a529b74c3e8e44f72832633ee3ca937d70942d Mon Sep 17 00:00:00 2001 From: Niklas Therning Date: Thu, 26 Apr 2012 21:35:20 +0200 Subject: [PATCH 04/22] Falling back to getpwuid() to determine user's home directory if HOME not set. --- fruitstrap.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fruitstrap.c b/fruitstrap.c index 0cd72777..3ed6bc33 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "MobileDevice.h" #define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" @@ -65,10 +66,19 @@ Boolean path_exists(CFTypeRef path) { } } +const char *get_home() { + const char* home = getenv("HOME"); + if (!home) { + struct passwd *pwd = getpwuid(getuid()); + home = pwd->pw_dir; + } + return home; +} + CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char* home = getenv("HOME"); + const char* home = get_home(); CFStringRef path; bool found = false; @@ -112,7 +122,7 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char *home = getenv("HOME"); + const char* home = get_home(); CFStringRef path; bool found = false; From bdff62f1e7f17da05a9ceceac626c85823705bef Mon Sep 17 00:00:00 2001 From: Niklas Therning Date: Fri, 27 Apr 2012 10:04:43 +0200 Subject: [PATCH 05/22] The child process and gdb process will now be killed when the parent process receives SIGTERM/SIGINT. --- fruitstrap.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fruitstrap.c b/fruitstrap.c index 3ed6bc33..8b7c3249 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -52,6 +52,7 @@ char *args = NULL; int timeout = 0; CFStringRef last_path = NULL; service_conn_t gdbfd; +int child_pid = 0; Boolean path_exists(CFTypeRef path) { if (CFGetTypeID(path) == CFStringGetTypeID()) { @@ -388,6 +389,11 @@ void start_remote_debug_server(AMDeviceRef device) { CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); } +void killed(int signum) { + killpg(child_pid, SIGTERM); + _exit(0); +} + void gdb_ready_handler(int signum) { _exit(0); @@ -487,6 +493,11 @@ void handle_device(AMDeviceRef device) { kill(parent, SIGHUP); // "No. I am your father." _exit(0); } + + child_pid = pid; + setpgid(pid, 0); // Set process group of child to child's pid + signal(SIGINT, killed); + signal(SIGTERM, killed); } void device_callback(struct am_device_notification_callback_info *info, void *arg) { From 9f693699ebe250683273b7b175eece86bfbdc25e Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 13 Sep 2012 08:31:58 -0400 Subject: [PATCH 06/22] Add even more search paths for Xcode. Added a command-line argument (-x) to specify the location of Xcode. --- fruitstrap.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/fruitstrap.c b/fruitstrap.c index 6693f515..e5f8ed4d 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -2,7 +2,9 @@ #import #include +#include #include +#include #include #include #include @@ -11,7 +13,7 @@ #define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" #define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" -#define GDB_SHELL "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7 -q -x " PREP_CMDS_PATH +#define GDB_ARGS "--arch armv7 -q -x " PREP_CMDS_PATH // approximation of what Xcode does: #define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ @@ -48,6 +50,7 @@ bool found_device = false, debug = false, verbose = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; +char *developer_path = NULL; int timeout = 0; CFStringRef last_path = NULL; service_conn_t gdbfd; @@ -72,9 +75,21 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef path; bool found = false; - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)"), home, version, build); - found = path_exists(path); - + if (developer_path) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), developer_path, version, build); + found = path_exists(path); + } + if (!found && developer_path) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Platforms/iPhoneOS.platform/DeviceSupport/%@"), developer_path, version); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)"), home, version, build); + found = path_exists(path); + } if (!found) { path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), version, build); @@ -91,11 +106,26 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { found = path_exists(path); } if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), version, build); + found = path_exists(path); + } + if (!found) { path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@"), version); found = path_exists(path); } + if (!found && verbose) + { + printf("Version: "); + fflush(stdout); + CFShow(version); + printf("Build: "); + fflush(stdout); + CFShow(build); + } + CFRelease(version); CFRelease(build); @@ -120,7 +150,7 @@ CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { found = path_exists(path); if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@/DeveloperDiskImage.dmg)"), version, build); + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg)"), version, build); found = path_exists(path); } if (!found) { @@ -139,6 +169,18 @@ CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); found = path_exists(path); } + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), version, build); + found = path_exists(path); + } + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), version); + found = path_exists(path); + } + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); + found = path_exists(path); + } CFRelease(version); CFRelease(build); @@ -380,7 +422,7 @@ void start_remote_debug_server(AMDeviceRef device) { void gdb_ready_handler(int signum) { - _exit(0); + _exit(0); } void handle_device(AMDeviceRef device) { @@ -473,7 +515,32 @@ void handle_device(AMDeviceRef device) { pid_t parent = getpid(); int pid = fork(); if (pid == 0) { - system(GDB_SHELL); // launch gdb + char requestedPath[PATH_MAX] = ""; + if (developer_path) { + snprintf(requestedPath, sizeof(requestedPath), "%s/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin", developer_path); + } + const char* gdbPaths[] = { + requestedPath, + "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin", + "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin", + }; + const char* gdbPath = 0; + int i; + for (i = 0; i < sizeof(gdbPaths) / sizeof(*gdbPaths); ++i) { + struct stat result; + if (*gdbPaths[i] && stat(gdbPaths[i], &result) == 0 && S_ISREG(result.st_mode)) { + gdbPath = gdbPaths[i]; + break; + } + } + if (gdbPath) { + char* command = malloc(strlen(gdbPath) + strlen(GDB_ARGS) + 2); + sprintf(command, "%s %s", gdbPath, GDB_ARGS); + system(command); // launch gdb + } else { + printf("Couldn't find gdb!\n"); + _exit(1); + } kill(parent, SIGHUP); // "No. I am your father." _exit(0); } @@ -496,7 +563,7 @@ void timeout_callback(CFRunLoopTimerRef timer, void *info) { } void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)]\n", app); + printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-x path to Xcode's Developer directory]\n", app); } int main(int argc, char *argv[]) { @@ -507,11 +574,12 @@ int main(int argc, char *argv[]) { { "args", required_argument, NULL, 'a' }, { "verbose", no_argument, NULL, 'v' }, { "timeout", required_argument, NULL, 't' }, + { "xcode", required_argument, NULL, 'x' }, { NULL, 0, NULL, 0 }, }; char ch; - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "dvi:b:a:t:x:", longopts, NULL)) != -1) { switch (ch) { case 'd': @@ -532,6 +600,9 @@ int main(int argc, char *argv[]) { case 't': timeout = atoi(optarg); break; + case 'x': + developer_path = optarg; + break; default: usage(argv[0]); return 1; From 4885b977c2b072546bd2ecbdc9ef6bbcc06259d2 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 13 Sep 2012 08:36:51 -0400 Subject: [PATCH 07/22] Updated makefile to tolerate different xcode paths better. --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index bd6beb41..614b7a2c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -IOS_CC = /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc +IOS_CC = xcrun -sdk iphoneos clang all: demo.app fruitstrap @@ -9,10 +9,10 @@ demo.app: demo Info.plist codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app demo: demo.c - $(IOS_CC) -arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -framework CoreFoundation -o demo demo.c + $(IOS_CC) -isysroot `xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk -arch armv7 -framework CoreFoundation -o demo demo.c fruitstrap: fruitstrap.c - gcc -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c + clang -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c install: all ./fruitstrap demo.app @@ -21,4 +21,4 @@ debug: all ./fruitstrap -d demo.app clean: - rm -rf *.app demo fruitstrap \ No newline at end of file + rm -rf *.app demo fruitstrap From 05f934d5013ca01de31ec6e500551c233ed71593 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 13 Sep 2012 08:39:27 -0400 Subject: [PATCH 08/22] Updated the readme. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1ac3fb8..ffbc3cd0 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro ## Usage -* `fruitstrap [-d] -b [device_id]` +* `fruitstrap [-d] -b [device_id] [-x xcode_path] [-v] [-t timeout] [-a args_to_app]` * Optional `-d` flag launches a remote GDB session after the app has been installed. * `` must be an iPhone application bundle, *not* an IPA. * Optional device id, useful when you have more than one iPhone/iPad connected to your computer +* Optional path to Xcode. Passing "-x `xcode-select -print-path`" should probably do the right thing. ## Demo From 24ee5946f66eec8725cad884da436d0f4a71d44d Mon Sep 17 00:00:00 2001 From: Connor Dunn Date: Tue, 25 Sep 2012 16:59:12 +0100 Subject: [PATCH 09/22] Combine various commits from different forks (use xcode-select, add unbuffered option, properly kill child process) to get a reasonably working version, also tidy up where we look for Xcode. --- README.md | 8 +-- fruitstrap.c | 181 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 130 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index f087557c..0a6e13c5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ -## This project is no longer maintained. - fruitstrap ========== Install and debug iPhone apps without using Xcode. Designed to work on unjailbroken devices. ## Requirements -* Mac OS X. Tested on Snow Leopard only. -* You need to have a valid iPhone development certificate installed. +* Mac OS X. Tested on Lion/Mountain Lion. +* You need to have a valid iPhone development certificate installed (or at least a correctly signed iOS app). * Xcode must be installed, along with the SDK for your iOS version. ## Usage -* `fruitstrap [-d] -b [device_id]` +* `fruitstrap [-d/--debug] [-i/--id device_id] -b/--bundle [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]` * Optional `-d` flag launches a remote GDB session after the app has been installed. * `` must be an iPhone application bundle, *not* an IPA. * Optional `device_id`; useful when you have more than one iPhone/iPad connected. diff --git a/fruitstrap.c b/fruitstrap.c index 6693f515..b7b33339 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -7,11 +7,12 @@ #include #include #include +#include #include "MobileDevice.h" #define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" #define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" -#define GDB_SHELL "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7 -q -x " PREP_CMDS_PATH +#define GDB_SHELL "`xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7f -i mi -q -x " PREP_CMDS_PATH // approximation of what Xcode does: #define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ @@ -36,7 +37,9 @@ set inferior-auto-start-cfm off\n\ set sharedLibrary load-rules dyld \".*libobjc.*\" all dyld \".*CoreFoundation.*\" all dyld \".*Foundation.*\" all dyld \".*libSystem.*\" all dyld \".*AppKit.*\" all dyld \".*PBGDBIntrospectionSupport.*\" all dyld \".*/usr/lib/dyld.*\" all dyld \".*CarbonDataFormatters.*\" all dyld \".*libauto.*\" all dyld \".*CFDataFormatters.*\" all dyld \"/System/Library/Frameworks\\\\\\\\|/System/Library/PrivateFrameworks\\\\\\\\|/usr/lib\" extern dyld \".*\" all exec \".*\" all\n\ sharedlibrary apply-load-rules all\n\ - set inferior-auto-start-dyld 1") + set inferior-auto-start-dyld 1\n\ + continue\n\ + quit") typedef struct am_device * AMDeviceRef; int AMDeviceSecureTransferPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); @@ -44,13 +47,14 @@ int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); int AMDeviceLookupApplications(AMDeviceRef device, int zero, CFDictionaryRef* result); -bool found_device = false, debug = false, verbose = false; +bool found_device = false, debug = false, verbose = false, unbuffered = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; int timeout = 0; CFStringRef last_path = NULL; service_conn_t gdbfd; +int child_pid = 0; Boolean path_exists(CFTypeRef path) { if (CFGetTypeID(path) == CFStringGetTypeID()) { @@ -65,37 +69,79 @@ Boolean path_exists(CFTypeRef path) { } } +CFStringRef copy_xcode_dev_path() { + FILE *fpipe = NULL; + char *command = "xcode-select -print-path"; + + if (!(fpipe = (FILE *)popen(command, "r"))) + { + perror("Error encountered while opening pipe"); + exit(EXIT_FAILURE); + } + + char buffer[256] = { '\0' }; + + fgets(buffer, sizeof(buffer), fpipe); + pclose(fpipe); + + strtok(buffer, "\n"); + return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); +} + + +const char *get_home() { + const char* home = getenv("HOME"); + if (!home) { + struct passwd *pwd = getpwuid(getuid()); + home = pwd->pw_dir; + } + return home; +} + CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char* home = getenv("HOME"); + const char* home = get_home(); CFStringRef path; bool found = false; + + CFStringRef xcodeDevPath = copy_xcode_dev_path(); - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)"), home, version, build); - found = path_exists(path); - + found = false; + // Try using xcode-select --print-path if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), version, build); - found = path_exists(path); - } + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/Latest"), xcodeDevPath); + found = path_exists(path); + } + // If not look in the default xcode location (xcode-select is sometimes wrong) if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@"), home, version); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@"), version); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@"), version); - found = path_exists(path); - } - + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/Platforms/iPhoneOS.platform/DeviceSupport/%@"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/Platforms/iPhoneOS.platform/DeviceSupport/Latest")); + found = path_exists(path); + } + + CFRelease(xcodeDevPath); CFRelease(version); CFRelease(build); @@ -112,40 +158,51 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char *home = getenv("HOME"); + const char* home = get_home(); CFStringRef path; bool found = false; + + CFStringRef xcodeDevPath = copy_xcode_dev_path(); - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), home, version, build); - found = path_exists(path); - - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@/DeveloperDiskImage.dmg)"), version, build); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/@%/DeveloperDiskImage.dmg"), home, version); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/@%/DeveloperDiskImage.dmg"), version); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/Latest/DeveloperDiskImage.dmg"), home); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); - found = path_exists(path); - } - + found = false; + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg"), xcodeDevPath); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); + found = path_exists(path); + } + + CFRelease(xcodeDevPath); CFRelease(version); CFRelease(build); if (!found) { CFRelease(path); - printf("[ !! ] Unable to locate DeviceSupport directory containing DeveloperDiskImage.dmg.\n"); + printf("[ !! ] Unable to locate DeviceSupport directory containing DeveloperDiskImage.dmg.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); exit(1); } @@ -378,6 +435,11 @@ void start_remote_debug_server(AMDeviceRef device) { CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); } +void killed(int signum) { + killpg(child_pid, SIGTERM); + _exit(0); +} + void gdb_ready_handler(int signum) { _exit(0); @@ -477,6 +539,11 @@ void handle_device(AMDeviceRef device) { kill(parent, SIGHUP); // "No. I am your father." _exit(0); } + + child_pid = pid; + setpgid(pid, 0); // Set process group of child to child's pid + signal(SIGINT, killed); + signal(SIGTERM, killed); } void device_callback(struct am_device_notification_callback_info *info, void *arg) { @@ -496,7 +563,7 @@ void timeout_callback(CFRunLoopTimerRef timer, void *info) { } void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)]\n", app); + printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]\n", app); } int main(int argc, char *argv[]) { @@ -507,11 +574,12 @@ int main(int argc, char *argv[]) { { "args", required_argument, NULL, 'a' }, { "verbose", no_argument, NULL, 'v' }, { "timeout", required_argument, NULL, 't' }, + { "unbuffered", no_argument, NULL, 'u' }, { NULL, 0, NULL, 0 }, }; char ch; - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u", longopts, NULL)) != -1) { switch (ch) { case 'd': @@ -532,6 +600,9 @@ int main(int argc, char *argv[]) { case 't': timeout = atoi(optarg); break; + case 'u': + unbuffered = 1; + break; default: usage(argv[0]); return 1; @@ -543,6 +614,8 @@ int main(int argc, char *argv[]) { exit(0); } + if (unbuffered) setbuf(stdout, NULL); + printf("------ Install phase ------\n"); assert(access(app_path, F_OK) == 0); From 9faf60c519187f74941e1ccbacbdf0de25aa332d Mon Sep 17 00:00:00 2001 From: Connor Dunn Date: Wed, 26 Sep 2012 10:43:36 +0100 Subject: [PATCH 10/22] Add function to find a file within Xcode, searching several locations, and use for finding debug stuff include gdb. Also add in unprompted's -x to allow the user to specify Xcode location. --- fruitstrap.c | 156 +++++++++++++++++++++++---------------------------- 1 file changed, 69 insertions(+), 87 deletions(-) diff --git a/fruitstrap.c b/fruitstrap.c index b7b33339..0107348e 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -12,7 +12,7 @@ #define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" #define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" -#define GDB_SHELL "`xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7f -i mi -q -x " PREP_CMDS_PATH +#define GDB_SHELL "--arch armv7f -i mi -q -x " PREP_CMDS_PATH // approximation of what Xcode does: #define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ @@ -51,6 +51,7 @@ bool found_device = false, debug = false, verbose = false, unbuffered = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; +char *developer_path = NULL; int timeout = 0; CFStringRef last_path = NULL; service_conn_t gdbfd; @@ -88,67 +89,59 @@ CFStringRef copy_xcode_dev_path() { return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); } +CFStringRef copy_xcode_path_for(CFStringRef search) { + CFStringRef xcodeDevPath = copy_xcode_dev_path(); + CFStringRef path; + bool found = false; + + // Try user specified path first if available + if (developer_path && !found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%@"), developer_path, search); + found = path_exists(path); + } + // Try using xcode-select --print-path + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, search); + found = path_exists(path); + } + // If not look in the default xcode location (xcode-select is sometimes wrong) + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/%@"), search); + found = path_exists(path); + } + + CFRelease(xcodeDevPath); -const char *get_home() { - const char* home = getenv("HOME"); - if (!home) { - struct passwd *pwd = getpwuid(getuid()); - home = pwd->pw_dir; + if (found) { + return path; + } else { + CFRelease(path); + return NULL; } - return home; } CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char* home = get_home(); - CFStringRef path; - bool found = false; - - CFStringRef xcodeDevPath = copy_xcode_dev_path(); + CFStringRef path = NULL; - found = false; // Try using xcode-select --print-path - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); - found = path_exists(path); + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build)); } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/Latest"), xcodeDevPath); - found = path_exists(path); + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)))); } - // If not look in the default xcode location (xcode-select is sometimes wrong) - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); - found = path_exists(path); + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest")); } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/Platforms/iPhoneOS.platform/DeviceSupport/%@"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/Platforms/iPhoneOS.platform/DeviceSupport/Latest")); - found = path_exists(path); - } - - CFRelease(xcodeDevPath); + CFRelease(version); CFRelease(build); - if (!found) + if (path == NULL) { - CFRelease(path); - printf("[ !! ] Unable to locate DeviceSupport directory.\n"); + printf("[ !! ] Unable to locate DeviceSupport directory.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); exit(1); } @@ -158,51 +151,25 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char* home = get_home(); - CFStringRef path; - bool found = false; - - CFStringRef xcodeDevPath = copy_xcode_dev_path(); + CFStringRef path = NULL; - found = false; - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), xcodeDevPath, CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg"), xcodeDevPath); - found = path_exists(path); + // Try using xcode-select --print-path + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build)); } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build); - found = path_exists(path); + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)))); } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3))); - found = path_exists(path); + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); - found = path_exists(path); - } - - CFRelease(xcodeDevPath); + CFRelease(version); CFRelease(build); - if (!found) { - CFRelease(path); - printf("[ !! ] Unable to locate DeviceSupport directory containing DeveloperDiskImage.dmg.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); + if (path == NULL) + { + printf("[ !! ] Unable to locate DeviceSupport directory.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); exit(1); } @@ -535,7 +502,18 @@ void handle_device(AMDeviceRef device) { pid_t parent = getpid(); int pid = fork(); if (pid == 0) { - system(GDB_SHELL); // launch gdb + CFStringRef path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin")); + if (path == NULL) { + printf("[ !! ] Unable to locate GDB.\n"); + exit(1); + } else { + CFStringRef gdb_cmd = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ %@"), path, CFSTR(GDB_SHELL)); + + // Convert CFStringRef to char* for system call + const char *char_gdb_cmd = CFStringGetCStringPtr(gdb_cmd, kCFStringEncodingMacRoman); + + system(char_gdb_cmd); // launch gdb + } kill(parent, SIGHUP); // "No. I am your father." _exit(0); } @@ -563,7 +541,7 @@ void timeout_callback(CFRunLoopTimerRef timer, void *info) { } void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]\n", app); + printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered] [-x path to Xcode's Developer directory]\n", app); } int main(int argc, char *argv[]) { @@ -575,11 +553,12 @@ int main(int argc, char *argv[]) { { "verbose", no_argument, NULL, 'v' }, { "timeout", required_argument, NULL, 't' }, { "unbuffered", no_argument, NULL, 'u' }, + { "xcode", required_argument, NULL, 'x' }, { NULL, 0, NULL, 0 }, }; char ch; - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u:x:", longopts, NULL)) != -1) { switch (ch) { case 'd': @@ -603,6 +582,9 @@ int main(int argc, char *argv[]) { case 'u': unbuffered = 1; break; + case 'x': + developer_path = optarg; + break; default: usage(argv[0]); return 1; From add97f62afac4ba3391b971751162f91d8b3a24e Mon Sep 17 00:00:00 2001 From: Connor Dunn Date: Wed, 26 Sep 2012 10:57:39 +0100 Subject: [PATCH 11/22] Remove incorrect comments. --- fruitstrap.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fruitstrap.c b/fruitstrap.c index 0107348e..dc2f657f 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -125,7 +125,6 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); CFStringRef path = NULL; - // Try using xcode-select --print-path if (path == NULL) { path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build)); } @@ -153,7 +152,6 @@ CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); CFStringRef path = NULL; - // Try using xcode-select --print-path if (path == NULL) { path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build)); } From d4ebc903543709442cf925db8324b4549e66af73 Mon Sep 17 00:00:00 2001 From: Connor Dunn Date: Thu, 27 Sep 2012 14:30:02 +0100 Subject: [PATCH 12/22] Add back in searching for DeviceSupport in the home directory as it is still used, also add a hacky search as a last resort for DeveloperDiskImage.dmg as Xcode seems to not always create a Latest symlink to follow. Also made whitespace consistent. --- fruitstrap.c | 158 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 56 deletions(-) diff --git a/fruitstrap.c b/fruitstrap.c index dc2f657f..7387552f 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -70,53 +70,88 @@ Boolean path_exists(CFTypeRef path) { } } +CFStringRef copy_long_shot_disk_image_path() { + FILE *fpipe = NULL; + char *command = "find `xcode-select --print-path` -name DeveloperDiskImage.dmg | tail -n 1"; + + if (!(fpipe = (FILE *)popen(command, "r"))) + { + perror("Error encountered while opening pipe"); + exit(EXIT_FAILURE); + } + + char buffer[256] = { '\0' }; + + fgets(buffer, sizeof(buffer), fpipe); + pclose(fpipe); + + strtok(buffer, "\n"); + return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); +} + CFStringRef copy_xcode_dev_path() { - FILE *fpipe = NULL; - char *command = "xcode-select -print-path"; + FILE *fpipe = NULL; + char *command = "xcode-select -print-path"; - if (!(fpipe = (FILE *)popen(command, "r"))) - { - perror("Error encountered while opening pipe"); - exit(EXIT_FAILURE); - } + if (!(fpipe = (FILE *)popen(command, "r"))) + { + perror("Error encountered while opening pipe"); + exit(EXIT_FAILURE); + } - char buffer[256] = { '\0' }; + char buffer[256] = { '\0' }; - fgets(buffer, sizeof(buffer), fpipe); - pclose(fpipe); + fgets(buffer, sizeof(buffer), fpipe); + pclose(fpipe); - strtok(buffer, "\n"); - return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); + strtok(buffer, "\n"); + return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); +} + +const char *get_home() { + const char* home = getenv("HOME"); + if (!home) { + struct passwd *pwd = getpwuid(getuid()); + home = pwd->pw_dir; + } + return home; } CFStringRef copy_xcode_path_for(CFStringRef search) { CFStringRef xcodeDevPath = copy_xcode_dev_path(); CFStringRef path; bool found = false; + const char* home = get_home(); + // Try user specified path first if available if (developer_path && !found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%@"), developer_path, search); - found = path_exists(path); + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%@"), developer_path, search); + found = path_exists(path); } // Try using xcode-select --print-path if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, search); - found = path_exists(path); - } - // If not look in the default xcode location (xcode-select is sometimes wrong) + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, search); + found = path_exists(path); + } + // If not look in the default xcode location (xcode-select is sometimes wrong) + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/%@"), search); + found = path_exists(path); + } + // If not look in the users home directory, Xcode can store device support stuff there if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/%@"), search); - found = path_exists(path); - } - - CFRelease(xcodeDevPath); + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/%@"), home, search); + found = path_exists(path); + } + + CFRelease(xcodeDevPath); if (found) { - return path; + return path; } else { - CFRelease(path); - return NULL; + CFRelease(path); + return NULL; } } @@ -126,15 +161,18 @@ CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef path = NULL; if (path == NULL) { - path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build)); - } - if (path == NULL) { - path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)))); - } - if (path == NULL) { - path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest")); - } - + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("iOS DeviceSupport/%@ (%@)"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@"), version)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest")); + } + CFRelease(version); CFRelease(build); @@ -153,21 +191,29 @@ CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { CFStringRef path = NULL; if (path == NULL) { - path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)), build)); - } - if (path == NULL) { - path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), CFStringCreateWithSubstring(NULL, version, CFRangeMake(0,3)))); - } - if (path == NULL) { - path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); - } - + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFStringCreateWithFormat(NULL, NULL, CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), version)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); + } + CFRelease(version); CFRelease(build); + + if (path == NULL) { + // Sometimes Latest seems to be missing in Xcode, in that case use find and hope for the best + path = copy_long_shot_disk_image_path(); + if (CFStringGetLength(path) < 5) { + path = NULL; + } + } if (path == NULL) { - printf("[ !! ] Unable to locate DeviceSupport directory.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); + printf("[ !! ] Unable to locate DeveloperDiskImage.dmg.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); exit(1); } @@ -407,7 +453,7 @@ void killed(int signum) { void gdb_ready_handler(int signum) { - _exit(0); + _exit(0); } void handle_device(AMDeviceRef device) { @@ -500,18 +546,18 @@ void handle_device(AMDeviceRef device) { pid_t parent = getpid(); int pid = fork(); if (pid == 0) { - CFStringRef path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin")); - if (path == NULL) { - printf("[ !! ] Unable to locate GDB.\n"); - exit(1); - } else { - CFStringRef gdb_cmd = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ %@"), path, CFSTR(GDB_SHELL)); + CFStringRef path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin")); + if (path == NULL) { + printf("[ !! ] Unable to locate GDB.\n"); + exit(1); + } else { + CFStringRef gdb_cmd = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ %@"), path, CFSTR(GDB_SHELL)); - // Convert CFStringRef to char* for system call - const char *char_gdb_cmd = CFStringGetCStringPtr(gdb_cmd, kCFStringEncodingMacRoman); + // Convert CFStringRef to char* for system call + const char *char_gdb_cmd = CFStringGetCStringPtr(gdb_cmd, kCFStringEncodingMacRoman); - system(char_gdb_cmd); // launch gdb - } + system(char_gdb_cmd); // launch gdb + } kill(parent, SIGHUP); // "No. I am your father." _exit(0); } From ef6d787635dad259d560d6157ad0cc22567c91ea Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Fri, 28 Sep 2012 11:05:00 -0400 Subject: [PATCH 13/22] Build with iOS 6.0 SDK for iOS 5.1. --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 11f2092b..681f3dfb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ IOS_CC = xcrun -sdk iphoneos clang +IOS_MIN_OS = 5.1 +IOS_SDK = 6.0 all: demo.app fruitstrap @@ -9,7 +11,7 @@ demo.app: demo Info.plist codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app demo: demo.c - $(IOS_CC) -isysroot `xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk -arch armv7 -framework CoreFoundation -o demo demo.c + $(IOS_CC) -isysroot `xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(IOS_SDK).sdk -mios-version-min=$(IOS_MIN_OS) -arch armv7 -framework CoreFoundation -o demo demo.c fruitstrap: fruitstrap.c clang -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c From 114dc35f8b3896d501f2ac020099124950bf21a5 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Fri, 28 Sep 2012 11:08:21 -0400 Subject: [PATCH 14/22] Fixed some warnings. --- MobileDevice.h | 7 ++++++- fruitstrap.c | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/MobileDevice.h b/MobileDevice.h index 1a39b098..437660bd 100644 --- a/MobileDevice.h +++ b/MobileDevice.h @@ -448,6 +448,11 @@ void AMDAddLogFileDescriptor(int fd); //kern_return_t AMDeviceSendMessage(service_conn_t socket, void *unused, CFPropertyListRef plist); //kern_return_t AMDeviceReceiveMessage(service_conn_t socket, CFDictionaryRef options, CFPropertyListRef * result); +typedef int (*am_device_install_application_callback)(CFDictionaryRef, int); + +mach_error_t AMDeviceInstallApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callback, void *user); +mach_error_t AMDeviceTransferApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callbackj, void *user); + /* ---------------------------------------------------------------------------- * Semi-private routines * ------------------------------------------------------------------------- */ @@ -486,4 +491,4 @@ typedef unsigned int (*t_performOperation)(struct am_restore_device *rdev, } #endif -#endif \ No newline at end of file +#endif diff --git a/fruitstrap.c b/fruitstrap.c index 7387552f..b75a716a 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -273,7 +273,7 @@ void mount_developer_image(AMDeviceRef device) { CFRelease(options); } -void transfer_callback(CFDictionaryRef dict, int arg) { +mach_error_t transfer_callback(CFDictionaryRef dict, int arg) { int percent; CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); @@ -290,14 +290,17 @@ void transfer_callback(CFDictionaryRef dict, int arg) { } last_path = CFStringCreateCopy(NULL, path); } + + return 0; } -void install_callback(CFDictionaryRef dict, int arg) { +mach_error_t install_callback(CFDictionaryRef dict, int arg) { int percent; CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); printf("[%3d%%] %s\n", (percent / 2) + 50, CFStringGetCStringPtr(status, kCFStringEncodingMacRoman)); + return 0; } void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { @@ -486,7 +489,7 @@ void handle_device(AMDeviceRef device) { CFRelease(relative_url); - int afcFd; + service_conn_t afcFd; assert(AMDeviceStartService(device, CFSTR("com.apple.afc"), &afcFd, NULL) == 0); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); @@ -503,7 +506,7 @@ void handle_device(AMDeviceRef device) { assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); - int installFd; + service_conn_t installFd; assert(AMDeviceStartService(device, CFSTR("com.apple.mobile.installation_proxy"), &installFd, NULL) == 0); assert(AMDeviceStopSession(device) == 0); From cb0619d3482e6ecdccbed8742cbbce9a17e66fc6 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Fri, 28 Sep 2012 11:16:08 -0400 Subject: [PATCH 15/22] Removed "-x" option. It only existed because I hadn't written the code to popen xcode-select. --- README.md | 3 +-- fruitstrap.c | 13 ++----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 214d4a91..3c2ae9af 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro ## Usage -* `fruitstrap [-d/--debug] [-i/--id device_id] -b/--bundle [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered] [-x/--xcode xcode-path]` +* `fruitstrap [-d/--debug] [-i/--id device_id] -b/--bundle [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]` * Optional `-d` flag launches a remote GDB session after the app has been installed. * `` must be an iPhone application bundle, *not* an IPA. * Optional device id, useful when you have more than one iPhone/iPad connected to your computer -* Optional path to Xcode. Passing "-x `xcode-select -print-path`" should probably do the right thing. ## Demo diff --git a/fruitstrap.c b/fruitstrap.c index b75a716a..d64e0fbd 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -51,7 +51,6 @@ bool found_device = false, debug = false, verbose = false, unbuffered = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; -char *developer_path = NULL; int timeout = 0; CFStringRef last_path = NULL; service_conn_t gdbfd; @@ -124,11 +123,6 @@ CFStringRef copy_xcode_path_for(CFStringRef search) { const char* home = get_home(); - // Try user specified path first if available - if (developer_path && !found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%@"), developer_path, search); - found = path_exists(path); - } // Try using xcode-select --print-path if (!found) { path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, search); @@ -588,7 +582,7 @@ void timeout_callback(CFRunLoopTimerRef timer, void *info) { } void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered] [-x path to Xcode's Developer directory]\n", app); + printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]\n", app); } int main(int argc, char *argv[]) { @@ -605,7 +599,7 @@ int main(int argc, char *argv[]) { }; char ch; - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u:x:", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u:", longopts, NULL)) != -1) { switch (ch) { case 'd': @@ -629,9 +623,6 @@ int main(int argc, char *argv[]) { case 'u': unbuffered = 1; break; - case 'x': - developer_path = optarg; - break; default: usage(argv[0]); return 1; From 8be8529e19164feadd8488dac36500a34a986ab1 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Fri, 28 Sep 2012 11:30:41 -0400 Subject: [PATCH 16/22] Made extra arguments to gdb optional and stripped off recent changes to the hardcoded ones. --- README.md | 4 +++- fruitstrap.c | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3c2ae9af..ec9af5de 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro ## Usage -* `fruitstrap [-d/--debug] [-i/--id device_id] -b/--bundle [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]` +* `fruitstrap [-d/--debug] [-i/--id device_id] -b/--bundle [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered] [-g/--gdbargs gdb_arguments]` * Optional `-d` flag launches a remote GDB session after the app has been installed. * `` must be an iPhone application bundle, *not* an IPA. * Optional device id, useful when you have more than one iPhone/iPad connected to your computer +* `` are passed as argv to the running app. +* `` are passed to gdb. ## Demo diff --git a/fruitstrap.c b/fruitstrap.c index d64e0fbd..9efadacf 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -12,7 +12,7 @@ #define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" #define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" -#define GDB_SHELL "--arch armv7f -i mi -q -x " PREP_CMDS_PATH +#define GDB_SHELL "--arch armv7f -x " PREP_CMDS_PATH // approximation of what Xcode does: #define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ @@ -51,6 +51,7 @@ bool found_device = false, debug = false, verbose = false, unbuffered = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; +char *gdb_args = ""; int timeout = 0; CFStringRef last_path = NULL; service_conn_t gdbfd; @@ -548,7 +549,7 @@ void handle_device(AMDeviceRef device) { printf("[ !! ] Unable to locate GDB.\n"); exit(1); } else { - CFStringRef gdb_cmd = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ %@"), path, CFSTR(GDB_SHELL)); + CFStringRef gdb_cmd = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ %@ %s"), path, CFSTR(GDB_SHELL), gdb_args); // Convert CFStringRef to char* for system call const char *char_gdb_cmd = CFStringGetCStringPtr(gdb_cmd, kCFStringEncodingMacRoman); @@ -594,12 +595,12 @@ int main(int argc, char *argv[]) { { "verbose", no_argument, NULL, 'v' }, { "timeout", required_argument, NULL, 't' }, { "unbuffered", no_argument, NULL, 'u' }, - { "xcode", required_argument, NULL, 'x' }, + { "gbdbargs", required_argument, NULL, 'g' }, { NULL, 0, NULL, 0 }, }; char ch; - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u:", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "dvi:b:a:t:u:g:", longopts, NULL)) != -1) { switch (ch) { case 'd': @@ -623,6 +624,9 @@ int main(int argc, char *argv[]) { case 'u': unbuffered = 1; break; + case 'g': + gdb_args = optarg; + break; default: usage(argv[0]); return 1; From 9be35795a0ea3e3c85dc9a03f1bbca39c25daf1e Mon Sep 17 00:00:00 2001 From: Connor Dunn Date: Mon, 1 Oct 2012 11:04:30 +0100 Subject: [PATCH 17/22] Add min OS X version to support Lion when building on Mountain Lion, add gdbargs to usage output. --- Makefile | 3 ++- fruitstrap.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 681f3dfb..87ca0eaf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ IOS_CC = xcrun -sdk iphoneos clang IOS_MIN_OS = 5.1 IOS_SDK = 6.0 +OSX_MIN = 10.7 all: demo.app fruitstrap @@ -14,7 +15,7 @@ demo: demo.c $(IOS_CC) -isysroot `xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(IOS_SDK).sdk -mios-version-min=$(IOS_MIN_OS) -arch armv7 -framework CoreFoundation -o demo demo.c fruitstrap: fruitstrap.c - clang -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c + clang -o fruitstrap -mmacosx-version-min=$(OSX_MIN) -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c install: all ./fruitstrap -b demo.app diff --git a/fruitstrap.c b/fruitstrap.c index 9efadacf..479ceb8f 100644 --- a/fruitstrap.c +++ b/fruitstrap.c @@ -583,7 +583,7 @@ void timeout_callback(CFRunLoopTimerRef timer, void *info) { } void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered]\n", app); + printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered] [-g/--gdbargs gdbarguments]\n", app); } int main(int argc, char *argv[]) { From e534f43299a8e43f4472391cdd381310bfac9d89 Mon Sep 17 00:00:00 2001 From: Fil Maj Date: Mon, 29 Oct 2012 14:30:04 -0700 Subject: [PATCH 18/22] added make target for building list-devices-compatible executable --- Makefile | 3 + listdevices.c | 917 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 920 insertions(+) create mode 100644 listdevices.c diff --git a/Makefile b/Makefile index 87ca0eaf..cf5e896c 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ demo: demo.c fruitstrap: fruitstrap.c clang -o fruitstrap -mmacosx-version-min=$(OSX_MIN) -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c +listdevices: listdevices.c + gcc -g -o listdevices -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks listdevices.c + install: all ./fruitstrap -b demo.app diff --git a/listdevices.c b/listdevices.c new file mode 100644 index 00000000..826c98ee --- /dev/null +++ b/listdevices.c @@ -0,0 +1,917 @@ +//TODO: don't copy/mount DeveloperDiskImage.dmg if it's already done - Xcode checks this somehow + +#import +#include +#include +#include +#include +#include +#include +#include +#include "MobileDevice.h" + +#define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" +#define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" +#define GDB_SHELL "`xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7f -i mi -q -x " PREP_CMDS_PATH + +#define PRINT(...) if (!quiet) { printf(__VA_ARGS__); fflush(stdout); } + +// approximation of what Xcode does: +#define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ + set auto-raise-load-levels 1\n\ + set shlib-path-substitutions /usr \"{ds_path}/Symbols/usr\" /System \"{ds_path}/Symbols/System\" \"{device_container}\" \"{disk_container}\" \"/private{device_container}\" \"{disk_container}\" /Developer \"{ds_path}/Symbols/Developer\"\n\ + set remote max-packet-size 1024\n\ + set sharedlibrary check-uuids on\n\ + set env NSUnbufferedIO YES\n\ + set minimal-signal-handling 1\n\ + set sharedlibrary load-rules \\\".*\\\" \\\".*\\\" container\n\ + set inferior-auto-start-dyld 0\n\ + file \"{disk_app}\"\n\ + set remote executable-directory {device_app}\n\ + set remote noack-mode 1\n\ + set trust-readonly-sections 1\n\ + target remote-mobile " FDVENDOR_PATH "\n\ + mem 0x1000 0x3fffffff cache\n\ + mem 0x40000000 0xffffffff none\n\ + mem 0x00000000 0x0fff none\n\ + run {args}\n\ + set minimal-signal-handling 0\n\ + set inferior-auto-start-cfm off\n\ + set sharedLibrary load-rules dyld \".*libobjc.*\" all dyld \".*CoreFoundation.*\" all dyld \".*Foundation.*\" all dyld \".*libSystem.*\" all dyld \".*AppKit.*\" all dyld \".*PBGDBIntrospectionSupport.*\" all dyld \".*/usr/lib/dyld.*\" all dyld \".*CarbonDataFormatters.*\" all dyld \".*libauto.*\" all dyld \".*CFDataFormatters.*\" all dyld \"/System/Library/Frameworks\\\\\\\\|/System/Library/PrivateFrameworks\\\\\\\\|/usr/lib\" extern dyld \".*\" all exec \".*\" all\n\ + sharedlibrary apply-load-rules all\n\ + set inferior-auto-start-dyld 1\n\ + continue\n\ + quit") + +typedef enum { + OP_NONE, + + OP_INSTALL, + OP_UNINSTALL, + OP_LIST_DEVICES, + OP_UPLOAD_FILE, + OP_LIST_FILES + +} operation_t; + +typedef struct am_device * AMDeviceRef; +int AMDeviceSecureTransferPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceLookupApplications(AMDeviceRef device, int zero, CFDictionaryRef* result); + +bool found_device = false, debug = false, verbose = false, quiet = false; +char *app_path = NULL; +char *device_id = NULL; +char *doc_file_path = NULL; +char *target_filename = NULL; +char *bundle_id = NULL; +char *args = NULL; +int timeout = 0; +operation_t operation = OP_INSTALL; +CFStringRef last_path = NULL; +service_conn_t gdbfd; + +Boolean path_exists(CFTypeRef path) { + if (CFGetTypeID(path) == CFStringGetTypeID()) { + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, true); + Boolean result = CFURLResourceIsReachable(url, NULL); + CFRelease(url); + return result; + } else if (CFGetTypeID(path) == CFURLGetTypeID()) { + return CFURLResourceIsReachable(path, NULL); + } else { + return false; + } +} + +CFStringRef copy_xcode_dev_path() { + FILE *fpipe = NULL; + char *command = "xcode-select -print-path"; + + if (!(fpipe = (FILE *)popen(command, "r"))) + { + perror("Error encountered while opening pipe"); + exit(EXIT_FAILURE); + } + + char buffer[256] = { '\0' }; + + fgets(buffer, sizeof(buffer), fpipe); + pclose(fpipe); + + strtok(buffer, "\n"); + return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); +} + +CFStringRef copy_device_support_path(AMDeviceRef device) { + CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); + CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); + const char* home = getenv("HOME"); + CFStringRef path; + bool found = false; + + CFStringRef xcodeDevPath = copy_xcode_dev_path(); + + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)"), home, version, build); + found = path_exists(path); + + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@"), home, version); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/Latest"), home); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), xcodeDevPath, version, build); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@"), xcodeDevPath, version); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/Latest"), xcodeDevPath); + found = path_exists(path); + } + + CFRelease(version); + CFRelease(build); + CFRelease(xcodeDevPath); + + if (!found) + { + PRINT("[ !! ] Unable to locate DeviceSupport directory.\n"); + CFRelease(path); + exit(EXIT_FAILURE); + } + + return path; +} + +CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { + CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); + CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); + const char *home = getenv("HOME"); + CFStringRef path; + bool found = false; + + CFStringRef xcodeDevPath = copy_xcode_dev_path(); + + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), home, version, build); + found = path_exists(path); + + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@/DeveloperDiskImage.dmg"), home, version); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/Latest/DeveloperDiskImage.dmg"), home); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), xcodeDevPath, version, build); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/%@/DeveloperDiskImage.dmg"), xcodeDevPath, version); + found = path_exists(path); + } + if (!found) + { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg"), xcodeDevPath); + found = path_exists(path); + } + + CFRelease(version); + CFRelease(build); + CFRelease(xcodeDevPath); + + if (!found) + { + PRINT("[ !! ] Unable to locate DeviceSupport directory containing DeveloperDiskImage.dmg.\n"); + + CFIndex pathLength = CFStringGetLength(path); + char *buffer = calloc(pathLength + 1, sizeof(char)); + Boolean success = CFStringGetCString(path, buffer, pathLength + 1, kCFStringEncodingUTF8); + CFRelease(path); + + if (success) PRINT("[ !! ] Last path checked: %s\n", buffer); + exit(EXIT_FAILURE); + } + + return path; +} + +void mount_callback(CFDictionaryRef dict, int arg) { + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + + if (CFEqual(status, CFSTR("LookingUpImage"))) { + PRINT("[ 0%%] Looking up developer disk image\n"); + } else if (CFEqual(status, CFSTR("CopyingImage"))) { + PRINT("[ 30%%] Copying DeveloperDiskImage.dmg to device\n"); + } else if (CFEqual(status, CFSTR("MountingImage"))) { + PRINT("[ 90%%] Mounting developer disk image\n"); + } +} + +void mount_developer_image(AMDeviceRef device) { + CFStringRef ds_path = copy_device_support_path(device); + CFStringRef image_path = copy_developer_disk_image_path(device); + CFStringRef sig_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@.signature"), image_path); + CFRelease(ds_path); + + if (verbose) { + PRINT("Device support path: "); + fflush(stdout); + CFShow(ds_path); + PRINT("Developer disk image: "); + fflush(stdout); + CFShow(image_path); + } + + FILE* sig = fopen(CFStringGetCStringPtr(sig_path, kCFStringEncodingMacRoman), "rb"); + void *sig_buf = malloc(128); + assert(fread(sig_buf, 1, 128, sig) == 128); + fclose(sig); + CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, 128, NULL); + CFRelease(sig_path); + + CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") }; + CFTypeRef values[] = { sig_data, CFSTR("Developer") }; + CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease(sig_data); + + int result = AMDeviceMountImage(device, image_path, options, &mount_callback, 0); + if (result == 0) { + PRINT("[ 95%%] Developer disk image mounted successfully\n"); + } else if (result == 0xe8000076 /* already mounted */) { + PRINT("[ 95%%] Developer disk image already mounted\n"); + } else { + PRINT("[ !! ] Unable to mount developer disk image. (%x)\n", result); + exit(EXIT_FAILURE); + } + + CFRelease(image_path); + CFRelease(options); +} + +void transfer_callback(CFDictionaryRef dict, int arg) { + int percent; + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + + if (CFEqual(status, CFSTR("CopyingFile"))) { + CFStringRef path = CFDictionaryGetValue(dict, CFSTR("Path")); + + if ((last_path == NULL || !CFEqual(path, last_path)) && !CFStringHasSuffix(path, CFSTR(".ipa"))) { + PRINT("[%3d%%] Copying %s to device\n", percent / 2, CFStringGetCStringPtr(path, kCFStringEncodingMacRoman)); + } + + if (last_path != NULL) { + CFRelease(last_path); + } + last_path = CFStringCreateCopy(NULL, path); + } +} + +void operation_callback(CFDictionaryRef dict, int arg) { + int percent; + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + PRINT("[%3d%%] %s\n", (percent / 2) + 50, CFStringGetCStringPtr(status, kCFStringEncodingMacRoman)); +} + +void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { + CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); + + struct msghdr message; + struct iovec iov[1]; + struct cmsghdr *control_message = NULL; + char ctrl_buf[CMSG_SPACE(sizeof(int))]; + char dummy_data[1]; + + memset(&message, 0, sizeof(struct msghdr)); + memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); + + dummy_data[0] = ' '; + iov[0].iov_base = dummy_data; + iov[0].iov_len = sizeof(dummy_data); + + message.msg_name = NULL; + message.msg_namelen = 0; + message.msg_iov = iov; + message.msg_iovlen = 1; + message.msg_controllen = CMSG_SPACE(sizeof(int)); + message.msg_control = ctrl_buf; + + control_message = CMSG_FIRSTHDR(&message); + control_message->cmsg_level = SOL_SOCKET; + control_message->cmsg_type = SCM_RIGHTS; + control_message->cmsg_len = CMSG_LEN(sizeof(int)); + + *((int *) CMSG_DATA(control_message)) = gdbfd; + + sendmsg(socket, &message, 0); + CFSocketInvalidate(s); + CFRelease(s); +} + +CFURLRef copy_device_app_url(AMDeviceRef device, CFStringRef identifier) { + CFDictionaryRef result; + assert(AMDeviceLookupApplications(device, 0, &result) == 0); + + CFDictionaryRef app_dict = CFDictionaryGetValue(result, identifier); + assert(app_dict != NULL); + + CFStringRef app_path = CFDictionaryGetValue(app_dict, CFSTR("Path")); + assert(app_path != NULL); + + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, app_path, kCFURLPOSIXPathStyle, true); + CFRelease(result); + return url; +} + +CFStringRef copy_disk_app_identifier(CFURLRef disk_app_url) { + CFURLRef plist_url = CFURLCreateCopyAppendingPathComponent(NULL, disk_app_url, CFSTR("Info.plist"), false); + CFReadStreamRef plist_stream = CFReadStreamCreateWithFile(NULL, plist_url); + CFReadStreamOpen(plist_stream); + CFPropertyListRef plist = CFPropertyListCreateWithStream(NULL, plist_stream, 0, kCFPropertyListImmutable, NULL, NULL); + CFStringRef bundle_identifier = CFRetain(CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"))); + CFReadStreamClose(plist_stream); + + CFRelease(plist_url); + CFRelease(plist_stream); + CFRelease(plist); + + return bundle_identifier; +} + +void write_gdb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { + CFMutableStringRef cmds = CFStringCreateMutableCopy(NULL, 0, GDB_PREP_CMDS); + CFRange range = { 0, CFStringGetLength(cmds) }; + + CFStringRef ds_path = copy_device_support_path(device); + CFStringFindAndReplace(cmds, CFSTR("{ds_path}"), ds_path, range, 0); + range.length = CFStringGetLength(cmds); + + if (args) { + CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingASCII); + CFStringFindAndReplace(cmds, CFSTR("{args}"), cf_args, range, 0); + CFRelease(cf_args); + } else { + CFStringFindAndReplace(cmds, CFSTR(" {args}"), CFSTR(""), range, 0); + } + range.length = CFStringGetLength(cmds); + + CFStringRef bundle_identifier = copy_disk_app_identifier(disk_app_url); + CFURLRef device_app_url = copy_device_app_url(device, bundle_identifier); + CFStringRef device_app_path = CFURLCopyFileSystemPath(device_app_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{device_app}"), device_app_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFStringRef disk_app_path = CFURLCopyFileSystemPath(disk_app_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{disk_app}"), disk_app_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFURLRef device_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, device_app_url); + CFStringRef device_container_path = CFURLCopyFileSystemPath(device_container_url, kCFURLPOSIXPathStyle); + CFMutableStringRef dcp_noprivate = CFStringCreateMutableCopy(NULL, 0, device_container_path); + range.length = CFStringGetLength(dcp_noprivate); + CFStringFindAndReplace(dcp_noprivate, CFSTR("/private/var/"), CFSTR("/var/"), range, 0); + range.length = CFStringGetLength(cmds); + CFStringFindAndReplace(cmds, CFSTR("{device_container}"), dcp_noprivate, range, 0); + range.length = CFStringGetLength(cmds); + + CFURLRef disk_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url); + CFStringRef disk_container_path = CFURLCopyFileSystemPath(disk_container_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{disk_container}"), disk_container_path, range, 0); + + CFDataRef cmds_data = CFStringCreateExternalRepresentation(NULL, cmds, kCFStringEncodingASCII, 0); + FILE *out = fopen(PREP_CMDS_PATH, "w"); + fwrite(CFDataGetBytePtr(cmds_data), CFDataGetLength(cmds_data), 1, out); + fclose(out); + + CFRelease(cmds); + if (ds_path != NULL) CFRelease(ds_path); + CFRelease(bundle_identifier); + CFRelease(device_app_url); + CFRelease(device_app_path); + CFRelease(disk_app_path); + CFRelease(device_container_url); + CFRelease(device_container_path); + CFRelease(dcp_noprivate); + CFRelease(disk_container_url); + CFRelease(disk_container_path); + CFRelease(cmds_data); +} + +void start_remote_debug_server(AMDeviceRef device) { + assert(AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL) == 0); + + CFSocketRef fdvendor = CFSocketCreate(NULL, AF_UNIX, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); + + int yes = 1; + setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + struct sockaddr_un address; + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + strcpy(address.sun_path, FDVENDOR_PATH); + address.sun_len = SUN_LEN(&address); + CFDataRef address_data = CFDataCreate(NULL, (const UInt8 *)&address, sizeof(address)); + + unlink(FDVENDOR_PATH); + + CFSocketSetAddress(fdvendor, address_data); + CFRelease(address_data); + CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); +} + +void gdb_ready_handler(int signum) +{ + _exit(EXIT_SUCCESS); +} + +void read_dir(service_conn_t afcFd, afc_connection* afc_conn_p, const char* dir) +{ + char *dir_ent; + + afc_connection afc_conn; + if (!afc_conn_p) { + afc_conn_p = &afc_conn; + AFCConnectionOpen(afcFd, 0, &afc_conn_p); + + } + + printf("%s\n", dir); + fflush(stdout); + + afc_dictionary afc_dict; + afc_dictionary* afc_dict_p = &afc_dict; + AFCFileInfoOpen(afc_conn_p, dir, &afc_dict_p); + + afc_directory afc_dir; + afc_directory* afc_dir_p = &afc_dir; + afc_error_t err = AFCDirectoryOpen(afc_conn_p, dir, &afc_dir_p); + + if (err != 0) + { + // Couldn't open dir - was probably a file + return; + } + + while(true) { + err = AFCDirectoryRead(afc_conn_p, afc_dir_p, &dir_ent); + + if (!dir_ent) + break; + + if (strcmp(dir_ent, ".") == 0 || strcmp(dir_ent, "..") == 0) + continue; + + char* dir_joined = malloc(strlen(dir) + strlen(dir_ent) + 2); + strcpy(dir_joined, dir); + if (dir_joined[strlen(dir)-1] != '/') + strcat(dir_joined, "/"); + strcat(dir_joined, dir_ent); + read_dir(afcFd, afc_conn_p, dir_joined); + free(dir_joined); + } + + AFCDirectoryClose(afc_conn_p, afc_dir_p); +} + +service_conn_t start_afc_service(AMDeviceRef device) { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + assert(AMDeviceValidatePairing(device) == 0); + assert(AMDeviceStartSession(device) == 0); + + service_conn_t afcFd; + assert(AMDeviceStartService(device, AMSVC_AFC, &afcFd, NULL) == 0); + + assert(AMDeviceStopSession(device) == 0); + assert(AMDeviceDisconnect(device) == 0); + return afcFd; +} + +service_conn_t start_install_proxy_service(AMDeviceRef device) { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + assert(AMDeviceValidatePairing(device) == 0); + assert(AMDeviceStartSession(device) == 0); + + service_conn_t installFd; + assert(AMDeviceStartService(device, CFSTR("com.apple.mobile.installation_proxy"), &installFd, NULL) == 0); + + assert(AMDeviceStopSession(device) == 0); + assert(AMDeviceDisconnect(device) == 0); + + return installFd; +} + +// Used to send files to app-specific sandbox (Documents dir) +service_conn_t start_house_arrest_service(AMDeviceRef device) { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + assert(AMDeviceValidatePairing(device) == 0); + assert(AMDeviceStartSession(device) == 0); + + service_conn_t houseFd; + + CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingASCII); + if (AMDeviceStartHouseArrestService(device, cf_bundle_id, 0, &houseFd, 0) != 0) + { + PRINT("Unable to find bundle with id: %s\n", bundle_id); + exit(1); + } + + assert(AMDeviceStopSession(device) == 0); + assert(AMDeviceDisconnect(device) == 0); + CFRelease(cf_bundle_id); + + return houseFd; +} + +void install_app(AMDeviceRef device) { + service_conn_t afcFd = start_afc_service(device); + + CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); + + assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0); + close(afcFd); + + service_conn_t installFd = start_install_proxy_service(device); + + CFStringRef keys[] = { CFSTR("PackageType") }; + CFStringRef values[] = { CFSTR("Developer") }; + CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + mach_error_t result = AMDeviceInstallApplication (installFd, path, options, operation_callback, NULL); + if (result != 0) + { + PRINT("AMDeviceInstallApplication failed: %d\n", result); + exit(1); + } + + close(installFd); + CFRelease(path); + CFRelease(options); +} + +void uninstall_app(AMDeviceRef device) { + CFStringRef path = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingASCII); + + service_conn_t installFd = start_install_proxy_service(device); + + mach_error_t result = AMDeviceUninstallApplication (installFd, path, NULL, operation_callback, NULL); + if (result != 0) + { + PRINT("AMDeviceUninstallApplication failed: %d\n", result); + exit(1); + } + + close(installFd); + CFRelease(path); +} + +char* get_filename_from_path(char* path) +{ + char *ptr = path + strlen(path); + while (ptr > path) + { + if (*ptr == '/') + break; + --ptr; + } + if (ptr+1 >= path+strlen(path)) + return NULL; + if (ptr == path) + return ptr; + return ptr+1; +} + +void* read_file_to_memory(char * path, size_t* file_size) +{ + struct stat buf; + int err = stat(path, &buf); + if (err < 0) + { + return NULL; + } + + *file_size = buf.st_size; + FILE* fd = fopen(path, "r"); + char* content = malloc(*file_size); + if (fread(content, *file_size, 1, fd) != 1) + { + fclose(fd); + return NULL; + } + fclose(fd); + return content; +} + +void list_files(AMDeviceRef device) +{ + service_conn_t houseFd = start_house_arrest_service(device); + + afc_connection afc_conn; + afc_connection* afc_conn_p = &afc_conn; + AFCConnectionOpen(houseFd, 0, &afc_conn_p); + + read_dir(houseFd, afc_conn_p, "/"); +} + +void upload_file(AMDeviceRef device) { + service_conn_t houseFd = start_house_arrest_service(device); + + afc_file_ref file_ref; + + afc_connection afc_conn; + afc_connection* afc_conn_p = &afc_conn; + AFCConnectionOpen(houseFd, 0, &afc_conn_p); + + // read_dir(houseFd, NULL, "/"); + + if (!target_filename) + { + target_filename = get_filename_from_path(doc_file_path); + } + char *target_path = malloc(sizeof("/Documents/") + strlen(target_filename) + 1); + strcat(target_path, "/Documents/"); + strcat(target_path, target_filename); + + size_t file_size; + void* file_content = read_file_to_memory(doc_file_path, &file_size); + + if (!file_content) + { + PRINT("Could not open file: %s\n", doc_file_path); + exit(-1); + } + + assert(AFCFileRefOpen(afc_conn_p, target_path, 3, &file_ref) == 0); + assert(AFCFileRefWrite(afc_conn_p, file_ref, file_content, file_size) == 0); + assert(AFCFileRefClose(afc_conn_p, file_ref) == 0); + assert(AFCConnectionClose(afc_conn_p) == 0); + + free(target_path); + free(file_content); +} + +void do_debug(AMDeviceRef device) { + CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); + + CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); + CFURLRef url = CFURLCopyAbsoluteURL(relative_url); + + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + assert(AMDeviceValidatePairing(device) == 0); + assert(AMDeviceStartSession(device) == 0); + + PRINT("------ Debug phase ------\n"); + + mount_developer_image(device); // put debugserver on the device + start_remote_debug_server(device); // start debugserver + write_gdb_prep_cmds(device, url); // dump the necessary gdb commands into a file + + CFRelease(path); + CFRelease(relative_url); + CFRelease(url); + + PRINT("[100%%] Connecting to remote debug server\n"); + PRINT("-------------------------\n"); + + signal(SIGHUP, gdb_ready_handler); + + pid_t parent = getpid(); + int pid = fork(); + if (pid == 0) { + system(GDB_SHELL); // launch gdb + kill(parent, SIGHUP); // "No. I am your father." + _exit(EXIT_SUCCESS); + } +} + +void handle_device(AMDeviceRef device) { + if (found_device) return; // handle one device only + + CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device); + + PRINT ("found device id\n"); + if (device_id != NULL) { + if(strcmp(device_id, CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())) == 0) { + found_device = true; + } else { + return; + } + } else { + if (operation == OP_LIST_DEVICES) { + printf ("%s\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); + fflush(stdout); + return; + } + found_device = true; + } + + if (operation == OP_INSTALL) { + PRINT("[ 0%%] Found device (%s), beginning install\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); + + install_app(device); + + PRINT("[100%%] Installed package %s\n", app_path); + + if (debug) + do_debug(device); + + } else if (operation == OP_UNINSTALL) { + PRINT("[ 0%%] Found device (%s), beginning uninstall\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); + + uninstall_app(device); + + PRINT("[100%%] uninstalled package %s\n", bundle_id); + + } else if (operation == OP_UPLOAD_FILE) { + PRINT("[ 0%%] Found device (%s), sending file\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); + + upload_file(device); + + PRINT("[100%%] file sent %s\n", doc_file_path); + + } else if (operation == OP_LIST_FILES) { + PRINT("[ 0%%] Found device (%s), listing / ...\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); + + list_files(device); + + PRINT("[100%%] done.\n"); + } + exit(0); +} + +void device_callback(struct am_device_notification_callback_info *info, void *arg) { + switch (info->msg) { + case ADNCI_MSG_CONNECTED: + if( info->dev->lockdown_conn ) { + handle_device(info->dev); + } + default: + break; + } +} + +void timeout_callback(CFRunLoopTimerRef timer, void *info) { + if (!found_device) { + PRINT("Timed out waiting for device.\n"); + exit(EXIT_FAILURE); + } +} + +void usage(const char* app) { + printf ("usage: %s [-q/--quiet] [-t/--timeout timeout(seconds)] [-v/--verbose] [] \n\n", app); + printf ("Commands available:\n"); + printf (" install [--id=device_id] --bundle=bundle.app [--debug] [--args=arguments] \n"); + printf (" * Install the specified app with optional arguments to the specified device, or all\n"); + printf (" attached devices if none are specified. \n\n"); + printf (" uninstall [--id=device_id] --bundle-id= \n"); + printf (" * Removes the specified bundle identifier (eg com.foo.MyApp) from the specified device,\n"); + printf (" or all attached devices if none are specified. \n\n"); + printf (" upload [--id=device_id] --bundle-id= --file=filename [--target=filename]\n"); + printf (" * Uploads a file to the documents directory of the app specified with the bundle \n"); + printf (" identifier (eg com.foo.MyApp) to the specified device, or all attached devices if\n"); + printf (" none are specified. \n\n"); + printf (" list-files [--id=device_id] --bundle-id= \n"); + printf (" * Lists the the files in the app-specific sandbox specified with the bundle \n"); + printf (" identifier (eg com.foo.MyApp) on the specified device, or all attached devices if\n"); + printf (" none are specified. \n\n"); + printf (" list-devices \n"); + printf (" * List all attached devices. \n\n"); + fflush(stdout); +} + +bool args_are_valid() { + return (operation == OP_INSTALL && app_path) || + (operation == OP_UNINSTALL && bundle_id) || + (operation == OP_UPLOAD_FILE && bundle_id && doc_file_path) || + (operation == OP_LIST_FILES && bundle_id) || + (operation == OP_LIST_DEVICES); +} + +int main(int argc, char *argv[]) { + static struct option global_longopts[]= { + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, 'v' }, + { "timeout", required_argument, NULL, 't' }, + + { "id", required_argument, NULL, 'i' }, + { "bundle", required_argument, NULL, 'b' }, + { "file", required_argument, NULL, 'f' }, + { "target", required_argument, NULL, 1 }, + { "bundle-id", required_argument, NULL, 0 }, + + { "debug", no_argument, NULL, 'd' }, + { "args", required_argument, NULL, 'a' }, + + { NULL, 0, NULL, 0 }, + }; + + char ch; + while ((ch = getopt_long(argc, argv, "qvi:b:f:da:t:", global_longopts, NULL)) != -1) + { + switch (ch) { + case 0: + bundle_id = optarg; + break; + case 'q': + quiet = 1; + break; + case 'v': + verbose = 1; + break; + case 'd': + debug = 1; + break; + case 't': + timeout = atoi(optarg); + break; + case 'b': + app_path = optarg; + break; + case 'f': + doc_file_path = optarg; + break; + case 1: + target_filename = optarg; + break; + case 'a': + args = optarg; + break; + case 'i': + device_id = optarg; + break; + + default: + usage(argv[0]); + return 1; + } + } + + if (optind >= argc) { + usage(argv [0]); + exit(EXIT_SUCCESS); + } + + operation = OP_NONE; + if (strcmp (argv [optind], "install") == 0) { + operation = OP_INSTALL; + } else if (strcmp (argv [optind], "uninstall") == 0) { + operation = OP_UNINSTALL; + } else if (strcmp (argv [optind], "list-devices") == 0) { + operation = OP_LIST_DEVICES; + } else if (strcmp (argv [optind], "upload") == 0) { + operation = OP_UPLOAD_FILE; + } else if (strcmp (argv [optind], "list-files") == 0) { + operation = OP_LIST_FILES; + } else { + usage (argv [0]); + exit (0); + } + + if (!args_are_valid()) { + usage(argv[0]); + exit(0); + } + + if (operation == OP_INSTALL) + assert(access(app_path, F_OK) == 0); + + AMDSetLogLevel(1+4+2+8+16+32+64+128); // otherwise syslog gets flooded with crap + if (timeout > 0) + { + CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + timeout, 0, 0, 0, timeout_callback, NULL); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); + PRINT("[....] Waiting up to %d seconds for iOS device to be connected\n", timeout); + } + else + { + PRINT("[....] Waiting for iOS device to be connected\n"); + } + + struct am_device_notification *notify; + AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, ¬ify); + + CFRunLoopRun(); +} + From 8159bb41ec76d8f01a90e71bdecbde78ada75985 Mon Sep 17 00:00:00 2001 From: Fil Maj Date: Mon, 29 Oct 2012 14:34:04 -0700 Subject: [PATCH 19/22] package json for good times --- package.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..9d87bedb --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name":"fruitstrap", + "version":"0.0.3", + "author":"Grant Hughes ", + "main":"fruitstrap", + "scripts":{ + "install":"make fruitstrap && make listdevices" + } +} From 98ff34a63730b0965238d10ce1dd5276080ec976 Mon Sep 17 00:00:00 2001 From: Matthew Purland Date: Wed, 14 Nov 2012 12:41:21 -0800 Subject: [PATCH 20/22] Allow certificate configurable from make command line via make CERT="iPhone Developer: ..." --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cf5e896c..ad193296 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,15 @@ IOS_MIN_OS = 5.1 IOS_SDK = 6.0 OSX_MIN = 10.7 +CERT="iPhone Developer" + all: demo.app fruitstrap demo.app: demo Info.plist mkdir -p demo.app cp demo demo.app/ cp Info.plist ResourceRules.plist demo.app/ - codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app + codesign -f -s $(CERT) --entitlements Entitlements.plist demo.app demo: demo.c $(IOS_CC) -isysroot `xcode-select -print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(IOS_SDK).sdk -mios-version-min=$(IOS_MIN_OS) -arch armv7 -framework CoreFoundation -o demo demo.c From f3cc10582421f3319655b99deb322ff0847f42cc Mon Sep 17 00:00:00 2001 From: Matthew Purland Date: Wed, 14 Nov 2012 12:45:45 -0800 Subject: [PATCH 21/22] Allow fruitstrap to be installed to /usr/local/bin through make install_os --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index ad193296..99e2aede 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ listdevices: listdevices.c install: all ./fruitstrap -b demo.app +install_os: fruitstrap + sudo mkdir -p /usr/local/bin + sudo cp fruitstrap /usr/local/bin/fruitstrap + debug: all ./fruitstrap -d -b demo.app From 3b8d9716a274ed9d4fe95e7f55bf31915d3f5126 Mon Sep 17 00:00:00 2001 From: Matthew Purland Date: Wed, 14 Nov 2012 12:53:22 -0800 Subject: [PATCH 22/22] Added install instructions for make install_os --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ec9af5de..c1ffa35c 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro * You need to have a valid iPhone development certificate installed (or at least a correctly signed iOS app). * Xcode must be installed, along with the SDK for your iOS version. +## Install + +* `make install_os` will compile and install fruitstrap to /usr/local/bin + ## Usage * `fruitstrap [-d/--debug] [-i/--id device_id] -b/--bundle [-a/--args arguments] [-t/--timeout timeout(seconds)] [-u/--unbuffered] [-g/--gdbargs gdb_arguments]`