2011-09-16 16:06:56 +02:00
//TODO: find mystery segfault in write_gdb_prep_cmds -> CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url)
//TODO: don't copy/mount DeveloperDiskImage.dmg if it's already done - Xcode checks this somehow
# import <CoreFoundation / CoreFoundation.h>
# include <unistd.h>
# include <sys/socket.h>
# include <sys/un.h>
# include <stdio.h>
# include <signal.h>
# include "MobileDevice.h"
# define FDVENDOR_PATH " / tmp / fruitstrap-remote-debugserver"
# define PREP_CMDS_PATH " / tmp / fruitstrap-gdb-prep-cmds"
# define GDB_SHELL " / Developer / Platforms / iPhoneOS.platform / Developer / usr / libexec / gdb / gdb-arm-apple-darwin --arch armv7 -q -x " PREP_CMDS_PATH
// approximation of what Xcode does:
# define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\
set auto - raise - load - levels 1 \ n \
set shlib - path - substitutions / usr \ " {ds_path}/Symbols/usr \" /System \" {ds_path}/Symbols/System \" \" {device_container} \" \" {disk_container} \" \" /private{device_container} \" \" {disk_container} \" /Developer \" {ds_path}/Symbols/Developer \" \n \
set remote max - packet - size 1024 \ n \
set sharedlibrary check - uuids on \ n \
set env NSUnbufferedIO YES \ n \
set minimal - signal - handling 1 \ n \
set sharedlibrary load - rules \ \ \ " .* \\ \" \\ \" .* \\ \" container \n \
set inferior - auto - start - dyld 0 \ n \
file \ " {disk_app} \" \n \
set remote executable - directory { device_app } \ n \
set remote noack - mode 1 \ n \
set trust - readonly - sections 1 \ n \
target remote - mobile " FDVENDOR_PATH " \ n \
mem 0x1000 0x3fffffff cache \ n \
mem 0x40000000 0xffffffff none \ n \
mem 0x00000000 0x0fff none \ n \
run \ n \
set minimal - signal - handling 0 \ 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 \
set inferior - auto - start - dyld 1 " )
typedef struct am_device * AMDeviceRef ;
int AMDeviceSecureTransferPath ( int zero , AMDeviceRef device , CFURLRef url , CFDictionaryRef options , void * callback , int cbarg ) ;
int AMDeviceSecureInstallApplication ( int zero , AMDeviceRef device , CFURLRef url , CFDictionaryRef options , void * callback , int cbarg ) ;
int AMDeviceMountImage ( AMDeviceRef device , CFStringRef image , CFDictionaryRef options , void * callback , int cbarg ) ;
int AMDeviceLookupApplications ( AMDeviceRef device , int zero , CFDictionaryRef * result ) ;
bool found_device = false , debug = false ;
char * app_path = NULL ;
2011-11-04 11:04:46 +01:00
char * device_id = NULL ;
2011-09-16 16:06:56 +02:00
CFStringRef last_path = NULL ;
service_conn_t gdbfd ;
2011-09-17 09:10:52 +02:00
Boolean path_exists ( CFTypeRef path ) {
if ( CFGetTypeID ( path ) = = CFStringGetTypeID ( ) ) {
CFURLRef url = CFURLCreateWithFileSystemPath ( NULL , path , kCFURLPOSIXPathStyle , true ) ;
Boolean result = CFURLResourceIsReachable ( url , NULL ) ;
CFRelease ( url ) ;
return result ;
} else if ( CFGetTypeID ( path ) = = CFURLGetTypeID ( ) ) {
return CFURLResourceIsReachable ( path , NULL ) ;
} else {
return false ;
}
}
2011-09-16 16:06:56 +02:00
CFStringRef copy_device_support_path ( AMDeviceRef device ) {
CFStringRef version = AMDeviceCopyValue ( device , 0 , CFSTR ( " ProductVersion " ) ) ;
CFStringRef build = AMDeviceCopyValue ( device , 0 , CFSTR ( " BuildVersion " ) ) ;
CFStringRef path_with_build = CFStringCreateWithFormat ( NULL , NULL , CFSTR ( " /Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@) " ) , version , build ) ;
CFStringRef path_without_build = CFStringCreateWithFormat ( NULL , NULL , CFSTR ( " /Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ " ) , version ) ;
CFRelease ( version ) ;
CFRelease ( build ) ;
// they tack the build number on for beta builds
2011-09-17 09:10:52 +02:00
// there is almost certainly a better way of doing this
if ( path_exists ( path_with_build ) ) {
2011-09-16 16:06:56 +02:00
CFRelease ( path_without_build ) ;
return path_with_build ;
2011-09-17 09:10:52 +02:00
} else if ( path_exists ( path_without_build ) ) {
2011-09-16 16:06:56 +02:00
CFRelease ( path_with_build ) ;
return path_without_build ;
} else {
2011-09-17 09:10:52 +02:00
printf ( " [ !! ] Unable to locate DeviceSupport directory. \n " ) ;
2011-09-16 16:06:56 +02:00
exit ( 1 ) ;
}
}
void mount_callback ( CFDictionaryRef dict , int arg ) {
CFStringRef status = CFDictionaryGetValue ( dict , CFSTR ( " Status " ) ) ;
if ( CFEqual ( status , CFSTR ( " LookingUpImage " ) ) ) {
printf ( " [ 0%%] Looking up developer disk image \n " ) ;
} else if ( CFEqual ( status , CFSTR ( " CopyingImage " ) ) ) {
printf ( " [ 30%%] Copying DeveloperDiskImage.dmg to device \n " ) ;
} else if ( CFEqual ( status , CFSTR ( " MountingImage " ) ) ) {
printf ( " [ 90%%] Mounting developer disk image \n " ) ;
}
}
void mount_developer_image ( AMDeviceRef device ) {
CFStringRef ds_path = copy_device_support_path ( device ) ;
CFStringRef image_path = CFStringCreateWithFormat ( NULL , NULL , CFSTR ( " %@/DeveloperDiskImage.dmg " ) , ds_path ) ;
CFStringRef sig_path = CFStringCreateWithFormat ( NULL , NULL , CFSTR ( " %@/DeveloperDiskImage.dmg.signature " ) , ds_path ) ;
CFRelease ( ds_path ) ;
FILE * sig = fopen ( CFStringGetCStringPtr ( sig_path , kCFStringEncodingMacRoman ) , " rb " ) ;
void * sig_buf = malloc ( 128 ) ;
assert ( fread ( sig_buf , 1 , 128 , sig ) = = 128 ) ;
fclose ( sig ) ;
CFDataRef sig_data = CFDataCreateWithBytesNoCopy ( NULL , sig_buf , 128 , NULL ) ;
CFRelease ( sig_path ) ;
CFTypeRef keys [ ] = { CFSTR ( " ImageSignature " ) , CFSTR ( " ImageType " ) } ;
CFTypeRef values [ ] = { sig_data , CFSTR ( " Developer " ) } ;
CFDictionaryRef options = CFDictionaryCreate ( NULL , ( const void * * ) & keys , ( const void * * ) & values , 2 , & kCFTypeDictionaryKeyCallBacks , & kCFTypeDictionaryValueCallBacks ) ;
CFRelease ( sig_data ) ;
int result = AMDeviceMountImage ( device , image_path , options , & mount_callback , 0 ) ;
if ( result = = 0 | | result = = 0xe8000076 /* already mounted */ ) {
printf ( " [ 95%%] Developer disk image mounted successfully \n " ) ;
} else {
printf ( " [ !! ] Unable to mount developer disk image. (%x) \n " , result ) ;
exit ( 1 ) ;
}
CFRelease ( image_path ) ;
CFRelease ( options ) ;
}
void transfer_callback ( CFDictionaryRef dict , int arg ) {
int percent ;
CFStringRef status = CFDictionaryGetValue ( dict , CFSTR ( " Status " ) ) ;
CFNumberGetValue ( CFDictionaryGetValue ( dict , CFSTR ( " PercentComplete " ) ) , kCFNumberSInt32Type , & percent ) ;
if ( CFEqual ( status , CFSTR ( " CopyingFile " ) ) ) {
CFStringRef path = CFDictionaryGetValue ( dict , CFSTR ( " Path " ) ) ;
if ( ( last_path = = NULL | | ! CFEqual ( path , last_path ) ) & & ! CFStringHasSuffix ( path , CFSTR ( " .ipa " ) ) ) {
printf ( " [%3d%%] Copying %s to device \n " , percent / 2 , CFStringGetCStringPtr ( path , kCFStringEncodingMacRoman ) ) ;
}
if ( last_path ! = NULL ) {
CFRelease ( last_path ) ;
}
last_path = CFStringCreateCopy ( NULL , path ) ;
}
}
void install_callback ( CFDictionaryRef dict , int arg ) {
int percent ;
CFStringRef status = CFDictionaryGetValue ( dict , CFSTR ( " Status " ) ) ;
CFNumberGetValue ( CFDictionaryGetValue ( dict , CFSTR ( " PercentComplete " ) ) , kCFNumberSInt32Type , & percent ) ;
printf ( " [%3d%%] %s \n " , ( percent / 2 ) + 50 , CFStringGetCStringPtr ( status , kCFStringEncodingMacRoman ) ) ;
}
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 ) ) = gdbfd ;
sendmsg ( socket , & message , 0 ) ;
CFSocketInvalidate ( s ) ;
CFRelease ( s ) ;
}
CFURLRef copy_device_app_url ( AMDeviceRef device , CFStringRef identifier ) {
CFDictionaryRef result ;
assert ( AMDeviceLookupApplications ( device , 0 , & result ) = = 0 ) ;
CFDictionaryRef app_dict = CFDictionaryGetValue ( result , identifier ) ;
assert ( app_dict ! = NULL ) ;
CFStringRef app_path = CFDictionaryGetValue ( app_dict , CFSTR ( " Path " ) ) ;
assert ( app_path ! = NULL ) ;
CFURLRef url = CFURLCreateWithFileSystemPath ( NULL , app_path , kCFURLPOSIXPathStyle , true ) ;
CFRelease ( result ) ;
return url ;
}
CFStringRef copy_disk_app_identifier ( CFURLRef disk_app_url ) {
CFURLRef plist_url = CFURLCreateCopyAppendingPathComponent ( NULL , disk_app_url , CFSTR ( " Info.plist " ) , false ) ;
CFReadStreamRef plist_stream = CFReadStreamCreateWithFile ( NULL , plist_url ) ;
CFReadStreamOpen ( plist_stream ) ;
CFPropertyListRef plist = CFPropertyListCreateWithStream ( NULL , plist_stream , 0 , kCFPropertyListImmutable , NULL , NULL ) ;
CFStringRef bundle_identifier = CFRetain ( CFDictionaryGetValue ( plist , CFSTR ( " CFBundleIdentifier " ) ) ) ;
CFReadStreamClose ( plist_stream ) ;
CFRelease ( plist_url ) ;
CFRelease ( plist_stream ) ;
CFRelease ( plist ) ;
return bundle_identifier ;
}
void write_gdb_prep_cmds ( AMDeviceRef device , CFURLRef disk_app_url ) {
CFMutableStringRef cmds = CFStringCreateMutableCopy ( NULL , 0 , GDB_PREP_CMDS ) ;
CFRange range = { 0 , CFStringGetLength ( cmds ) } ;
CFStringRef ds_path = copy_device_support_path ( device ) ;
CFStringFindAndReplace ( cmds , CFSTR ( " {ds_path} " ) , ds_path , range , 0 ) ;
CFStringRef bundle_identifier = copy_disk_app_identifier ( disk_app_url ) ;
CFURLRef device_app_url = copy_device_app_url ( device , bundle_identifier ) ;
CFStringRef device_app_path = CFURLCopyFileSystemPath ( device_app_url , kCFURLPOSIXPathStyle ) ;
CFStringFindAndReplace ( cmds , CFSTR ( " {device_app} " ) , device_app_path , range , 0 ) ;
CFStringRef disk_app_path = CFURLCopyFileSystemPath ( disk_app_url , kCFURLPOSIXPathStyle ) ;
CFStringFindAndReplace ( cmds , CFSTR ( " {disk_app} " ) , disk_app_path , range , 0 ) ;
CFURLRef device_container_url = CFURLCreateCopyDeletingLastPathComponent ( NULL , device_app_url ) ;
CFStringRef device_container_path = CFURLCopyFileSystemPath ( device_container_url , kCFURLPOSIXPathStyle ) ;
CFMutableStringRef dcp_noprivate = CFStringCreateMutableCopy ( NULL , 0 , device_container_path ) ;
CFStringFindAndReplace ( dcp_noprivate , CFSTR ( " /private/var/ " ) , CFSTR ( " /var/ " ) , range , 0 ) ;
CFStringFindAndReplace ( cmds , CFSTR ( " {device_container} " ) , dcp_noprivate , range , 0 ) ;
CFURLRef disk_container_url = CFURLCreateCopyDeletingLastPathComponent ( NULL , disk_app_url ) ;
CFStringRef disk_container_path = CFURLCopyFileSystemPath ( disk_container_url , kCFURLPOSIXPathStyle ) ;
CFStringFindAndReplace ( cmds , CFSTR ( " {disk_container} " ) , disk_container_path , range , 0 ) ;
CFDataRef cmds_data = CFStringCreateExternalRepresentation ( NULL , cmds , kCFStringEncodingASCII , 0 ) ;
FILE * out = fopen ( PREP_CMDS_PATH , " w " ) ;
fwrite ( CFDataGetBytePtr ( cmds_data ) , CFDataGetLength ( cmds_data ) , 1 , out ) ;
fclose ( out ) ;
CFRelease ( cmds ) ;
2011-09-17 09:10:52 +02:00
if ( ds_path ! = NULL ) CFRelease ( ds_path ) ;
2011-09-16 16:06:56 +02:00
CFRelease ( bundle_identifier ) ;
CFRelease ( device_app_url ) ;
CFRelease ( device_app_path ) ;
CFRelease ( disk_app_path ) ;
CFRelease ( device_container_url ) ;
CFRelease ( device_container_path ) ;
CFRelease ( dcp_noprivate ) ;
CFRelease ( disk_container_url ) ;
CFRelease ( disk_container_path ) ;
CFRelease ( cmds_data ) ;
}
void start_remote_debug_server ( AMDeviceRef device ) {
assert ( AMDeviceStartService ( device , CFSTR ( " com.apple.debugserver " ) , & gdbfd , NULL ) = = 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 , FDVENDOR_PATH ) ;
CFDataRef address_data = CFDataCreate ( NULL , ( const UInt8 * ) & address , sizeof ( address ) ) ;
unlink ( FDVENDOR_PATH ) ;
CFSocketSetAddress ( fdvendor , address_data ) ;
CFRelease ( address_data ) ;
CFRunLoopAddSource ( CFRunLoopGetMain ( ) , CFSocketCreateRunLoopSource ( NULL , fdvendor , 0 ) , kCFRunLoopCommonModes ) ;
}
void handle_device ( AMDeviceRef device ) {
if ( found_device ) return ; // handle one device only
2011-11-04 11:04:46 +01:00
CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier ( device ) ;
if ( device_id ! = NULL ) {
if ( strcmp ( device_id , CFStringGetCStringPtr ( found_device_id , CFStringGetSystemEncoding ( ) ) ) = = 0 ) {
found_device = true ;
} else {
return ;
}
} else {
found_device = true ;
}
2011-09-16 16:06:56 +02:00
CFRetain ( device ) ; // don't know if this is necessary?
2011-11-04 11:04:46 +01:00
printf ( " [ 0%%] Found device (%s), beginning install \n " , CFStringGetCStringPtr ( found_device_id , CFStringGetSystemEncoding ( ) ) ) ;
2011-09-16 16:06:56 +02:00
AMDeviceConnect ( device ) ;
assert ( AMDeviceIsPaired ( device ) ) ;
assert ( AMDeviceValidatePairing ( device ) = = 0 ) ;
assert ( AMDeviceStartSession ( device ) = = 0 ) ;
CFStringRef path = CFStringCreateWithCString ( NULL , app_path , kCFStringEncodingASCII ) ;
CFURLRef relative_url = CFURLCreateWithFileSystemPath ( NULL , path , kCFURLPOSIXPathStyle , false ) ;
CFURLRef url = CFURLCopyAbsoluteURL ( relative_url ) ;
CFRelease ( path ) ;
CFRelease ( relative_url ) ;
CFStringRef keys [ ] = { CFSTR ( " PackageType " ) } ;
CFStringRef values [ ] = { CFSTR ( " Developer " ) } ;
CFDictionaryRef options = CFDictionaryCreate ( NULL , ( const void * * ) & keys , ( const void * * ) & values , 1 , & kCFTypeDictionaryKeyCallBacks , & kCFTypeDictionaryValueCallBacks ) ;
mach_error_t transfer_error = AMDeviceSecureTransferPath ( 0 , device , url , options , & transfer_callback , 0 ) ;
if ( transfer_error ) {
printf ( " [ !! ] Unable to transfer package to device. (%x) \n " , transfer_error ) ;
exit ( 1 ) ;
}
mach_error_t install_error = AMDeviceSecureInstallApplication ( 0 , device , url , options , & install_callback , 0 ) ;
if ( install_error ) {
printf ( " [ !! ] Unable to install package. (%x) \n " , install_error ) ;
exit ( 1 ) ;
}
CFRelease ( options ) ;
printf ( " [100%%] Installed package %s \n " , app_path ) ;
if ( ! debug ) exit ( 0 ) ; // no debug phase
printf ( " ------ Debug phase ------ \n " ) ;
mount_developer_image ( device ) ; // put debugserver on the device
start_remote_debug_server ( device ) ; // start debugserver
write_gdb_prep_cmds ( device , url ) ; // dump the necessary gdb commands into a file
CFRelease ( url ) ;
printf ( " [100%%] Connecting to remote debug server \n " ) ;
printf ( " ------------------------- \n " ) ;
pid_t parent = getpid ( ) ;
int pid = fork ( ) ;
if ( pid = = 0 ) {
system ( GDB_SHELL ) ; // launch gdb
kill ( parent , SIGTERM ) ; // "No. I am your father."
_exit ( 0 ) ;
}
}
void device_callback ( struct am_device_notification_callback_info * info , void * arg ) {
switch ( info - > msg ) {
case ADNCI_MSG_CONNECTED :
handle_device ( info - > dev ) ;
default :
break ;
}
}
int main ( int argc , char * argv [ ] ) {
if ( argc < 2 | | argc > 3 ) {
2011-11-04 11:04:46 +01:00
printf ( " usage: %s [-d] <app> [device_id] \n " , argv [ 0 ] ) ;
2011-09-16 16:06:56 +02:00
exit ( 1 ) ;
}
2011-11-04 11:04:46 +01:00
2011-09-16 16:06:56 +02:00
if ( strcmp ( argv [ 1 ] , " -d " ) = = 0 ) {
2011-11-04 11:04:46 +01:00
assert ( argc = = 3 | | argc = = 4 ) ;
2011-09-16 16:06:56 +02:00
debug = true ;
app_path = argv [ 2 ] ;
2011-11-04 11:04:46 +01:00
if ( argc = = 4 ) {
device_id = argv [ 3 ] ;
}
2011-09-16 16:06:56 +02:00
printf ( " ------ Install phase ------ \n " ) ;
} else {
2011-11-04 11:04:46 +01:00
assert ( argc = = 2 | | argc = = 3 ) ;
2011-09-16 16:06:56 +02:00
app_path = argv [ 1 ] ;
2011-11-04 11:04:46 +01:00
if ( argc = = 3 ) {
device_id = argv [ 2 ] ;
}
2011-09-16 16:06:56 +02:00
}
assert ( access ( app_path , F_OK ) = = 0 ) ;
AMDSetLogLevel ( 5 ) ; // otherwise syslog gets flooded with crap
printf ( " [....] Waiting for iOS device to be connected \n " ) ;
struct am_device_notification * notify ;
AMDeviceNotificationSubscribe ( & device_callback , 0 , 0 , NULL , & notify ) ;
CFRunLoopRun ( ) ;
}