Second commit to add the ability to download OS symbols from an Appleā¦ (#535)
* Second commit to add the ability to download OS symbols from an Apple device with iOS-deploy. This commit adapts the test program code from Apple's dsc_extractor.cpp file in their dyld repo. This dlopens Xcode's dsc_extractor.bundle to extract the symbols from the dyld cache file downloaded in the previous commit.
See last few lines in https://opensource.apple.com/source/dyld/dyld-655.1.1/launch-cache/dsc_extractor.cpp.auto.html
diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m
index 721776d..cb3de88 100644
--- a/src/ios-deploy/ios-deploy.m
+++ b/src/ios-deploy/ios-deploy.m
@@ -137,6 +137,7 @@
uint32_t symbols_file_paths_command = 0x30303030;
uint32_t symbols_download_file_command = 0x01000000;
CFStringRef symbols_service_name = CFSTR("com.apple.dt.fetchsymbols");
+const int symbols_logging_interval_ms = 250;
const size_t sizeof_uint32_t = sizeof(uint32_t);
@@ -2101,12 +2102,8 @@
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);
- }
+ check_error(AMDeviceSecureStartService(device, symbols_service_name,
+ NULL, &dbgServiceConnection));
uint32_t bytes_sent = AMDServiceConnectionSend(dbgServiceConnection, &command,
sizeof_uint32_t);
@@ -2163,11 +2160,12 @@
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;
+ uint64_t last_time =
+ get_current_time_in_milliseconds() / symbols_logging_interval_ms;
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
@@ -2177,7 +2175,8 @@
dbgServiceConnection, map + total_bytes_read, bytes_to_download);
total_bytes_read += bytes_read;
- uint64_t current_time = get_current_time_in_milliseconds() / 250;
+ uint64_t current_time =
+ get_current_time_in_milliseconds() / symbols_logging_interval_ms;
// We can process several packets per second which would result
// in spamming output so only log if any of the following are
// true:
@@ -2194,11 +2193,11 @@
});
}
}
-
+
munmap(map, file_size);
}
-void download_dyld_file(AMDeviceRef device, uint32_t dyld_index,
+CFStringRef download_dyld_file(AMDeviceRef device, uint32_t dyld_index,
CFStringRef filepath) {
start_symbols_service_with_command(device, symbols_download_file_command);
@@ -2216,7 +2215,7 @@
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(
@@ -2229,21 +2228,121 @@
});
write_dyld_file(download_path, file_size);
-
- CFRelease(download_path);
+
AMDeviceStopSession(device);
AMDeviceDisconnect(device);
+ return download_path;
+}
+
+CFStringRef create_dsc_bundle_path_for_device(AMDeviceRef device) {
+ CFStringRef xcode_dev_path = copy_xcode_dev_path();
+
+ AMDeviceConnect(device);
+ CFStringRef device_class = AMDeviceCopyValue(device, 0, CFSTR("DeviceClass"));
+ AMDeviceDisconnect(device);
+
+ CFStringRef platform_name;
+ if (CFStringCompare(CFSTR("AppleTV"), device_class, 0) == kCFCompareEqualTo) {
+ platform_name = CFSTR("AppleTVOS");
+ } else if (CFStringCompare(CFSTR("Watch"), device_class, 0) ==
+ kCFCompareEqualTo) {
+ platform_name = CFSTR("WatchOS");
+ } else {
+ platform_name = CFSTR("iPhoneOS");
+ }
+
+ return CFStringCreateWithFormat(
+ NULL, NULL,
+ CFSTR("%@/Platforms/%@.platform/usr/lib/dsc_extractor.bundle"),
+ xcode_dev_path, platform_name);
+}
+
+typedef int (*extractor_proc)(const char *shared_cache_file_path, const char *extraction_root_path,
+ void (^progress)(unsigned current, unsigned total));
+
+void dyld_shared_cache_extract_dylibs(CFStringRef dsc_extractor_bundle_path,
+ CFStringRef shared_cache_file_path,
+ const char *extraction_root_path) {
+ const char *dsc_extractor_bundle_path_ptr =
+ CFStringGetCStringPtr(dsc_extractor_bundle_path, kCFStringEncodingUTF8);
+ void *handle = dlopen(dsc_extractor_bundle_path_ptr, RTLD_LAZY);
+ if (handle == NULL) {
+ on_error(@"%s could not be loaded", dsc_extractor_bundle_path);
+ }
+
+ extractor_proc proc = (extractor_proc)dlsym(
+ handle, "dyld_shared_cache_extract_dylibs_progress");
+ if (proc == NULL) {
+ on_error(
+ @"%s did not have dyld_shared_cache_extract_dylibs_progress symbol",
+ dsc_extractor_bundle_path);
+ }
+
+ const char *shared_cache_file_path_ptr =
+ CFStringGetCStringPtr(shared_cache_file_path, kCFStringEncodingUTF8);
+
+ NSLogJSON(@{@"Event": @"DyldCacheExtract",
+ @"Source": (__bridge NSString *)shared_cache_file_path,
+ @"Destination": (__bridge NSString *)extraction_root_path,
+ });
+
+ __block uint64_t last_time =
+ get_current_time_in_milliseconds() / symbols_logging_interval_ms;
+ __block unsigned files_extracted = 0;
+ __block unsigned files_total = 0;
+ int result =
+ (*proc)(shared_cache_file_path_ptr, extraction_root_path,
+ ^(unsigned c, unsigned total) {
+ uint64_t current_time =
+ get_current_time_in_milliseconds() / symbols_logging_interval_ms;
+ if (!verbose && last_time == current_time) return;
+
+ last_time = current_time;
+ files_extracted = c;
+ files_total = total;
+
+ int percent = (double)c / total * 100;
+ NSLogOut(@"%d/%d (%d%%)", c, total, percent);
+ NSLogJSON(@{@"Event": @"DyldCacheExtractProgress",
+ @"Extracted": @(c),
+ @"Total": @(total),
+ @"Percent": @(percent),
+ });
+ });
+ if (result == 0) {
+ NSLogOut(@"Finished extracting %s.", shared_cache_file_path_ptr);
+ files_extracted = files_total;
+ } else {
+ NSLogOut(@"Failed to extract %s, exit code %d.", shared_cache_file_path_ptr, result);
+ }
+ int percent = (double)files_extracted / files_total * 100;
+ NSLogJSON(@{@"Event": @"DyldCacheExtractProgress",
+ @"Code": @(result),
+ @"Extracted": @(files_extracted),
+ @"Total": @(files_total),
+ @"Percent": @(percent),
+ });
}
void download_device_symbols(AMDeviceRef device) {
dbgServiceConnection = NULL;
CFArrayRef files = get_dyld_file_paths(device);
CFIndex files_count = CFArrayGetCount(files);
+ NSLogOut(@"Downloading symbols files: %@", files);
+ NSLogJSON(@{@"Event": @"SymbolsDownload",
+ @"Files": (__bridge NSArray *)files,
+ });
+ CFStringRef dsc_extractor_bundle = create_dsc_bundle_path_for_device(device);
for (uint32_t i = 0; i < files_count; ++i) {
CFStringRef filepath = (CFStringRef)CFArrayGetValueAtIndex(files, i);
- download_dyld_file(device, i, filepath);
+ CFStringRef download_path = download_dyld_file(device, i, filepath);
+ dyld_shared_cache_extract_dylibs(dsc_extractor_bundle, download_path,
+ symbols_download_directory);
+ CFRelease(download_path);
}
+
+ CFRelease(dsc_extractor_bundle);
}
void handle_device(AMDeviceRef device) {