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