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