#import #include #include "builtin_debugger_cmds.h" #include "rmtask.m" typedef void *am_device_t; typedef void *afc_conn_t; typedef void *afc_dir_t; typedef void *afc_dict_t; typedef void *afc_fileref_t; typedef void *am_device_notif_context_t; static am_device_t am_device_from_notif_context(am_device_notif_context_t info) { return *(void **)info; } typedef void (*am_device_subscribe_cb)(am_device_notif_context_t info); #define SET_FUNC(name) \ static fptr_##name _##name = NULL #define LOOKUP_FUNC(handler, name) \ do { \ _##name = (fptr_##name)(dlsym(handler, #name)); \ if (_##name == NULL) { \ printf("can't lookup function %s\n", #name); \ exit(1); \ } \ } \ while (0) typedef int (*fptr_AMDeviceNotificationSubscribe)(am_device_subscribe_cb, int, int, int, void **); SET_FUNC(AMDeviceNotificationSubscribe); typedef CFStringRef (*fptr_AMDeviceGetName)(am_device_t); SET_FUNC(AMDeviceGetName); typedef int (*fptr_AMDeviceLookupApplications)(am_device_t, int, void **); SET_FUNC(AMDeviceLookupApplications); typedef CFStringRef (*fptr_AMDeviceCopyValue)(am_device_t, unsigned int, CFStringRef); SET_FUNC(AMDeviceCopyValue); typedef int (*fptr_AMDeviceMountImage)(am_device_t, CFStringRef, CFDictionaryRef, void *, int); SET_FUNC(AMDeviceMountImage); typedef int (*fptr_AMDeviceConnect)(am_device_t); SET_FUNC(AMDeviceConnect); typedef int (*fptr_AMDeviceValidatePairing)(am_device_t); SET_FUNC(AMDeviceValidatePairing); typedef int (*fptr_AMDeviceStartSession)(am_device_t); SET_FUNC(AMDeviceStartSession); typedef int (*fptr_AMDeviceStartService)(am_device_t, CFStringRef, int *, void *); SET_FUNC(AMDeviceStartService); typedef int (*fptr_AFCConnectionOpen)(int, void *, afc_conn_t *conn); SET_FUNC(AFCConnectionOpen); typedef int (*fptr_AFCDirectoryOpen)(afc_conn_t, const char *, afc_dir_t *); SET_FUNC(AFCDirectoryOpen); typedef int (*fptr_AFCDirectoryRead)(afc_conn_t, afc_dir_t, char **); SET_FUNC(AFCDirectoryRead); typedef int (*fptr_AFCDirectoryCreate)(afc_conn_t, const char *); SET_FUNC(AFCDirectoryCreate); typedef int (*fptr_AFCDirectoryClose)(afc_conn_t, afc_dir_t); SET_FUNC(AFCDirectoryClose); typedef int (*fptr_AFCFileRefOpen)(afc_conn_t, const char *, int, afc_fileref_t *); SET_FUNC(AFCFileRefOpen); typedef int (*fptr_AFCFileInfoOpen)(afc_conn_t, const char *, afc_dict_t *); SET_FUNC(AFCFileInfoOpen); typedef int (*fptr_AFCKeyValueRead)(afc_dict_t, char **, char **); SET_FUNC(AFCKeyValueRead); typedef int (*fptr_AFCKeyValueClose)(afc_dict_t); SET_FUNC(AFCKeyValueClose); typedef int (*fptr_AFCFileRefRead)(afc_conn_t, afc_fileref_t, void *, size_t *); SET_FUNC(AFCFileRefRead); typedef int (*fptr_AFCFileRefWrite)(afc_conn_t, afc_fileref_t, const void *, size_t); SET_FUNC(AFCFileRefWrite); typedef int (*fptr_AFCFileRefClose)(afc_conn_t, afc_fileref_t); SET_FUNC(AFCFileRefClose); static void init_private_funcs(void) { void *handler = dlopen("/System/Library/PrivateFrameworks/MobileDevice.framework/MobileDevice", 0); LOOKUP_FUNC(handler, AMDeviceNotificationSubscribe); LOOKUP_FUNC(handler, AMDeviceGetName); LOOKUP_FUNC(handler, AMDeviceLookupApplications); LOOKUP_FUNC(handler, AMDeviceCopyValue); LOOKUP_FUNC(handler, AMDeviceMountImage); LOOKUP_FUNC(handler, AMDeviceConnect); LOOKUP_FUNC(handler, AMDeviceValidatePairing); LOOKUP_FUNC(handler, AMDeviceStartSession); LOOKUP_FUNC(handler, AMDeviceStartService); LOOKUP_FUNC(handler, AFCConnectionOpen); LOOKUP_FUNC(handler, AFCDirectoryOpen); LOOKUP_FUNC(handler, AFCDirectoryRead); LOOKUP_FUNC(handler, AFCDirectoryCreate); LOOKUP_FUNC(handler, AFCDirectoryClose); LOOKUP_FUNC(handler, AFCFileRefOpen); LOOKUP_FUNC(handler, AFCFileInfoOpen); LOOKUP_FUNC(handler, AFCKeyValueRead); LOOKUP_FUNC(handler, AFCKeyValueClose); LOOKUP_FUNC(handler, AFCFileRefRead); LOOKUP_FUNC(handler, AFCFileRefWrite); LOOKUP_FUNC(handler, AFCFileRefClose); } static bool debug_mode = false; static bool discovery_mode = false; static bool console_mode = false; static bool logs_mode = false; #define LOG(fmt, ...) \ do { \ if (debug_mode) { \ fprintf(stderr, "log: "); \ fprintf(stderr, fmt, ##__VA_ARGS__); \ fprintf(stderr, "\n"); \ } \ } \ while (0) #define PERFORM(what, call) \ do { \ LOG(what); \ int code = call; \ if (code != 0) { \ fprintf(stderr, "Error when %s: code %d\n", what, code); \ fprintf(stderr, "Make sure RubyMotion is using a valid (non-expired) provisioning profile\nand that no other process (iTunes, Xcode) is connected to your iOS device\nat the same time (even through Wi-Fi).\n"); \ exit(1); \ } \ } \ while (0) static void send_plist(NSFileHandle *handle, id plist) { NSError *error = nil; NSData *data = [NSPropertyListSerialization dataWithPropertyList:plist format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; if (data == nil) { fprintf(stderr, "error when serializing property list %s: %s\n", [[plist description] UTF8String], [[error description] UTF8String]); exit(1); } uint32_t nlen = CFSwapInt32HostToBig([data length]); [handle writeData:[[[NSData alloc] initWithBytes:&nlen length:sizeof(nlen)] autorelease]]; [handle writeData:data]; } static id read_plist(NSFileHandle *handle) { NSData *datalen = [handle readDataOfLength:4]; if ([datalen length] < 4) { fprintf(stderr, "error: datalen packet not found\n"); exit(1); } uint32_t *lenp = (uint32_t *)[datalen bytes]; uint32_t len = CFSwapInt32BigToHost(*lenp); NSMutableData *data = [NSMutableData data]; while (true) { NSData *chunk = [handle readDataOfLength:len]; if (chunk == nil) { break; } [data appendData:chunk]; if ([chunk length] == len) { break; } len -= [data length]; } NSError *error = nil; id plist = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:&error]; if (plist == nil) { fprintf(stderr, "error when deserializing property list data %s: %s\n", [[data description] UTF8String], [[error description] UTF8String]); exit(1); } return plist; } static NSString *device_id = nil; static NSString *app_package_path = nil; static NSData *app_package_data = nil; static NSDictionary * read_info_plist(void) { NSString *app_path = [[app_package_path stringByDeletingPathExtension] stringByAppendingString:@".app"]; NSDictionary *info_plist = [NSDictionary dictionaryWithContentsOfFile: [app_path stringByAppendingPathComponent:@"Info.plist"]]; assert(info_plist != nil); return info_plist; } static NSString * remote_app_path(am_device_t dev) { NSDictionary *info_plist = read_info_plist(); NSString *app_identifier = [info_plist objectForKey:@"CFBundleIdentifier"]; assert(app_identifier != nil); NSDictionary *apps = nil; const int res = _AMDeviceLookupApplications(dev, 0, (void **)&apps); assert(res == 0); NSDictionary *app = [apps objectForKey:app_identifier]; assert(app != nil); NSString *app_remote_path = [app objectForKey:@"Path"]; assert(app_remote_path != nil); return app_remote_path; } static void setup_device_connection(am_device_t dev) { PERFORM("connecting to device", _AMDeviceConnect(dev)); PERFORM("pairing device", _AMDeviceValidatePairing(dev)); PERFORM("creating lockdown session", _AMDeviceStartSession(dev)); } static void install_application(am_device_t dev) { setup_device_connection(dev); int afc_fd = 0; PERFORM("starting file copy service", _AMDeviceStartService(dev, CFSTR("com.apple.afc"), &afc_fd, NULL)); assert(afc_fd > 0); int ipc_fd = 0; PERFORM("starting installer proxy service", _AMDeviceStartService(dev, CFSTR("com.apple.mobile.installation_proxy"), &ipc_fd, NULL)); assert(ipc_fd > 0); afc_conn_t afc_conn = NULL; PERFORM("opening file copy connection", _AFCConnectionOpen(afc_fd, 0, &afc_conn)); assert(afc_conn != NULL); NSString *staging_dir = @"PublicStaging"; NSString *remote_pkg_path = [NSString stringWithFormat:@"%@/%@", staging_dir, [app_package_path lastPathComponent]]; PERFORM("creating staging directory", _AFCDirectoryCreate(afc_conn, [staging_dir fileSystemRepresentation])); afc_fileref_t afc_fileref = NULL; PERFORM("opening remote package path", _AFCFileRefOpen(afc_conn, [remote_pkg_path fileSystemRepresentation], 0x3 /* write */, &afc_fileref)); assert(afc_fileref != NULL); PERFORM("writing data", _AFCFileRefWrite(afc_conn, afc_fileref, [app_package_data bytes], [app_package_data length])); PERFORM("closing remote package path", _AFCFileRefClose(afc_conn, afc_fileref)); NSFileHandle *handle = [[NSFileHandle alloc] initWithFileDescriptor:ipc_fd closeOnDealloc:NO]; LOG("sending install command"); send_plist(handle, [NSDictionary dictionaryWithObjectsAndKeys: @"Install", @"Command", remote_pkg_path, @"PackagePath", nil]); while (true) { id plist = read_plist(handle); if (plist == nil) { break; } id error = [plist objectForKey:@"Error"]; if (error != nil) { fprintf(stderr, "error: %s\n", [[error description] UTF8String]); exit(1); } id percent = [plist objectForKey:@"PercentComplete"]; id status = [plist objectForKey:@"Status"]; int percent_int = percent == nil ? 100 : [percent intValue]; const char *status_str = status == nil ? "Unknown" : [status UTF8String]; LOG("progress report: %d%% status: %s", percent_int, status_str); if (percent_int == 100) { break; } } LOG("package has been successfully installed on device"); if (getenv("install_only") != NULL) { printf("%s\n", [remote_app_path(dev) UTF8String]); } [handle release]; } static void mount_cb(id dict, int arg) { if (dict != nil && [dict isKindOfClass:[NSDictionary class]]) { id status = [dict objectForKey:@"Status"]; if (status != nil && [status isKindOfClass:[NSString class]]) { LOG("mounting status: %s", [status UTF8String]); } } } static int gdb_fd = 0; #include #include static void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { CFSocketNativeHandle socket = (CFSocketNativeHandle) (*((CFSocketNativeHandle *)data)); struct msghdr message; struct iovec iov[1]; struct cmsghdr *control_message = NULL; char ctrl_buf[CMSG_SPACE(sizeof(int))]; char dummy_data[1]; memset(&message, 0, sizeof(struct msghdr)); memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); dummy_data[0] = ' '; iov[0].iov_base = dummy_data; iov[0].iov_len = sizeof(dummy_data); message.msg_name = NULL; message.msg_namelen = 0; message.msg_iov = iov; message.msg_iovlen = 1; message.msg_controllen = CMSG_SPACE(sizeof(int)); message.msg_control = ctrl_buf; control_message = CMSG_FIRSTHDR(&message); control_message->cmsg_level = SOL_SOCKET; control_message->cmsg_type = SCM_RIGHTS; control_message->cmsg_len = CMSG_LEN(sizeof(int)); *((int *) CMSG_DATA(control_message)) = gdb_fd; sendmsg(socket, &message, 0); CFSocketInvalidate(s); CFRelease(s); } static id gdb_task = nil; static void sigforwarder(int sig) { if (gdb_task != nil) { kill([gdb_task processIdentifier], sig); } } #define WITH_DEBUG 1 static char tohex(int x) { assert(x >= 0 && x <= 16); static char *hexchars = "0123456789ABCDEF"; return hexchars[x]; } static void gdb_send_str(const char *buf) { send(gdb_fd, buf, strlen(buf), 0); } static NSData * gdb_recv_pkt() { NSMutableData *data = [NSMutableData data]; while (true) { char buf[100]; ssize_t len = recv(gdb_fd, buf, sizeof buf, 0); if (len <= 0) { break; } assert(len <= sizeof buf); [data appendBytes:buf length:len]; } if ([data length] > 0) { gdb_send_str("+"); } return data; } static void gdb_send_pkt(const char *buf) { char *buf2 = (char *)malloc(32*1024); assert(buf2 != NULL); memset(buf2, 0, 32*1024); long cnt = strlen(buf); unsigned char csum = 0; char *p = buf2; *p++ = '$'; for (int i = 0; i < cnt; i++) { csum += buf[i]; *p++ = buf[i]; } *p++ = '#'; *p++ = tohex((csum >> 4) & 0xf); *p++ = tohex(csum & 0xf); *p = '\0'; gdb_send_str(buf2); gdb_recv_pkt(); free(buf2); } static void gdb_start_app(NSString *app_path) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 500000; setsockopt(gdb_fd, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval)); char *cmds[] = { "XXX", // Will be replaced by the app path. "Hc0", "c", NULL, }; const char *apppath = [app_path UTF8String]; cmds[0] = malloc(2000); assert(cmds[0] != NULL); char *p = cmds[0]; sprintf(p, "A%ld,0,", strlen(apppath) * 2); p += strlen(p); const char* q = apppath; while (*q) { *p++ = tohex((*q >> 4) & 0xf); *p++ = tohex(*q & 0xf); q++; } *p = '\0'; char **cmd = cmds; while (*cmd != NULL) { gdb_send_pkt(*cmd); cmd++; gdb_recv_pkt(); gdb_recv_pkt(); } } static void open_console(am_device_t dev) { setup_device_connection(dev); int syslog_fd = 0; PERFORM("starting syslog relay service", _AMDeviceStartService(dev, CFSTR("com.apple.syslog_relay"), &syslog_fd, NULL)); assert(syslog_fd > 0); while (true) { char buf[100]; ssize_t len = recv(syslog_fd, buf, sizeof buf, 0); if (len == -1) { fprintf(stderr, "error when reading syslog: %s", strerror(errno)); break; } buf[len] = '\0'; printf("%s", buf); } } static void start_debug_server(am_device_t dev) { // We need .app and .dSYM bundles nearby. NSString *app_path = [[app_package_path stringByDeletingPathExtension] stringByAppendingString:@".app"]; if (![[NSFileManager defaultManager] fileExistsAtPath:app_path]) { fprintf(stderr, "%s does not exist\n", [app_path fileSystemRepresentation]); return; } NSString *dsym_path = [[app_package_path stringByDeletingPathExtension] stringByAppendingString:@".dSYM"]; if (![[NSFileManager defaultManager] fileExistsAtPath:dsym_path]) { fprintf(stderr, "%s does not exist\n", [dsym_path fileSystemRepresentation]); return; } // Locate DeveloperDiskImage.dmg on the filesystem. char *xcode_dir = getenv("XCODE_DIR"); assert(xcode_dir != NULL); NSString *device_supports_path = [[NSString stringWithUTF8String:xcode_dir] stringByAppendingPathComponent: @"Platforms/iPhoneOS.platform/DeviceSupport"]; NSString *product_version = (NSString *)_AMDeviceCopyValue(dev, 0, CFSTR("ProductVersion")); assert(product_version != nil); NSString *device_support_path = nil; for (NSString *path in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:device_supports_path error:nil]) { NSRange r = [path rangeOfString:@" "]; NSString *path_version = r.location == NSNotFound ? path : [path substringToIndex:r.location]; if ([product_version isEqualToString:path_version]) { device_support_path = [device_supports_path stringByAppendingPathComponent:path]; break; } if ([product_version hasPrefix:path_version] && device_support_path == nil) { device_support_path = [device_supports_path stringByAppendingPathComponent:path]; } } if (device_support_path == nil) { fprintf(stderr, "cannot find developer disk image in `%s' for version `%s'\n", [device_supports_path fileSystemRepresentation], [product_version UTF8String]); return; } NSString *image_path = [device_support_path stringByAppendingPathComponent: @"DeveloperDiskImage.dmg"]; if (![[NSFileManager defaultManager] fileExistsAtPath:image_path]) { fprintf(stderr, "%s does not exist\n", [image_path fileSystemRepresentation]); return; } if (![[NSFileManager defaultManager] fileExistsAtPath:[device_support_path stringByAppendingPathComponent:@"Symbols"]]) { fprintf(stderr, "*** Symbols not found in `%s': debugger might be "\ "slow to attach.\n", [device_support_path fileSystemRepresentation]); } // Mount the .dmg remotely on the device. NSString *image_sig_path = [image_path stringByAppendingString: @".signature"]; if (![[NSFileManager defaultManager] fileExistsAtPath:image_sig_path]) { fprintf(stderr, "%s does not exist\n", [image_sig_path fileSystemRepresentation]); return; } NSData *image_sig_data = [NSData dataWithContentsOfFile:image_sig_path]; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: image_sig_data, @"ImageSignature", @"Developer", @"ImageType", nil]; LOG("mounting developer disk image: %s", [image_path fileSystemRepresentation]); /*int result =*/ _AMDeviceMountImage(dev, (CFStringRef)image_path, (CFDictionaryRef)options, mount_cb, 0); // Start debug server. gdb_fd = 0; PERFORM("starting debug server service", _AMDeviceStartService(dev, CFSTR("com.apple.debugserver"), &gdb_fd, NULL)); assert(gdb_fd > 0); NSString *app_name = [read_info_plist() objectForKey:@"CFBundleName"]; assert(app_name != nil); NSString *app_remote_path = remote_app_path(dev); // Do we need to attach a debugger? If not, we simply run the app. if (getenv("debug") == NULL) { NSDate *app_launch_date = [NSDate date]; gdb_start_app(app_remote_path); fprintf(stderr, "*** Application is running on the device, use ^C to quit.\n"); // Start the syslog service. int syslog_fd = 0; PERFORM("starting syslog relay service", _AMDeviceStartService(dev, CFSTR("com.apple.syslog_relay"), &syslog_fd, NULL)); assert(syslog_fd > 0); // Connect and read the output. NSMutableString *data = [NSMutableString string]; NSString *syslog_match = [app_name stringByAppendingString:@"["]; bool logs_since_app_launch_date = false; while (true) { char buf[100]; ssize_t len = recv(syslog_fd, buf, sizeof buf, 0); if (len == -1) { fprintf(stderr, "error when reading syslog: %s", strerror(errno)); break; } assert(len <= sizeof buf); buf[len] = '\0'; // Split the output into lines. char *p = strrchr(buf, '\n'); if (p == NULL) { NSString *tmp = [[NSString alloc] initWithCString:buf encoding:NSUTF8StringEncoding]; if (tmp != nil) { [data appendString:tmp]; [tmp release]; } } else { NSString *tmp = [[NSString alloc] initWithBytes:buf length:p-buf encoding:NSUTF8StringEncoding]; if (tmp != nil) { [data appendString:tmp]; [tmp release]; } // Parse lines. NSArray *lines = [data componentsSeparatedByString:@"\n"]; for (NSString *line in lines) { // Filter by app name. if ([line rangeOfString:syslog_match].location == NSNotFound) { continue; } // Filter by date. if (!logs_since_app_launch_date) { NSArray *words = [line componentsSeparatedByString: @" "]; int index_month = 0; int index_day = 1; int index_time = 2; if ([[words objectAtIndex:index_day] isEqualToString:@""]) { index_day++; index_time++; } NSString *str = [NSString stringWithFormat: @"%@ %@ %@", [words objectAtIndex:index_month], [words objectAtIndex:index_day], [words objectAtIndex:index_time]]; NSDate *date = [NSDate dateWithNaturalLanguageString: str]; if ([date compare:app_launch_date] == NSOrderedDescending) { logs_since_app_launch_date = true; } else { continue; } } // Yeepee, we can print that one! printf("%s\n", [line UTF8String]); } data = [[[NSMutableString alloc] initWithCString:p+1 encoding:NSUTF8StringEncoding] autorelease]; } } CFRunLoopRun(); return; } // Connect the debug server socket to a UNIX socket file. // gdb-only, but since RubyMine also uses it, we keep it around. NSString *tmpdir = NSTemporaryDirectory(); assert(tmpdir != nil); char gdb_unix_socket_path[PATH_MAX]; snprintf(gdb_unix_socket_path, sizeof gdb_unix_socket_path, "%s/rubymotion-remote-gdb-XXXXXX", [tmpdir fileSystemRepresentation]); assert(mktemp(gdb_unix_socket_path) != NULL); unlink(gdb_unix_socket_path); CFSocketRef fdvendor = CFSocketCreate(NULL, AF_UNIX, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); struct sockaddr_un address; memset(&address, 0, sizeof(address)); address.sun_family = AF_UNIX; strcpy(address.sun_path, gdb_unix_socket_path); address.sun_len = SUN_LEN(&address); CFDataRef address_data = CFDataCreate(NULL, (const UInt8 *)&address, sizeof(address)); int yes = 1; setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); CFSocketSetAddress(fdvendor, address_data); CFRelease(address_data); CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); // If we need to attach an external debugger, we can stop here. if (getenv("no_start")) { fprintf(stderr, "device_support_path: %s\nremote_app_path: %s\n"\ "debug_server_socket_path: %s\n", [device_support_path fileSystemRepresentation], [app_remote_path fileSystemRepresentation], gdb_unix_socket_path); pause(); } // Attach the debugger: gdb or lldb. NSString *gdb_path = [[NSString stringWithUTF8String:xcode_dir] stringByAppendingPathComponent: @"Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin"]; if ([[NSFileManager defaultManager] fileExistsAtPath:gdb_path]) { // Prepare gdb commands file. NSString *cmds_path = [NSString pathWithComponents: [NSArray arrayWithObjects:NSTemporaryDirectory(), @"_deploygdbcmds", nil]]; NSString *cmds = [NSString stringWithFormat:@""\ "set shlib-path-substitutions /usr \"%@/Symbols/usr\" /System \"%@/Symbols/System\" /Developer \"%@/Symbols/Developer\" \"%@\" \"%@\"\n"\ "set remote max-packet-size 1024\n"\ "set inferior-auto-start-dyld 0\n"\ "set remote executable-directory %@\n"\ "set remote noack-mode 1\n"\ "set sharedlibrary load-rules \\\".*\\\" \\\".*\\\" container\n"\ "set minimal-signal-handling 1\n"\ "set mi-show-protections off\n"\ "target remote-mobile %s\n"\ "file \"%@\"\n"\ "add-dsym \"%@\"\n"\ "run\n"\ "set minimal-signal-handling 0\n"\ "set inferior-auto-start-dyld 1\n"\ "set inferior-auto-start-cfm off\n"\ "set sharedLibrary load-rules dyld \".*libobjc.*\" all dyld \".*CoreFoundation.*\" all dyld \".*Foundation.*\" all dyld \".*libSystem.*\" all dyld \".*AppKit.*\" all dyld \".*PBGDBIntrospectionSupport.*\" all dyld \".*/usr/lib/dyld.*\" all dyld \".*CarbonDataFormatters.*\" all dyld \".*libauto.*\" all dyld \".*CFDataFormatters.*\" all dyld \"/System/Library/Frameworks\\\\\\\\|/System/Library/PrivateFrameworks\\\\\\\\|/usr/lib\" extern dyld \".*\" all exec \".*\" all\n"\ "sharedlibrary apply-load-rules all\n", device_support_path, device_support_path, device_support_path, [[app_remote_path stringByDeletingLastPathComponent] stringByReplacingOccurrencesOfString:@"/private/var" withString:@"/var"], [app_path stringByDeletingLastPathComponent], app_remote_path, gdb_unix_socket_path, app_path, dsym_path]; cmds = [cmds stringByAppendingFormat:@"%s\n", BUILTIN_DEBUGGER_CMDS]; NSString *user_cmds = [NSString stringWithContentsOfFile: @"debugger_cmds" encoding:NSUTF8StringEncoding error:nil]; if (user_cmds != nil) { cmds = [cmds stringByAppendingString:user_cmds]; cmds = [cmds stringByAppendingString:@"\n"]; } if (getenv("no_continue") == NULL) { cmds = [cmds stringByAppendingString:@"continue\n"]; } assert([cmds writeToFile:cmds_path atomically:YES encoding:NSUTF8StringEncoding error:nil]); // Start gdb. float product_version_f = [product_version floatValue]; NSString *remote_arch = product_version_f < 5.0 ? @"armv6" : @"armv7"; // Forward ^C to gdb. signal(SIGINT, sigforwarder); // TODO : need NSTask to launch gdb gdb_task = [[NSTask launchedTaskWithLaunchPath:gdb_path arguments:[NSArray arrayWithObjects:@"--arch", remote_arch, @"-q", @"-x", cmds_path, nil]] retain]; } else { NSString *lldb_path = [[NSString stringWithUTF8String:xcode_dir] stringByAppendingPathComponent:@"usr/bin/lldb"]; if (![[NSFileManager defaultManager] fileExistsAtPath:lldb_path]) { fprintf(stderr, "Can't locate either gdb or lldb within Xcode"); exit(1); } NSString *tmpdir = NSTemporaryDirectory(); assert(tmpdir != nil); char lldb_socket_path[PATH_MAX]; snprintf(lldb_socket_path, sizeof lldb_socket_path, "%s/rubymotion-lldb-XXXXXX", [NSTemporaryDirectory() fileSystemRepresentation]); assert(mktemp(lldb_socket_path) != NULL); // Prepare lldb commands file. NSString *py_cmds = [NSString stringWithFormat:@""\ "# -*- coding: utf-8 -*-\n"\ "import socket\n"\ "import sys\n"\ "import lldb\n"\ "sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n"\ "sock.connect(\"%s\")\n"\ "debugger = lldb.debugger\n"\ "debugger.SetAsync(False)\n"\ "error = lldb.SBError()\n"\ "target = debugger.CreateTarget(\"%@\", \"armv7s-apple-ios\", \"remote-ios\", False, error)\n"\ "module = target.FindModule(target.GetExecutable())\n"\ "filespec = lldb.SBFileSpec(\"%@\", False)\n"\ "module.SetPlatformFileSpec(filespec)\n"\ "lldb.debugger.HandleCommand(\"target modules search-paths add /usr '%@/Symbols/usr' /System '%@/Symbols/System' /Developer '%@/Symbols/Developer'\")\n"\ "error = lldb.SBError()\n"\ "listener = lldb.debugger.GetListener()\n"\ "process = target.ConnectRemote(listener, \"fd://%%s\" %% sock.fileno(), None, error)\n"\ "lldb.debugger.HandleCommand(\"process plugin packet send 'QSetEnableAsyncProfiling;enable:1;interval_usec:1000000;scan_type:0xfffffeff;'\")\n"\ "error = lldb.SBError()\n"\ "process.RemoteLaunch(None, None, None, None, None, None, 0, False, error)\n"\ "process.Continue()\n"\ "", lldb_socket_path, app_path, app_remote_path, device_support_path, device_support_path, device_support_path]; NSString *py_cmds_path = [NSString pathWithComponents: [NSArray arrayWithObjects:NSTemporaryDirectory(), @"_deploylldbcmds.py", nil]]; assert([py_cmds writeToFile:py_cmds_path atomically:YES encoding:NSUTF8StringEncoding error:nil]); NSString *cmds = [NSString stringWithFormat:@""\ "command script import /Library/RubyMotion/lldb/lldb.py\n"\ "command script import %@\n"\ "breakpoint set --name rb_exc_raise\n"\ "breakpoint set --name malloc_error_break\n"\ "", py_cmds_path]; NSString *user_cmds = [NSString stringWithContentsOfFile: @"debugger_cmds" encoding:NSUTF8StringEncoding error:nil]; if (user_cmds != nil) { cmds = [cmds stringByAppendingString:user_cmds]; cmds = [cmds stringByAppendingString:@"\n"]; } NSString *cmds_path = [NSString pathWithComponents: [NSArray arrayWithObjects:NSTemporaryDirectory(), @"_deploylldbcmds", nil]]; assert([cmds writeToFile:cmds_path atomically:YES encoding:NSUTF8StringEncoding error:nil]); // Forward ^C to lldb. signal(SIGINT, sigforwarder); // Run lldb. gdb_task = [[RMTask launchedTaskWithLaunchPath:lldb_path arguments:[NSArray arrayWithObjects:@"-s", cmds_path, nil]] retain]; // Connect to the lldb UNIX socket. const int lldb_socket = socket(PF_LOCAL, SOCK_STREAM, 0); if (lldb_socket == -1) { perror("socket()"); goto exit; } struct sockaddr_un sa = {0}; sa.sun_family = AF_UNIX; strncpy(sa.sun_path, lldb_socket_path, sizeof(sa.sun_path)); if (bind(lldb_socket, (struct sockaddr*)&sa, sizeof(struct sockaddr_un)) == -1) { perror("bind()"); goto exit; } if (listen(lldb_socket, 5) == -1) { perror("listen()"); goto exit; } int lldb_fd; if ((lldb_fd = accept(lldb_socket, NULL, NULL)) == -1) { perror("connect()"); goto exit; } fcntl(lldb_fd, F_SETFL, O_NONBLOCK); fcntl(gdb_fd, F_SETFL, O_NONBLOCK); // We are connected, start redirecting input to/from the debug server. char buf[2048]; ssize_t len; while (true) { fd_set read_fds; FD_ZERO(&read_fds); FD_SET(gdb_fd, &read_fds); FD_SET(lldb_fd, &read_fds); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 10000; int n = select(lldb_fd + 1, &read_fds, NULL, NULL, &tv); if (n == -1 && errno != EINTR) { perror("select()"); break; } if (n > 0) { if (FD_ISSET(lldb_fd, &read_fds)) { len = read(lldb_fd, buf, sizeof buf); if (len > 0) { #if 0 buf[len] = '\0'; printf("lldb -> device: %ld bytes: %s\n", len, buf); #endif write(gdb_fd, buf, len); } else { break; } } if (FD_ISSET(gdb_fd, &read_fds)) { len = read(gdb_fd, buf, sizeof buf); if (len > 0) { #if 0 buf[len] = '\0'; printf("device -> lldb: %ld bytes: %s\n", len, buf); #endif write(lldb_fd, buf, len); } else { break; } } } } } exit: [gdb_task waitUntilExit]; } static NSDictionary * read_remote_file_info(afc_conn_t afc_conn, const char *path) { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; afc_dict_t afc_dict = NULL; if (_AFCFileInfoOpen(afc_conn, path, &afc_dict) != 0 || afc_dict == NULL) { return nil; } while (true) { char *key = NULL; char *value = NULL; if (_AFCKeyValueRead(afc_dict, &key, &value) != 0 || key == NULL || value == NULL) { break; } [dict setValue:[NSString stringWithUTF8String:value] forKey:[NSString stringWithUTF8String:key]]; } _AFCKeyValueClose(afc_dict); // We don't check for errors here. return dict; } static NSData * read_remote_file_data(afc_conn_t afc_conn, const char *path) { NSMutableData *data = [NSMutableData data]; afc_fileref_t afc_fileref = NULL; PERFORM("opening crash log", _AFCFileRefOpen(afc_conn, path, 0x2 /* read */, &afc_fileref)); assert(afc_fileref != NULL); while (true) { char buf[1024]; size_t length = sizeof buf; memset(buf, 0, sizeof buf); PERFORM("read crash log data", _AFCFileRefRead(afc_conn, afc_fileref, buf, &length)); if (length > 0) { [data appendBytes:buf length:length]; } else { break; } } PERFORM("closing crash log", _AFCFileRefClose(afc_conn, afc_fileref)); return data; } static void recursive_retrieve_crash_reports(afc_conn_t afc_conn, const char *root, NSString *app_name) { NSString *app_path_pattern = [NSString stringWithFormat:@"%s%@_", root, app_name]; // Figure out where the .dSYM bundle is. NSString *dsym_path = [[app_package_path stringByDeletingPathExtension] stringByAppendingPathExtension:@"dSYM"]; assert([[NSFileManager defaultManager] fileExistsAtPath:dsym_path]); // Get the directory where the files will be saved. const char *_local_dir = getenv("CRASH_REPORTS_DIR"); assert(_local_dir != NULL); NSString *local_dir = [NSString stringWithUTF8String:_local_dir]; assert([[NSFileManager defaultManager] fileExistsAtPath:local_dir]); // Prepare the temporary directory where we will download the files. NSString *tmp_dir = [NSTemporaryDirectory() stringByAppendingPathComponent:@"RubyMotion-Device-Crashlogs"]; [[NSFileManager defaultManager] createDirectoryAtPath:tmp_dir withIntermediateDirectories:true attributes:nil error:nil]; afc_dir_t afc_dir = NULL; PERFORM("opening crash report directory", _AFCDirectoryOpen(afc_conn, root, &afc_dir)); assert(afc_dir != NULL); NSString *last_generated_path = nil; while (true) { // Read a directory entry, skip private files. char *path = NULL; PERFORM("reading crash report directory path", _AFCDirectoryRead(afc_conn, afc_dir, &path)); if (path == NULL) { break; } if (*path == '.') { continue; } // Retrieve the file info dictionary from the file. char full_path[1024]; snprintf(full_path, sizeof full_path, "%s%s", root, path); NSDictionary *path_info = read_remote_file_info(afc_conn, full_path); if (path_info == nil) { continue; } NSString *fileType = [path_info objectForKey:@"st_ifmt"]; if (fileType == nil) { continue; } if ([fileType isEqualToString:@"S_IFDIR"]) { // A directory. // Recursive iteration does not seem to be required, app crash // reports seem to always be in the top-level directory. Only // system stuff is in separate directories. //strlcat(full_path, "/", sizeof full_path); //recursive_retrieve_crash_reports(afc_conn, full_path); } else if ([fileType isEqualToString:@"S_IFREG"]) { // A regular file! NSString *ns_full_path = [NSString stringWithUTF8String:full_path]; if ([ns_full_path rangeOfString:app_path_pattern].location != 0) { // Does not look like a crash report from our app. continue; } // Calculate what will be the local path on the file system. NSString *local_path = [local_dir stringByAppendingPathComponent: [ns_full_path lastPathComponent]]; if ([[local_path pathExtension] isEqualToString:@"synced"]) { local_path = [local_path stringByDeletingPathExtension]; } if ([[local_path pathExtension] isEqualToString:@"plist"]) { local_path = [local_path stringByDeletingPathExtension]; } local_path = [local_path stringByAppendingPathExtension:@"crash"]; // No need to retrieve the file if we already worked on it. if ([[NSFileManager defaultManager] fileExistsAtPath:local_path]) { continue; } // Read the data. It should be a property list. The actual crash // report is a long string associated to the `description' key. NSData *data = read_remote_file_data(afc_conn, full_path); id obj = [NSPropertyListSerialization propertyListWithData:data options:0 format:nil error:nil]; if (obj == nil || ![obj isKindOfClass:[NSDictionary class]]) { fprintf(stderr, "Error when parsing crash report data from `%s'\n", full_path); exit(1); } id content = [(NSDictionary *)obj valueForKey:@"description"]; if (content == nil || ![content isKindOfClass:[NSString class]]) { fprintf(stderr, "Crash report data from `%s' has no content\n", full_path); exit(1); } // Write the data on the file system. NSError *error = nil; NSString *tmp_path = [tmp_dir stringByAppendingPathComponent: [local_path lastPathComponent]]; if (![(NSString *)content writeToFile:tmp_path atomically:true encoding:NSUTF8StringEncoding error:&error]) { fprintf(stderr, "Error when writing crash report at path `%s': %s\n", [local_path fileSystemRepresentation], [[error description] UTF8String]); exit(1); } // Run symbolicate. char cmd_line[5000]; char *xcode_dir = getenv("XCODE_DIR"); assert(xcode_dir != NULL); snprintf(cmd_line, sizeof cmd_line, "DEVELOPER_DIR=\"%s\" %s/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash -o \"%s\" \"%s\" \"%s\" >& /dev/null", xcode_dir, xcode_dir, [local_path fileSystemRepresentation], [tmp_path fileSystemRepresentation], [dsym_path fileSystemRepresentation]); system(cmd_line); printf("New crash report: %s\n", [local_path fileSystemRepresentation]); last_generated_path = local_path; } } PERFORM("closing crash report directory", _AFCDirectoryClose(afc_conn, afc_dir)); if (last_generated_path == nil) { printf("Unable to find any crash report file on the device for this app that hasn't been processed yet. Already-processed crash report files for this app might be in `%s'.\n", _local_dir); } } static void retrieve_crash_reports(am_device_t dev) { setup_device_connection(dev); // Figure out the app name from the given .ipa path. NSString *app_name = [[app_package_path lastPathComponent] stringByDeletingPathExtension]; int afc_fd = 0; PERFORM("starting crash report copy service", _AMDeviceStartService(dev, CFSTR("com.apple.crashreportcopymobile"), &afc_fd, NULL)); assert(afc_fd > 0); afc_conn_t afc_conn = NULL; PERFORM("opening file copy connection", _AFCConnectionOpen(afc_fd, 0, &afc_conn)); assert(afc_conn != NULL); recursive_retrieve_crash_reports(afc_conn, "/", app_name); } static void device_go(am_device_t dev) { if (logs_mode) { retrieve_crash_reports(dev); } else { install_application(dev); if (getenv("install_only") == NULL) { start_debug_server(dev); } } } static void device_subscribe_cb(am_device_notif_context_t ctx) { am_device_t dev = am_device_from_notif_context(ctx); CFStringRef name = _AMDeviceGetName(dev); if (name != NULL) { if (discovery_mode) { printf("%s\n", [(id)name UTF8String]); exit(0); } else if (console_mode) { open_console(dev); exit(0); } else if ([(id)name isEqualToString:device_id]) { LOG("found usb mobile device %s", [(id)name UTF8String]); device_go(dev); exit(0); } } } static void usage(void) { system("open http://www.youtube.com/watch?v=1orMXD_Ijbs&feature=fvst"); //fprintf(stderr, "usage: deploy [-d] \n"); exit(1); } int main(int argc, char **argv) { /*NSAutoreleasePool *pool =*/ [[NSAutoreleasePool alloc] init]; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-d") == 0) { debug_mode = true; } else if (strcmp(argv[i], "-D") == 0) { discovery_mode = true; } else if (strcmp(argv[i], "-c") == 0) { console_mode = true; } else if (strcmp(argv[i], "-l") == 0) { logs_mode = true; } else { if (device_id == nil) { device_id = [[NSString stringWithUTF8String:argv[i]] retain]; } else { if (app_package_path != nil) { usage(); } app_package_path = [[NSString stringWithUTF8String:argv[i]] retain]; } } } if (!discovery_mode && !console_mode) { if (device_id == nil || app_package_path == nil) { usage(); } app_package_data = [[NSData dataWithContentsOfFile:app_package_path] retain]; if (app_package_data == nil) { fprintf(stderr, "can't read data from %s\n", [app_package_path fileSystemRepresentation]); exit(1); } } init_private_funcs(); void *notif = NULL; PERFORM("subscribing to device notification", _AMDeviceNotificationSubscribe(device_subscribe_cb, 0, 0, 0, ¬if)); // Run one second, should be enough to catch an attached device. [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow:1]]; if (!discovery_mode) { fprintf(stderr, "error: can't find device ID %s\n", [device_id UTF8String]); } exit(1); }