29 Commits
1.0.5 ... 1.0.9

Author SHA1 Message Date
Shazron Abdullah
5f90ebb32b Fixes #50 - Publish 1.0.9 version 2014-08-01 15:20:24 -07:00
Shazron Abdullah
54a62bfdd0 Fixes #49 - Update README with new help text #49 2014-07-31 17:49:44 -07:00
Alexandre Gomes
3799a8a535 Implements #48 - Wifi Support & Checks
Signed-off-by: Shazron Abdullah <shazron@gmail.com>
2014-07-31 12:42:54 -07:00
Neo Alienson
e466aeac66 Implements #42 - Merge file listing feature from phildrip/fruitstrap.
Signed-off-by: Shazron Abdullah <shazron@gmail.com>
2014-07-30 16:26:04 -07:00
Shazron Abdullah
ffe40a0fac Merge pull request #43 from black-square/master
--args command supports arguments with spaces
2014-07-29 16:54:03 -07:00
Dmitriy Shesterkin
c5d206b72c Added an ability to pass arguments with space character to the application by using the command "--args":
./ios-deploy  ...  --args "\"TEST_FILTER=some text here\" arg2 arg3"
2014-06-27 14:53:57 -03:00
Shazron Abdullah
6fc2ee5e38 Updated npm version to 1.0.8 2014-05-12 18:09:05 -07:00
Shazron Abdullah
443abe4d38 Updated version to 1.0.8 2014-05-12 18:05:45 -07:00
Shazron Abdullah
42a7dbcc1f Updated README for --uninstall option. 2014-05-09 16:57:46 -07:00
Shazron Abdullah
4f9afb7499 Update version to 1.0.7 2014-05-09 16:53:41 -07:00
Shazron Abdullah
f7283216ef Merge pull request #35 from wjywbs/remove-app
Fixes #35 - Add uninstall feature.
2014-05-09 16:51:37 -07:00
wjywbs
cfb086ee44 Add uninstall feature. 2014-05-09 16:02:51 -04:00
Shazron Abdullah
9e52fd16d7 Added CONTRIBUTING file 2014-05-01 23:11:25 -07:00
Shazron Abdullah
6c42fd3cf9 Merge pull request #33 from Unity-Technologies/master
Fix for lldb's "^D" and "quit" spam when running without a terminal.
2014-04-29 17:35:50 -07:00
Julius Trinkunas
3ee1bca513 Stop lldb's "^D" and "quit" spam in a non terminal environment. 2014-04-29 14:33:00 +03:00
Julius Trinkunas
0a9418a564 Add friendly message for code-signing check fails. 2014-04-29 14:33:00 +03:00
Shazron Abdullah
72e83d06e1 Merge pull request #27 from oNaiPs/better_sdk_makefile
Better sdk makefile
2014-04-03 12:35:22 -07:00
Shazron Abdullah
bab1d5bded Merge pull request #26 from oNaiPs/fix_402653028_error
Added version to Info.plist file, fixes -402653028 error
2014-04-03 12:34:40 -07:00
Jose Pereira
f0d5d58a8b Separated sdk version from full path, easier to set after updates 2014-04-03 11:45:57 -07:00
Jose Pereira
8fc098a877 Added version to Info.plist file, fixes -402653028 error 2014-04-03 11:42:57 -07:00
Shazron Abdullah
bad5fe321b Merge pull request #25 from Unity-Technologies/master
Fix exiting and propagate app exit code in non interactive mode.
2014-04-02 10:35:05 -07:00
Julius Trinkunas
79c4cc5c6a Fix exiting and propagate app exit code in non interactive mode. 2014-04-01 16:20:14 +03:00
Shazron Abdullah
f54f67c418 Added the two new flags from 1.0.6 to the README 2014-03-27 15:32:19 -07:00
Shazron Abdullah
f1a2c40566 Increment version to 1.0.6 2014-03-27 15:11:35 -07:00
Shazron Abdullah
c92682d148 Merge pull request #22 from Unity-Technologies/master
Add non interactive mode.
2014-03-27 12:35:25 -07:00
Julius Trinkunas
08f3b92306 Fix hangup when launching from scripts. 2014-03-27 16:14:14 +02:00
Julius Trinkunas
a4f3c2c84b Add non interactive mode. 2014-03-26 19:13:48 +02:00
Julius Trinkunas
a3a99e6553 Remove python wrapper shell. 2014-03-26 18:59:01 +02:00
wjywbs
7f2231bd39 Read port number in command line to debug app in multiple devices. 2014-03-24 14:37:03 -07:00
8 changed files with 709 additions and 80 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
demo
demo.app
ios-deploy
ios-deploy.dSYM
/.DS_Store
*~

31
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,31 @@
## Contributing to ios-deploy
Github url:
https://github.com/phonegap/ios-deploy
Git clone url:
https://github.com/phonegap/ios-deploy.git
## Filing an issue
Please run the commands below in your Terminal.app and include it in the issue:
```
1. sw_vers -productVersion
2. ios-deploy -V
3. xcodebuild -version
4. xcode-select --print-path
5. gcc --version
6. lldb --version
```
Also include **command line arguments** you used for ios-deploy.
## Sending a Pull Request
Please **create a topic branch** for your issue before submitting your pull request. You will be asked to re-submit if your pull request contains unrelated commits.
Please elaborate regarding the problem the pull request is supposed to solve, and perhaps also link to any relevant issues the pull request is trying to fix.

