//TODO: don't copy/mount DeveloperDiskImage.dmg if it's already done - Xcode checks this somehow #import #import #include #include #include #include #include #include #include #include #include #include #include #include #include "MobileDevice.h" #define APP_VERSION "1.3.0" #define PREP_CMDS_PATH "/tmp/fruitstrap-lldb-prep-cmds-" #define LLDB_SHELL "lldb -s " PREP_CMDS_PATH /* * Startup script passed to lldb. * To see how xcode interacts with lldb, put this into .lldbinit: * log enable -v -f /Users/vargaz/lldb.log lldb all * log enable -v -f /Users/vargaz/gdb-remote.log gdb-remote all */ #define LLDB_PREP_CMDS CFSTR("\ platform select remote-ios --sysroot {symbols_path}\n\ target create \"{disk_app}\"\n\ script fruitstrap_device_app=\"{device_app}\"\n\ script fruitstrap_connect_url=\"connect://127.0.0.1:{device_port}\"\n\ command script import \"{python_file_path}\"\n\ command script add -f {python_command}.connect_command connect\n\ command script add -s asynchronous -f {python_command}.run_command run\n\ command script add -s asynchronous -f {python_command}.autoexit_command autoexit\n\ command script add -s asynchronous -f {python_command}.safequit_command safequit\n\ connect\n\ ") const char* lldb_prep_no_cmds = ""; const char* lldb_prep_interactive_cmds = "\ run\n\ "; const char* lldb_prep_noninteractive_justlaunch_cmds = "\ run\n\ safequit\n\ "; const char* lldb_prep_noninteractive_cmds = "\ run\n\ autoexit\n\ "; /* * Some things do not seem to work when using the normal commands like process connect/launch, so we invoke them * through the python interface. Also, Launch () doesn't seem to work when ran from init_module (), so we add * a command which can be used by the user to run it. */ #define LLDB_FRUITSTRAP_MODULE CFSTR("\ import lldb\n\ import sys\n\ import shlex\n\ \n\ def connect_command(debugger, command, result, internal_dict):\n\ # These two are passed in by the script which loads us\n\ connect_url = internal_dict['fruitstrap_connect_url']\n\ error = lldb.SBError()\n\ \n\ process = lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(), connect_url, None, error)\n\ \n\ # Wait for connection to succeed\n\ listener = lldb.target.GetDebugger().GetListener()\n\ listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged)\n\ events = []\n\ state = (process.GetState() or lldb.eStateInvalid)\n\ while state != lldb.eStateConnected:\n\ event = lldb.SBEvent()\n\ if listener.WaitForEvent(1, event):\n\ state = process.GetStateFromEvent(event)\n\ events.append(event)\n\ else:\n\ state = lldb.eStateInvalid\n\ \n\ # Add events back to queue, otherwise lldb freezes\n\ for event in events:\n\ listener.AddEvent(event)\n\ \n\ def run_command(debugger, command, result, internal_dict):\n\ device_app = internal_dict['fruitstrap_device_app']\n\ error = lldb.SBError()\n\ lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n\ lldb.target.Launch(lldb.SBLaunchInfo(shlex.split('{args}')), error)\n\ lockedstr = ': Locked'\n\ if lockedstr in str(error):\n\ print('\\nDevice Locked\\n')\n\ sys.exit(254)\n\ else:\n\ print(str(error))\n\ \n\ def safequit_command(debugger, command, result, internal_dict):\n\ process = lldb.target.process\n\ listener = debugger.GetListener()\n\ listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n\ event = lldb.SBEvent()\n\ while True:\n\ if listener.WaitForEvent(1, event):\n\ state = process.GetStateFromEvent(event)\n\ else:\n\ state = lldb.eStateInvalid\n\ process.Detach()\n\ sys.exit(0)\n\ \n\ def autoexit_command(debugger, command, result, internal_dict):\n\ process = lldb.target.process\n\ listener = debugger.GetListener()\n\ listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n\ event = lldb.SBEvent()\n\ while True:\n\ if listener.WaitForEvent(1, event):\n\ state = process.GetStateFromEvent(event)\n\ else:\n\ state = lldb.eStateInvalid\n\ \n\ stdout = process.GetSTDOUT(1024)\n\ while stdout:\n\ sys.stdout.write(stdout)\n\ stdout = process.GetSTDOUT(1024)\n\ \n\ stderr = process.GetSTDERR(1024)\n\ while stderr:\n\ sys.stdout.write(stderr)\n\ stderr = process.GetSTDERR(1024)\n\ \n\ if lldb.SBProcess.EventIsProcessEvent(event):\n\ if state == lldb.eStateExited:\n\ sys.exit(process.GetExitStatus())\n\ if state == lldb.eStateStopped:\n\ debugger.HandleCommand('frame select')\n\ debugger.HandleCommand('bt')\n\ sys.exit({exitcode_app_crash})\n\ ") typedef struct am_device * AMDeviceRef; mach_error_t AMDeviceSecureStartService(struct am_device *device, CFStringRef service_name, unsigned int *unknown, service_conn_t *handle); 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); mach_error_t AMDeviceLookupApplications(AMDeviceRef device, CFDictionaryRef options, CFDictionaryRef *result); int AMDeviceGetInterfaceType(struct am_device *device); bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, detect_only = false, install = true, uninstall = false; bool command_only = false; char *command = NULL; char *target_filename = NULL; char *upload_pathname = NULL; char *bundle_id = NULL; bool interactive = true; bool justlaunch = false; char *app_path = NULL; char *device_id = NULL; char *args = NULL; char *list_root = NULL; int timeout = 0; int port = 12345; CFStringRef last_path = NULL; service_conn_t gdbfd; pid_t parent = 0; // PID of child process running lldb pid_t child = 0; // Signal sent from child to parent process when LLDB finishes. const int SIGLLDB = SIGUSR1; AMDeviceRef best_device_match = NULL; // Error codes we report on different failures, so scripts can distinguish between user app exit // codes and our exit codes. For non app errors we use codes in reserved 128-255 range. const int exitcode_error = 253; const int exitcode_app_crash = 254; 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 find_path(CFStringRef rootPath, CFStringRef namePattern, CFStringRef expression) { FILE *fpipe = NULL; CFStringRef quotedRootPath = rootPath; CFStringRef cf_command; CFRange slashLocation; if (CFStringGetCharacterAtIndex(rootPath, 0) != '`') { quotedRootPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("'%@'"), rootPath); } slashLocation = CFStringFind(namePattern, CFSTR("/"), 0); if (slashLocation.location == kCFNotFound) { cf_command = CFStringCreateWithFormat(NULL, NULL, CFSTR("find %@ -name '%@' %@ 2>/dev/null | sort | tail -n 1"), quotedRootPath, namePattern, expression); } else { cf_command = CFStringCreateWithFormat(NULL, NULL, CFSTR("find %@ -path '%@' %@ 2>/dev/null | sort | tail -n 1"), quotedRootPath, namePattern, expression); } if (quotedRootPath != rootPath) { CFRelease(quotedRootPath); } char command[1024] = { '\0' }; CFStringGetCString(cf_command, command, sizeof(command), kCFStringEncodingUTF8); CFRelease(cf_command); if (!(fpipe = (FILE *)popen(command, "r"))) { perror("Error encountered while opening pipe"); exit(exitcode_error); } char buffer[256] = { '\0' }; fgets(buffer, sizeof(buffer), fpipe); pclose(fpipe); strtok(buffer, "\n"); return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); } CFStringRef copy_long_shot_disk_image_path() { return find_path(CFSTR("`xcode-select --print-path`"), CFSTR("DeveloperDiskImage.dmg"), CFSTR("")); } CFStringRef copy_xcode_dev_path() { static char xcode_dev_path[256] = { '\0' }; if (strlen(xcode_dev_path) == 0) { FILE *fpipe = NULL; char *command = "xcode-select -print-path"; if (!(fpipe = (FILE *)popen(command, "r"))) { perror("Error encountered while opening pipe"); exit(exitcode_error); } char buffer[256] = { '\0' }; fgets(buffer, sizeof(buffer), fpipe); pclose(fpipe); strtok(buffer, "\n"); strcpy(xcode_dev_path, buffer); } return CFStringCreateWithCString(NULL, xcode_dev_path, 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 subPath, CFStringRef search) { CFStringRef xcodeDevPath = copy_xcode_dev_path(); CFStringRef path; bool found = false; const char* home = get_home(); CFRange slashLocation; // Try using xcode-select --print-path if (!found) { path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@/%@"), xcodeDevPath, subPath, search); found = path_exists(path); } // Try find `xcode-select --print-path` with search as a name pattern if (!found) { slashLocation = CFStringFind(search, CFSTR("/"), 0); if (slashLocation.location == kCFNotFound) { path = find_path(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, subPath), search, CFSTR("-maxdepth 1")); } else { path = find_path(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, subPath), search, CFSTR("")); } found = CFStringGetLength(path) > 0 && 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/%@&%@"), subPath, 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("%s/Library/Developer/Xcode/%@/%@"), home, subPath, search); found = path_exists(path); } CFRelease(xcodeDevPath); if (found) { return path; } else { CFRelease(path); return NULL; } } // Please ensure that device is connected or the name will be unknown const CFStringRef get_device_hardware_name(const AMDeviceRef device) { CFStringRef model = AMDeviceCopyValue(device, 0, CFSTR("HardwareModel")); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("M68AP"), kCFCompareNonliteral)) return CFSTR("iPhone"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N45AP"), kCFCompareNonliteral)) return CFSTR("iPod touch"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N82AP"), kCFCompareNonliteral)) return CFSTR("iPhone 3G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N72AP"), kCFCompareNonliteral)) return CFSTR("iPod touch 2G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N88AP"), kCFCompareNonliteral)) return CFSTR("iPhone 3GS"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N18AP"), kCFCompareNonliteral)) return CFSTR("iPod touch 3G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("K48AP"), kCFCompareNonliteral)) return CFSTR("iPad"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N90AP"), kCFCompareNonliteral)) return CFSTR("iPhone 4 (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N81AP"), kCFCompareNonliteral)) return CFSTR("iPod touch 4G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("K66AP"), kCFCompareNonliteral)) return CFSTR("Apple TV 2G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N92AP"), kCFCompareNonliteral)) return CFSTR("iPhone 4 (CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N90BAP"), kCFCompareNonliteral)) return CFSTR("iPhone 4 (GSM, revision A)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("K93AP"), kCFCompareNonliteral)) return CFSTR("iPad 2"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("K94AP"), kCFCompareNonliteral)) return CFSTR("iPad 2 (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("K95AP"), kCFCompareNonliteral)) return CFSTR("iPad 2 (CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("K93AAP"), kCFCompareNonliteral)) return CFSTR("iPad 2 (Wi-Fi, revision A)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("P105AP"), kCFCompareNonliteral)) return CFSTR("iPad mini"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("P106AP"), kCFCompareNonliteral)) return CFSTR("iPad mini (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("P107AP"), kCFCompareNonliteral)) return CFSTR("iPad mini (CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N94AP"), kCFCompareNonliteral)) return CFSTR("iPhone 4S"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N41AP"), kCFCompareNonliteral)) return CFSTR("iPhone 5 (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N42AP"), kCFCompareNonliteral)) return CFSTR("iPhone 5 (Global/CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N48AP"), kCFCompareNonliteral)) return CFSTR("iPhone 5c (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N49AP"), kCFCompareNonliteral)) return CFSTR("iPhone 5c (Global/CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N51AP"), kCFCompareNonliteral)) return CFSTR("iPhone 5s (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N53AP"), kCFCompareNonliteral)) return CFSTR("iPhone 5s (Global/CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N61AP"), kCFCompareNonliteral)) return CFSTR("iPhone 6 (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("J1AP"), kCFCompareNonliteral)) return CFSTR("iPad 3"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("J2AP"), kCFCompareNonliteral)) return CFSTR("iPad 3 (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("J2AAP"), kCFCompareNonliteral)) return CFSTR("iPad 3 (CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("P101AP"), kCFCompareNonliteral)) return CFSTR("iPad 4"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("P102AP"), kCFCompareNonliteral)) return CFSTR("iPad 4 (GSM)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("P103AP"), kCFCompareNonliteral)) return CFSTR("iPad 4 (CDMA)"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("N78AP"), kCFCompareNonliteral)) return CFSTR("iPod touch 5G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("A1509"), kCFCompareNonliteral)) return CFSTR("iPod touch 5G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("J33AP"), kCFCompareNonliteral)) return CFSTR("Apple TV 3G"); if (kCFCompareEqualTo == CFStringCompare(model,CFSTR("J33IAP"), kCFCompareNonliteral)) return CFSTR("Apple TV 3.1G"); return model; //return CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), hwmodel); //return CFSTR("Unknown Device"); } char * MYCFStringCopyUTF8String(CFStringRef aString) { if (aString == NULL) { return NULL; } CFIndex length = CFStringGetLength(aString); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); char *buffer = (char *)malloc(maxSize); if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) { return buffer; } return NULL; } CFStringRef get_device_full_name(const AMDeviceRef device) { CFStringRef full_name = NULL, device_udid = AMDeviceCopyDeviceIdentifier(device), device_name = NULL, model_name = NULL; AMDeviceConnect(device); device_name = AMDeviceCopyValue(device, 0, CFSTR("DeviceName")), model_name = get_device_hardware_name(device); if (verbose) { char *devName = MYCFStringCopyUTF8String(device_name); printf("Device Name:[%s]\n",devName); CFShow(device_name); printf("\n"); free(devName); char *mdlName = MYCFStringCopyUTF8String(model_name); printf("Model Name:[%s]\n",mdlName); printf("MM: [%s]\n",CFStringGetCStringPtr(model_name, kCFStringEncodingUTF8)); CFShow(model_name); printf("\n"); free(mdlName); } if(device_name != NULL && model_name != NULL) { full_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ '%@' (%@)"), model_name, device_name, device_udid); } else { full_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("(%@ss)"), device_udid); } AMDeviceDisconnect(device); if(device_udid != NULL) CFRelease(device_udid); if(device_name != NULL) CFRelease(device_name); if(model_name != NULL) CFRelease(model_name); return full_name; } CFStringRef get_device_interface_name(const AMDeviceRef device) { // AMDeviceGetInterfaceType(device) 0=Unknown, 1 = Direct/USB, 2 = Indirect/WIFI switch(AMDeviceGetInterfaceType(device)) { case 1: return CFSTR("USB"); case 2: return CFSTR("WIFI"); default: return CFSTR("Unknown Connection"); } } CFMutableArrayRef get_device_product_version_parts(AMDeviceRef device) { CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, version, CFSTR(".")); CFMutableArrayRef result = CFArrayCreateMutableCopy(NULL, CFArrayGetCount(parts), parts); CFRelease(version); CFRelease(parts); return result; } CFStringRef copy_device_support_path(AMDeviceRef device) { CFStringRef version = NULL; CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); CFStringRef path = NULL; CFMutableArrayRef version_parts = get_device_product_version_parts(device); while (CFArrayGetCount(version_parts) > 0) { version = CFStringCreateByCombiningStrings(NULL, version_parts, CFSTR(".")); if (path == NULL) { path = copy_xcode_path_for(CFSTR("iOS DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), version, build)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), version, build)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (*)"), version)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), version); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest"), CFSTR("")); } CFRelease(version); if (path != NULL) { break; } CFArrayRemoveValueAtIndex(version_parts, CFArrayGetCount(version_parts) - 1); } CFRelease(version_parts); CFRelease(build); 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(exitcode_error); } return path; } CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { CFStringRef version = NULL; CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); CFStringRef path = NULL; CFMutableArrayRef version_parts = get_device_product_version_parts(device); while (CFArrayGetCount(version_parts) > 0) { version = CFStringCreateByCombiningStrings(NULL, version_parts, CFSTR(".")); if (path == NULL) { path = copy_xcode_path_for(CFSTR("iOS DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)/DeveloperDiskImage.dmg"), version, build)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)/DeveloperDiskImage.dmg"), version, build)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("*/%@ (*)/DeveloperDiskImage.dmg"), version)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/DeveloperDiskImage.dmg"), version)); } if (path == NULL) { path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest"), CFSTR("DeveloperDiskImage.dmg")); } CFRelease(version); if (path != NULL) { break; } CFArrayRemoveValueAtIndex(version_parts, CFArrayGetCount(version_parts) - 1); } CFRelease(version_parts); CFRelease(build); if (path == NULL) { 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(exitcode_error); } return path; } void mount_callback(CFDictionaryRef dict, int arg) { CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); if (CFEqual(status, CFSTR("LookingUpImage"))) { printf("[ 0%%] Looking up developer disk image\n"); } else if (CFEqual(status, CFSTR("CopyingImage"))) { printf("[ 30%%] Copying DeveloperDiskImage.dmg to device\n"); } else if (CFEqual(status, CFSTR("MountingImage"))) { printf("[ 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); if (verbose) { printf("Device support path: %s\n", CFStringGetCStringPtr(ds_path, CFStringGetSystemEncoding())); printf("Developer disk image: %s\n", CFStringGetCStringPtr(image_path, CFStringGetSystemEncoding())); } CFRelease(ds_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) { printf("[ 95%%] Developer disk image mounted successfully\n"); } else if (result == 0xe8000076 /* already mounted */) { printf("[ 95%%] Developer disk image already mounted\n"); } else { printf("[ !! ] Unable to mount developer disk image. (%x)\n", result); exit(exitcode_error); } CFRelease(image_path); CFRelease(options); } mach_error_t 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"))) { printf("[%3d%%] Copying %s to device\n", percent / 2, CFStringGetCStringPtr(path, kCFStringEncodingMacRoman)); } if (last_path != NULL) { CFRelease(last_path); } last_path = CFStringCreateCopy(NULL, path); } return 0; } 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; } CFURLRef copy_device_app_url(AMDeviceRef device, CFStringRef identifier) { CFDictionaryRef result = nil; NSArray *a = [NSArray arrayWithObjects: @"CFBundleIdentifier", // absolute must @"ApplicationDSID", @"ApplicationType", @"CFBundleExecutable", @"CFBundleDisplayName", @"CFBundleIconFile", @"CFBundleName", @"CFBundleShortVersionString", @"CFBundleSupportedPlatforms", @"CFBundleURLTypes", @"CodeInfoIdentifier", @"Container", @"Entitlements", @"HasSettingsBundle", @"IsUpgradeable", @"MinimumOSVersion", @"Path", @"SignerIdentity", @"UIDeviceFamily", @"UIFileSharingEnabled", @"UIStatusBarHidden", @"UISupportedInterfaceOrientations", nil]; NSDictionary *optionsDict = [NSDictionary dictionaryWithObject:a forKey:@"ReturnAttributes"]; CFDictionaryRef options = (CFDictionaryRef)optionsDict; afc_error_t resultStatus = AMDeviceLookupApplications(device, options, &result); assert(resultStatus == 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_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { CFStringRef ds_path = copy_device_support_path(device); CFStringRef symbols_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("'%@/Symbols'"), ds_path); CFMutableStringRef cmds = CFStringCreateMutableCopy(NULL, 0, LLDB_PREP_CMDS); CFRange range = { 0, CFStringGetLength(cmds) }; CFStringFindAndReplace(cmds, CFSTR("{symbols_path}"), symbols_path, range, 0); range.length = CFStringGetLength(cmds); CFStringFindAndReplace(cmds, CFSTR("{ds_path}"), ds_path, range, 0); range.length = CFStringGetLength(cmds); CFMutableStringRef pmodule = CFStringCreateMutableCopy(NULL, 0, LLDB_FRUITSTRAP_MODULE); CFRange rangeLLDB = { 0, CFStringGetLength(pmodule) }; CFStringRef exitcode_app_crash_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), exitcode_app_crash); CFStringFindAndReplace(pmodule, CFSTR("{exitcode_app_crash}"), exitcode_app_crash_str, rangeLLDB, 0); rangeLLDB.length = CFStringGetLength(pmodule); if (args) { CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingASCII); CFStringFindAndReplace(cmds, CFSTR("{args}"), cf_args, range, 0); rangeLLDB.length = CFStringGetLength(pmodule); CFStringFindAndReplace(pmodule, CFSTR("{args}"), cf_args, rangeLLDB, 0); //printf("write_lldb_prep_cmds:args: [%s][%s]\n", CFStringGetCStringPtr (cmds,kCFStringEncodingMacRoman), // CFStringGetCStringPtr(pmodule, kCFStringEncodingMacRoman)); CFRelease(cf_args); } else { CFStringFindAndReplace(cmds, CFSTR("{args}"), CFSTR(""), range, 0); CFStringFindAndReplace(pmodule, CFSTR("{args}"), CFSTR(""), rangeLLDB, 0); //printf("write_lldb_prep_cmds: [%s][%s]\n", CFStringGetCStringPtr (cmds,kCFStringEncodingMacRoman), // CFStringGetCStringPtr(pmodule, kCFStringEncodingMacRoman)); } 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); CFStringRef device_port = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), port); CFStringFindAndReplace(cmds, CFSTR("{device_port}"), device_port, 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); char python_file_path[300] = "/tmp/fruitstrap_"; char python_command[300] = "fruitstrap_"; if(device_id != NULL) { strcat(python_file_path, device_id); strcat(python_command, device_id); } strcat(python_file_path, ".py"); CFStringRef cf_python_command = CFStringCreateWithCString(NULL, python_command, kCFStringEncodingASCII); CFStringFindAndReplace(cmds, CFSTR("{python_command}"), cf_python_command, range, 0); range.length = CFStringGetLength(cmds); CFStringRef cf_python_file_path = CFStringCreateWithCString(NULL, python_file_path, kCFStringEncodingASCII); CFStringFindAndReplace(cmds, CFSTR("{python_file_path}"), cf_python_file_path, range, 0); range.length = CFStringGetLength(cmds); CFDataRef cmds_data = CFStringCreateExternalRepresentation(NULL, cmds, kCFStringEncodingASCII, 0); char prep_cmds_path[300] = PREP_CMDS_PATH; if(device_id != NULL) strcat(prep_cmds_path, device_id); FILE *out = fopen(prep_cmds_path, "w"); fwrite(CFDataGetBytePtr(cmds_data), CFDataGetLength(cmds_data), 1, out); // Write additional commands based on mode we're running in const char* extra_cmds; if (!interactive) { if (justlaunch) extra_cmds = lldb_prep_noninteractive_justlaunch_cmds; else extra_cmds = lldb_prep_noninteractive_cmds; } else if (nostart) extra_cmds = lldb_prep_no_cmds; else extra_cmds = lldb_prep_interactive_cmds; fwrite(extra_cmds, strlen(extra_cmds), 1, out); fclose(out); CFDataRef pmodule_data = CFStringCreateExternalRepresentation(NULL, pmodule, kCFStringEncodingASCII, 0); out = fopen(python_file_path, "w"); fwrite(CFDataGetBytePtr(pmodule_data), CFDataGetLength(pmodule_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); CFRelease(cf_python_command); CFRelease(cf_python_file_path); } CFSocketRef server_socket; CFSocketRef lldb_socket; CFWriteStreamRef serverWriteStream = NULL; CFWriteStreamRef lldbWriteStream = NULL; int kill_ptree(pid_t root, int signum); void server_callback (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { int res; if (CFDataGetLength (data) == 0) { // FIXME: Close the socket //shutdown (CFSocketGetNative (lldb_socket), SHUT_RDWR); //close (CFSocketGetNative (lldb_socket)); CFSocketInvalidate(lldb_socket); CFSocketInvalidate(server_socket); int mypid = getpid(); assert((child != 0) && (child != mypid)); //child should not be here if ((parent != 0) && (parent == mypid) && (child != 0)) { if (verbose) { printf("Got an empty packet hence killing child (%d) tree\n", child); } kill_ptree(child, SIGHUP); } exit(exitcode_error); return; } res = write (CFSocketGetNative (lldb_socket), CFDataGetBytePtr (data), CFDataGetLength (data)); } void lldb_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { //printf ("lldb: %s\n", CFDataGetBytePtr (data)); if (CFDataGetLength (data) == 0) return; write (gdbfd, CFDataGetBytePtr (data), CFDataGetLength (data)); } void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); assert (callbackType == kCFSocketAcceptCallBack); //PRINT ("callback!\n"); lldb_socket = CFSocketCreateWithNative(NULL, socket, kCFSocketDataCallBack, &lldb_callback, NULL); CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, lldb_socket, 0), kCFRunLoopCommonModes); } void start_remote_debug_server(AMDeviceRef device) { char buf [256]; int res, err, i; char msg [256]; int chsum, len; struct stat s; socklen_t buflen; struct sockaddr name; int namelen; assert(AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL) == 0); assert (gdbfd); /* * The debugserver connection is through a fd handle, while lldb requires a host/port to connect, so create an intermediate * socket to transfer data. */ server_socket = CFSocketCreateWithNative (NULL, gdbfd, kCFSocketDataCallBack, &server_callback, NULL); CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, server_socket, 0), kCFRunLoopCommonModes); struct sockaddr_in addr4; memset(&addr4, 0, sizeof(addr4)); addr4.sin_len = sizeof(addr4); addr4.sin_family = AF_INET; addr4.sin_port = htons(port); addr4.sin_addr.s_addr = htonl(INADDR_ANY); CFSocketRef fdvendor = CFSocketCreate(NULL, PF_INET, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); int yes = 1; setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); int flag = 1; res = setsockopt(CFSocketGetNative(fdvendor), IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); assert (res == 0); CFDataRef address_data = CFDataCreate(NULL, (const UInt8 *)&addr4, sizeof(addr4)); CFSocketSetAddress(fdvendor, address_data); CFRelease(address_data); CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); } void kill_ptree_inner(pid_t root, int signum, struct kinfo_proc *kp, int kp_len) { int i; for (i = 0; i < kp_len; i++) { if (kp[i].kp_eproc.e_ppid == root) { kill_ptree_inner(kp[i].kp_proc.p_pid, signum, kp, kp_len); } } if (root != getpid()) { kill(root, signum); } } int kill_ptree(pid_t root, int signum) { int mib[3]; size_t len; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_ALL; if (sysctl(mib, 3, NULL, &len, NULL, 0) == -1) { return -1; } struct kinfo_proc *kp = calloc(1, len); if (!kp) { return -1; } if (sysctl(mib, 3, kp, &len, NULL, 0) == -1) { free(kp); return -1; } kill_ptree_inner(root, signum, kp, len / sizeof(struct kinfo_proc)); free(kp); return 0; } void killed(int signum) { // SIGKILL needed to kill lldb, probably a better way to do this. kill(0, SIGKILL); _exit(0); } void lldb_finished_handler(int signum) { int status = 0; if (waitpid(child, &status, 0) == -1) perror("waitpid failed"); _exit(WEXITSTATUS(status)); } void bring_process_to_foreground() { if (setpgid(0, 0) == -1) perror("setpgid failed"); signal(SIGTTOU, SIG_IGN); if (tcsetpgrp(STDIN_FILENO, getpid()) == -1) perror("tcsetpgrp failed"); signal(SIGTTOU, SIG_DFL); } void setup_dummy_pipe_on_stdin(int pfd[2]) { if (pipe(pfd) == -1) perror("pipe failed"); if (dup2(pfd[0], STDIN_FILENO) == -1) perror("dup2 failed"); } void setup_lldb(AMDeviceRef device, CFURLRef url) { CFStringRef device_full_name = get_device_full_name(device), device_interface_name = get_device_interface_name(device); AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); printf("------ Debug phase ------\n"); if(AMDeviceGetInterfaceType(device) == 2) { printf("Cannot debug %s over %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding())); exit(0); } printf("Starting debug of %s connected through %s...\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding())); mount_developer_image(device); // put debugserver on the device start_remote_debug_server(device); // start debugserver write_lldb_prep_cmds(device, url); // dump the necessary lldb commands into a file CFRelease(url); printf("[100%%] Connecting to remote debug server\n"); printf("-------------------------\n"); setpgid(getpid(), 0); signal(SIGHUP, killed); signal(SIGINT, killed); signal(SIGTERM, killed); // Need this before fork to avoid race conditions. For child process we remove this right after fork. signal(SIGLLDB, lldb_finished_handler); parent = getpid(); } void launch_debugger(AMDeviceRef device, CFURLRef url) { setup_lldb(device, url); int pid = fork(); if (pid == 0) { signal(SIGHUP, SIG_DFL); signal(SIGLLDB, SIG_DFL); child = getpid(); int pfd[2] = {-1, -1}; if (isatty(STDIN_FILENO)) // If we are running on a terminal, then we need to bring process to foreground for input // to work correctly on lldb's end. bring_process_to_foreground(); else // If lldb is running in a non terminal environment, then it freaks out spamming "^D" and // "quit". It seems this is caused by read() on stdin returning EOF in lldb. To hack around // this we setup a dummy pipe on stdin, so read() would block expecting "user's" input. setup_dummy_pipe_on_stdin(pfd); char lldb_shell[400]; sprintf(lldb_shell, LLDB_SHELL); if(device_id != NULL) strcat(lldb_shell, device_id); int status = system(lldb_shell); // launch lldb if (status == -1) perror("failed launching lldb"); close(pfd[0]); close(pfd[1]); // Notify parent we're exiting kill(parent, SIGLLDB); // Pass lldb exit code _exit(WEXITSTATUS(status)); } else if (pid > 0) { child = pid; } else { perror("fork failed"); exit(exitcode_error); } } void launch_debugger_and_exit(AMDeviceRef device, CFURLRef url) { setup_lldb(device,url); int pfd[2] = {-1, -1}; if (pipe(pfd) == -1) perror("Pipe failed"); int pid = fork(); if (pid == 0) { signal(SIGHUP, SIG_DFL); signal(SIGLLDB, SIG_DFL); child = getpid(); if (dup2(pfd[0],STDIN_FILENO) == -1) perror("dup2 failed"); char lldb_shell[400]; sprintf(lldb_shell, LLDB_SHELL); if(device_id != NULL) strcat(lldb_shell, device_id); int status = system(lldb_shell); // launch lldb if (status == -1) perror("failed launching lldb"); close(pfd[0]); // Notify parent we're exiting kill(parent, SIGLLDB); // Pass lldb exit code _exit(WEXITSTATUS(status)); } else if (pid > 0) { child = pid; if (verbose) printf("Waiting for child [Child: %d][Parent: %d]\n", child, parent); } else { perror("fork failed"); exit(exitcode_error); } } CFStringRef get_bundle_id(CFURLRef app_url) { if (app_url == NULL) return NULL; CFURLRef url = CFURLCreateCopyAppendingPathComponent(NULL, app_url, CFSTR("Info.plist"), false); if (url == NULL) return NULL; CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, url); CFRelease(url); if (stream == NULL) return NULL; CFPropertyListRef plist = NULL; if (CFReadStreamOpen(stream) == TRUE) { plist = CFPropertyListCreateWithStream(NULL, stream, 0, kCFPropertyListImmutable, NULL, NULL); } CFReadStreamClose(stream); CFRelease(stream); if (plist == NULL) return NULL; const void *value = CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier")); CFStringRef bundle_id = NULL; if (value != NULL) bundle_id = CFRetain(value); CFRelease(plist); return bundle_id; } void read_dir(service_conn_t afcFd, afc_connection* afc_conn_p, const char* dir, void(*callback)(afc_connection *conn,const char *dir,int file)) { 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); afc_dictionary* afc_dict_p; char *key, *val; int not_dir = 0; AFCFileInfoOpen(afc_conn_p, dir, &afc_dict_p); while((AFCKeyValueRead(afc_dict_p,&key,&val) == 0) && key && val) { if (strcmp(key,"st_ifmt")==0) { not_dir = strcmp(val,"S_IFDIR"); break; } } AFCKeyValueClose(afc_dict_p); if (not_dir) { if (callback) (*callback)(afc_conn_p, dir, not_dir); return; } afc_directory* afc_dir_p; afc_error_t err = AFCDirectoryOpen(afc_conn_p, dir, &afc_dir_p); if (err != 0) { // Couldn't open dir - was probably a file return; } else { if (callback) (*callback)(afc_conn_p, dir, not_dir); } while(true) { err = AFCDirectoryRead(afc_conn_p, afc_dir_p, &dir_ent); if (err != 0 || !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, callback); free(dir_joined); } AFCDirectoryClose(afc_conn_p, afc_dir_p); } // 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; if (bundle_id == NULL) { printf("Bundle id is not specified\n"); exit(1); } CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingASCII); if (AMDeviceStartHouseArrestService(device, cf_bundle_id, 0, &houseFd, 0) != 0) { printf("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; } 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_p; if (AFCConnectionOpen(houseFd, 0, &afc_conn_p) == 0) { read_dir(houseFd, afc_conn_p, list_root?list_root:"/", NULL); AFCConnectionClose(afc_conn_p); } } void copy_file_callback(afc_connection* afc_conn_p, const char *name,int file) { const char *local_name=name; if (*local_name=='/') local_name++; if (*local_name=='\0') return; if (file) { afc_file_ref fref; int err = AFCFileRefOpen(afc_conn_p,name,1,&fref); if (err) { fprintf(stderr,"AFCFileRefOpen(\"%s\") failed: %d\n",name,err); return; } FILE *fp = fopen(local_name,"w"); if (fp==NULL) { fprintf(stderr,"fopen(\"%s\",\"w\") failer: %s\n",local_name,strerror(errno)); AFCFileRefClose(afc_conn_p,fref); return; } char buf[4096]; size_t sz=sizeof(buf); while (AFCFileRefRead(afc_conn_p,fref,buf,&sz)==0 && sz) { fwrite(buf,sz,1,fp); sz = sizeof(buf); } AFCFileRefClose(afc_conn_p,fref); fclose(fp); } else { if (mkdir(local_name,0777) && errno!=EEXIST) fprintf(stderr,"mkdir(\"%s\") failed: %s\n",local_name,strerror(errno)); } } void mkdirhier(char *path) { char *slash; struct stat buf; if (path[0]=='.' && path[1]=='/') path+=2; if ((slash = strrchr(path,'/'))) { *slash = '\0'; if (stat(path,&buf)==0) { *slash = '/'; return; } mkdirhier(path); mkdir (path,0777); *slash = '/'; } return; } void download_tree(AMDeviceRef device) { service_conn_t houseFd = start_house_arrest_service(device); afc_connection* afc_conn_p = NULL; char *dirname = NULL; if (AFCConnectionOpen(houseFd, 0, &afc_conn_p) == 0) do { if (target_filename) { dirname = strdup(target_filename); mkdirhier(dirname); if (mkdir(dirname,0777) && errno!=EEXIST) { fprintf(stderr,"mkdir(\"%s\") failed: %s\n",dirname,strerror(errno)); break; } if (chdir(dirname)) { fprintf(stderr,"chdir(\"%s\") failed: %s\n",dirname,strerror(errno)); break; } } read_dir(houseFd, afc_conn_p, list_root?list_root:"/", copy_file_callback); } while(0); if (dirname) free(dirname); if (afc_conn_p) AFCConnectionClose(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, "/", NULL); if (!target_filename) { target_filename = get_filename_from_path(upload_pathname); } size_t file_size; void* file_content = read_file_to_memory(upload_pathname, &file_size); if (!file_content) { printf("Could not open file: %s\n", upload_pathname); exit(-1); } // Make sure the directory was created { char *dirpath = strdup(target_filename); char *c = dirpath, *lastSlash = dirpath; while(*c) { if(*c == '/') { lastSlash = c; } c++; } *lastSlash = '\0'; assert(AFCDirectoryCreate(afc_conn_p, dirpath) == 0); } int ret = AFCFileRefOpen(afc_conn_p, target_filename, 3, &file_ref); if (ret == 0x000a) { printf("Cannot write to %s. Permission error.\n", target_filename); exit(1); } if (ret == 0x0009) { printf("Target %s is a directory.\n", target_filename); exit(1); } assert(ret == 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(file_content); } void handle_device(AMDeviceRef device) { //if (found_device) // return; // handle one device only CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device), device_full_name = get_device_full_name(device), device_interface_name = get_device_interface_name(device); if (detect_only) { printf("[....] Found %s connected through %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding())); return; } if (device_id != NULL) { if(strcmp(device_id, CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())) == 0) { found_device = true; } else { printf("Skipping %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding())); return; } } else { device_id = MYCFStringCopyUTF8String(found_device_id); found_device = true; } printf("[....] Using %s (%s).\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); if (command_only) { if (strcmp("list", command) == 0) { list_files(device); } else if (strcmp("upload", command) == 0) { upload_file(device); } else if (strcmp("download", command) == 0) { download_tree(device); } exit(0); } CFRetain(device); // don't know if this is necessary? CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); CFURLRef url = CFURLCopyAbsoluteURL(relative_url); CFRelease(relative_url); if (uninstall) { printf("------ Uninstall phase ------\n"); CFStringRef bundle_id = get_bundle_id(url); if (bundle_id == NULL) { printf("Error: Unable to get bundle id from package %s\n Uninstall failed\n", app_path); } else { AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); assert(AMDeviceSecureUninstallApplication(0, device, bundle_id, 0, NULL, 0) == 0); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); printf("[ OK ] Uninstalled package with bundle id %s\n", CFStringGetCStringPtr(bundle_id, CFStringGetSystemEncoding())); } } if(install) { printf("------ Install phase ------\n"); printf("[ 0%%] Found %s connected through %s, beginning install\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding())); AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); // NOTE: the secure version doesn't seem to require us to start the AFC service service_conn_t afcFd; assert(AMDeviceSecureStartService(device, CFSTR("com.apple.afc"), NULL, &afcFd) == 0); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); CFStringRef keys[] = { CFSTR("PackageType") }; CFStringRef values[] = { CFSTR("Developer") }; CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); //assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0); assert(AMDeviceSecureTransferPath(0, device, url, options, transfer_callback, 0)==0); close(afcFd); AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); // // NOTE: the secure version doesn't seem to require us to start the installation_proxy service // // Although I can't find it right now, I in some code that the first param of AMDeviceSecureInstallApplication was a "dontStartInstallProxy" // // implying this is done for us by iOS already //service_conn_t installFd; //assert(AMDeviceSecureStartService(device, CFSTR("com.apple.mobile.installation_proxy"), NULL, &installFd) == 0); //mach_error_t result = AMDeviceInstallApplication(installFd, path, options, install_callback, NULL); mach_error_t result = AMDeviceSecureInstallApplication(0, device, url, options, install_callback, 0); if (result != 0) { char* error = "Unknown error."; if (result == 0xe8008015) error = "Your application failed code-signing checks. Check your certificates, provisioning profiles, and bundle ids."; printf("AMDeviceInstallApplication failed: 0x%X: %s\n", result, error); exit(exitcode_error); } // close(installFd); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); CFRelease(path); CFRelease(options); printf("[100%%] Installed package %s\n", app_path); } if (!debug) exit(0); // no debug phase if (justlaunch) launch_debugger_and_exit(device, url); else launch_debugger(device, url); } void device_callback(struct am_device_notification_callback_info *info, void *arg) { switch (info->msg) { case ADNCI_MSG_CONNECTED: if(device_id != NULL || !debug || AMDeviceGetInterfaceType(info->dev) != 2) { handle_device(info->dev); } else if(best_device_match == NULL) { best_device_match = info->dev; CFRetain(best_device_match); } default: break; } } void timeout_callback(CFRunLoopTimerRef timer, void *info) { if ((!found_device) && (!detect_only)) { if(best_device_match != NULL) { handle_device(best_device_match); CFRelease(best_device_match); best_device_match = NULL; } if(!found_device) { printf("[....] Timed out waiting for device.\n"); exit(exitcode_error); } } else { if (!debug) printf("[....] No more devices found.\n"); else { int mypid = getpid(); if ((parent != 0) && (parent == mypid) && (child != 0)) { if (verbose) { printf("Timeout. Killing child (%d) tree\n", child); } kill_ptree(child, SIGHUP); } } exit(0); } } void usage(const char* app) { printf( "Usage: %s [OPTION]...\n" " -d, --debug launch the app in GDB after installation\n" " -i, --id the id of the device to connect to\n" " -c, --detect only detect if the device is connected\n" " -b, --bundle the path to the app bundle to be installed\n" " -a, --args command line arguments to pass to the app when launching it\n" " -t, --timeout number of seconds to wait for a device to be connected\n" " -u, --unbuffered don't buffer stdout\n" " -g, --gdbargs extra arguments to pass to GDB when starting the debugger\n" " -x, --gdbexec GDB commands script file\n" " -n, --nostart do not start the app when debugging\n" " -I, --noninteractive start in non interactive mode (quit when app crashes or exits)\n" " -L, --justlaunch just launch the app and exit lldb\n" " -v, --verbose enable verbose output\n" " -m, --noinstall directly start debugging without app install (-d not required)\n" " -p, --port port used for device, default: 12345 \n" " -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) \n" " -1, --bundle_id specify bundle id for list and upload\n" " -l, --list list files\n" " -o, --upload upload file\n" " -w, --download download app tree\n" " -2, --to use together with up/download file/tree. specify target\n" " -V, --version print the executable version \n", app); } void show_version() { printf("%s\n", APP_VERSION); } int main(int argc, char *argv[]) { static struct option longopts[] = { { "debug", no_argument, NULL, 'd' }, { "id", required_argument, NULL, 'i' }, { "bundle", required_argument, NULL, 'b' }, { "args", required_argument, NULL, 'a' }, { "verbose", no_argument, NULL, 'v' }, { "timeout", required_argument, NULL, 't' }, { "gdbexec", no_argument, NULL, 'x' }, { "unbuffered", no_argument, NULL, 'u' }, { "nostart", no_argument, NULL, 'n' }, { "noninteractive", no_argument, NULL, 'I' }, { "justlaunch", no_argument, NULL, 'L' }, { "detect", no_argument, NULL, 'c' }, { "version", no_argument, NULL, 'V' }, { "noinstall", no_argument, NULL, 'm' }, { "port", required_argument, NULL, 'p' }, { "uninstall", no_argument, NULL, 'r' }, { "list", optional_argument, NULL, 'l' }, { "bundle_id", required_argument, NULL, '1'}, { "upload", required_argument, NULL, 'o'}, { "download", optional_argument, NULL, 'w'}, { "to", required_argument, NULL, '2'}, { NULL, 0, NULL, 0 }, }; char ch; while ((ch = getopt_long(argc, argv, "VmcdvunrILi:b:a:t:g:x:p:1:2:o:l::w::", longopts, NULL)) != -1) { switch (ch) { case 'm': install = 0; debug = 1; break; case 'd': debug = 1; break; case 'i': device_id = optarg; break; case 'b': app_path = optarg; break; case 'a': args = optarg; break; case 'v': verbose = 1; break; case 't': timeout = atoi(optarg); break; case 'u': unbuffered = 1; break; case 'n': nostart = 1; break; case 'I': interactive = false; break; case 'L': interactive = false; justlaunch = true; break; case 'c': detect_only = true; break; case 'V': show_version(); return exitcode_error; case 'p': port = atoi(optarg); break; case 'r': uninstall = 1; break; case '1': bundle_id = optarg; break; case '2': target_filename = optarg; break; case 'o': command_only = true; upload_pathname = optarg; command = "upload"; break; case 'l': command_only = true; command = "list"; list_root = optarg; break; case 'w': command_only = true; command = "download"; list_root = optarg; break; default: usage(argv[0]); return exitcode_error; } } if (!app_path && !detect_only && !command_only) { usage(argv[0]); exit(exitcode_error); } if (unbuffered) { setbuf(stdout, NULL); setbuf(stderr, NULL); } if (detect_only && timeout == 0) { timeout = 5; } if (app_path) { assert(access(app_path, F_OK) == 0); } AMDSetLogLevel(5); // 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); printf("[....] Waiting up to %d seconds for iOS device to be connected\n", timeout); } else { printf("[....] Waiting for iOS device to be connected\n"); } struct am_device_notification *notify; AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, ¬ify); CFRunLoopRun(); }