diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m index 81f049b1..2f61927c 100644 --- a/src/ios-deploy/ios-deploy.m +++ b/src/ios-deploy/ios-deploy.m @@ -134,7 +134,6 @@ bool _json_output = false; NSMutableArray *_file_meta_info = nil; int port = 0; // 0 means "dynamically assigned" -ServiceConnRef dbgServiceConnection = NULL; pid_t parent = 0; // PID of child process running lldb pid_t child = 0; @@ -142,10 +141,10 @@ const int SIGLLDB = SIGUSR1; NSString* tmpUUID; struct am_device_notification *notify; -CFRunLoopSourceRef lldb_socket_runloop; -CFRunLoopSourceRef server_socket_runloop; CFRunLoopSourceRef fdvendor_runloop; +CFMutableDictionaryRef debugserver_active_connections; + uint32_t symbols_file_paths_command = 0x30303030; uint32_t symbols_download_file_command = 0x01000000; CFStringRef symbols_service_name = CFSTR("com.apple.dt.fetchsymbols"); @@ -1190,74 +1189,94 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { CFRelease(pmodule); } -CFSocketRef server_socket; -CFSocketRef lldb_socket; -CFWriteStreamRef serverWriteStream = NULL; -CFWriteStreamRef lldbWriteStream = NULL; - int kill_ptree(pid_t root, int signum); -void -server_callback (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) -{ + +const CFStringRef kDbgConnectionPropertyServiceConnection = CFSTR("service_connection"); +const CFStringRef kDbgConnectionPropertyLLDBSocket = CFSTR("lldb_socket"); +const CFStringRef kDbgConnectionPropertyLLDBSocketRunLoop = CFSTR("lldb_socket_runloop"); +const CFStringRef kDbgConnectionPropertyServerSocket = CFSTR("server_socket"); +const CFStringRef kDbgConnectionPropertyServerSocketRunLoop = CFSTR("server_socket_runloop"); + +CFSocketContext get_socket_context(CFNumberRef connection_id) { + CFSocketContext context = { 0, (void*)connection_id, NULL, NULL, NULL }; + return context; +} + +CFMutableDictionaryRef get_connection_properties(CFNumberRef connection_id) { + // This is no-op if the key already exists + CFDictionaryAddValue(debugserver_active_connections, connection_id, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + + return (CFMutableDictionaryRef)CFDictionaryGetValue(debugserver_active_connections, connection_id); +} + +void close_connection(CFNumberRef connection_id) { + CFMutableDictionaryRef connection_properties = get_connection_properties(connection_id); + + ServiceConnRef dbgServiceConnection = (ServiceConnRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyServiceConnection); + AMDServiceConnectionInvalidate(dbgServiceConnection); + CFRelease(dbgServiceConnection); + + CFSocketRef lldb_socket = (CFSocketRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyLLDBSocket); + CFSocketInvalidate(lldb_socket); + CFRelease(lldb_socket); + + CFRunLoopSourceRef lldb_socket_runloop = (CFRunLoopSourceRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyLLDBSocketRunLoop); + CFRunLoopRemoveSource(CFRunLoopGetMain(), lldb_socket_runloop, kCFRunLoopCommonModes); + CFRelease(lldb_socket_runloop); + + CFSocketRef server_socket = (CFSocketRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyServerSocket); + CFSocketInvalidate(server_socket); + CFRelease(server_socket); + + CFRunLoopSourceRef server_socket_runloop = (CFRunLoopSourceRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyServerSocketRunLoop); + CFRunLoopRemoveSource(CFRunLoopGetMain(), server_socket_runloop, kCFRunLoopCommonModes); + CFRelease(server_socket_runloop); + + CFDictionaryRemoveValue(debugserver_active_connections, connection_id); +} + +void server_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { + CFNumberRef connection_id = (CFNumberRef)info; + + CFMutableDictionaryRef connection_properties = get_connection_properties(connection_id); + + ServiceConnRef dbgServiceConnection = (ServiceConnRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyServiceConnection); + CFSocketRef lldb_socket = (CFSocketRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyLLDBSocket); + char buffer[0x1000]; - int bytesRead = AMDServiceConnectionReceive(dbgServiceConnection, buffer, sizeof(buffer)); - if (bytesRead == 0) - { - // close the socket on which we've got end-of-file, the server_socket. - CFSocketInvalidate(s); - CFRelease(s); - return; - } - write(CFSocketGetNative (lldb_socket), buffer, bytesRead); - while (bytesRead == sizeof(buffer)) - { + int bytesRead; + do { bytesRead = AMDServiceConnectionReceive(dbgServiceConnection, buffer, sizeof(buffer)); - if (bytesRead > 0) + if (bytesRead == 0) { - write(CFSocketGetNative (lldb_socket), buffer, bytesRead); + // close the socket on which we've got end-of-file, the server_socket. + close_connection(connection_id); + return; } + write(CFSocketGetNative(lldb_socket), buffer, bytesRead); } + while (bytesRead == sizeof(buffer)); } void lldb_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { - //printf ("lldb: %s\n", CFDataGetBytePtr (data)); + CFNumberRef connection_id = (CFNumberRef)info; - if (CFDataGetLength (data) == 0) { + CFMutableDictionaryRef connection_properties = get_connection_properties(connection_id); + + ServiceConnRef dbgServiceConnection = (ServiceConnRef)CFDictionaryGetValue(connection_properties, kDbgConnectionPropertyServiceConnection); + + if (CFDataGetLength(data) == 0) { // close the socket on which we've got end-of-file, the lldb_socket. - CFSocketInvalidate(s); - CFRelease(s); + close_connection(connection_id); return; } int __unused sent = AMDServiceConnectionSend(dbgServiceConnection, CFDataGetBytePtr(data), CFDataGetLength (data)); assert (CFDataGetLength (data) == sent); } -void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { - CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); - - assert (callbackType == kCFSocketAcceptCallBack); - //PRINT ("callback!\n"); - - lldb_socket = CFSocketCreateWithNative(NULL, socket, kCFSocketDataCallBack, &lldb_callback, NULL); - int flag = 1; - int res = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(flag)); - if (res == -1) { - on_sys_error(@"Setting socket option failed."); - } - if (lldb_socket_runloop) { - CFRelease(lldb_socket_runloop); - } - lldb_socket_runloop = CFSocketCreateRunLoopSource(NULL, lldb_socket, 0); - CFRunLoopAddSource(CFRunLoopGetMain(), lldb_socket_runloop, kCFRunLoopCommonModes); - - CFSocketInvalidate(s); - CFRelease(s); -} - -void start_remote_debug_server(AMDeviceRef device) { - - dbgServiceConnection = NULL; +ServiceConnRef start_remote_debug_server(AMDeviceRef device) { + ServiceConnRef dbgServiceConnection = NULL; CFStringRef serviceName = CFSTR("com.apple.debugserver"); CFStringRef keys[] = { CFSTR("MinIPhoneVersion"), CFSTR("MinAppleTVVersion"), CFSTR("MinWatchVersion") }; CFStringRef values[] = { CFSTR("14.0"), CFSTR("14.0"), CFSTR("7.0") }; // Not sure about older watchOS versions @@ -1302,16 +1321,69 @@ void start_remote_debug_server(AMDeviceRef device) { disable_ssl(dbgServiceConnection); } + return dbgServiceConnection; +} + +void create_remote_debug_server_socket(CFNumberRef connection_id, AMDeviceRef device) { + CFMutableDictionaryRef connection_properties = get_connection_properties(connection_id); + + ServiceConnRef dbgServiceConnection = start_remote_debug_server(device); + CFDictionaryAddValue(connection_properties, kDbgConnectionPropertyServiceConnection, dbgServiceConnection); + /* * The debugserver connection is through a fd handle, while lldb requires a host/port to connect, so create an intermediate * socket to transfer data. */ - server_socket = CFSocketCreateWithNative (NULL, AMDServiceConnectionGetSocket(dbgServiceConnection), kCFSocketReadCallBack, &server_callback, NULL); - if (server_socket_runloop) { - CFRelease(server_socket_runloop); - } - server_socket_runloop = CFSocketCreateRunLoopSource(NULL, server_socket, 0); + CFSocketContext context = get_socket_context(connection_id); + CFSocketRef server_socket = CFSocketCreateWithNative (NULL, AMDServiceConnectionGetSocket(dbgServiceConnection), kCFSocketReadCallBack, &server_callback, &context); + CFDictionaryAddValue(connection_properties, kDbgConnectionPropertyServerSocket, server_socket); + + CFRunLoopSourceRef server_socket_runloop = CFSocketCreateRunLoopSource(NULL, server_socket, 0); CFRunLoopAddSource(CFRunLoopGetMain(), server_socket_runloop, kCFRunLoopCommonModes); + CFDictionaryAddValue(connection_properties, kDbgConnectionPropertyServerSocketRunLoop, server_socket_runloop); +} + +void create_local_lldb_socket(CFNumberRef connection_id, CFSocketNativeHandle socket) { + CFMutableDictionaryRef connection_properties = get_connection_properties(connection_id); + + CFSocketContext context = get_socket_context(connection_id); + CFSocketRef lldb_socket = CFSocketCreateWithNative(NULL, socket, kCFSocketDataCallBack, &lldb_callback, &context); + CFDictionaryAddValue(connection_properties, kDbgConnectionPropertyLLDBSocket, lldb_socket); + + int flag = 1; + int res = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(flag)); + if (res == -1) { + on_sys_error(@"Setting socket option failed."); + } + CFRunLoopSourceRef lldb_socket_runloop = CFSocketCreateRunLoopSource(NULL, lldb_socket, 0); + CFRunLoopAddSource(CFRunLoopGetMain(), lldb_socket_runloop, kCFRunLoopCommonModes); + CFDictionaryAddValue(connection_properties, kDbgConnectionPropertyLLDBSocketRunLoop, lldb_socket_runloop); +} + +void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { + static int next_connection_id = 0; + CFNumberRef connection_id = CFAutorelease(CFNumberCreate(NULL, kCFNumberIntType, &next_connection_id)); + + assert (callbackType == kCFSocketAcceptCallBack); + + if (debugserver_only) { + // In case of server mode, we start the debug server connection every time we accept a connection + create_remote_debug_server_socket(connection_id, (AMDeviceRef)info); + ++next_connection_id; + } + + CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); + create_local_lldb_socket(connection_id, socket); + + if (!debugserver_only) { + // Stop listening after first connection in case not in server mode + CFSocketInvalidate(s); + CFRelease(s); + } +} + +void start_debug_server_multiplexer(AMDeviceRef device) { + debugserver_active_connections = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); struct sockaddr_in addr4; memset(&addr4, 0, sizeof(addr4)); @@ -1320,7 +1392,8 @@ void start_remote_debug_server(AMDeviceRef device) { addr4.sin_port = htons(port); addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - CFSocketRef fdvendor = CFSocketCreate(NULL, PF_INET, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); + CFSocketContext context = { 0, device, NULL, NULL, NULL }; + CFSocketRef fdvendor = CFSocketCreate(NULL, PF_INET, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, &context); if (port) { int yes = 1; @@ -1432,15 +1505,21 @@ void setup_lldb(AMDeviceRef device, CFURLRef url) { NSLogOut(@"Starting debug of %@ connected through %@...", device_full_name, device_interface_name); mount_developer_image(device); // put debugserver on the device - start_remote_debug_server(device); // start debugserver - if (!debugserver_only) + + start_debug_server_multiplexer(device); // start debugserver proxy listener + if (debugserver_only) { + NSLogOut(@"[100%%] Listening for lldb connections"); + } + else { + create_remote_debug_server_socket(0, device); // start debugserver write_lldb_prep_cmds(device, url); // dump the necessary lldb commands into a file + NSLogOut(@"[100%%] Connecting to remote debug server"); + } + NSLogOut(@"-------------------------"); + if (url != NULL) CFRelease(url); - NSLogOut(@"[100%%] Connecting to remote debug server"); - NSLogOut(@"-------------------------"); - setpgid(getpid(), 0); signal(SIGHUP, killed); signal(SIGINT, killed); @@ -2352,19 +2431,21 @@ void check_developer_mode(AMDeviceRef device) { } #endif +ServiceConnRef symbolsServiceConnection = NULL; + void start_symbols_service_with_command(AMDeviceRef device, uint32_t command) { connect_and_start_session(device); check_error(AMDeviceSecureStartService(device, symbols_service_name, - NULL, &dbgServiceConnection)); + NULL, &symbolsServiceConnection)); - uint32_t bytes_sent = AMDServiceConnectionSend(dbgServiceConnection, &command, + uint32_t bytes_sent = AMDServiceConnectionSend(symbolsServiceConnection, &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, + uint32_t bytes_read = AMDServiceConnectionReceive(symbolsServiceConnection, &response, sizeof_uint32_t); if (bytes_read != sizeof_uint32_t) { on_error(@"Read %d bytes but was expecting %d.", bytes_read, sizeof_uint32_t); @@ -2379,7 +2460,7 @@ CFArrayRef get_dyld_file_paths(AMDeviceRef device) { CFPropertyListFormat format; CFDictionaryRef dict = NULL; uint64_t bytes_read = - AMDServiceConnectionReceiveMessage(dbgServiceConnection, &dict, &format); + AMDServiceConnectionReceiveMessage(symbolsServiceConnection, &dict, &format); if (bytes_read == -1) { on_error(@"Received %d bytes after succesfully starting command %d.", bytes_read, symbols_file_paths_command); @@ -2424,7 +2505,7 @@ void write_dyld_file(CFStringRef dest, uint64_t file_size) { // 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); + symbolsServiceConnection, map + total_bytes_read, bytes_to_download); total_bytes_read += bytes_read; uint64_t current_time = @@ -2455,13 +2536,13 @@ CFStringRef download_dyld_file(AMDeviceRef device, uint32_t dyld_index, uint32_t index = CFSwapInt32HostToBig(dyld_index); uint64_t bytes_sent = - AMDServiceConnectionSend(dbgServiceConnection, &index, sizeof_uint32_t); + AMDServiceConnectionSend(symbolsServiceConnection, &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, + uint64_t bytes_read = AMDServiceConnectionReceive(symbolsServiceConnection, &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)); @@ -2580,7 +2661,7 @@ void dyld_shared_cache_extract_dylibs(CFStringRef dsc_extractor_bundle_path, } void download_device_symbols(AMDeviceRef device) { - dbgServiceConnection = NULL; + symbolsServiceConnection = NULL; CFArrayRef files = get_dyld_file_paths(device); CFIndex files_count = CFArrayGetCount(files); NSLogOut(@"Downloading symbols files: %@", files); @@ -2628,6 +2709,7 @@ void download_device_symbols(AMDeviceRef device) { uint32_t instruments_current_message_id = 0; +ServiceConnRef instrumentsServiceConnection = NULL; NSDictionary* instruments_available_channels = nil; static const uint32 DTXMessageHeaderMagic = 0x1F3D5B79; @@ -2717,7 +2799,7 @@ void instruments_send_message(int channel, NSString* selector, const NSArray** auxillaryValues) DTXMessageHeader messageHeader; do { - uint32_t bytes_read = AMDServiceConnectionReceive(dbgServiceConnection, &messageHeader, sizeof(messageHeader)); + uint32_t bytes_read = AMDServiceConnectionReceive(instrumentsServiceConnection, &messageHeader, sizeof(messageHeader)); if (bytes_read != sizeof(messageHeader)) { on_error(@"Error communicating with instruments server: Error in reading response"); } @@ -2825,7 +2907,7 @@ void instruments_receive_message(id* returnValue, NSArray** auxillaryValues) void *buffer_current = buffer; uint32_t remaining_bytes = messageHeader.length; while (remaining_bytes > 0) { - uint32_t bytes_read = AMDServiceConnectionReceive(dbgServiceConnection, buffer_current, remaining_bytes); + uint32_t bytes_read = AMDServiceConnectionReceive(instrumentsServiceConnection, buffer_current, remaining_bytes); if (bytes_read <= 0) { on_error(@"Error communicating with instruments server: Error in reading response"); } @@ -2944,15 +3026,15 @@ void instruments_connect_service(AMDeviceRef device) { bool useSecureProxy = AMDeviceIsAtLeastVersionOnPlatform(device, version); // Start the instruments server - assert(dbgServiceConnection == NULL); + assert(instrumentsServiceConnection == NULL); CFStringRef serviceName = useSecureProxy ? CFSTR("com.apple.instruments.remoteserver.DVTSecureSocketProxy") : CFSTR("com.apple.instruments.remoteserver"); - check_error(AMDeviceSecureStartService(device, serviceName, NULL, &dbgServiceConnection)); + check_error(AMDeviceSecureStartService(device, serviceName, NULL, &instrumentsServiceConnection)); - assert(dbgServiceConnection != NULL); + assert(instrumentsServiceConnection != NULL); if (!useSecureProxy) { - disable_ssl(dbgServiceConnection); + disable_ssl(instrumentsServiceConnection); } check_error(AMDeviceStopSession(device)); @@ -3080,9 +3162,9 @@ void handle_device(AMDeviceRef device) { download_provisioning_profile(device); #if defined(IOS_DEPLOY_FEATURE_DEVELOPER_MODE) } else if (strcmp("check_developer_mode", command) == 0) { - check_developer_mode(device); + check_developer_mode(device); #endif - } + } exit(0); } @@ -3231,6 +3313,43 @@ void handle_device(AMDeviceRef device) { } } +void log_device_disconnected(AMDeviceRef device) { + CFStringRef device_interface_name = get_device_interface_name(device); + CFStringRef device_uuid = CFAutorelease(AMDeviceCopyDeviceIdentifier(device)); + + if (_json_output) { + NSLogJSON(@{@"Event": @"DeviceDisconnected", + @"Interface": (__bridge NSString *)device_interface_name, + @"Device": get_device_json_dict(device) + }); + } + else { + NSLogOut(@"[....] Disconnected %@ from %@.", device_uuid, device_interface_name); + } +} + +void handle_device_disconnected(AMDeviceRef device) { + CFStringRef device_interface_name = get_device_interface_name(device); + CFStringRef device_uuid = AMDeviceCopyDeviceIdentifier(device); + + if (detect_only) { + log_device_disconnected(device); + } + else { + NSLogVerbose(@"[....] Disconnected %@ from %@.", device_uuid, device_interface_name); + } + + if (debugserver_only) { + CFStringRef deviceCFSTR = CFAutorelease(CFStringCreateWithCString(NULL, device_id, kCFStringEncodingUTF8)); + if (CFStringCompare(deviceCFSTR, device_uuid, kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + log_device_disconnected(device); + exit(0); + } + } + + CFRelease(device_uuid); +} + void device_callback(struct am_device_notification_callback_info *info, void *arg) { switch (info->msg) { case ADNCI_MSG_CONNECTED: @@ -3249,16 +3368,7 @@ void device_callback(struct am_device_notification_callback_info *info, void *ar } case ADNCI_MSG_DISCONNECTED: { - CFStringRef device_interface_name = get_device_interface_name(info->dev); - CFStringRef device_uuid = AMDeviceCopyDeviceIdentifier(info->dev); - NSLogVerbose(@"[....] Disconnected %@ from %@.", device_uuid, device_interface_name); - if (detect_only && _json_output) { - NSLogJSON(@{@"Event": @"DeviceDisconnected", - @"Interface": (__bridge NSString *)device_interface_name, - @"Device": get_device_json_dict(info->dev) - }); - } - CFRelease(device_uuid); + handle_device_disconnected(info->dev); break; } default: