Add missing \n to uses of legacy D() macro. This should make the legacy logging easier to read (and harder to miss important stuff). On POSIX, use gettid() from libcutils instead of pthread_self() so that the output shows a more reasonable number instead of a pointer value. This should be ok since libbase's logging already uses gettid(). Win32: Don't let the Win32 last error get overwritten by API calls after the original error'ing API. When encountering an unknown error, log the specific error code. Change-Id: Ib8f72754efa7ba895d2f1cd914251fec2a1d894c Signed-off-by: Spencer Low <CompareAndSwap@gmail.com>
752 lines
20 KiB
C++
752 lines
20 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* implement the "debug-ports" and "track-debug-ports" device services */
|
|
|
|
#define TRACE_TAG TRACE_JDWP
|
|
|
|
#include "sysdeps.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "adb.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>
|
|
|
|
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 = reinterpret_cast<JdwpProcess*>(
|
|
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 = reinterpret_cast<JdwpProcess*>(_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)];
|
|
int flags;
|
|
|
|
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;
|
|
|
|
flags = fcntl(proc->socket,F_GETFL,0);
|
|
|
|
if (flags == -1) {
|
|
D("failed to get cntl flags for socket %d: %s\n",
|
|
proc->pid, strerror(errno));
|
|
goto CloseProcess;
|
|
|
|
}
|
|
|
|
if (fcntl(proc->socket, F_SETFL, flags & ~O_NONBLOCK) == -1) {
|
|
D("failed to remove O_NONBLOCK flag for socket %d: %s\n",
|
|
proc->pid, strerror(errno));
|
|
goto CloseProcess;
|
|
}
|
|
|
|
for (;;) {
|
|
ret = sendmsg(proc->socket, &msg, 0);
|
|
if (ret >= 0) {
|
|
adb_close(fd);
|
|
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 (fcntl(proc->socket, F_SETFL, flags) == -1) {
|
|
D("failed to set O_NONBLOCK flag for socket %d: %s\n",
|
|
proc->pid, strerror(errno));
|
|
goto CloseProcess;
|
|
}
|
|
|
|
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;
|
|
}
|
|
D("socketpair: (%d,%d)\n", fds[0], fds[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)
|
|
|
|
struct JdwpControl {
|
|
int listen_socket;
|
|
fdevent* fde;
|
|
};
|
|
|
|
|
|
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);
|
|
close_on_exec(s);
|
|
|
|
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
|
|
**/
|
|
|
|
struct JdwpSocket {
|
|
asocket socket;
|
|
int pass;
|
|
};
|
|
|
|
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 = reinterpret_cast<JdwpSocket*>(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...
|
|
**/
|
|
|
|
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 = reinterpret_cast<JdwpTracker*>(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 */
|