FileDocCategorySizeDatePackage
Debugger.javaAPI DocAndroid 1.5 API12259Wed May 06 22:41:08 BST 2009com.android.ddmlib

Debugger

public class Debugger extends Object
This represents a pending or established connection with a JDWP debugger.

Fields Summary
private static final int
INITIAL_BUF_SIZE
private static final int
MAX_BUF_SIZE
private ByteBuffer
mReadBuffer
private static final int
PRE_DATA_BUF_SIZE
private ByteBuffer
mPreDataBuffer
private int
mConnState
private static final int
ST_NOT_CONNECTED
private static final int
ST_AWAIT_SHAKE
private static final int
ST_READY
private Client
mClient
private int
mListenPort
private ServerSocketChannel
mListenChannel
private SocketChannel
mChannel
Constructors Summary
Debugger(Client client, int listenPort)
Create a new Debugger object, configured to listen for connections on a specific port.


                       
          

        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.i("ddms", "Created: " + this.toString());
    
Methods Summary
synchronized java.nio.channels.SocketChannelaccept()
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.

        return accept(mListenChannel);
    
synchronized java.nio.channels.SocketChannelaccept(java.nio.channels.ServerSocketChannel listenChan)
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.


        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;
    
synchronized voidclose()
Close the socket that's listening for new connections and (if we're connected) the debugger data socket.

        try {
            if (mListenChannel != null) {
                mListenChannel.close();
            }
            mListenChannel = null;
            closeData();
        } catch (IOException ioe) {
            Log.w("ddms", "Failed to close listener " + this);
        }
    
synchronized voidcloseData()
Close the data connection only.

        try {
            if (mChannel != null) {
                mChannel.close();
                mChannel = null;
                mConnState = ST_NOT_CONNECTED;

                ClientData cd = mClient.getClientData();
                cd.setDebuggerConnectionStatus(ClientData.DEBUGGER_DEFAULT);
                mClient.update(Client.CHANGE_DEBUGGER_INTEREST);
            }
        } catch (IOException ioe) {
            Log.w("ddms", "Failed to close data " + this);
        }
    
voidforwardPacketToClient(JdwpPacket packet)
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.

        mClient.sendAndConsume(packet);
    
ClientgetClient()
Return the Client being debugged.

        return mClient;
    
JdwpPacketgetJdwpPacket()
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.

        /*
         * 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.i("ddms", "Good handshake from debugger");
                    JdwpPacket.consumeHandshake(mReadBuffer);
                    sendHandshake();
                    mConnState = ST_READY;

                    ClientData cd = mClient.getClientData();
                    cd.setDebuggerConnectionStatus(ClientData.DEBUGGER_ATTACHED);
                    mClient.update(Client.CHANGE_DEBUGGER_INTEREST);

                    // 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.i("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;
    
booleanisDebuggerAttached()
Returns "true" if a debugger is currently attached to us.

        return mChannel != null;
    
voidread()
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.

        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");
    
voidregisterListener(java.nio.channels.Selector sel)
Register the debugger's listen socket with the Selector.

        mListenChannel.register(sel, SelectionKey.OP_ACCEPT, this);
    
synchronized voidsendAndConsume(JdwpPacket packet)
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.


        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);
        }
    
private synchronized voidsendHandshake()
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).

        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();
        }
    
public java.lang.StringtoString()
Represent the Debugger as a string.

        // mChannel != null means we have connection, ST_READY means it's going
        return "[Debugger " + mListenPort + "-->" + mClient.getClientData().getPid()
                + ((mConnState != ST_READY) ? " inactive]" : " active]");