com.android.ddmlib.Debugger Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ddmlib Show documentation
Show all versions of ddmlib Show documentation
Library providing APIs to talk to Android devices
/*
* Copyright (C) 2007 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.
*/
package com.android.ddmlib;
import com.android.ddmlib.ClientData.DebuggerStatus;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* This represents a pending or established connection with a JDWP debugger.
*/
class Debugger {
/*
* Messages from the debugger should be pretty small; may not even
* need an expanding-buffer implementation for this.
*/
private static final int INITIAL_BUF_SIZE = 1 * 1024;
private static final int MAX_BUF_SIZE = 32 * 1024;
private ByteBuffer mReadBuffer;
private static final int PRE_DATA_BUF_SIZE = 256;
private ByteBuffer mPreDataBuffer;
/* connection state */
private int mConnState;
private static final int ST_NOT_CONNECTED = 1;
private static final int ST_AWAIT_SHAKE = 2;
private static final int ST_READY = 3;
/* peer */
private Client mClient; // client we're forwarding to/from
private int mListenPort; // listen to me
private ServerSocketChannel mListenChannel;
/* this goes up and down; synchronize methods that access the field */
private SocketChannel mChannel;
/**
* Create a new Debugger object, configured to listen for connections
* on a specific port.
*/
Debugger(Client client, int listenPort) throws IOException {
mClient = client;
mListenPort = listenPort;
mListenChannel = ServerSocketChannel.open();
mListenChannel.configureBlocking(false); // required for Selector
InetSocketAddress addr = new InetSocketAddress(
InetAddress.getByName("localhost"), //$NON-NLS-1$
listenPort);
mListenChannel.socket().setReuseAddress(true); // enable SO_REUSEADDR
mListenChannel.socket().bind(addr);
mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
mPreDataBuffer = ByteBuffer.allocate(PRE_DATA_BUF_SIZE);
mConnState = ST_NOT_CONNECTED;
Log.d("ddms", "Created: " + this.toString());
}
/**
* Returns "true" if a debugger is currently attached to us.
*/
boolean isDebuggerAttached() {
return mChannel != null;
}
/**
* Represent the Debugger as a string.
*/
@Override
public String toString() {
// mChannel != null means we have connection, ST_READY means it's going
return "[Debugger " + mListenPort + "-->" + mClient.getClientData().getPid()
+ ((mConnState != ST_READY) ? " inactive]" : " active]");
}
/**
* Register the debugger's listen socket with the Selector.
*/
void registerListener(Selector sel) throws IOException {
mListenChannel.register(sel, SelectionKey.OP_ACCEPT, this);
}
/**
* Return the Client being debugged.
*/
Client getClient() {
return mClient;
}
/**
* Accept a new connection, but only if we don't already have one.
*
* Must be synchronized with other uses of mChannel and mPreBuffer.
*
* Returns "null" if we're already talking to somebody.
*/
synchronized SocketChannel accept() throws IOException {
return accept(mListenChannel);
}
/**
* Accept a new connection from the specified listen channel. This
* is so we can listen on a dedicated port for the "current" client,
* where "current" is constantly in flux.
*
* Must be synchronized with other uses of mChannel and mPreBuffer.
*
* Returns "null" if we're already talking to somebody.
*/
synchronized SocketChannel accept(ServerSocketChannel listenChan)
throws IOException {
if (listenChan != null) {
SocketChannel newChan;
newChan = listenChan.accept();
if (mChannel != null) {
Log.w("ddms", "debugger already talking to " + mClient
+ " on " + mListenPort);
newChan.close();
return null;
}
mChannel = newChan;
mChannel.configureBlocking(false); // required for Selector
mConnState = ST_AWAIT_SHAKE;
return mChannel;
}
return null;
}
/**
* Close the data connection only.
*/
synchronized void closeData() {
try {
if (mChannel != null) {
mChannel.close();
mChannel = null;
mConnState = ST_NOT_CONNECTED;
ClientData cd = mClient.getClientData();
cd.setDebuggerConnectionStatus(DebuggerStatus.DEFAULT);
mClient.update(Client.CHANGE_DEBUGGER_STATUS);
}
} catch (IOException ioe) {
Log.w("ddms", "Failed to close data " + this);
}
}
/**
* Close the socket that's listening for new connections and (if
* we're connected) the debugger data socket.
*/
synchronized void close() {
try {
if (mListenChannel != null) {
mListenChannel.close();
}
mListenChannel = null;
closeData();
} catch (IOException ioe) {
Log.w("ddms", "Failed to close listener " + this);
}
}
// TODO: ?? add a finalizer that verifies the channel was closed
/**
* Read data from our channel.
*
* This is called when data is known to be available, and we don't yet
* have a full packet in the buffer. If the buffer is at capacity,
* expand it.
*/
void read() throws IOException {
int count;
if (mReadBuffer.position() == mReadBuffer.capacity()) {
if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
throw new BufferOverflowException();
}
Log.d("ddms", "Expanding read buffer to "
+ mReadBuffer.capacity() * 2);
ByteBuffer newBuffer =
ByteBuffer.allocate(mReadBuffer.capacity() * 2);
mReadBuffer.position(0);
newBuffer.put(mReadBuffer); // leaves "position" at end
mReadBuffer = newBuffer;
}
count = mChannel.read(mReadBuffer);
Log.v("ddms", "Read " + count + " bytes from " + this);
if (count < 0) throw new IOException("read failed");
}
/**
* Return information for the first full JDWP packet in the buffer.
*
* If we don't yet have a full packet, return null.
*
* If we haven't yet received the JDWP handshake, we watch for it here
* and consume it without admitting to have done so. We also send
* the handshake response to the debugger, along with any pending
* pre-connection data, which is why this can throw an IOException.
*/
JdwpPacket getJdwpPacket() throws IOException {
/*
* On entry, the data starts at offset 0 and ends at "position".
* "limit" is set to the buffer capacity.
*/
if (mConnState == ST_AWAIT_SHAKE) {
int result;
result = JdwpPacket.findHandshake(mReadBuffer);
//Log.v("ddms", "findHand: " + result);
switch (result) {
case JdwpPacket.HANDSHAKE_GOOD:
Log.d("ddms", "Good handshake from debugger");
JdwpPacket.consumeHandshake(mReadBuffer);
sendHandshake();
mConnState = ST_READY;
ClientData cd = mClient.getClientData();
cd.setDebuggerConnectionStatus(DebuggerStatus.ATTACHED);
mClient.update(Client.CHANGE_DEBUGGER_STATUS);
// see if we have another packet in the buffer
return getJdwpPacket();
case JdwpPacket.HANDSHAKE_BAD:
// not a debugger, throw an exception so we drop the line
Log.d("ddms", "Bad handshake from debugger");
throw new IOException("bad handshake");
case JdwpPacket.HANDSHAKE_NOTYET:
break;
default:
Log.e("ddms", "Unknown packet while waiting for client handshake");
}
return null;
} else if (mConnState == ST_READY) {
if (mReadBuffer.position() != 0) {
Log.v("ddms", "Checking " + mReadBuffer.position() + " bytes");
}
return JdwpPacket.findPacket(mReadBuffer);
} else {
Log.e("ddms", "Receiving data in state = " + mConnState);
}
return null;
}
/**
* Forward a packet to the client.
*
* "mClient" will never be null, though it's possible that the channel
* in the client has closed and our send attempt will fail.
*
* Consumes the packet.
*/
void forwardPacketToClient(JdwpPacket packet) throws IOException {
mClient.sendAndConsume(packet);
}
/**
* Send the handshake to the debugger. We also send along any packets
* we already received from the client (usually just a VM_START event,
* if anything at all).
*/
private synchronized void sendHandshake() throws IOException {
ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpPacket.HANDSHAKE_LEN);
JdwpPacket.putHandshake(tempBuffer);
int expectedLength = tempBuffer.position();
tempBuffer.flip();
if (mChannel.write(tempBuffer) != expectedLength) {
throw new IOException("partial handshake write");
}
expectedLength = mPreDataBuffer.position();
if (expectedLength > 0) {
Log.d("ddms", "Sending " + mPreDataBuffer.position()
+ " bytes of saved data");
mPreDataBuffer.flip();
if (mChannel.write(mPreDataBuffer) != expectedLength) {
throw new IOException("partial pre-data write");
}
mPreDataBuffer.clear();
}
}
/**
* Send a packet to the debugger.
*
* Ideally, we can do this with a single channel write. If that doesn't
* happen, we have to prevent anybody else from writing to the channel
* until this packet completes, so we synchronize on the channel.
*
* Another goal is to avoid unnecessary buffer copies, so we write
* directly out of the JdwpPacket's ByteBuffer.
*
* We must synchronize on "mChannel" before writing to it. We want to
* coordinate the buffered data with mChannel creation, so this whole
* method is synchronized.
*/
synchronized void sendAndConsume(JdwpPacket packet)
throws IOException {
if (mChannel == null) {
/*
* Buffer this up so we can send it to the debugger when it
* finally does connect. This is essential because the VM_START
* message might be telling the debugger that the VM is
* suspended. The alternative approach would be for us to
* capture and interpret VM_START and send it later if we
* didn't choose to un-suspend the VM for our own purposes.
*/
Log.d("ddms", "Saving packet 0x"
+ Integer.toHexString(packet.getId()));
packet.movePacket(mPreDataBuffer);
} else {
packet.writeAndConsume(mChannel);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy