#import #include #include "builtin_debugger_cmds.h" typedef void *am_device_t; typedef void *afc_conn_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_AFCDirectoryCreate)(afc_conn_t, const char *); SET_FUNC(AFCDirectoryCreate); typedef int (*fptr_AFCFileRefOpen)(afc_conn_t, const char *, int, afc_fileref_t *); SET_FUNC(AFCFileRefOpen); 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, AFCDirectoryCreate); LOOKUP_FUNC(handler, AFCFileRefOpen); LOOKUP_FUNC(handler, AFCFileRefWrite); LOOKUP_FUNC(handler, AFCFileRefClose); } static bool debug_mode = false; static bool discovery_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 void install_application(am_device_t dev) { PERFORM("connecting to device", _AMDeviceConnect(dev)); PERFORM("pairing device", _AMDeviceValidatePairing(dev)); PERFORM("creating lockdown session", _AMDeviceStartSession(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"); [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 NSTask *gdb_task = nil; static void sigforwarder(int sig) { if (gdb_task != nil) { kill([gdb_task processIdentifier], sig); } } static void start_debugger(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 hasPrefix:path_version]) { device_support_path = [device_supports_path stringByAppendingPathComponent:path]; break; } } 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; } // 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 and connect it to a UNIX socket file. 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); gdb_fd = 0; PERFORM("starting installer proxy service", _AMDeviceStartService(dev, CFSTR("com.apple.debugserver"), &gdb_fd, NULL)); assert(gdb_fd > 0); CFSocketRef fdvendor = CFSocketCreate(NULL, AF_UNIX, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); int yes = 1; setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); 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)); unlink(gdb_unix_socket_path); CFSocketSetAddress(fdvendor, address_data); CFRelease(address_data); CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); // Prepare gdb commands file. NSDictionary *info_plist = [NSDictionary dictionaryWithContentsOfFile: [app_path stringByAppendingPathComponent:@"Info.plist"]]; assert(info_plist != nil); NSString *app_identifier = [info_plist objectForKey:@"CFBundleIdentifier"]; assert(app_identifier); 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"]; 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:NSASCIIStringEncoding error:nil]); // Start gdb. NSString *gdb_path = [[NSString stringWithUTF8String:xcode_dir] stringByAppendingPathComponent: @"Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin"]; float product_version_f = [product_version floatValue]; NSString *remote_arch = product_version_f < 5.0 ? @"armv6" : @"armv7"; // Forward ^C to gdb. signal(SIGINT, sigforwarder); gdb_task = [[NSTask launchedTaskWithLaunchPath:gdb_path arguments:[NSArray arrayWithObjects:@"--arch", remote_arch, @"-q", @"-x", cmds_path, nil]] retain]; [gdb_task waitUntilExit]; } static void device_go(am_device_t dev) { install_application(dev); if (getenv("debug") != NULL) { start_debugger(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 ([(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 (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) { 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); }