View File

@@ -10,6 +10,8 @@
</array>
<key>CFBundleExecutable</key>
<string>demo</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleIdentifier</key>
<string>demo</string>
<key>CFBundleResourceSpecification</key>

View File

@@ -1,6 +1,8 @@
IOS_SDK_VERSION = 7.1
IOS_CC = gcc -ObjC
IOS_SDK = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk
DEVICE_SUPPORT = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/DeviceSupport
IOS_SDK = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(IOS_SDK_VERSION).sdk
all: clean ios-deploy

View File

@@ -453,6 +453,8 @@ typedef int (*am_device_install_application_callback)(CFDictionaryRef, int);
mach_error_t AMDeviceInstallApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callback, void *user);
mach_error_t AMDeviceTransferApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callbackj, void *user);
int AMDeviceSecureUninstallApplication(int unknown0, struct am_device *device, CFStringRef bundle_id, int unknown1, void *callback, int callback_arg);
/* ----------------------------------------------------------------------------
* Semi-private routines
* ------------------------------------------------------------------------- */

View File

@@ -10,7 +10,7 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro
## Usage
./ios-deploy [OPTION]...
Usage: ./ios-deploy [OPTION]...
-d, --debug launch the app in GDB after installation
-i, --id <device_id> the id of the device to connect to
-c, --detect only detect if the device is connected
@@ -21,8 +21,15 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro
-g, --gdbargs <args> extra arguments to pass to GDB when starting the debugger
-x, --gdbexec <file> GDB commands script file
-n, --nostart do not start the app when debugging
-I, --noninteractive start in non interactive mode (quit when app crashes or exits)
-v, --verbose enable verbose output
-m, --noinstall directly start debugging without app install (-d not required)
-p, --port <number> port used for device, default: 12345
-r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared)
-1, --bundle_id <bundle id> specify bundle id for list and upload
-l, --list list files
-o, --upload <file> upload file
-2, --to <target pathname> use together with upload file. specify target for upload
-V, --version print the executable version
## Demo

View File

