diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m index 7792ba89..721776d6 100644 --- a/src/ios-deploy/ios-deploy.m +++ b/src/ios-deploy/ios-deploy.m @@ -3,6 +3,7 @@ #import #import #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -92,6 +94,8 @@ int AMDServiceConnectionSend(ServiceConnRef con, const void * data, size_t size); int AMDServiceConnectionReceive(ServiceConnRef con, void * data, size_t size); +uint64_t AMDServiceConnectionReceiveMessage(ServiceConnRef con, CFPropertyListRef message, CFPropertyListFormat *format); +uint64_t AMDServiceConnectionSendMessage(ServiceConnRef con, CFPropertyListRef message, CFPropertyListFormat format); bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, debugserver_only = false, detect_only = false, install = true, uninstall = false, no_wifi = false; bool faster_path_search = false; @@ -112,6 +116,7 @@ char *envs = NULL; char *list_root = NULL; const char * custom_script_path = NULL; +char *symbols_download_directory = NULL; int _timeout = 0; int _detectDeadlockTimeout = 0; bool _json_output = false; @@ -129,6 +134,12 @@ CFRunLoopSourceRef server_socket_runloop; CFRunLoopSourceRef fdvendor_runloop; +uint32_t symbols_file_paths_command = 0x30303030; +uint32_t symbols_download_file_command = 0x01000000; +CFStringRef symbols_service_name = CFSTR("com.apple.dt.fetchsymbols"); + +const size_t sizeof_uint32_t = sizeof(uint32_t); + // 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_timeout = 252; @@ -229,6 +240,9 @@ void NSLogJSON(NSDictionary* jsonDict) { } } +uint64_t get_current_time_in_milliseconds() { + return clock_gettime_nsec_np(CLOCK_REALTIME) / (1000 * 1000); +} BOOL mkdirp(NSString* path) { NSError* error = nil; @@ -713,16 +727,17 @@ void mount_developer_image(AMDeviceRef device) { on_error(@"Unable to mount developer disk image. (%x)", result); } - + CFStringRef symbols_path = copy_device_support_path(device, CFSTR("Symbols")); if (symbols_path != NULL) { NSLogOut(@"Symbol Path: %@", symbols_path); NSLogJSON(@{@"Event": @"MountDeveloperImage", @"SymbolsPath": (__bridge NSString *)symbols_path - }); CFRelease(symbols_path); + }); + CFRelease(symbols_path); } - + CFRelease(image_path); CFRelease(options); } @@ -1922,7 +1937,7 @@ void upload_file(AMDeviceRef device) afc_conn_p = start_afc_service(device); } else { afc_conn_p = start_house_arrest_service(device); - } + } assert(afc_conn_p); if (!target_filename) @@ -2084,6 +2099,153 @@ void uninstall_app(AMDeviceRef device) { } } +void start_symbols_service_with_command(AMDeviceRef device, uint32_t command) { + connect_and_start_session(device); + int start_err = AMDeviceSecureStartService(device, symbols_service_name, NULL, + &dbgServiceConnection); + if (start_err != 0) { + on_error(@"Failed to start service: %x %s", start_err, + symbols_service_name); + } + + uint32_t bytes_sent = AMDServiceConnectionSend(dbgServiceConnection, &command, + sizeof_uint32_t); + if (bytes_sent != sizeof_uint32_t) { + on_error(@"Sent %d bytes but was expecting %d.", bytes_sent, sizeof_uint32_t); + } + + uint32_t response; + uint32_t bytes_read = AMDServiceConnectionReceive(dbgServiceConnection, + &response, sizeof_uint32_t); + if (bytes_read != sizeof_uint32_t) { + on_error(@"Read %d bytes but was expecting %d.", bytes_read, sizeof_uint32_t); + } else if (response != command) { + on_error(@"Failed to get confirmation response for: %s", command); + } +} + +CFArrayRef get_dyld_file_paths(AMDeviceRef device) { + start_symbols_service_with_command(device, symbols_file_paths_command); + + CFPropertyListFormat format; + CFDictionaryRef dict = NULL; + uint64_t bytes_read = + AMDServiceConnectionReceiveMessage(dbgServiceConnection, &dict, &format); + if (bytes_read == -1) { + on_error(@"Received %d bytes after succesfully starting command %d.", bytes_read, + symbols_file_paths_command); + } + AMDeviceStopSession(device); + AMDeviceDisconnect(device); + + CFStringRef files_key = CFSTR("files"); + if (!CFDictionaryContainsKey(dict, files_key)) { + on_error(@"Incoming messasge did not contain key '%@', %@", files_key, dict); + } + return CFDictionaryGetValue(dict, files_key); +} + +void write_dyld_file(CFStringRef dest, uint64_t file_size) { + // Prepare the destination file by mapping it into memory. + int fd = open(CFStringGetCStringPtr(dest, kCFStringEncodingUTF8), + O_RDWR | O_CREAT, 0644); + if (fd == -1) { + on_sys_error(@"Failed to open %@.", dest); + } + if (lseek(fd, file_size - 1, SEEK_SET) == -1) { + on_sys_error(@"Failed to lseek to last byte."); + } + if (write(fd, "", 1) == -1) { + on_sys_error(@"Failed to write to last byte."); + } + void *map = mmap(NULL, file_size, PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + on_sys_error(@"Failed to mmap %@.", dest); + } + close(fd); + + // Read the file content packet by packet until we've copied the entire file + // to disk. + uint64_t total_bytes_read = 0; + uint64_t last_time = get_current_time_in_milliseconds() / 250; + while (total_bytes_read < file_size) { + uint64_t bytes_remaining = file_size - total_bytes_read; + // This fails for some reason if we try to download more than + // INT_MAX bytes at a time. + uint64_t bytes_to_download = MIN(bytes_remaining, INT_MAX - 1); + uint64_t bytes_read = AMDServiceConnectionReceive( + dbgServiceConnection, map + total_bytes_read, bytes_to_download); + total_bytes_read += bytes_read; + + uint64_t current_time = get_current_time_in_milliseconds() / 250; + // We can process several packets per second which would result + // in spamming output so only log if any of the following are + // true: + // - Running in verbose mode. + // - It's been at least a quarter second since the last log. + // - We finished processing the last packet. + if (verbose || last_time != current_time || total_bytes_read == file_size) { + last_time = current_time; + int percent = (double)total_bytes_read / file_size * 100; + NSLogOut(@"%llu/%llu (%d%%)", total_bytes_read, file_size, percent); + NSLogJSON(@{@"Event": @"DyldCacheDownloadProgress", + @"BytesRead": @(total_bytes_read), + @"Percent": @(percent), + }); + } + } + + munmap(map, file_size); +} + +void download_dyld_file(AMDeviceRef device, uint32_t dyld_index, + CFStringRef filepath) { + start_symbols_service_with_command(device, symbols_download_file_command); + + uint32_t index = CFSwapInt32HostToBig(dyld_index); + uint64_t bytes_sent = + AMDServiceConnectionSend(dbgServiceConnection, &index, sizeof_uint32_t); + if (bytes_sent != sizeof_uint32_t) { + on_error(@"Sent %d bytes but was expecting %d.", bytes_sent, sizeof_uint32_t); + } + + uint64_t file_size = 0; + uint64_t bytes_read = AMDServiceConnectionReceive(dbgServiceConnection, + &file_size, sizeof(uint64_t)); + if (bytes_read != sizeof(uint64_t)) { + on_error(@"Read %d bytes but was expecting %d.", bytes_read, sizeof(uint64_t)); + } + file_size = CFSwapInt64BigToHost(file_size); + + CFStringRef download_path = CFStringCreateWithFormat( + NULL, NULL, CFSTR("%s%@"), symbols_download_directory, filepath); + mkdirp( + ((__bridge NSString *)download_path).stringByDeletingLastPathComponent); + NSLogOut(@"Downloading %@ to %@.", filepath, download_path); + NSLogJSON(@{@"Event": @"DyldCacheDownload", + @"Source": (__bridge NSString *)filepath, + @"Destination": (__bridge NSString *)download_path, + @"Size": @(file_size), + }); + + write_dyld_file(download_path, file_size); + + CFRelease(download_path); + AMDeviceStopSession(device); + AMDeviceDisconnect(device); +} + +void download_device_symbols(AMDeviceRef device) { + dbgServiceConnection = NULL; + CFArrayRef files = get_dyld_file_paths(device); + CFIndex files_count = CFArrayGetCount(files); + + for (uint32_t i = 0; i < files_count; ++i) { + CFStringRef filepath = (CFStringRef)CFArrayGetValueAtIndex(files, i); + download_dyld_file(device, i, filepath); + } +} + void handle_device(AMDeviceRef device) { NSLogVerbose(@"Already found device? %d", found_device); @@ -2144,6 +2306,8 @@ void handle_device(AMDeviceRef device) { list_bundle_id(device); } else if (strcmp("get_battery_level", command) == 0) { get_battery_level(device); + } else if (strcmp("symbols", command) == 0) { + download_device_symbols(device); } exit(0); } @@ -2399,6 +2563,7 @@ void usage(const char* app) { @" --detect_deadlocks start printing backtraces for all threads periodically after specific amount of seconds\n" @" -f, --file_system specify file system for mkdir / list / upload / download / rm\n" @" -F, --non-recursively specify non-recursively walk directory\n" + @" -S, --symbols download OS symbols. must specify a directory to store the downloaded symbols\n" @" -j, --json format output as JSON\n" @" -k, --key keys for the properties of the bundle. Joined by ',' and used only with -B and -j \n" @" --custom-script