summaryrefslogtreecommitdiffstats
path: root/tools/idevicecrashreport.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/idevicecrashreport.c')
-rw-r--r--tools/idevicecrashreport.c529
1 files changed, 529 insertions, 0 deletions
diff --git a/tools/idevicecrashreport.c b/tools/idevicecrashreport.c
new file mode 100644
index 0000000..09bd537
--- /dev/null
+++ b/tools/idevicecrashreport.c
@@ -0,0 +1,529 @@
+/*
+ * idevicecrashreport.c
+ * Simple utility to move crash reports from a device to a local directory.
+ *
+ * Copyright (c) 2014 Martin Szulecki. All Rights Reserved.
+ * Copyright (c) 2014 Nikias Bassen. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicecrashreport"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
+#include <libimobiledevice-glue/utils.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/service.h>
+#include <libimobiledevice/afc.h>
+#include <plist/plist.h>
+
+#ifdef WIN32
+#include <windows.h>
+#define S_IFLNK S_IFREG
+#define S_IFSOCK S_IFREG
+#endif
+
+#define CRASH_REPORT_MOVER_SERVICE "com.apple.crashreportmover"
+#define CRASH_REPORT_COPY_MOBILE_SERVICE "com.apple.crashreportcopymobile"
+
+const char* target_directory = NULL;
+static int extract_raw_crash_reports = 0;
+static int keep_crash_reports = 0;
+
+static int file_exists(const char* path)
+{
+ struct stat tst;
+#ifdef WIN32
+ return (stat(path, &tst) == 0);
+#else
+ return (lstat(path, &tst) == 0);
+#endif
+}
+
+static int extract_raw_crash_report(const char* filename)
+{
+ int res = 0;
+ plist_t report = NULL;
+ char* raw = NULL;
+ char* raw_filename = strdup(filename);
+
+ /* create filename with '.crash' extension */
+ char* p = strrchr(raw_filename, '.');
+ if ((p == NULL) || (strcmp(p, ".plist") != 0)) {
+ free(raw_filename);
+ return res;
+ }
+ strcpy(p, ".crash");
+
+ /* read plist crash report */
+ if (plist_read_from_file(filename, &report, NULL)) {
+ plist_t description_node = plist_dict_get_item(report, "description");
+ if (description_node && plist_get_node_type(description_node) == PLIST_STRING) {
+ plist_get_string_val(description_node, &raw);
+
+ if (raw != NULL) {
+ /* write file */
+ buffer_write_to_filename(raw_filename, raw, strlen(raw));
+ free(raw);
+ res = 1;
+ }
+ }
+ }
+
+ if (report)
+ plist_free(report);
+
+ if (raw_filename)
+ free(raw_filename);
+
+ return res;
+}
+
+static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char* device_directory, const char* host_directory, const char* filename_filter)
+{
+ afc_error_t afc_error;
+ int k;
+ int res = -1;
+ int crash_report_count = 0;
+ uint64_t handle;
+ char source_filename[512];
+ char target_filename[512];
+
+ if (!afc)
+ return res;
+
+ char** list = NULL;
+ afc_error = afc_read_directory(afc, device_directory, &list);
+ if (afc_error != AFC_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not read device directory '%s'\n", device_directory);
+ return res;
+ }
+
+ /* ensure we have a trailing slash */
+ strcpy(source_filename, device_directory);
+ if (source_filename[strlen(source_filename)-1] != '/') {
+ strcat(source_filename, "/");
+ }
+ int device_directory_length = strlen(source_filename);
+
+ /* ensure we have a trailing slash */
+ strcpy(target_filename, host_directory);
+ if (target_filename[strlen(target_filename)-1] != '/') {
+ strcat(target_filename, "/");
+ }
+ int host_directory_length = strlen(target_filename);
+
+ /* loop over file entries */
+ for (k = 0; list[k]; k++) {
+ if (!strcmp(list[k], ".") || !strcmp(list[k], "..")) {
+ continue;
+ }
+
+ char **fileinfo = NULL;
+ struct stat stbuf;
+ memset(&stbuf, '\0', sizeof(struct stat));
+
+ /* assemble absolute source filename */
+ strcpy(((char*)source_filename) + device_directory_length, list[k]);
+
+ /* assemble absolute target filename */
+#ifdef WIN32
+ /* replace every ':' with '-' since ':' is an illegal character for file names in windows */
+ char* current_pos = strchr(list[k], ':');
+ while (current_pos) {
+ *current_pos = '-';
+ current_pos = strchr(current_pos, ':');
+ }
+#endif
+ char* p = strrchr(list[k], '.');
+ if (p != NULL && !strncmp(p, ".synced", 7)) {
+ /* make sure to strip ".synced" extension as seen on iOS 5 */
+ size_t newlen = p - list[k];
+ strncpy(((char*)target_filename) + host_directory_length, list[k], newlen);
+ target_filename[host_directory_length + newlen] = '\0';
+ } else {
+ strcpy(((char*)target_filename) + host_directory_length, list[k]);
+ }
+
+ /* get file information */
+ afc_get_file_info(afc, source_filename, &fileinfo);
+ if (!fileinfo) {
+ printf("Failed to read information for '%s'. Skipping...\n", source_filename);
+ continue;
+ }
+
+ /* parse file information */
+ int i;
+ for (i = 0; fileinfo[i]; i+=2) {
+ if (!strcmp(fileinfo[i], "st_size")) {
+ stbuf.st_size = atoll(fileinfo[i+1]);
+ } else if (!strcmp(fileinfo[i], "st_ifmt")) {
+ if (!strcmp(fileinfo[i+1], "S_IFREG")) {
+ stbuf.st_mode = S_IFREG;
+ } else if (!strcmp(fileinfo[i+1], "S_IFDIR")) {
+ stbuf.st_mode = S_IFDIR;
+ } else if (!strcmp(fileinfo[i+1], "S_IFLNK")) {
+ stbuf.st_mode = S_IFLNK;
+ } else if (!strcmp(fileinfo[i+1], "S_IFBLK")) {
+ stbuf.st_mode = S_IFBLK;
+ } else if (!strcmp(fileinfo[i+1], "S_IFCHR")) {
+ stbuf.st_mode = S_IFCHR;
+ } else if (!strcmp(fileinfo[i+1], "S_IFIFO")) {
+ stbuf.st_mode = S_IFIFO;
+ } else if (!strcmp(fileinfo[i+1], "S_IFSOCK")) {
+ stbuf.st_mode = S_IFSOCK;
+ }
+ } else if (!strcmp(fileinfo[i], "st_nlink")) {
+ stbuf.st_nlink = atoi(fileinfo[i+1]);
+ } else if (!strcmp(fileinfo[i], "st_mtime")) {
+ stbuf.st_mtime = (time_t)(atoll(fileinfo[i+1]) / 1000000000);
+ } else if (!strcmp(fileinfo[i], "LinkTarget")) {
+ /* report latest crash report filename */
+ printf("Link: %s\n", (char*)target_filename + strlen(target_directory));
+
+ /* remove any previous symlink */
+ if (file_exists(target_filename)) {
+ remove(target_filename);
+ }
+
+#ifndef WIN32
+ /* use relative filename */
+ char* b = strrchr(fileinfo[i+1], '/');
+ if (b == NULL) {
+ b = fileinfo[i+1];
+ } else {
+ b++;
+ }
+
+ /* create a symlink pointing to latest log */
+ if (symlink(b, target_filename) < 0) {
+ fprintf(stderr, "Can't create symlink to %s\n", b);
+ }
+#endif
+
+ if (!keep_crash_reports)
+ afc_remove_path(afc, source_filename);
+
+ res = 0;
+ }
+ }
+
+ /* free file information */
+ afc_dictionary_free(fileinfo);
+
+ /* recurse into child directories */
+ if (S_ISDIR(stbuf.st_mode)) {
+#ifdef WIN32
+ mkdir(target_filename);
+#else
+ mkdir(target_filename, 0755);
+#endif
+ res = afc_client_copy_and_remove_crash_reports(afc, source_filename, target_filename, filename_filter);
+
+ /* remove directory from device */
+ if (!keep_crash_reports)
+ afc_remove_path(afc, source_filename);
+ } else if (S_ISREG(stbuf.st_mode)) {
+ if (filename_filter != NULL && strstr(source_filename, filename_filter) == NULL) {
+ continue;
+ }
+
+ /* copy file to host */
+ afc_error = afc_file_open(afc, source_filename, AFC_FOPEN_RDONLY, &handle);
+ if(afc_error != AFC_E_SUCCESS) {
+ if (afc_error == AFC_E_OBJECT_NOT_FOUND) {
+ continue;
+ }
+ fprintf(stderr, "Unable to open device file '%s' (%d). Skipping...\n", source_filename, afc_error);
+ continue;
+ }
+
+ FILE* output = fopen(target_filename, "wb");
+ if(output == NULL) {
+ fprintf(stderr, "Unable to open local file '%s'. Skipping...\n", target_filename);
+ afc_file_close(afc, handle);
+ continue;
+ }
+
+ printf("%s: %s\n", (keep_crash_reports ? "Copy": "Move") , (char*)target_filename + strlen(target_directory));
+
+ uint32_t bytes_read = 0;
+ uint32_t bytes_total = 0;
+ unsigned char data[0x1000];
+
+ afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read);
+ while(afc_error == AFC_E_SUCCESS && bytes_read > 0) {
+ fwrite(data, 1, bytes_read, output);
+ bytes_total += bytes_read;
+ afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read);
+ }
+ afc_file_close(afc, handle);
+ fclose(output);
+
+ if ((uint32_t)stbuf.st_size != bytes_total) {
+ fprintf(stderr, "File size mismatch. Skipping...\n");
+ continue;
+ }
+
+ /* remove file from device */
+ if (!keep_crash_reports) {
+ afc_remove_path(afc, source_filename);
+ }
+
+ /* extract raw crash information into separate '.crash' file */
+ if (extract_raw_crash_reports) {
+ extract_raw_crash_report(target_filename);
+ }
+
+ crash_report_count++;
+
+ res = 0;
+ }
+ }
+ afc_dictionary_free(list);
+
+ /* no reports, no error */
+ if (crash_report_count == 0)
+ res = 0;
+
+ return res;
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] DIRECTORY\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Move crash reports from device to a local DIRECTORY.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -e, --extract extract raw crash report into separate '.crash' file\n"
+ " -k, --keep copy but do not remove crash reports from device\n"
+ " -d, --debug enable communication debugging\n"
+ " -f, --filter NAME filter crash reports by NAME (case sensitive)\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+int main(int argc, char* argv[])
+{
+ idevice_t device = NULL;
+ lockdownd_client_t lockdownd = NULL;
+ afc_client_t afc = NULL;
+
+ idevice_error_t device_error = IDEVICE_E_SUCCESS;
+ lockdownd_error_t lockdownd_error = LOCKDOWN_E_SUCCESS;
+ afc_error_t afc_error = AFC_E_SUCCESS;
+
+ const char* udid = NULL;
+ int use_network = 0;
+ const char* filename_filter = NULL;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { "filter", required_argument, NULL, 'f' },
+ { "extract", no_argument, NULL, 'e' },
+ { "keep", no_argument, NULL, 'k' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:nvf:ek", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ case 'f':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: filter argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ filename_filter = optarg;
+ break;
+ case 'e':
+ extract_raw_crash_reports = 1;
+ break;
+ case 'k':
+ keep_crash_reports = 1;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* ensure a target directory was supplied */
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: missing target directory.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ target_directory = argv[0];
+
+ /* check if target directory exists */
+ if (!file_exists(target_directory)) {
+ fprintf(stderr, "ERROR: Directory '%s' does not exist.\n", target_directory);
+ return 1;
+ }
+
+ device_error = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (device_error != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
+ }
+ return -1;
+ }
+
+ lockdownd_error = lockdownd_client_new_with_handshake(device, &lockdownd, TOOL_NAME);
+ if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lockdownd_error);
+ idevice_free(device);
+ return -1;
+ }
+
+ /* start crash log mover service */
+ lockdownd_service_descriptor_t service = NULL;
+ lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_MOVER_SERVICE, &service);
+ if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_MOVER_SERVICE, lockdownd_strerror(lockdownd_error));
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ /* trigger move operation on device */
+ service_client_t svcmove = NULL;
+ service_error_t service_error = service_client_new(device, service, &svcmove);
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ if (service_error != SERVICE_E_SUCCESS) {
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ /* read "ping" message which indicates the crash logs have been moved to a safe harbor */
+ char *ping = malloc(4);
+ memset(ping, '\0', 4);
+ int attempts = 0;
+ while ((strncmp(ping, "ping", 4) != 0) && (attempts < 10)) {
+ uint32_t bytes = 0;
+ service_error = service_receive_with_timeout(svcmove, ping, 4, &bytes, 2000);
+ if (service_error == SERVICE_E_SUCCESS || service_error == SERVICE_E_TIMEOUT) {
+ attempts++;
+ continue;
+ }
+
+ fprintf(stderr, "ERROR: Crash logs could not be moved. Connection interrupted (%d).\n", service_error);
+ break;
+ }
+ service_client_free(svcmove);
+ free(ping);
+
+ if (device_error != IDEVICE_E_SUCCESS || attempts > 10) {
+ fprintf(stderr, "ERROR: Failed to receive ping message from crash report mover.\n");
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_COPY_MOBILE_SERVICE, &service);
+ if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_COPY_MOBILE_SERVICE, lockdownd_strerror(lockdownd_error));
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+ lockdownd_client_free(lockdownd);
+
+ afc = NULL;
+ afc_error = afc_client_new(device, service, &afc);
+ if(afc_error != AFC_E_SUCCESS) {
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+ /* recursively copy crash reports from the device to a local directory */
+ if (afc_client_copy_and_remove_crash_reports(afc, ".", target_directory, filename_filter) < 0) {
+ fprintf(stderr, "ERROR: Failed to get crash reports from device.\n");
+ afc_client_free(afc);
+ idevice_free(device);
+ return -1;
+ }
+
+ printf("Done.\n");
+
+ afc_client_free(afc);
+ idevice_free(device);
+
+ return 0;
+}