xamarin-macios/runtime/monotouch-debug.m

1184 строки
33 KiB
Mathematica
Исходник Обычный вид История

2016-04-21 15:19:32 +03:00
//
// debug.m: Debugging code for MonoTouch
//
// Authors:
// Geoff Norton
// Rolf Bjarne Kvinge <rolf@xamarin.com>
//
// Copyright 2009 Novell, Inc.
// Copyright 2011-2013 Xamarin Inc.
//
#ifdef DEBUG
#include <UIKit/UIKit.h>
#include <zlib.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <pthread.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <sys/shm.h>
#include "xamarin/xamarin.h"
#include "runtime-internal.h"
#include "monotouch-debug.h"
#include "product.h"
// permanent connection variables
int monodevelop_port = -1;
int sdb_fd = -1;
int profiler_fd = -1;
int heapshot_fd = -1; // this is the socket to write 'heapshot' to to requests heapshots from the profiler
int heapshot_port = -1;
char *profiler_description = NULL;
// old variables
int output_port;
int debug_port;
char *debug_host = NULL;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static bool debugging_configured = false;
static bool profiler_configured = false;
static bool config_timedout = false;
static bool usb_debugging = false;
static const char *connection_mode = "default"; // this is set from the cmd line, can be either 'usb', 'wifi' or 'none'
int monotouch_connect_usb ();
int monotouch_connect_wifi (NSMutableArray *hosts);
int monotouch_debug_listen (int debug_port, int output_port);
int monotouch_debug_connect (NSMutableArray *hosts, int debug_port, int output_port);
void monotouch_configure_debugging ();
void monotouch_load_profiler ();
void monotouch_load_debugger ();
bool monotouch_process_connection (int fd);
static struct timeval wait_tv;
static struct timespec wait_ts;
void
monotouch_set_connection_mode (const char *mode)
{
connection_mode = mode;
}
void
monotouch_set_monodevelop_port (int port)
{
monodevelop_port = port;
}
void
monotouch_start_debugging ()
{
bool debug_enabled = strcmp (connection_mode, "none");
if (xamarin_debug_mode) {
if (debug_enabled) {
// wait for debug configuration to finish
gettimeofday(&wait_tv, NULL);
wait_ts.tv_sec = wait_tv.tv_sec + 2;
wait_ts.tv_nsec = wait_tv.tv_usec * 1000;
pthread_mutex_lock (&mutex);
while (!debugging_configured && !config_timedout) {
if (pthread_cond_timedwait (&cond, &mutex, &wait_ts) == ETIMEDOUT)
config_timedout = true;
}
pthread_mutex_unlock (&mutex);
if (!config_timedout)
monotouch_load_debugger ();
} else {
LOG (PRODUCT ": Not connecting to the IDE, debug has been disabled\n");
}
char *trace = getenv ("MONO_TRACE");
if (trace && *trace) {
if (!strncmp (trace, "--trace=", 8))
trace += 8;
mono_jit_set_trace_options (trace);
}
}
}
void
monotouch_start_profiling ()
{
bool debug_enabled = strcmp (connection_mode, "none");
if (xamarin_debug_mode && debug_enabled) {
// wait for profiler configuration to finish
pthread_mutex_lock (&mutex);
while (!profiler_configured && !config_timedout) {
if (pthread_cond_timedwait (&cond, &mutex, &wait_ts) == ETIMEDOUT)
config_timedout = true;
}
pthread_mutex_unlock (&mutex);
if (!config_timedout)
monotouch_load_profiler ();
}
}
static NSString *
get_preference (NSArray *preferences, NSUserDefaults *defaults, NSString *lookupKey)
{
NSDictionary *dict;
// Apple appears to return nil if the user has never opened the Settings, so we
// manually parse it here. This has the added benefits that if people don't open
// settings we can control the default from MD
// User Preferences have the highest precedence
for (dict in preferences) {
NSString *key = [dict objectForKey:@"Key"];
if (![key isEqualToString:lookupKey])
continue;
return [dict objectForKey:@"DefaultValue"];
}
// Global Defaults have the second highest precedence
return defaults ? [defaults stringForKey:lookupKey] : nil;
}
void monotouch_configure_debugging ()
{
// This method is invoked on a separate thread
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *bundle_path = [NSString stringWithUTF8String:xamarin_get_bundle_path ()];
NSString *settings_path = [bundle_path stringByAppendingPathComponent:@"Settings.bundle"];
NSString *root_plist = [settings_path stringByAppendingPathComponent:@"Root.plist"];
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile: root_plist];
NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
NSMutableArray *hosts = [NSMutableArray array];
bool debug_enabled;
NSString *monodevelop_host;
NSString *monotouch_debug_enabled;
if (!strcmp (connection_mode, "none")) {
// nothing to do
return;
}
// If debugging is enabled
monotouch_debug_enabled = get_preference (preferences, NULL, @"__monotouch_debug_enabled");
if (monotouch_debug_enabled != nil) {
debug_enabled = [monotouch_debug_enabled isEqualToString:@"1"];
} else {
debug_enabled = [defaults boolForKey:@"__monotouch_debug_enabled"];
}
// We get the IPs of the dev machine + one port (monodevelop_port).
// We start up a thread (using the same thread that we have to start up
// anyway to initialize cocoa threading) and then establishes several
// connections to MD (for usb we listen for connections and for wifi we
// connect to MD using any of the IPs we got). MD then sends instructions
// on those connections telling us what to do with them. We never stop
// processing connections and commands from that thread - this way MD can
// send an exit request when MD wants us to exit.
monodevelop_host = get_preference (preferences, defaults, @"__monodevelop_host");
if (monodevelop_host != nil && ![monodevelop_host isEqualToString:@"automatic"]) {
[hosts addObject:monodevelop_host];
LOG (PRODUCT ": Added host from settings to look for the IDE: %s\n", [monodevelop_host UTF8String]);
}
char *evar = getenv ("__XAMARIN_DEBUG_PORT__");
if (evar && *evar) {
if (monodevelop_port == -1) {
monodevelop_port = strtol (evar, NULL, 10);
LOG (PRODUCT ": Found port %i in environment variables\n", monodevelop_port);
}
unsetenv ("__XAMARIN_DEBUG_PORT__");
}
evar = getenv ("__XAMARIN_DEBUG_HOSTS__");
if (evar && *evar) {
NSArray *ips = [[NSString stringWithUTF8String:evar] componentsSeparatedByString:@";"];
for (int i = 0; i < [ips count]; i++) {
NSString *ip = [ips objectAtIndex:i];
if (![hosts containsObject:ip]) {
[hosts addObject:ip];
LOG (PRODUCT ": Found host %s in environment variables\n", [ip UTF8String]);
}
}
unsetenv ("__XAMARIN_DEBUG_HOSTS__");
}
#if MONOTOUCH && (defined(__i386__) || defined (__x86_64__))
// Try to read shared memory as well
key_t shmkey = ftok ("/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mtouch", 0);
if (shmkey == -1) {
LOG (PRODUCT ": Could not create shared memory key: %s\n", strerror (errno));
} else {
int shmsize = 1024;
int shmid = shmget (shmkey, shmsize, 0);
if (shmid == -1) {
LOG (PRODUCT ": Could not get shared memory id: %s\n", strerror (errno));
} else {
void *ptr = shmat (shmid, NULL, SHM_RDONLY);
if (ptr == NULL || ptr == (void *) -1) {
LOG (PRODUCT ": Could not map shared memory: %s\n", strerror (errno));
} else {
LOG (PRODUCT ": Read %i bytes from shared memory: %p with key %i and id %i\n", shmsize, ptr, shmkey, shmid);
// Make a local copy of the shared memory, so that it doesn't change while we're parsing it.
char *data = strndup ((const char *) ptr, shmsize); // strndup will null-terminate
char *line = data;
// Parse!
while (*line) {
char *nline = line;
// find the end of the line, null-terminate the line and make 'nptr' to the next line.
do {
if (*nline == '\n') {
*nline = 0;
nline++;
break;
}
} while (*++nline);
if (!strncmp (line, "__XAMARIN_DEBUG_PORT__=", 23)) {
int shm_monodevelop_port = strtol (line + 23, NULL, 10);
if (monodevelop_port == -1) {
monodevelop_port = shm_monodevelop_port;
LOG (PRODUCT ": Found port %i in shared memory\n", monodevelop_port);
} else {
LOG (PRODUCT ": Found port %i in shared memory, but not overriding existing port %i\n", shm_monodevelop_port, monodevelop_port);
}
} else {
LOG (PRODUCT ": Unknown data found in shared memory: %s\n", line);
}
line = nline;
}
free (data);
shmdt (ptr);
}
}
}
#endif
// Finally, fall back to loading values from MonoTouchDebugConfiguration.txt
FILE *debug_conf = fopen ("MonoTouchDebugConfiguration.txt", "r");
if (debug_conf != NULL) {
bool add_hosts = [hosts count] == 0;
char line [128];
int i;
while (!feof (debug_conf)) {
if (fgets (line, sizeof (line), debug_conf) != NULL) {
// Remove trailing newline
for (i = 0; line[i]; i++) {
if (line [i] == '\n' || line [i] == '\r') {
line [i] = 0;
break;
}
}
if (!strncmp ("IP: ", line, 4)) {
if (add_hosts) {
NSString *ip;
ip = [NSString stringWithUTF8String:line + 4];
if (![hosts containsObject:ip]) {
[hosts addObject:ip];
LOG (PRODUCT ": Added IP to look for the IDE: %s\n", [ip UTF8String]);
}
}
} else if (!strncmp ("USB Debugging: ", line, 15) && (connection_mode == NULL || !strcmp (connection_mode, "default"))) {
#if defined(__arm__) || defined(__aarch64__)
usb_debugging = !strncmp ("USB Debugging: 1", line, 16);
#endif
} else if (!strncmp ("Port: ", line, 6) && monodevelop_port == -1) {
monodevelop_port = strtol (line + 6, NULL, 10);
}
}
}
fclose (debug_conf);
}
if (debug_enabled) {
int rv;
// connection_mode is set from the command line, and will override any other setting
if (connection_mode != NULL) {
if (!strcmp (connection_mode, "usb")) {
usb_debugging = true;
} else if (!strcmp (connection_mode, "wifi")) {
usb_debugging = false;
}
}
if (monodevelop_port <= 0) {
LOG (PRODUCT ": Invalid IDE Port: %i\n", monodevelop_port);
} else {
LOG (PRODUCT ": IDE Port: %i Transport: %s\n", monodevelop_port, usb_debugging ? "USB" : "WiFi");
if (usb_debugging) {
rv = monotouch_connect_usb ();
} else {
rv = monotouch_connect_wifi (hosts);
}
}
}
profiler_configured = true;
debugging_configured = true;
pthread_mutex_lock (&mutex);
pthread_cond_signal (&cond);
pthread_mutex_unlock (&mutex);
}
void sdb_connect (const char *address)
{
gboolean shaked;
shaked = mono_debugger_agent_transport_handshake ();
if (!shaked)
NSLog (@PRODUCT ": Handshake error with IDE.");
return;
}
void sdb_close1 (void)
{
shutdown (sdb_fd, SHUT_RD);
}
void sdb_close2 (void)
{
shutdown (sdb_fd, SHUT_RDWR);
}
gboolean send_uninterrupted (int fd, const void *buf, int len)
{
int res;
do {
res = send (fd, buf, len, 0);
} while (res == -1 && errno == EINTR);
return res == len;
}
int recv_uninterrupted (int fd, void *buf, int len)
{
int res;
int total = 0;
int flags = 0;
do {
res = recv (fd, (char *) buf + total, len - total, flags);
if (res > 0)
total += res;
} while ((res > 0 && total < len) || (res == -1 && errno == EINTR));
return total;
}
gboolean sdb_send (void *buf, int len)
{
return send_uninterrupted (sdb_fd, buf, len);
}
int sdb_recv (void *buf, int len)
{
return recv_uninterrupted (sdb_fd, buf, len);
}
int monotouch_connect_wifi (NSMutableArray *ips)
{
int listen_port = monodevelop_port;
unsigned char sockaddr[sizeof (struct sockaddr_in6)];
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sockaddr;
struct sockaddr_in *sin = (struct sockaddr_in *) sockaddr;
int family, waiting, len, rv, i;
int ip_count = [ips count];
const char *family_str;
int connected;
const char *ip;
int *sockets;
long flags;
if (ip_count == 0) {
NSLog (@PRODUCT ": No IPs to connect to.");
return 2;
}
sockets = (int *) malloc (sizeof (int) * ip_count);
for (i = 0; i < ip_count; i++)
sockets[i] = -2;
// Open a socket and try to establish a connection for each IP
do {
waiting = 0;
connected = -1;
for (i = 0; i < ip_count; i++) {
if (sockets [i] == -1)
continue;
ip = [[ips objectAtIndex:i] UTF8String];
memset (sockaddr, 0, sizeof (sockaddr));
// Parse the host IP, assuming IPv4 and falling back to IPv6
if ((rv = inet_pton (AF_INET, ip, &sin->sin_addr)) == 1) {
len = sin->sin_len = sizeof (struct sockaddr_in);
family = sin->sin_family = AF_INET;
sin->sin_port = htons (listen_port);
family_str = "IPv4";
} else if (rv == 0 && (rv = inet_pton (AF_INET6, ip, &sin6->sin6_addr)) == 1) {
len = sin6->sin6_len = sizeof (struct sockaddr_in6);
family = sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons (listen_port);
family_str = "IPv6";
} else {
NSLog (@PRODUCT ": Error parsing '%s': %s", ip, errno ? strerror (errno) : "unsupported address type");
sockets[i] = -1;
continue;
}
if ((sockets[i] = socket (family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
NSLog (@PRODUCT ": Failed to create %s socket: %s", family_str, strerror (errno));
continue;
}
// Make the socket non-blocking
flags = fcntl (sockets[i], F_GETFL, NULL);
fcntl (sockets[i], F_SETFL, flags | O_NONBLOCK);
// Connect to the host
if ((rv = connect (sockets[i], (struct sockaddr *) sockaddr, len)) == 0) {
// connection completed, this is our man.
connected = i;
break;
}
if (rv < 0 && errno != EINPROGRESS) {
NSLog (@PRODUCT ": Failed to connect to %s on port %d: %s", ip, listen_port, strerror (errno));
close (sockets[i]);
sockets[i] = -1;
continue;
}
// asynchronous connect
waiting++;
}
// Wait for async socket connections to become available
while (connected == -1 && waiting > 0) {
socklen_t optlen = sizeof (int);
fd_set rset, wset, xset;
struct timeval tv;
int max_fd = -1;
int error;
tv.tv_sec = 2;
tv.tv_usec = 0;
FD_ZERO (&rset);
FD_ZERO (&wset);
FD_ZERO (&xset);
for (i = 0; i < ip_count; i++) {
if (sockets[i] < 0)
continue;
max_fd = MAX (max_fd, sockets[i]);
FD_SET (sockets[i], &rset);
FD_SET (sockets[i], &wset);
FD_SET (sockets[i], &xset);
}
if ((rv = select (max_fd + 1, &rset, &wset, &xset, &tv)) == 0) {
// timeout hit, no connections available.
free (sockets);
return 1;
}
if (rv < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
// irrecoverable error
NSLog (@PRODUCT ": Error while waiting for connections: %s", strerror (errno));
free (sockets);
return 1;
}
for (i = 0; i < ip_count; i++) {
if (sockets[i] < 0)
continue;
if (FD_ISSET (sockets[i], &xset)) {
// exception on this socket
close (sockets[i]);
sockets[i] = -1;
waiting--;
continue;
}
if (!FD_ISSET (sockets[i], &rset) && !FD_ISSET (sockets[i], &wset)) {
// still waiting...
continue;
}
// okay, this socket is ready for reading or writing...
if (getsockopt (sockets[i], SOL_SOCKET, SO_ERROR, &error, &optlen) < 0) {
NSLog (@PRODUCT ": Error while trying to get socket options for %s: %s", [[ips objectAtIndex:i] UTF8String], strerror (errno));
close (sockets[i]);
sockets[i] = -1;
waiting--;
continue;
}
if (error != 0) {
NSLog (@PRODUCT ": Socket error while connecting to IDE on %s:%d: %s", [[ips objectAtIndex:i] UTF8String], listen_port, strerror (error));
close (sockets[i]);
sockets[i] = -1;
waiting--;
continue;
}
// success!
connected = i;
break;
}
}
if (connected == -1) {
free (sockets);
return 1;
}
// close the remaining sockets
for (i = 0; i < ip_count; i++) {
if (i == connected || sockets[i] < 0)
continue;
close (sockets[i]);
sockets[i] = -1;
}
LOG (PRODUCT ": Established connection with the IDE (fd: %i)\n", sockets [connected]);
} while (monotouch_process_connection (sockets [connected]));
free (sockets);
return 0;
}
int monotouch_connect_usb ()
{
int listen_port = monodevelop_port;
struct sockaddr_in listen_addr;
int listen_socket = -1;
int fd;
socklen_t len;
int rv;
fd_set rset;
struct timeval tv;
struct timeval start;
struct timeval now;
int flags;
// Create the listen socket and set it up
listen_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_socket == -1) {
NSLog (@PRODUCT ": Could not create socket for the IDE to connect to: %s", strerror (errno));
return 1;
}
flags = 1;
if (setsockopt (listen_socket, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof (flags)) == -1) {
NSLog (@PRODUCT ": Could not set SO_REUSEADDR on the listening socket (%s)", strerror (errno));
// not a fatal failure
}
// Bind
memset (&listen_addr, 0, sizeof (listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons (listen_port);
listen_addr.sin_addr.s_addr = INADDR_ANY;
rv = bind (listen_socket, (struct sockaddr *) &listen_addr, sizeof (listen_addr));
if (rv == -1) {
NSLog (@PRODUCT ": Could not bind to address: %s", strerror (errno));
rv = 2;
goto cleanup;
}
// Make the socket non-blocking
flags = fcntl (listen_socket, F_GETFL, NULL);
flags |= O_NONBLOCK;
fcntl (listen_socket, F_SETFL, flags);
rv = listen (listen_socket, 1);
if (rv == -1) {
NSLog (@PRODUCT ": Could not listen for the IDE: %s", strerror (errno));
rv = 2;
goto cleanup;
}
// Wait for connections
start.tv_sec = 0;
start.tv_usec = 0;
do {
FD_ZERO (&rset);
FD_SET (listen_socket, &rset);
do {
// Calculate how long we can wait if we can only work for 2s since we started
gettimeofday (&now, NULL);
if (start.tv_sec == 0) {
start.tv_sec = now.tv_sec;
start.tv_usec = now.tv_usec;
tv.tv_sec = 2;
tv.tv_usec = 0;
} else if ((start.tv_sec + 2 == now.tv_sec && start.tv_usec < now.tv_usec) || start.tv_sec + 2 < now.tv_sec) {
// timeout
} else {
tv.tv_sec = start.tv_sec + 2 - now.tv_sec;
if (start.tv_usec > now.tv_usec) {
tv.tv_usec = start.tv_usec - now.tv_usec;
} else {
tv.tv_sec--;
tv.tv_usec = 1000000 + start.tv_usec - now.tv_usec;
}
}
// LOG (PRODUCT ": Waiting for connections from the IDE, sec: %i usec: %i\n", (int) tv.tv_sec, (int) tv.tv_usec);
if ((rv = select (listen_socket + 1, &rset, NULL, NULL, &tv)) == 0) {
// timeout hit, no connections available.
LOG (PRODUCT ": Listened for connections from the IDE for 2 seconds, nobody connected.\n");
rv = 3;
goto cleanup;
}
} while (rv == -1 && errno == EINTR);
if (rv == -1) {
NSLog (@PRODUCT ": Failed while waiting for the IDE to connect: %s", strerror (errno));
rv = 2;
goto cleanup;
}
len = sizeof (struct sockaddr_in);
fd = accept (listen_socket, (struct sockaddr *) &listen_addr, &len);
if (fd == -1) {
NSLog (@PRODUCT ": Failed to accept connection from the IDE: %s", strerror (errno));
rv = 3;
goto cleanup;
}
flags = 1;
if (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flags, sizeof (flags)) < 0) {
NSLog (@PRODUCT ": Could not set TCP_NODELAY on socket (%s)", strerror (errno));
// not a fatal failure
}
LOG (PRODUCT ": Successfully received USB connection from the IDE on port %i, fd: %i\n", listen_port, fd);
} while (monotouch_process_connection (fd));
LOG (PRODUCT ": Successfully talked to the IDE. Will continue startup now.\n");
cleanup:
close (listen_socket);
return rv;
}
void
monotouch_dump_objc_api (Class klass)
{
unsigned int c;
Ivar *vars;
Method *methods;
objc_property_t *props;
printf ("Dumping class %p = %s\n", klass, class_getName (klass));
vars = class_copyIvarList (klass, &c);
printf ("\t%i instance variables:\n", c);
for (int i = 0; i < c; i++)
printf ("\t\t#%i: %s\n", i + 1, ivar_getName (vars [i]));
free (vars);
methods = class_copyMethodList (klass, &c);
printf ("\t%i instance methods:\n", c);
for (int i = 0; i < c; i++)
printf ("\t\t#%i: %s\n", i + 1, sel_getName (method_getName (methods [i])));
free (methods);
props = class_copyPropertyList (klass, &c);
printf ("\t%i instance properties:\n", c);
for (int i = 0; i < c; i++)
printf ("\t\t#%i: %s\n", i + 1, property_getName (props [i]));
free (props);
fflush (stdout);
}
void
monotouch_load_debugger ()
{
// main thread only
if (sdb_fd != -1) {
DebuggerTransport transport;
transport.name = "custom_transport";
transport.connect = sdb_connect;
transport.close1 = sdb_close1;
transport.close2 = sdb_close2;
transport.send = sdb_send;
transport.recv = sdb_recv;
mono_debugger_agent_register_transport (&transport);
mono_debugger_agent_parse_options ("transport=custom_transport,address=dummy,embedding=1");
LOG (PRODUCT ": Debugger loaded with custom transport (fd: %i)\n", sdb_fd);
} else {
LOG (PRODUCT ": Debugger not loaded (disabled).\n");
}
}
void
monotouch_load_profiler ()
{
// TODO: make this generic enough for other profilers to work too
// Main thread only
if (profiler_description != NULL) {
mono_profiler_load (profiler_description);
LOG (PRODUCT ": Profiler loaded: %s\n", profiler_description);
free (profiler_description);
profiler_description = NULL;
} else {
LOG (PRODUCT ": Profiler not loaded (disabled)\n");
}
}
// returns true if it's necessary to create more
// connections to process more data.
bool
monotouch_process_connection (int fd)
{
// make sure the fd/socket blocks on reads/writes
fcntl (fd, F_SETFL, fcntl (fd, F_GETFL, NULL) & ~O_NONBLOCK);
while (true) {
char command [257];
int rv;
unsigned char cmd_len;
rv = recv_uninterrupted (fd, &cmd_len, 1);
if (rv <= 0) {
LOG (PRODUCT ": Error while receiving command from the IDE (%s)\n", strerror (errno));
return false;
}
rv = recv_uninterrupted (fd, command, cmd_len);
if (rv <= 0) {
LOG (PRODUCT ": Error while receiving command from the IDE (%s)\n", strerror (errno));
return false;
}
// null-terminate
command [cmd_len] = 0;
LOG (PRODUCT ": Processing: '%s'\n", command);
if (!strcmp (command, "connect output")) {
dup2 (fd, 1);
dup2 (fd, 2);
return true;
} else if (!strcmp (command, "connect stdout")) {
dup2 (fd, 1);
return true;
} else if (!strcmp (command, "connect stderr")) {
dup2 (fd, 2);
return true;
} else if (!strcmp (command, "discard")) {
return true;
} else if (!strcmp (command, "ping")) {
if (!send_uninterrupted (fd, "pong", 5))
LOG (PRODUCT ": Got keepalive request from the IDE, but could not send response back (%s)\n", strerror (errno));
} else if (!strcmp (command, "exit process")) {
LOG (PRODUCT ": The IDE requested an exit, will exit immediately.\n");
fflush (stderr);
exit (0);
} else if (!strncmp (command, "start debugger: ", 16)) {
const char *debugger = command + 16;
bool use_fd = false;
if (!strcmp (debugger, "no")) {
/* disabled */
} else if (!strcmp (debugger, "sdb")) {
sdb_fd = fd;
use_fd = true;
}
debugging_configured = true;
pthread_mutex_lock (&mutex);
pthread_cond_signal (&cond);
pthread_mutex_unlock (&mutex);
if (use_fd)
return true;
} else if (!strncmp (command, "start profiler: ", 16)) {
// initialize the log profiler if we're debugging
const char *prof = command + 16;
bool use_fd = false;
if (!strcmp (prof, "no")) {
/* disabled */
} else if (!strncmp (prof, "log:", 4)) {
#if defined(__i386__) || defined (__x86_64__)
profiler_description = strdup (prof);
#else
use_fd = true;
profiler_fd = fd;
profiler_description = xamarin_strdup_printf ("%s,output=#%i", prof, profiler_fd);
#endif
xamarin_set_gc_pump_enabled (false);
} else {
LOG (PRODUCT ": Unknown profiler, expect unexpected behavior (%s)\n", prof);
profiler_description = strdup (prof);
}
profiler_configured = true;
pthread_mutex_lock (&mutex);
pthread_cond_signal (&cond);
pthread_mutex_unlock (&mutex);
if (use_fd)
return true;
} else if (!strncmp (command, "set heapshot port: ", 19)) {
heapshot_port = strtol (command + 19, NULL, 0);
LOG (PRODUCT ": HeapShot port is now: %i\n", heapshot_port);
} else if (!strcmp (command, "heapshot")) {
if (heapshot_fd == -1) {
struct sockaddr_in heapshot_addr;
memset (&heapshot_addr, 0, sizeof (heapshot_addr));
heapshot_addr.sin_len = sizeof (heapshot_addr);
heapshot_addr.sin_port = htons (heapshot_port);
heapshot_addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
heapshot_addr.sin_family = AF_INET;
if ((heapshot_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
LOG (PRODUCT ": Failed to create socket to connect to profiler: %s\n", strerror (errno));
} else if (connect (heapshot_fd, (struct sockaddr *) &heapshot_addr, sizeof (heapshot_addr)) != 0) {
LOG (PRODUCT ": Failed to connect to profiler to request a heapshot: %s\n", strerror (errno));
close (heapshot_fd);
heapshot_fd = -1;
} else {
// Success!
}
}
if (heapshot_fd != -1) {
if (!send_uninterrupted (heapshot_fd, "heapshot\n", 9))
LOG (PRODUCT ": Failed to request heapshot: %s\n", strerror (errno));
}
} else {
LOG (PRODUCT ": Unknown command received from the IDE: '%s'\n", command);
}
}
}
int monotouch_debug_listen (int debug_port, int output_port)
{
struct sockaddr_in listen_addr;
int listen_socket;
int output_socket;
socklen_t len;
int rv;
long flags;
int flag;
fd_set rset;
struct timeval tv;
// Create the listen socket and set it up
listen_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_socket == -1) {
NSLog (@PRODUCT ": Could not create socket for the IDE to connect to: %s", strerror (errno));
return 1;
} else {
flag = 1;
if (setsockopt (listen_socket, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (flag)) == -1) {
NSLog (@PRODUCT ": Could not set SO_REUSEADDR on the listening socket (%s)", strerror (errno));
// not a fatal failure
}
memset (&listen_addr, 0, sizeof (listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons (output_port);
listen_addr.sin_addr.s_addr = INADDR_ANY;
rv = bind (listen_socket, (struct sockaddr *) &listen_addr, sizeof (listen_addr));
if (rv == -1) {
NSLog (@PRODUCT ": Could not bind to address: %s", strerror (errno));
close (listen_socket);
return 2;
} else {
// Make the socket non-blocking
flags = fcntl (listen_socket, F_GETFL, NULL);
flags |= O_NONBLOCK;
fcntl (listen_socket, F_SETFL, flags);
rv = listen (listen_socket, 1);
if (rv == -1) {
NSLog (@PRODUCT ": Could not listen for the IDE: %s", strerror (errno));
close (listen_socket);
return 2;
} else {
// Yay!
}
}
}
tv.tv_sec = 2;
tv.tv_usec = 0;
FD_ZERO (&rset);
FD_SET (listen_socket, &rset);
do {
if ((rv = select (listen_socket + 1, &rset, NULL, NULL, &tv)) == 0) {
// timeout hit, no connections available.
NSLog (@PRODUCT ": Listened for connections from the IDE for 2 seconds, nobody connected.");
close (listen_socket);
return 3;
}
} while (rv == -1 && errno == EINTR);
if (rv == -1) {
NSLog (@PRODUCT ": Failed while waiting for the IDE to connect: %s", strerror (errno));
close (listen_socket);
return 2;
}
len = sizeof (struct sockaddr_in);
output_socket = accept (listen_socket, (struct sockaddr *) &listen_addr, &len);
if (output_socket == -1) {
NSLog (@PRODUCT ": Failed to accept connection from the IDE: %s", strerror (errno));
close (listen_socket);
return 3;
}
flag = 1;
if (setsockopt (output_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof (flag)) < 0) {
NSLog (@PRODUCT ": Could not set TCP_NODELAY on socket (%s)", strerror (errno));
// not a fatal failure
}
LOG (PRODUCT ": Successfully received USB connection from the IDE on port %i.\n", output_port);
// make the socket block on reads/writes
flags = fcntl (output_socket, F_GETFL, NULL);
fcntl (output_socket, F_SETFL, flags & ~O_NONBLOCK);
dup2 (output_socket, 1);
dup2 (output_socket, 2);
close (listen_socket); // no need to listen anymore
debug_host = strdup ("127.0.0.1");
return 0;
}
// SUCCESS = 0
// FAILURE > 0
int monotouch_debug_connect (NSMutableArray *ips, int debug_port, int output_port)
{
unsigned char sockaddr[sizeof (struct sockaddr_in6)];
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sockaddr;
struct sockaddr_in *sin = (struct sockaddr_in *) sockaddr;
int family, waiting, len, rv, i;
int ip_count = [ips count];
const char *family_str;
int connected = -1;
const char *ip;
int *sockets;
long flags;
if (ip_count == 0) {
NSLog (@PRODUCT ": No IPs to connect to.");
return 2;
}
sockets = (int *) malloc (sizeof (int) * ip_count);
for (i = 0; i < ip_count; i++)
sockets[i] = -1;
// Open a socket and try to establish a connection for each IP
waiting = 0;
for (i = 0; i < ip_count; i++) {
ip = [[ips objectAtIndex:i] UTF8String];
memset (sockaddr, 0, sizeof (sockaddr));
// Parse the host IP, assuming IPv4 and falling back to IPv6
if ((rv = inet_pton (AF_INET, ip, &sin->sin_addr)) == 1) {
len = sin->sin_len = sizeof (struct sockaddr_in);
family = sin->sin_family = AF_INET;
sin->sin_port = htons (output_port);
family_str = "IPv4";
} else if (rv == 0 && (rv = inet_pton (AF_INET6, ip, &sin6->sin6_addr)) == 1) {
len = sin6->sin6_len = sizeof (struct sockaddr_in6);
family = sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons (output_port);
family_str = "IPv6";
} else {
NSLog (@PRODUCT ": Error parsing '%s': %s", ip, errno ? strerror (errno) : "unsupported address type");
sockets[i] = -1;
continue;
}
if ((sockets[i] = socket (family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
NSLog (@PRODUCT ": Failed to create %s socket: %s", family_str, strerror (errno));
continue;
}
// Make the socket non-blocking
flags = fcntl (sockets[i], F_GETFL, NULL);
fcntl (sockets[i], F_SETFL, flags | O_NONBLOCK);
// Connect to the host
if ((rv = connect (sockets[i], (struct sockaddr *) sockaddr, len)) == 0) {
// connection completed, this is our man.
connected = i;
break;
}
if (rv < 0 && errno != EINPROGRESS) {
NSLog (@PRODUCT ": Failed to connect to %s on port %d: %s", ip, output_port, strerror (errno));
close (sockets[i]);
sockets[i] = -1;
continue;
}
// asynchronous connect
waiting++;
}
// Wait for async socket connections to become available
while (connected == -1 && waiting > 0) {
socklen_t optlen = sizeof (int);
fd_set rset, wset, xset;
struct timeval tv;
int max_fd = -1;
int error;
tv.tv_sec = 2;
tv.tv_usec = 0;
FD_ZERO (&rset);
FD_ZERO (&wset);
FD_ZERO (&xset);
for (i = 0; i < ip_count; i++) {
if (sockets[i] < 0)
continue;
max_fd = MAX (max_fd, sockets[i]);
FD_SET (sockets[i], &rset);
FD_SET (sockets[i], &wset);
FD_SET (sockets[i], &xset);
}
if ((rv = select (max_fd + 1, &rset, &wset, &xset, &tv)) == 0) {
// timeout hit, no connections available.
free (sockets);
return 1;
}
if (rv < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
// irrecoverable error
NSLog (@PRODUCT ": Error while waiting for connections: %s", strerror (errno));
free (sockets);
return 1;
}
for (i = 0; i < ip_count; i++) {
if (sockets[i] < 0)
continue;
if (FD_ISSET (sockets[i], &xset)) {
// exception on this socket
close (sockets[i]);
sockets[i] = -1;
waiting--;
continue;
}
if (!FD_ISSET (sockets[i], &rset) && !FD_ISSET (sockets[i], &wset)) {
// still waiting...
continue;
}
// okay, this socket is ready for reading or writing...
if (getsockopt (sockets[i], SOL_SOCKET, SO_ERROR, &error, &optlen) < 0) {
NSLog (@PRODUCT ": Error while trying to get socket options for %s: %s", [[ips objectAtIndex:i] UTF8String], strerror (errno));
close (sockets[i]);
sockets[i] = -1;
waiting--;
continue;
}
if (error != 0) {
NSLog (@PRODUCT ": Socket error while connecting to the IDE on %s:%d: %s", [[ips objectAtIndex:i] UTF8String], output_port, strerror (error));
close (sockets[i]);
sockets[i] = -1;
waiting--;
continue;
}
// success!
connected = i;
break;
}
}
if (connected == -1) {
free (sockets);
return 1;
}
// make the socket block on reads/writes
flags = fcntl (sockets[connected], F_GETFL, NULL);
fcntl (sockets[connected], F_SETFL, flags & ~O_NONBLOCK);
LOG (PRODUCT ": Connected output to the IDE on %s:%d\n", [[ips objectAtIndex:i] UTF8String], output_port);
dup2 (sockets[connected], 1);
dup2 (sockets[connected], 2);
debug_host = strdup ([[ips objectAtIndex:connected] UTF8String]);
// close the remaining sockets
for (i = 0; i < ip_count; i++) {
if (i == connected || sockets[i] < 0)
continue;
close (sockets[i]);
sockets[i] = -1;
}
free (sockets);
return 0;
}
#else
int fix_ranlib_warning_about_no_symbols_v2;
#endif /* DEBUG */