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 java.io.IOException; 20 import java.nio.ByteBuffer; 21 22 /** 23 * Handle thread status updates. 24 */ 25 final class HandleThread extends ChunkHandler { 26 27 public static final int CHUNK_THEN = type("THEN"); 28 public static final int CHUNK_THCR = type("THCR"); 29 public static final int CHUNK_THDE = type("THDE"); 30 public static final int CHUNK_THST = type("THST"); 31 public static final int CHUNK_THNM = type("THNM"); 32 public static final int CHUNK_STKL = type("STKL"); 33 34 private static final HandleThread mInst = new HandleThread(); 35 36 // only read/written by requestThreadUpdates() 37 private static volatile boolean mThreadStatusReqRunning = false; 38 private static volatile boolean mThreadStackTraceReqRunning = false; 39 HandleThread()40 private HandleThread() {} 41 42 43 /** 44 * Register for the packets we expect to get from the client. 45 */ register(MonitorThread mt)46 public static void register(MonitorThread mt) { 47 mt.registerChunkHandler(CHUNK_THCR, mInst); 48 mt.registerChunkHandler(CHUNK_THDE, mInst); 49 mt.registerChunkHandler(CHUNK_THST, mInst); 50 mt.registerChunkHandler(CHUNK_THNM, mInst); 51 mt.registerChunkHandler(CHUNK_STKL, mInst); 52 } 53 54 /** 55 * Client is ready. 56 */ 57 @Override clientReady(Client client)58 public void clientReady(Client client) throws IOException { 59 Log.d("ddm-thread", "Now ready: " + client); 60 if (client.isThreadUpdateEnabled()) 61 sendTHEN(client, true); 62 } 63 64 /** 65 * Client went away. 66 */ 67 @Override clientDisconnected(Client client)68 public void clientDisconnected(Client client) {} 69 70 /** 71 * Chunk handler entry point. 72 */ 73 @Override handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId)74 public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) { 75 76 Log.d("ddm-thread", "handling " + ChunkHandler.name(type)); 77 78 if (type == CHUNK_THCR) { 79 handleTHCR(client, data); 80 } else if (type == CHUNK_THDE) { 81 handleTHDE(client, data); 82 } else if (type == CHUNK_THST) { 83 handleTHST(client, data); 84 } else if (type == CHUNK_THNM) { 85 handleTHNM(client, data); 86 } else if (type == CHUNK_STKL) { 87 handleSTKL(client, data); 88 } else { 89 handleUnknownChunk(client, type, data, isReply, msgId); 90 } 91 } 92 93 /* 94 * Handle a thread creation message. 95 * 96 * We should be tolerant of receiving a duplicate create message. (It 97 * shouldn't happen with the current implementation.) 98 */ handleTHCR(Client client, ByteBuffer data)99 private void handleTHCR(Client client, ByteBuffer data) { 100 int threadId, nameLen; 101 String name; 102 103 threadId = data.getInt(); 104 nameLen = data.getInt(); 105 name = getString(data, nameLen); 106 107 Log.v("ddm-thread", "THCR: " + threadId + " '" + name + "'"); 108 109 client.getClientData().addThread(threadId, name); 110 client.update(Client.CHANGE_THREAD_DATA); 111 } 112 113 /* 114 * Handle a thread death message. 115 */ handleTHDE(Client client, ByteBuffer data)116 private void handleTHDE(Client client, ByteBuffer data) { 117 int threadId; 118 119 threadId = data.getInt(); 120 Log.v("ddm-thread", "THDE: " + threadId); 121 122 client.getClientData().removeThread(threadId); 123 client.update(Client.CHANGE_THREAD_DATA); 124 } 125 126 /* 127 * Handle a thread status update message. 128 * 129 * Response has: 130 * (1b) header len 131 * (1b) bytes per entry 132 * (2b) thread count 133 * Then, for each thread: 134 * (4b) threadId (matches value from THCR) 135 * (1b) thread status 136 * (4b) tid 137 * (4b) utime 138 * (4b) stime 139 */ handleTHST(Client client, ByteBuffer data)140 private void handleTHST(Client client, ByteBuffer data) { 141 int headerLen, bytesPerEntry, extraPerEntry; 142 int threadCount; 143 144 headerLen = (data.get() & 0xff); 145 bytesPerEntry = (data.get() & 0xff); 146 threadCount = data.getShort(); 147 148 headerLen -= 4; // we've read 4 bytes 149 while (headerLen-- > 0) 150 data.get(); 151 152 extraPerEntry = bytesPerEntry - 18; // we want 18 bytes 153 154 Log.v("ddm-thread", "THST: threadCount=" + threadCount); 155 156 /* 157 * For each thread, extract the data, find the appropriate 158 * client, and add it to the ClientData. 159 */ 160 for (int i = 0; i < threadCount; i++) { 161 int threadId, status, tid, utime, stime; 162 boolean isDaemon = false; 163 164 threadId = data.getInt(); 165 status = data.get(); 166 tid = data.getInt(); 167 utime = data.getInt(); 168 stime = data.getInt(); 169 if (bytesPerEntry >= 18) 170 isDaemon = (data.get() != 0); 171 172 Log.v("ddm-thread", " id=" + threadId 173 + ", status=" + status + ", tid=" + tid 174 + ", utime=" + utime + ", stime=" + stime); 175 176 ClientData cd = client.getClientData(); 177 ThreadInfo threadInfo = cd.getThread(threadId); 178 if (threadInfo != null) 179 threadInfo.updateThread(status, tid, utime, stime, isDaemon); 180 else 181 Log.d("ddms", "Thread with id=" + threadId + " not found"); 182 183 // slurp up any extra 184 for (int slurp = extraPerEntry; slurp > 0; slurp--) 185 data.get(); 186 } 187 188 client.update(Client.CHANGE_THREAD_DATA); 189 } 190 191 /* 192 * Handle a THNM (THread NaMe) message. We get one of these after 193 * somebody calls Thread.setName() on a running thread. 194 */ handleTHNM(Client client, ByteBuffer data)195 private void handleTHNM(Client client, ByteBuffer data) { 196 int threadId, nameLen; 197 String name; 198 199 threadId = data.getInt(); 200 nameLen = data.getInt(); 201 name = getString(data, nameLen); 202 203 Log.v("ddm-thread", "THNM: " + threadId + " '" + name + "'"); 204 205 ThreadInfo threadInfo = client.getClientData().getThread(threadId); 206 if (threadInfo != null) { 207 threadInfo.setThreadName(name); 208 client.update(Client.CHANGE_THREAD_DATA); 209 } else { 210 Log.d("ddms", "Thread with id=" + threadId + " not found"); 211 } 212 } 213 214 215 /** 216 * Parse an incoming STKL. 217 */ handleSTKL(Client client, ByteBuffer data)218 private void handleSTKL(Client client, ByteBuffer data) { 219 StackTraceElement[] trace; 220 int i, threadId, stackDepth; 221 @SuppressWarnings("unused") 222 int future; 223 224 future = data.getInt(); 225 threadId = data.getInt(); 226 227 Log.v("ddms", "STKL: " + threadId); 228 229 /* un-serialize the StackTraceElement[] */ 230 stackDepth = data.getInt(); 231 trace = new StackTraceElement[stackDepth]; 232 for (i = 0; i < stackDepth; i++) { 233 String className, methodName, fileName; 234 int len, lineNumber; 235 236 len = data.getInt(); 237 className = getString(data, len); 238 len = data.getInt(); 239 methodName = getString(data, len); 240 len = data.getInt(); 241 if (len == 0) { 242 fileName = null; 243 } else { 244 fileName = getString(data, len); 245 } 246 lineNumber = data.getInt(); 247 248 trace[i] = new StackTraceElement(className, methodName, fileName, 249 lineNumber); 250 } 251 252 ThreadInfo threadInfo = client.getClientData().getThread(threadId); 253 if (threadInfo != null) { 254 threadInfo.setStackCall(trace); 255 client.update(Client.CHANGE_THREAD_STACKTRACE); 256 } else { 257 Log.d("STKL", String.format( 258 "Got stackcall for thread %1$d, which does not exists (anymore?).", //$NON-NLS-1$ 259 threadId)); 260 } 261 } 262 263 264 /** 265 * Send a THEN (THread notification ENable) request to the client. 266 */ sendTHEN(Client client, boolean enable)267 public static void sendTHEN(Client client, boolean enable) 268 throws IOException { 269 270 ByteBuffer rawBuf = allocBuffer(1); 271 JdwpPacket packet = new JdwpPacket(rawBuf); 272 ByteBuffer buf = getChunkDataBuf(rawBuf); 273 274 if (enable) 275 buf.put((byte)1); 276 else 277 buf.put((byte)0); 278 279 finishChunkPacket(packet, CHUNK_THEN, buf.position()); 280 Log.d("ddm-thread", "Sending " + name(CHUNK_THEN) + ": " + enable); 281 client.sendAndConsume(packet, mInst); 282 } 283 284 285 /** 286 * Send a STKL (STacK List) request to the client. The VM will suspend 287 * the target thread, obtain its stack, and return it. If the thread 288 * is no longer running, a failure result will be returned. 289 */ sendSTKL(Client client, int threadId)290 public static void sendSTKL(Client client, int threadId) 291 throws IOException { 292 293 if (false) { 294 Log.d("ddm-thread", "would send STKL " + threadId); 295 return; 296 } 297 298 ByteBuffer rawBuf = allocBuffer(4); 299 JdwpPacket packet = new JdwpPacket(rawBuf); 300 ByteBuffer buf = getChunkDataBuf(rawBuf); 301 302 buf.putInt(threadId); 303 304 finishChunkPacket(packet, CHUNK_STKL, buf.position()); 305 Log.d("ddm-thread", "Sending " + name(CHUNK_STKL) + ": " + threadId); 306 client.sendAndConsume(packet, mInst); 307 } 308 309 310 /** 311 * This is called periodically from the UI thread. To avoid locking 312 * the UI while we request the updates, we create a new thread. 313 * 314 */ requestThreadUpdate(final Client client)315 static void requestThreadUpdate(final Client client) { 316 if (client.isDdmAware() && client.isThreadUpdateEnabled()) { 317 if (mThreadStatusReqRunning) { 318 Log.w("ddms", "Waiting for previous thread update req to finish"); 319 return; 320 } 321 322 new Thread("Thread Status Req") { 323 @Override 324 public void run() { 325 mThreadStatusReqRunning = true; 326 try { 327 sendTHST(client); 328 } catch (IOException ioe) { 329 Log.d("ddms", "Unable to request thread updates from " 330 + client + ": " + ioe.getMessage()); 331 } finally { 332 mThreadStatusReqRunning = false; 333 } 334 } 335 }.start(); 336 } 337 } 338 requestThreadStackCallRefresh(final Client client, final int threadId)339 static void requestThreadStackCallRefresh(final Client client, final int threadId) { 340 if (client.isDdmAware() && client.isThreadUpdateEnabled()) { 341 if (mThreadStackTraceReqRunning ) { 342 Log.w("ddms", "Waiting for previous thread stack call req to finish"); 343 return; 344 } 345 346 new Thread("Thread Status Req") { 347 @Override 348 public void run() { 349 mThreadStackTraceReqRunning = true; 350 try { 351 sendSTKL(client, threadId); 352 } catch (IOException ioe) { 353 Log.d("ddms", "Unable to request thread stack call updates from " 354 + client + ": " + ioe.getMessage()); 355 } finally { 356 mThreadStackTraceReqRunning = false; 357 } 358 } 359 }.start(); 360 } 361 362 } 363 364 /* 365 * Send a THST request to the specified client. 366 */ sendTHST(Client client)367 private static void sendTHST(Client client) throws IOException { 368 ByteBuffer rawBuf = allocBuffer(0); 369 JdwpPacket packet = new JdwpPacket(rawBuf); 370 ByteBuffer buf = getChunkDataBuf(rawBuf); 371 372 // nothing much to say 373 374 finishChunkPacket(packet, CHUNK_THST, buf.position()); 375 Log.d("ddm-thread", "Sending " + name(CHUNK_THST)); 376 client.sendAndConsume(packet, mInst); 377 } 378 } 379 380