Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 177 additions & 6 deletions src/ios-deploy/ios-deploy.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
Expand All @@ -13,6 +14,7 @@
#include <getopt.h>
#include <pwd.h>
#include <dlfcn.h>
#include <time.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -2399,6 +2563,7 @@ void usage(const char* app) {
@" --detect_deadlocks <sec> 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 <list_bundle_id> and -j <json> \n"
@" --custom-script <script> path to custom python script to execute in lldb\n"
Expand Down Expand Up @@ -2460,14 +2625,15 @@ int main(int argc, char *argv[]) {
{ "file_system", no_argument, NULL, 'f'},
{ "non-recursively", no_argument, NULL, 'F'},
{ "key", optional_argument, NULL, 'k' },
{ "symbols", required_argument, NULL, 'S' },
{ "custom-script", required_argument, NULL, 1001},
{ "custom-command", required_argument, NULL, 1002},
{ "faster-path-search", no_argument, NULL, 1003},
{ NULL, 0, NULL, 0 },
};
int ch;

while ((ch = getopt_long(argc, argv, "VmcdvunrILefFD:R:X:i:b:a:t:p:1:2:o:l:w:9BWjNs:OE:CA:k:", longopts, NULL)) != -1)
while ((ch = getopt_long(argc, argv, "VmcdvunrILefFD:R:X:i:b:a:t:p:1:2:o:l:w:9BWjNs:OE:CA:k:S:", longopts, NULL)) != -1)
{
switch (ch) {
case 'm':
Expand All @@ -2489,6 +2655,11 @@ int main(int argc, char *argv[]) {
case 's':
envs = optarg;
break;
case 'S':
symbols_download_directory = optarg;
command = "symbols";
command_only = true;
break;
case 'v':
verbose = true;
break;
Expand Down Expand Up @@ -2633,7 +2804,7 @@ int main(int argc, char *argv[]) {

if (!app_path && !detect_only && !debugserver_only && !command_only) {
usage(argv[0]);
on_error(@"One of -[b|c|o|l|w|D|N|R|X|e|B|C|9] is required to proceed!");
on_error(@"One of -[b|c|o|l|w|D|N|R|X|e|B|C|S|9] is required to proceed!");
}

if (unbuffered) {
Expand Down