@@ -16,9 +16,9 @@
#include <netinet/tcp.h>
#include "MobileDevice.h"
#define APP_VERSION "1.0.5"
#define APP_VERSION "1.0.9"
#define PREP_CMDS_PATH "/tmp/fruitstrap-lldb-prep-cmds-"
#define LLDB_SHELL "python -u -c $'import time\ntime.sleep(1.0)\n%swhile True: time.sleep(0.1); cmd = raw_input(); print (cmd)' | lldb -s " PREP_CMDS_PATH
#define LLDB_SHELL "lldb -s " PREP_CMDS_PATH
/*
* Startup script passed to lldb.
@@ -30,11 +30,25 @@
platform select remote-ios --sysroot {symbols_path}\n\
target create \"{disk_app}\"\n\
script fruitstrap_device_app=\"{device_app}\"\n\
script fruitstrap_connect_url=\"connect://127.0.0.1:12345\"\n\
script fruitstrap_handle_command=\"command script add -s asynchronous -f {python_command}.fsrun_command run\"\n\
script fruitstrap_connect_url=\"connect://127.0.0.1:{device_port}\"\n\
command script import \"{python_file_path}\"\n\
command script add -f {python_command}.connect_command connect\n\
command script add -s asynchronous -f {python_command}.run_command run\n\
command script add -s asynchronous -f {python_command}.autoexit_command autoexit\n\
connect\n\
")
const char* lldb_prep_no_cmds = "";
const char* lldb_prep_interactive_cmds = "\
run\n\
";
const char* lldb_prep_noninteractive_cmds = "\
run\n\
autoexit\n\
";
/*
* Some things do not seem to work when using the normal commands like process connect/launch, so we invoke them
* through the python interface. Also, Launch () doesn't seem to work when ran from init_module (), so we add
@@ -42,37 +56,103 @@
*/
#define LLDB_FRUITSTRAP_MODULE CFSTR("\
import lldb\n\
import sys\n\
import shlex\n\
\n\
def __lldb_init_module(debugger, internal_dict):\n\
def connect_command(debugger, command, result, internal_dict):\n\
# These two are passed in by the script which loads us\n\
device_app=internal_dict['fruitstrap_device_app']\n\
connect_url=internal_dict['fruitstrap_connect_url']\n\
handle_command = internal_dict['fruitstrap_handle_command']\n\
lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n\
lldb.debugger.HandleCommand(handle_command)\n\
error=lldb.SBError()\n\
lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(),connect_url,None,error)\n\
connect_url = internal_dict['fruitstrap_connect_url']\n\
error = lldb.SBError()\n\
\n\
def fsrun_command(debugger, command, result, internal_dict):\n\
error=lldb.SBError()\n\
lldb.target.Launch(lldb.SBLaunchInfo(['{args}']),error)\n\
process = lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(), connect_url, None, error)\n\
\n\
# Wait for connection to succeed\n\
listener = lldb.target.GetDebugger().GetListener()\n\
listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged)\n\
events = []\n\
state = lldb.eStateInvalid\n\
while state != lldb.eStateConnected:\n\
event = lldb.SBEvent()\n\
if listener.WaitForEvent(1, event):\n\
state = process.GetStateFromEvent(event)\n\
events.append(event)\n\
else:\n\
state = lldb.eStateInvalid\n\
\n\
# Add events back to queue, otherwise lldb freezes\n\
for event in events:\n\
listener.AddEvent(event)\n\
\n\
def run_command(debugger, command, result, internal_dict):\n\
device_app = internal_dict['fruitstrap_device_app']\n\
error = lldb.SBError()\n\
lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n\
lldb.target.Launch(lldb.SBLaunchInfo(shlex.split('{args}')), error)\n\
print str(error)\n\
\n\
def autoexit_command(debugger, command, result, internal_dict):\n\
process = lldb.target.process\n\
listener = debugger.GetListener()\n\
listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n\
event = lldb.SBEvent()\n\
while True:\n\
if listener.WaitForEvent(1, event):\n\
state = process.GetStateFromEvent(event)\n\
else:\n\
state = lldb.eStateInvalid\n\
\n\
stdout = process.GetSTDOUT(1024)\n\
while stdout:\n\
sys.stdout.write(stdout)\n\
stdout = process.GetSTDOUT(1024)\n\
\n\
stderr = process.GetSTDERR(1024)\n\
while stderr:\n\
sys.stdout.write(stderr)\n\
stderr = process.GetSTDERR(1024)\n\
\n\
if lldb.SBProcess.EventIsProcessEvent(event):\n\
if state == lldb.eStateExited:\n\
sys.exit(process.GetExitStatus())\n\
if state == lldb.eStateStopped:\n\
debugger.HandleCommand('frame select')\n\
debugger.HandleCommand('bt')\n\
sys.exit({exitcode_app_crash})\n\
")
typedef struct am_device * AMDeviceRef;
mach_error_t AMDeviceSecureStartService(struct am_device *device, CFStringRef service_name, unsigned int *unknown, service_conn_t *handle);
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);
mach_error_t AMDeviceLookupApplications(AMDeviceRef device, CFDictionaryRef options, CFDictionaryRef *result);
int AMDeviceGetInterfaceType(struct am_device *device);
bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, detect_only = false, install = true;
bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, detect_only = false, install = true, uninstall = false;
bool command_only = false;
char *command = NULL;
char *target_filename = NULL;
char *upload_pathname = NULL;
char *bundle_id = NULL;
bool interactive = true;
char *app_path = NULL;
char *device_id = NULL;
char *args = NULL;
int timeout = 0;
int port = 12345;
CFStringRef last_path = NULL;
service_conn_t gdbfd;
pid_t parent = 0;
// PID of child process running lldb
pid_t child = 0;
// Signal sent from child to parent process when LLDB finishes.
const int SIGLLDB = SIGUSR1;
AMDeviceRef best_device_match = NULL;
// Error codes we report on different failures, so scripts can distinguish between user app exit
// codes and our exit codes. For non app errors we use codes in reserved 128-255 range.
const int exitcode_error = 253;
const int exitcode_app_crash = 254;
Boolean path_exists(CFTypeRef path) {
if (CFGetTypeID(path) == CFStringGetTypeID()) {
@@ -105,7 +185,7 @@ CFStringRef find_path(CFStringRef rootPath, CFStringRef namePattern, CFStringRef
if (!(fpipe = (FILE *)popen(command, "r")))
{
perror("Error encountered while opening pipe");
exit(EXIT_FAILURE);
exit(exitcode_error);
}
char buffer[256] = { '\0' };
@@ -130,7 +210,7 @@ CFStringRef copy_xcode_dev_path() {
if (!(fpipe = (FILE *)popen(command, "r")))
{
perror("Error encountered while opening pipe");
exit(EXIT_FAILURE);
exit(exitcode_error);
}
char buffer[256] = { '\0' };
@@ -191,6 +271,125 @@ CFStringRef copy_xcode_path_for(CFStringRef subPath, CFStringRef search) {
}
}
// Please ensure that device is connected or the name will be unknown
const CFStringRef get_device_hardware_name(const AMDeviceRef device) {
CFStringRef model = AMDeviceCopyValue(device, 0, CFSTR("HardwareModel"));
const char *hwmodel = CFStringGetCStringPtr(model, CFStringGetSystemEncoding());
if (hwmodel && !strcmp("M68AP", hwmodel))
return CFSTR("iPhone");
if (hwmodel && !strcmp("N45AP", hwmodel))
return CFSTR("iPod touch");
if (hwmodel && !strcmp("N82AP", hwmodel))
return CFSTR("iPhone 3G");
if (hwmodel && !strcmp("N72AP", hwmodel))
return CFSTR("iPod touch 2G");
if (hwmodel && !strcmp("N88AP", hwmodel))
return CFSTR("iPhone 3GS");
if (hwmodel && !strcmp("N18AP", hwmodel))
return CFSTR("iPod touch 3G");
if (hwmodel && !strcmp("K48AP", hwmodel))
return CFSTR("iPad");
if (hwmodel && !strcmp("N90AP", hwmodel))
return CFSTR("iPhone 4 (GSM)");
if (hwmodel && !strcmp("N81AP", hwmodel))
return CFSTR("iPod touch 4G");
if (hwmodel && !strcmp("K66AP", hwmodel))
return CFSTR("Apple TV 2G");
if (hwmodel && !strcmp("N92AP", hwmodel))
return CFSTR("iPhone 4 (CDMA)");
if (hwmodel && !strcmp("N90BAP", hwmodel))
return CFSTR("iPhone 4 (GSM, revision A)");
if (hwmodel && !strcmp("K93AP", hwmodel))
return CFSTR("iPad 2");
if (hwmodel && !strcmp("K94AP", hwmodel))
return CFSTR("iPad 2 (GSM)");
if (hwmodel && !strcmp("K95AP", hwmodel))
return CFSTR("iPad 2 (CDMA)");
if (hwmodel && !strcmp("K93AAP", hwmodel))
return CFSTR("iPad 2 (Wi-Fi, revision A)");
if (hwmodel && !strcmp("P105AP", hwmodel))
return CFSTR("iPad mini");
if (hwmodel && !strcmp("P106AP", hwmodel))
return CFSTR("iPad mini (GSM)");
if (hwmodel && !strcmp("P107AP", hwmodel))
return CFSTR("iPad mini (CDMA)");
if (hwmodel && !strcmp("N94AP", hwmodel))
return CFSTR("iPhone 4S");
if (hwmodel && !strcmp("N41AP", hwmodel))
return CFSTR("iPhone 5 (GSM)");
if (hwmodel && !strcmp("N42AP", hwmodel))
return CFSTR("iPhone 5 (Global/CDMA)");
if (hwmodel && !strcmp("N48AP", hwmodel))
return CFSTR("iPhone 5c (GSM)");
if (hwmodel && !strcmp("N49AP", hwmodel))
return CFSTR("iPhone 5c (Global/CDMA)");
if (hwmodel && !strcmp("N51AP", hwmodel))
return CFSTR("iPhone 5s (GSM)");
if (hwmodel && !strcmp("N53AP", hwmodel))
return CFSTR("iPhone 5s (Global/CDMA)");
if (hwmodel && !strcmp("J1AP", hwmodel))
return CFSTR("iPad 3");
if (hwmodel && !strcmp("J2AP", hwmodel))
return CFSTR("iPad 3 (GSM)");
if (hwmodel && !strcmp("J2AAP", hwmodel))
return CFSTR("iPad 3 (CDMA)");
if (hwmodel && !strcmp("P101AP", hwmodel))
return CFSTR("iPad 4");
if (hwmodel && !strcmp("P102AP", hwmodel))
return CFSTR("iPad 4 (GSM)");
if (hwmodel && !strcmp("P103AP", hwmodel))
return CFSTR("iPad 4 (CDMA)");
if (hwmodel && !strcmp("N78AP", hwmodel))
return CFSTR("iPod touch 5G");
if (hwmodel && !strcmp("J33AP", hwmodel))
return CFSTR("Apple TV 3G");
if (hwmodel && !strcmp("J33IAP", hwmodel))
return CFSTR("Apple TV 3.1G");
return CFSTR("Unknown Device");
}
CFStringRef get_device_full_name(const AMDeviceRef device) {
CFStringRef full_name = NULL,
device_udid = AMDeviceCopyDeviceIdentifier(device),
device_name = NULL,
model_name = NULL;
AMDeviceConnect(device);
device_name = AMDeviceCopyValue(device, 0, CFSTR("DeviceName")),
model_name = get_device_hardware_name(device);
if(device_name != NULL && model_name != NULL)
full_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ '%@' (%@)"), model_name, device_name, device_udid);
else
full_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("(%@)"), device_udid);
AMDeviceDisconnect(device);
if(device_udid != NULL)
CFRelease(device_udid);
if(device_name != NULL)
CFRelease(device_name);
if(model_name != NULL)
CFRelease(model_name);
return full_name;
}
CFStringRef get_device_interface_name(const AMDeviceRef device) {
// AMDeviceGetInterfaceType(device) 0=Unknown, 1 = Direct/USB, 2 = Indirect/WIFI
switch(AMDeviceGetInterfaceType(device)) {
case 1:
return CFSTR("USB");
case 2:
return CFSTR("WIFI");
default:
return CFSTR("Unknown Connection");
}
}
CFMutableArrayRef get_device_product_version_parts(AMDeviceRef device) {
CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion"));
CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, version, CFSTR("."));
@@ -236,7 +435,7 @@ CFStringRef copy_device_support_path(AMDeviceRef device) {
if (path == NULL)
{
printf("[ !! ] Unable to locate DeviceSupport directory.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n");
exit(1);
exit(exitcode_error);
}
return path;
@@ -261,7 +460,7 @@ CFStringRef copy_developer_disk_image_path(CFStringRef deviceSupportPath) {
if (path == NULL)
{
printf("[ !! ] Unable to locate DeveloperDiskImage.dmg.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n");
exit(1);
exit(exitcode_error);
}
return path;
@@ -309,7 +508,7 @@ void mount_developer_image(AMDeviceRef device) {
printf("[ 95%%] Developer disk image already mounted\n");
} else {
printf("[ !! ] Unable to mount developer disk image. (%x)\n", result);
exit(1);
exit(exitcode_error);
}
CFRelease(image_path);
@@ -420,18 +619,17 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) {
range.length = CFStringGetLength(cmds);
CFMutableStringRef pmodule = CFStringCreateMutableCopy(NULL, 0, LLDB_FRUITSTRAP_MODULE);
CFRange rangeLLDB = { 0, CFStringGetLength(pmodule) };
CFStringRef exitcode_app_crash_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), exitcode_app_crash);
CFStringFindAndReplace(pmodule, CFSTR("{exitcode_app_crash}"), exitcode_app_crash_str, rangeLLDB, 0);
rangeLLDB.length = CFStringGetLength(pmodule);
if (args) {
CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingASCII);
CFStringFindAndReplace(cmds, CFSTR("{args}"), cf_args, range, 0);
//format the arguments 'arg1 arg2 ....' to an argument list ['arg1', 'arg2', ...]
CFMutableStringRef argsListLLDB = CFStringCreateMutableCopy(NULL, 0, cf_args);
CFRange rangeLLDB = { 0, CFStringGetLength(argsListLLDB) };
CFStringFindAndReplace(argsListLLDB, CFSTR(" "), CFSTR(" "), rangeLLDB, 0);//remove multiple spaces
rangeLLDB.length = CFStringGetLength(argsListLLDB);
CFStringFindAndReplace(argsListLLDB, CFSTR(" "), CFSTR("', '"), rangeLLDB, 0);
rangeLLDB.length = CFStringGetLength(pmodule);
CFStringFindAndReplace(pmodule, CFSTR("{args}"), argsListLLDB, rangeLLDB, 0);
CFStringFindAndReplace(pmodule, CFSTR("{args}"), cf_args, rangeLLDB, 0);
CFRelease(cf_args);
} else {
@@ -449,6 +647,10 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) {
CFStringFindAndReplace(cmds, CFSTR("{disk_app}"), disk_app_path, range, 0);
range.length = CFStringGetLength(cmds);
CFStringRef device_port = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), port);
CFStringFindAndReplace(cmds, CFSTR("{device_port}"), device_port, range, 0);
range.length = CFStringGetLength(cmds);
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);
@@ -483,6 +685,15 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) {
strcat(prep_cmds_path, device_id);
FILE *out = fopen(prep_cmds_path, "w");
fwrite(CFDataGetBytePtr(cmds_data), CFDataGetLength(cmds_data), 1, out);
// Write additional commands based on mode we're running in
const char* extra_cmds;
if (!interactive)
extra_cmds = lldb_prep_noninteractive_cmds;
else if (nostart)
extra_cmds = lldb_prep_no_cmds;
else
extra_cmds = lldb_prep_interactive_cmds;
fwrite(extra_cmds, strlen(extra_cmds), 1, out);
fclose(out);
CFDataRef pmodule_data = CFStringCreateExternalRepresentation(NULL, pmodule, kCFStringEncodingASCII, 0);
@@ -571,7 +782,7 @@ void start_remote_debug_server(AMDeviceRef device) {
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(12345);
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
CFSocketRef fdvendor = CFSocketCreate(NULL, PF_INET, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL);
@@ -635,10 +846,33 @@ void killed(int signum) {
void lldb_finished_handler(int signum)
{
_exit(0);
int status = 0;
if (waitpid(child, &status, 0) == -1)
perror("waitpid failed");
_exit(WEXITSTATUS(status));
}
void bring_process_to_foreground() {
if (setpgid(0, 0) == -1)
perror("setpgid failed");
signal(SIGTTOU, SIG_IGN);
if (tcsetpgrp(STDIN_FILENO, getpid()) == -1)
perror("tcsetpgrp failed");
signal(SIGTTOU, SIG_DFL);
}
void setup_dummy_pipe_on_stdin(int pfd[2]) {
if (pipe(pfd) == -1)
perror("pipe failed");
if (dup2(pfd[0], STDIN_FILENO) == -1)
perror("dup2 failed");
}
void launch_debugger(AMDeviceRef device, CFURLRef url) {
CFStringRef device_full_name = get_device_full_name(device),
device_interface_name = get_device_interface_name(device);
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
@@ -646,6 +880,14 @@ void launch_debugger(AMDeviceRef device, CFURLRef url) {
printf("------ Debug phase ------\n");
if(AMDeviceGetInterfaceType(device) == 2)
{
printf("Cannot debug %s over %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
exit(0);
}
printf("Starting debug of %s connected through %s...\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
mount_developer_image(device); // put debugserver on the device
start_remote_debug_server(device); // start debugserver
write_lldb_prep_cmds(device, url); // dump the necessary lldb commands into a file
@@ -655,30 +897,278 @@ void launch_debugger(AMDeviceRef device, CFURLRef url) {
printf("[100%%] Connecting to remote debug server\n");
printf("-------------------------\n");
signal(SIGHUP, lldb_finished_handler);
setpgid(getpid(), 0);
signal(SIGHUP, killed);
signal(SIGINT, killed);
signal(SIGTERM, killed);
// Need this before fork to avoid race conditions. For child process we remove this right after fork.
signal(SIGLLDB, lldb_finished_handler);
parent = getpid();
int pid = fork();
if (pid == 0) {
char *runOption = nostart ? "" : "print \\\'run\\\'\n";
char lldb_shell[400];
sprintf(lldb_shell, LLDB_SHELL, runOption);
signal(SIGHUP, SIG_DFL);
signal(SIGLLDB, SIG_DFL);
child = getpid();
int pfd[2] = {-1, -1};
if (isatty(STDIN_FILENO))
// If we are running on a terminal, then we need to bring process to foreground for input
// to work correctly on lldb's end.
bring_process_to_foreground();
else
// If lldb is running in a non terminal environment, then it freaks out spamming "^D" and
// "quit". It seems this is caused by read() on stdin returning EOF in lldb. To hack around
// this we setup a dummy pipe on stdin, so read() would block expecting "user's" input.
setup_dummy_pipe_on_stdin(pfd);
char lldb_shell[400];
sprintf(lldb_shell, LLDB_SHELL);
if(device_id != NULL)
strcat(lldb_shell, device_id);
system(lldb_shell); // launch lldb
kill(parent, SIGHUP); // "No. I am your father."
_exit(0);
int status = system(lldb_shell); // launch lldb
if (status == -1)
perror("failed launching lldb");
close(pfd[0]);
close(pfd[1]);
// Notify parent we're exiting
kill(parent, SIGLLDB);
// Pass lldb exit code
_exit(WEXITSTATUS(status));
} else if (pid > 0) {
child = pid;
} else {
perror("fork failed");
exit(exitcode_error);
}
}
CFStringRef get_bundle_id(CFURLRef app_url)
{
if (app_url == NULL)
return NULL;
CFURLRef url = CFURLCreateCopyAppendingPathComponent(NULL, app_url, CFSTR("Info.plist"), false);
if (url == NULL)
return NULL;
CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, url);
CFRelease(url);
if (stream == NULL)
return NULL;
CFPropertyListRef plist = NULL;
if (CFReadStreamOpen(stream) == TRUE) {
plist = CFPropertyListCreateWithStream(NULL, stream, 0,
kCFPropertyListImmutable, NULL, NULL);
}
CFReadStreamClose(stream);
CFRelease(stream);
if (plist == NULL)
return NULL;
const void *value = CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"));
CFStringRef bundle_id = NULL;
if (value != NULL)
bundle_id = CFRetain(value);
CFRelease(plist);
return bundle_id;
}
void read_dir(service_conn_t afcFd, afc_connection* afc_conn_p, const char* dir)
{
char *dir_ent;
afc_connection afc_conn;
if (!afc_conn_p) {
afc_conn_p = &afc_conn;
AFCConnectionOpen(afcFd, 0, &afc_conn_p);
}
printf("%s\n", dir);
afc_dictionary afc_dict;
afc_dictionary* afc_dict_p = &afc_dict;
AFCFileInfoOpen(afc_conn_p, dir, &afc_dict_p);
afc_directory afc_dir;
afc_directory* afc_dir_p = &afc_dir;
afc_error_t err = AFCDirectoryOpen(afc_conn_p, dir, &afc_dir_p);
if (err != 0)
{
// Couldn't open dir - was probably a file
return;
}
while(true) {
err = AFCDirectoryRead(afc_conn_p, afc_dir_p, &dir_ent);
if (!dir_ent)
break;
if (strcmp(dir_ent, ".") == 0 || strcmp(dir_ent, "..") == 0)
continue;
char* dir_joined = malloc(strlen(dir) + strlen(dir_ent) + 2);
strcpy(dir_joined, dir);
if (dir_joined[strlen(dir)-1] != '/')
strcat(dir_joined, "/");
strcat(dir_joined, dir_ent);
read_dir(afcFd, afc_conn_p, dir_joined);
free(dir_joined);
}
AFCDirectoryClose(afc_conn_p, afc_dir_p);
}
// Used to send files to app-specific sandbox (Documents dir)
service_conn_t start_house_arrest_service(AMDeviceRef device) {
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
assert(AMDeviceStartSession(device) == 0);
service_conn_t houseFd;
if (bundle_id == NULL) {
printf("Bundle id is not specified\n");
exit(1);
}
CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingASCII);
if (AMDeviceStartHouseArrestService(device, cf_bundle_id, 0, &houseFd, 0) != 0)
{
printf("Unable to find bundle with id: %s\n", bundle_id);
exit(1);
}
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
CFRelease(cf_bundle_id);
return houseFd;
}
char* get_filename_from_path(char* path)
{
char *ptr = path + strlen(path);
while (ptr > path)
{
if (*ptr == '/')
break;
--ptr;
}
if (ptr+1 >= path+strlen(path))
return NULL;
if (ptr == path)
return ptr;
return ptr+1;
}
void* read_file_to_memory(char * path, size_t* file_size)
{
struct stat buf;
int err = stat(path, &buf);
if (err < 0)
{
return NULL;
}
*file_size = buf.st_size;
FILE* fd = fopen(path, "r");
char* content = malloc(*file_size);
if (fread(content, *file_size, 1, fd) != 1)
{
fclose(fd);
return NULL;
}
fclose(fd);
return content;
}
void list_files(AMDeviceRef device)
{
service_conn_t houseFd = start_house_arrest_service(device);
afc_connection afc_conn;
afc_connection* afc_conn_p = &afc_conn;
AFCConnectionOpen(houseFd, 0, &afc_conn_p);
read_dir(houseFd, afc_conn_p, "/");
}
void upload_file(AMDeviceRef device) {
service_conn_t houseFd = start_house_arrest_service(device);
afc_file_ref file_ref;
afc_connection afc_conn;
afc_connection* afc_conn_p = &afc_conn;
AFCConnectionOpen(houseFd, 0, &afc_conn_p);
// read_dir(houseFd, NULL, "/");
if (!target_filename)
{
target_filename = get_filename_from_path(upload_pathname);
}
size_t file_size;
void* file_content = read_file_to_memory(upload_pathname, &file_size);
if (!file_content)
{
printf("Could not open file: %s\n", upload_pathname);
exit(-1);
}
// Make sure the directory was created
{
char *dirpath = strdup(target_filename);
char *c = dirpath, *lastSlash = dirpath;
while(*c) {
if(*c == '/') {
lastSlash = c;
}
c++;
}
*lastSlash = '\0';
assert(AFCDirectoryCreate(afc_conn_p, dirpath) == 0);
}
int ret = AFCFileRefOpen(afc_conn_p, target_filename, 3, &file_ref);
if (ret == 0x000a) {
printf("Cannot write to %s. Permission error.\n", target_filename);
exit(1);
}
if (ret == 0x0009) {
printf("Target %s is a directory.\n", target_filename);
exit(1);
}
assert(ret == 0);
assert(AFCFileRefWrite(afc_conn_p, file_ref, file_content, file_size) == 0);
assert(AFCFileRefClose(afc_conn_p, file_ref) == 0);
assert(AFCConnectionClose(afc_conn_p) == 0);
free(file_content);
}
void handle_device(AMDeviceRef device) {
if (found_device) return; // handle one device only
CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device);
if (found_device)
return; // handle one device only
CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device),
device_full_name = get_device_full_name(device),
device_interface_name = get_device_interface_name(device);
if (device_id != NULL) {
if(strcmp(device_id, CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())) == 0) {
@@ -691,7 +1181,16 @@ void handle_device(AMDeviceRef device) {
}
if (detect_only) {
printf("[....] Found device (%s).\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding()));
printf("[....] Found %s connected through %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
exit(0);
}
if (command_only) {
if (strcmp("list", command) == 0) {
list_files(device);
} else if (strcmp("upload", command) == 0) {
upload_file(device);
}
exit(0);
}
@@ -704,8 +1203,30 @@ void handle_device(AMDeviceRef device) {
CFRelease(relative_url);
if (uninstall) {
printf("------ Uninstall phase ------\n");
CFStringRef bundle_id = get_bundle_id(url);
if (bundle_id == NULL) {
printf("Error: Unable to get bundle id from package %s\n Uninstall failed\n", app_path);
} else {
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
assert(AMDeviceStartSession(device) == 0);
assert(AMDeviceSecureUninstallApplication(0, device, bundle_id, 0, NULL, 0) == 0);
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
printf("[ OK ] Uninstalled package with bundle id %s\n", CFStringGetCStringPtr(bundle_id, CFStringGetSystemEncoding()));
}
}
if(install) {
printf("[ 0%%] Found device (%s), beginning install\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding()));
printf("------ Install phase ------\n");
printf("[ 0%%] Found %s connected through %s, beginning install\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
@@ -713,53 +1234,72 @@ void handle_device(AMDeviceRef device) {
assert(AMDeviceStartSession(device) == 0);
// NOTE: the secure version doesn't seem to require us to start the AFC service
service_conn_t afcFd;
assert(AMDeviceStartService(device, CFSTR("com.apple.afc"), &afcFd, NULL) == 0);
assert(AMDeviceSecureStartService(device, CFSTR("com.apple.afc"), NULL, &afcFd) == 0);
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0);
close(afcFd);
CFStringRef keys[] = { CFSTR("PackageType") };
CFStringRef values[] = { CFSTR("Developer") };
CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0);
assert(AMDeviceSecureTransferPath(0, device, url, options, transfer_callback, 0)==0);
close(afcFd);
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
assert(AMDeviceStartSession(device) == 0);
service_conn_t installFd;
assert(AMDeviceStartService(device, CFSTR("com.apple.mobile.installation_proxy"), &installFd, NULL) == 0);
// // NOTE: the secure version doesn't seem to require us to start the installation_proxy service
// // Although I can't find it right now, I in some code that the first param of AMDeviceSecureInstallApplication was a "dontStartInstallProxy"
// // implying this is done for us by iOS already
//service_conn_t installFd;
//assert(AMDeviceSecureStartService(device, CFSTR("com.apple.mobile.installation_proxy"), NULL, &installFd) == 0);
//mach_error_t result = AMDeviceInstallApplication(installFd, path, options, install_callback, NULL);
mach_error_t result = AMDeviceSecureInstallApplication(0, device, url, options, install_callback, 0);
if (result != 0)
{
char* error = "Unknown error.";
if (result == 0xe8008015)
error = "Your application failed code-signing checks. Check your certificates, provisioning profiles, and bundle ids.";
printf("AMDeviceInstallApplication failed: 0x%X: %s\n", result, error);
exit(exitcode_error);
}
// close(installFd);
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
mach_error_t result = AMDeviceInstallApplication(installFd, path, options, install_callback, NULL);
if (result != 0)
{
printf("AMDeviceInstallApplication failed: %d\n", result);
exit(1);
}
close(installFd);
CFRelease(path);
CFRelease(options);
printf("[100%%] Installed package %s\n", app_path);
}
if (!debug) exit(0); // no debug phase
if (!debug)
exit(0); // no debug phase
launch_debugger(device, url);
}
void device_callback(struct am_device_notification_callback_info *info, void *arg) {
switch (info->msg) {
case ADNCI_MSG_CONNECTED:
if(device_id != NULL || !debug || AMDeviceGetInterfaceType(info->dev) != 2) {
handle_device(info->dev);
} else if(best_device_match == NULL) {
best_device_match = info->dev;
CFRetain(best_device_match);
}
default:
break;
}
@@ -767,8 +1307,17 @@ void device_callback(struct am_device_notification_callback_info *info, void *ar
void timeout_callback(CFRunLoopTimerRef timer, void *info) {
if (!found_device) {
if(best_device_match != NULL) {
handle_device(best_device_match);
CFRelease(best_device_match);
best_device_match = NULL;
}
if(!found_device) {
printf("[....] Timed out waiting for device.\n");
exit(1);
exit(exitcode_error);
}
}
}
@@ -785,8 +1334,15 @@ void usage(const char* app) {
" -g, --gdbargs <args> extra arguments to pass to GDB when starting the debugger\n"
" -x, --gdbexec <file> GDB commands script file\n"
" -n, --nostart do not start the app when debugging\n"
" -I, --noninteractive start in non interactive mode (quit when app crashes or exits)\n"
" -v, --verbose enable verbose output\n"
" -m, --noinstall directly start debugging without app install (-d not required) \n"
" -m, --noinstall directly start debugging without app install (-d not required)\n"
" -p, --port <number> port used for device, default: 12345 \n"
" -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) \n"
" -1, --bundle_id <bundle id> specify bundle id for list and upload\n"
" -l, --list list files\n"
" -o, --upload <file> upload file\n"
" -2, --to <target pathname> use together with upload file. specify target for upload\n"
" -V, --version print the executable version \n",
app);
}
@@ -806,14 +1362,21 @@ int main(int argc, char *argv[]) {
{ "gdbexec", no_argument, NULL, 'x' },
{ "unbuffered", no_argument, NULL, 'u' },
{ "nostart", no_argument, NULL, 'n' },
{ "noninteractive", no_argument, NULL, 'I' },
{ "detect", no_argument, NULL, 'c' },
{ "version", no_argument, NULL, 'V' },
{ "noinstall", no_argument, NULL, 'm' },
{ "port", required_argument, NULL, 'p' },
{ "uninstall", no_argument, NULL, 'r' },
{ "list", no_argument, NULL, 'l' },
{ "bundle_id", required_argument, NULL, '1'},
{ "upload", required_argument, NULL, 'o'},
{ "to", required_argument, NULL, '2'},
{ NULL, 0, NULL, 0 },
};
char ch;
while ((ch = getopt_long(argc, argv, "Vmcdvuni:b:a:t:g:x:", longopts, NULL)) != -1)
while ((ch = getopt_long(argc, argv, "VmcdvunlrIi:b:a:t:g:x:p:1:2:o:", longopts, NULL)) != -1)
{
switch (ch) {
case 'm':
@@ -844,21 +1407,45 @@ int main(int argc, char *argv[]) {
case 'n':
nostart = 1;
break;
case 'I':
interactive = false;
break;
case 'c':
detect_only = true;
break;
case 'V':
show_version();
return 1;
return exitcode_error;
case 'p':
port = atoi(optarg);
break;
case 'r':
uninstall = 1;
break;
case '1':
bundle_id = optarg;
break;
case '2':
target_filename = optarg;
break;
case 'o':
command_only = true;
upload_pathname = optarg;
command = "upload";
break;
case 'l':
command_only = true;
command = "list";
break;
default:
usage(argv[0]);
return 1;
return exitcode_error;
}
}
if (!app_path && !detect_only) {
if (!app_path && !detect_only && !command_only) {
usage(argv[0]);
exit(0);
exit(exitcode_error);
}
if (unbuffered) {
@@ -870,10 +1457,6 @@ int main(int argc, char *argv[]) {
timeout = 5;
}
if (!detect_only) {
printf("------ Install phase ------\n");
}
if (app_path) {
assert(access(app_path, F_OK) == 0);
}
@@ -894,3 +1477,4 @@ int main(int argc, char *argv[]) {
AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, &notify);
CFRunLoopRun();
}

View File

@@ -1,7 +1,7 @@
{
"name": "ios-deploy",
"version": "1.0.5",
"description": "launch iOS apps iOS devices from the command line (Xcode 4)",
"version": "1.0.9",
"description": "launch iOS apps iOS devices from the command line (Xcode 5)",
"main": "ios-deploy",
"scripts": {
"preinstall": "make ios-deploy"