• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ddmlib;
18 
19 import com.android.ddmlib.ClientData.DebuggerStatus;
20 
21 import java.io.IOException;
22 import java.net.InetAddress;
23 import java.net.InetSocketAddress;
24 import java.nio.BufferOverflowException;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.SelectionKey;
27 import java.nio.channels.Selector;
28 import java.nio.channels.ServerSocketChannel;
29 import java.nio.channels.SocketChannel;
30 
31 /**
32  * This represents a pending or established connection with a JDWP debugger.
33  */
34 class Debugger {
35 
36     /*
37      * Messages from the debugger should be pretty small; may not even
38      * need an expanding-buffer implementation for this.
39      */
40     private static final int INITIAL_BUF_SIZE = 1 * 1024;
41     private static final int MAX_BUF_SIZE = 32 * 1024;
42     private ByteBuffer mReadBuffer;
43 
44     private static final int PRE_DATA_BUF_SIZE = 256;
45     private ByteBuffer mPreDataBuffer;
46 
47     /* connection state */
48     private int mConnState;
49     private static final int ST_NOT_CONNECTED = 1;
50     private static final int ST_AWAIT_SHAKE   = 2;
51     private static final int ST_READY         = 3;
52 
53     /* peer */
54     private Client mClient;         // client we're forwarding to/from
55     private int mListenPort;        // listen to me
56     private ServerSocketChannel mListenChannel;
57 
58     /* this goes up and down; synchronize methods that access the field */
59     private SocketChannel mChannel;
60 
61     /**
62      * Create a new Debugger object, configured to listen for connections
63      * on a specific port.
64      */
Debugger(Client client, int listenPort)65     Debugger(Client client, int listenPort) throws IOException {
66 
67         mClient = client;
68         mListenPort = listenPort;
69 
70         mListenChannel = ServerSocketChannel.open();
71         mListenChannel.configureBlocking(false);        // required for Selector
72 
73         InetSocketAddress addr = new InetSocketAddress(
74                 InetAddress.getByName("localhost"), //$NON-NLS-1$
75                 listenPort);
76         mListenChannel.socket().setReuseAddress(true);  // enable SO_REUSEADDR
77         mListenChannel.socket().bind(addr);
78 
79         mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
80         mPreDataBuffer = ByteBuffer.allocate(PRE_DATA_BUF_SIZE);
81         mConnState = ST_NOT_CONNECTED;
82 
83         Log.d("ddms", "Created: " + this.toString());
84     }
85 
86     /**
87      * Returns "true" if a debugger is currently attached to us.
88      */
isDebuggerAttached()89     boolean isDebuggerAttached() {
90         return mChannel != null;
91     }
92 
93     /**
94      * Represent the Debugger as a string.
95      */
96     @Override
toString()97     public String toString() {
98         // mChannel != null means we have connection, ST_READY means it's going
99         return "[Debugger " + mListenPort + "-->" + mClient.getClientData().getPid()
100                 + ((mConnState != ST_READY) ? " inactive]" : " active]");
101     }
102 
103     /**
104      * Register the debugger's listen socket with the Selector.
105      */
registerListener(Selector sel)106     void registerListener(Selector sel) throws IOException {
107         mListenChannel.register(sel, SelectionKey.OP_ACCEPT, this);
108     }
109 
110     /**
111      * Return the Client being debugged.
112      */
getClient()113     Client getClient() {
114         return mClient;
115     }
116 
117     /**
118      * Accept a new connection, but only if we don't already have one.
119      *
120      * Must be synchronized with other uses of mChannel and mPreBuffer.
121      *
122      * Returns "null" if we're already talking to somebody.
123      */
accept()124     synchronized SocketChannel accept() throws IOException {
125         return accept(mListenChannel);
126     }
127 
128     /**
129      * Accept a new connection from the specified listen channel.  This
130      * is so we can listen on a dedicated port for the "current" client,
131      * where "current" is constantly in flux.
132      *
133      * Must be synchronized with other uses of mChannel and mPreBuffer.
134      *
135      * Returns "null" if we're already talking to somebody.
136      */
accept(ServerSocketChannel listenChan)137     synchronized SocketChannel accept(ServerSocketChannel listenChan)
138         throws IOException {
139 
140         if (listenChan != null) {
141             SocketChannel newChan;
142 
143             newChan = listenChan.accept();
144             if (mChannel != null) {
145                 Log.w("ddms", "debugger already talking to " + mClient
146                     + " on " + mListenPort);
147                 newChan.close();
148                 return null;
149             }
150             mChannel = newChan;
151             mChannel.configureBlocking(false);         // required for Selector
152             mConnState = ST_AWAIT_SHAKE;
153             return mChannel;
154         }
155 
156         return null;
157     }
158 
159     /**
160      * Close the data connection only.
161      */
closeData()162     synchronized void closeData() {
163         try {
164             if (mChannel != null) {
165                 mChannel.close();
166                 mChannel = null;
167                 mConnState = ST_NOT_CONNECTED;
168 
169                 ClientData cd = mClient.getClientData();
170                 cd.setDebuggerConnectionStatus(DebuggerStatus.DEFAULT);
171                 mClient.update(Client.CHANGE_DEBUGGER_STATUS);
172             }
173         } catch (IOException ioe) {
174             Log.w("ddms", "Failed to close data " + this);
175         }
176     }
177 
178     /**
179      * Close the socket that's listening for new connections and (if
180      * we're connected) the debugger data socket.
181      */
close()182     synchronized void close() {
183         try {
184             if (mListenChannel != null) {
185                 mListenChannel.close();
186             }
187             mListenChannel = null;
188             closeData();
189         } catch (IOException ioe) {
190             Log.w("ddms", "Failed to close listener " + this);
191         }
192     }
193 
194     // TODO: ?? add a finalizer that verifies the channel was closed
195 
196     /**
197      * Read data from our channel.
198      *
199      * This is called when data is known to be available, and we don't yet
200      * have a full packet in the buffer.  If the buffer is at capacity,
201      * expand it.
202      */
read()203     void read() throws IOException {
204         int count;
205 
206         if (mReadBuffer.position() == mReadBuffer.capacity()) {
207             if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
208                 throw new BufferOverflowException();
209             }
210             Log.d("ddms", "Expanding read buffer to "
211                 + mReadBuffer.capacity() * 2);
212 
213             ByteBuffer newBuffer =
214                     ByteBuffer.allocate(mReadBuffer.capacity() * 2);
215             mReadBuffer.position(0);
216             newBuffer.put(mReadBuffer);     // leaves "position" at end
217 
218             mReadBuffer = newBuffer;
219         }
220 
221         count = mChannel.read(mReadBuffer);
222         Log.v("ddms", "Read " + count + " bytes from " + this);
223         if (count < 0) throw new IOException("read failed");
224     }
225 
226     /**
227      * Return information for the first full JDWP packet in the buffer.
228      *
229      * If we don't yet have a full packet, return null.
230      *
231      * If we haven't yet received the JDWP handshake, we watch for it here
232      * and consume it without admitting to have done so.  We also send
233      * the handshake response to the debugger, along with any pending
234      * pre-connection data, which is why this can throw an IOException.
235      */
getJdwpPacket()236     JdwpPacket getJdwpPacket() throws IOException {
237         /*
238          * On entry, the data starts at offset 0 and ends at "position".
239          * "limit" is set to the buffer capacity.
240          */
241         if (mConnState == ST_AWAIT_SHAKE) {
242             int result;
243 
244             result = JdwpPacket.findHandshake(mReadBuffer);
245             //Log.v("ddms", "findHand: " + result);
246             switch (result) {
247                 case JdwpPacket.HANDSHAKE_GOOD:
248                     Log.d("ddms", "Good handshake from debugger");
249                     JdwpPacket.consumeHandshake(mReadBuffer);
250                     sendHandshake();
251                     mConnState = ST_READY;
252 
253                     ClientData cd = mClient.getClientData();
254                     cd.setDebuggerConnectionStatus(DebuggerStatus.ATTACHED);
255                     mClient.update(Client.CHANGE_DEBUGGER_STATUS);
256 
257                     // see if we have another packet in the buffer
258                     return getJdwpPacket();
259                 case JdwpPacket.HANDSHAKE_BAD:
260                     // not a debugger, throw an exception so we drop the line
261                     Log.d("ddms", "Bad handshake from debugger");
262                     throw new IOException("bad handshake");
263                 case JdwpPacket.HANDSHAKE_NOTYET:
264                     break;
265                 default:
266                     Log.e("ddms", "Unknown packet while waiting for client handshake");
267             }
268             return null;
269         } else if (mConnState == ST_READY) {
270             if (mReadBuffer.position() != 0) {
271                 Log.v("ddms", "Checking " + mReadBuffer.position() + " bytes");
272             }
273             return JdwpPacket.findPacket(mReadBuffer);
274         } else {
275             Log.e("ddms", "Receiving data in state = " + mConnState);
276         }
277 
278         return null;
279     }
280 
281     /**
282      * Forward a packet to the client.
283      *
284      * "mClient" will never be null, though it's possible that the channel
285      * in the client has closed and our send attempt will fail.
286      *
287      * Consumes the packet.
288      */
forwardPacketToClient(JdwpPacket packet)289     void forwardPacketToClient(JdwpPacket packet) throws IOException {
290         mClient.sendAndConsume(packet);
291     }
292 
293     /**
294      * Send the handshake to the debugger.  We also send along any packets
295      * we already received from the client (usually just a VM_START event,
296      * if anything at all).
297      */
sendHandshake()298     private synchronized void sendHandshake() throws IOException {
299         ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpPacket.HANDSHAKE_LEN);
300         JdwpPacket.putHandshake(tempBuffer);
301         int expectedLength = tempBuffer.position();
302         tempBuffer.flip();
303         if (mChannel.write(tempBuffer) != expectedLength) {
304             throw new IOException("partial handshake write");
305         }
306 
307         expectedLength = mPreDataBuffer.position();
308         if (expectedLength > 0) {
309             Log.d("ddms", "Sending " + mPreDataBuffer.position()
310                     + " bytes of saved data");
311             mPreDataBuffer.flip();
312             if (mChannel.write(mPreDataBuffer) != expectedLength) {
313                 throw new IOException("partial pre-data write");
314             }
315             mPreDataBuffer.clear();
316         }
317     }
318 
319     /**
320      * Send a packet to the debugger.
321      *
322      * Ideally, we can do this with a single channel write.  If that doesn't
323      * happen, we have to prevent anybody else from writing to the channel
324      * until this packet completes, so we synchronize on the channel.
325      *
326      * Another goal is to avoid unnecessary buffer copies, so we write
327      * directly out of the JdwpPacket's ByteBuffer.
328      *
329      * We must synchronize on "mChannel" before writing to it.  We want to
330      * coordinate the buffered data with mChannel creation, so this whole
331      * method is synchronized.
332      */
sendAndConsume(JdwpPacket packet)333     synchronized void sendAndConsume(JdwpPacket packet)
334         throws IOException {
335 
336         if (mChannel == null) {
337             /*
338              * Buffer this up so we can send it to the debugger when it
339              * finally does connect.  This is essential because the VM_START
340              * message might be telling the debugger that the VM is
341              * suspended.  The alternative approach would be for us to
342              * capture and interpret VM_START and send it later if we
343              * didn't choose to un-suspend the VM for our own purposes.
344              */
345             Log.d("ddms", "Saving packet 0x"
346                     + Integer.toHexString(packet.getId()));
347             packet.movePacket(mPreDataBuffer);
348         } else {
349             packet.writeAndConsume(mChannel);
350         }
351     }
352 }
353 
354