/* implement the "debug-ports" and "track-debug-ports" device services */ #include "sysdeps.h" #define TRACE_TAG TRACE_JDWP #include "adb.h" #include <errno.h> #include <stdio.h> #include <string.h> /* here's how these things work. when adbd starts, it creates a unix server socket named @vm-debug-control (@ is a shortcut for "first byte is zero" to use the private namespace instead of the file system) when a new JDWP daemon thread starts in a new VM process, it creates a connection to @vm-debug-control to announce its availability. JDWP thread @vm-debug-control | | |-------------------------------> | | hello I'm in process <pid> | | | | | the connection is kept alive. it will be closed automatically if the JDWP process terminates (this allows adbd to detect dead processes). adbd thus maintains a list of "active" JDWP processes. it can send its content to clients through the "device:debug-ports" service, or even updates through the "device:track-debug-ports" service. when a debugger wants to connect, it simply runs the command equivalent to "adb forward tcp:<hostport> jdwp:<pid>" "jdwp:<pid>" is a new forward destination format used to target a given JDWP process on the device. when sutch a request arrives, adbd does the following: - first, it calls socketpair() to create a pair of equivalent sockets. - it attaches the first socket in the pair to a local socket which is itself attached to the transport's remote socket: - it sends the file descriptor of the second socket directly to the JDWP process with the help of sendmsg() JDWP thread @vm-debug-control | | | <----------------------| | OK, try this file descriptor | | | | | then, the JDWP thread uses this new socket descriptor as its pass-through connection to the debugger (and receives the JDWP-Handshake message, answers to it, etc...) this gives the following graphics: ____________________________________ | | | ADB Server (host) | | | Debugger <---> LocalSocket <----> RemoteSocket | | ^^ | |___________________________||_______| || Transport || (TCP for emulator - USB for device) || || ___________________________||_______ | || | | ADBD (device) || | | VV | JDWP <======> LocalSocket <----> RemoteSocket | | | |____________________________________| due to the way adb works, this doesn't need a special socket type or fancy handling of socket termination if either the debugger or the JDWP process closes the connection. THIS IS THE SIMPLEST IMPLEMENTATION I COULD FIND, IF YOU HAPPEN TO HAVE A BETTER IDEA, LET ME KNOW - Digit **********************************************************************/ /** JDWP PID List Support Code ** for each JDWP process, we record its pid and its connected socket **/ #define MAX_OUT_FDS 4 #if !ADB_HOST #include <sys/socket.h> #include <sys/un.h> typedef struct JdwpProcess JdwpProcess; struct JdwpProcess { JdwpProcess* next; JdwpProcess* prev; int pid; int socket; fdevent* fde; char in_buff[4]; /* input character to read PID */ int in_len; /* number from JDWP process */ int out_fds[MAX_OUT_FDS]; /* output array of file descriptors */ int out_count; /* to send to the JDWP process */ }; static JdwpProcess _jdwp_list; static int jdwp_process_list( char* buffer, int bufferlen ) { char* end = buffer + bufferlen; char* p = buffer; JdwpProcess* proc = _jdwp_list.next; for ( ; proc != &_jdwp_list; proc = proc->next ) { int len; /* skip transient connections */ if (proc->pid < 0) continue; len = snprintf(p, end-p, "%d\n", proc->pid); if (p + len >= end) break; p += len; } p[0] = 0; return (p - buffer); } static int jdwp_process_list_msg( char* buffer, int bufferlen ) { char head[5]; int len = jdwp_process_list( buffer+4, bufferlen-4 ); snprintf(head, sizeof head, "%04x", len); memcpy(buffer, head, 4); return len + 4; } static void jdwp_process_list_updated(void); static void jdwp_process_free( JdwpProcess* proc ) { if (proc) { int n; proc->prev->next = proc->next; proc->next->prev = proc->prev; if (proc->socket >= 0) { adb_shutdown(proc->socket); adb_close(proc->socket); proc->socket = -1; } if (proc->fde != NULL) { fdevent_destroy(proc->fde); proc->fde = NULL; } proc->pid = -1; for (n = 0; n < proc->out_count; n++) { adb_close(proc->out_fds[n]); } proc->out_count = 0; free(proc); jdwp_process_list_updated(); } } static void jdwp_process_event(int, unsigned, void*); /* forward */ static JdwpProcess* jdwp_process_alloc( int socket ) { JdwpProcess* proc = calloc(1,sizeof(*proc)); if (proc == NULL) { D("not enough memory to create new JDWP process\n"); return NULL; } proc->socket = socket; proc->pid = -1; proc->next = proc; proc->prev = proc; proc->fde = fdevent_create( socket, jdwp_process_event, proc ); if (proc->fde == NULL) { D("could not create fdevent for new JDWP process\n" ); free(proc); return NULL; } proc->fde->state |= FDE_DONT_CLOSE; proc->in_len = 0; proc->out_count = 0; /* append to list */ proc->next = &_jdwp_list; proc->prev = proc->next->prev; proc->prev->next = proc; proc->next->prev = proc; /* start by waiting for the PID */ fdevent_add(proc->fde, FDE_READ); return proc; } static void jdwp_process_event( int socket, unsigned events, void* _proc ) { JdwpProcess* proc = _proc; if (events & FDE_READ) { if (proc->pid < 0) { /* read the PID as a 4-hexchar string */ char* p = proc->in_buff + proc->in_len; int size = 4 - proc->in_len; char temp[5]; while (size > 0) { int len = recv( socket, p, size, 0 ); if (len < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) return; /* this can fail here if the JDWP process crashes very fast */ D("weird unknown JDWP process failure: %s\n", strerror(errno)); goto CloseProcess; } if (len == 0) { /* end of stream ? */ D("weird end-of-stream from unknown JDWP process\n"); goto CloseProcess; } p += len; proc->in_len += len; size -= len; } /* we have read 4 characters, now decode the pid */ memcpy(temp, proc->in_buff, 4); temp[4] = 0; if (sscanf( temp, "%04x", &proc->pid ) != 1) { D("could not decode JDWP %p PID number: '%s'\n", proc, temp); goto CloseProcess; } /* all is well, keep reading to detect connection closure */ D("Adding pid %d to jdwp process list\n", proc->pid); jdwp_process_list_updated(); } else { /* the pid was read, if we get there it's probably because the connection * was closed (e.g. the JDWP process exited or crashed) */ char buf[32]; for (;;) { int len = recv(socket, buf, sizeof(buf), 0); if (len <= 0) { if (len < 0 && errno == EINTR) continue; if (len < 0 && errno == EAGAIN) return; else { D("terminating JDWP %d connection: %s\n", proc->pid, strerror(errno)); break; } } else { D( "ignoring unexpected JDWP %d control socket activity (%d bytes)\n", proc->pid, len ); } } CloseProcess: if (proc->pid >= 0) D( "remove pid %d to jdwp process list\n", proc->pid ); jdwp_process_free(proc); return; } } if (events & FDE_WRITE) { D("trying to write to JDWP pid controli (count=%d first=%d) %d\n", proc->pid, proc->out_count, proc->out_fds[0]); if (proc->out_count > 0) { int fd = proc->out_fds[0]; int n, ret; struct cmsghdr* cmsg; struct msghdr msg; struct iovec iov; char dummy = '!'; char buffer[sizeof(struct cmsghdr) + sizeof(int)]; iov.iov_base = &dummy; iov.iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = buffer; msg.msg_controllen = sizeof(buffer); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = msg.msg_controllen; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; ((int*)CMSG_DATA(cmsg))[0] = fd; for (;;) { ret = sendmsg(proc->socket, &msg, 0); if (ret >= 0) break; if (errno == EINTR) continue; D("sending new file descriptor to JDWP %d failed: %s\n", proc->pid, strerror(errno)); goto CloseProcess; } D("sent file descriptor %d to JDWP process %d\n", fd, proc->pid); for (n = 1; n < proc->out_count; n++) proc->out_fds[n-1] = proc->out_fds[n]; if (--proc->out_count == 0) fdevent_del( proc->fde, FDE_WRITE ); } } } int create_jdwp_connection_fd(int pid) { JdwpProcess* proc = _jdwp_list.next; D("looking for pid %d in JDWP process list\n", pid); for ( ; proc != &_jdwp_list; proc = proc->next ) { if (proc->pid == pid) { goto FoundIt; } } D("search failed !!\n"); return -1; FoundIt: { int fds[2]; if (proc->out_count >= MAX_OUT_FDS) { D("%s: too many pending JDWP connection for pid %d\n", __FUNCTION__, pid); return -1; } if (adb_socketpair(fds) < 0) { D("%s: socket pair creation failed: %s\n", __FUNCTION__, strerror(errno)); return -1; } proc->out_fds[ proc->out_count ] = fds[1]; if (++proc->out_count == 1) fdevent_add( proc->fde, FDE_WRITE ); return fds[0]; } } /** VM DEBUG CONTROL SOCKET ** ** we do implement a custom asocket to receive the data **/ /* name of the debug control Unix socket */ #define JDWP_CONTROL_NAME "\0jdwp-control" #define JDWP_CONTROL_NAME_LEN (sizeof(JDWP_CONTROL_NAME)-1) typedef struct { int listen_socket; fdevent* fde; } JdwpControl; static void jdwp_control_event(int s, unsigned events, void* user); static int jdwp_control_init( JdwpControl* control, const char* sockname, int socknamelen ) { struct sockaddr_un addr; socklen_t addrlen; int s; int maxpath = sizeof(addr.sun_path); int pathlen = socknamelen; if (pathlen >= maxpath) { D( "vm debug control socket name too long (%d extra chars)\n", pathlen+1-maxpath ); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; memcpy(addr.sun_path, sockname, socknamelen); s = socket( AF_UNIX, SOCK_STREAM, 0 ); if (s < 0) { D( "could not create vm debug control socket. %d: %s\n", errno, strerror(errno)); return -1; } addrlen = (pathlen + sizeof(addr.sun_family)); if (bind(s, (struct sockaddr*)&addr, addrlen) < 0) { D( "could not bind vm debug control socket: %d: %s\n", errno, strerror(errno) ); adb_close(s); return -1; } if ( listen(s, 4) < 0 ) { D("listen failed in jdwp control socket: %d: %s\n", errno, strerror(errno)); adb_close(s); return -1; } control->listen_socket = s; control->fde = fdevent_create(s, jdwp_control_event, control); if (control->fde == NULL) { D( "could not create fdevent for jdwp control socket\n" ); adb_close(s); return -1; } /* only wait for incoming connections */ fdevent_add(control->fde, FDE_READ); D("jdwp control socket started (%d)\n", control->listen_socket); return 0; } static void jdwp_control_event( int s, unsigned events, void* _control ) { JdwpControl* control = (JdwpControl*) _control; if (events & FDE_READ) { struct sockaddr addr; socklen_t addrlen = sizeof(addr); int s = -1; JdwpProcess* proc; do { s = adb_socket_accept( control->listen_socket, &addr, &addrlen ); if (s < 0) { if (errno == EINTR) continue; if (errno == ECONNABORTED) { /* oops, the JDWP process died really quick */ D("oops, the JDWP process died really quick\n"); return; } /* the socket is probably closed ? */ D( "weird accept() failed on jdwp control socket: %s\n", strerror(errno) ); return; } } while (s < 0); proc = jdwp_process_alloc( s ); if (proc == NULL) return; } } static JdwpControl _jdwp_control; /** "jdwp" local service implementation ** this simply returns the list of known JDWP process pids **/ typedef struct { asocket socket; int pass; } JdwpSocket; static void jdwp_socket_close( asocket* s ) { asocket* peer = s->peer; remove_socket(s); if (peer) { peer->peer = NULL; peer->close(peer); } free(s); } static int jdwp_socket_enqueue( asocket* s, apacket* p ) { /* you can't write to this asocket */ put_apacket(p); s->peer->close(s->peer); return -1; } static void jdwp_socket_ready( asocket* s ) { JdwpSocket* jdwp = (JdwpSocket*)s; asocket* peer = jdwp->socket.peer; /* on the first call, send the list of pids, * on the second one, close the connection */ if (jdwp->pass == 0) { apacket* p = get_apacket(); p->len = jdwp_process_list((char*)p->data, MAX_PAYLOAD); peer->enqueue(peer, p); jdwp->pass = 1; } else { peer->close(peer); } } asocket* create_jdwp_service_socket( void ) { JdwpSocket* s = calloc(sizeof(*s),1); if (s == NULL) return NULL; install_local_socket(&s->socket); s->socket.ready = jdwp_socket_ready; s->socket.enqueue = jdwp_socket_enqueue; s->socket.close = jdwp_socket_close; s->pass = 0; return &s->socket; } /** "track-jdwp" local service implementation ** this periodically sends the list of known JDWP process pids ** to the client... **/ typedef struct JdwpTracker JdwpTracker; struct JdwpTracker { asocket socket; JdwpTracker* next; JdwpTracker* prev; int need_update; }; static JdwpTracker _jdwp_trackers_list; static void jdwp_process_list_updated(void) { char buffer[1024]; int len; JdwpTracker* t = _jdwp_trackers_list.next; len = jdwp_process_list_msg(buffer, sizeof(buffer)); for ( ; t != &_jdwp_trackers_list; t = t->next ) { apacket* p = get_apacket(); asocket* peer = t->socket.peer; memcpy(p->data, buffer, len); p->len = len; peer->enqueue( peer, p ); } } static void jdwp_tracker_close( asocket* s ) { JdwpTracker* tracker = (JdwpTracker*) s; asocket* peer = s->peer; if (peer) { peer->peer = NULL; peer->close(peer); } remove_socket(s); tracker->prev->next = tracker->next; tracker->next->prev = tracker->prev; free(s); } static void jdwp_tracker_ready( asocket* s ) { JdwpTracker* t = (JdwpTracker*) s; if (t->need_update) { apacket* p = get_apacket(); t->need_update = 0; p->len = jdwp_process_list_msg((char*)p->data, sizeof(p->data)); s->peer->enqueue(s->peer, p); } } static int jdwp_tracker_enqueue( asocket* s, apacket* p ) { /* you can't write to this socket */ put_apacket(p); s->peer->close(s->peer); return -1; } asocket* create_jdwp_tracker_service_socket( void ) { JdwpTracker* t = calloc(sizeof(*t),1); if (t == NULL) return NULL; t->next = &_jdwp_trackers_list; t->prev = t->next->prev; t->next->prev = t; t->prev->next = t; install_local_socket(&t->socket); t->socket.ready = jdwp_tracker_ready; t->socket.enqueue = jdwp_tracker_enqueue; t->socket.close = jdwp_tracker_close; t->need_update = 1; return &t->socket; } int init_jdwp(void) { _jdwp_list.next = &_jdwp_list; _jdwp_list.prev = &_jdwp_list; _jdwp_trackers_list.next = &_jdwp_trackers_list; _jdwp_trackers_list.prev = &_jdwp_trackers_list; return jdwp_control_init( &_jdwp_control, JDWP_CONTROL_NAME, JDWP_CONTROL_NAME_LEN ); } #endif /* !ADB_HOST */