All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.ddmlib.Client Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * 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.annotations.NonNull;
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.jdwp.JdwpAgent;
import com.android.ddmlib.jdwp.JdwpProtocol;

import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

/**
 * This represents a single client, usually a Dalvik VM process.
 * 

This class gives access to basic client information, as well as methods to perform actions * on the client. *

More detailed information, usually updated in real time, can be access through the * {@link ClientData} class. Each Client object has its own ClientData * accessed through {@link #getClientData()}. */ public class Client extends JdwpAgent { private static final int SERVER_PROTOCOL_VERSION = 1; /** Client change bit mask: application name change */ public static final int CHANGE_NAME = 0x0001; /** Client change bit mask: debugger status change */ public static final int CHANGE_DEBUGGER_STATUS = 0x0002; /** Client change bit mask: debugger port change */ public static final int CHANGE_PORT = 0x0004; /** Client change bit mask: thread update flag change */ public static final int CHANGE_THREAD_MODE = 0x0008; /** Client change bit mask: thread data updated */ public static final int CHANGE_THREAD_DATA = 0x0010; /** Client change bit mask: heap update flag change */ public static final int CHANGE_HEAP_MODE = 0x0020; /** Client change bit mask: head data updated */ public static final int CHANGE_HEAP_DATA = 0x0040; /** Client change bit mask: native heap data updated */ public static final int CHANGE_NATIVE_HEAP_DATA = 0x0080; /** Client change bit mask: thread stack trace updated */ public static final int CHANGE_THREAD_STACKTRACE = 0x0100; /** Client change bit mask: allocation information updated */ public static final int CHANGE_HEAP_ALLOCATIONS = 0x0200; /** Client change bit mask: allocation information updated */ public static final int CHANGE_HEAP_ALLOCATION_STATUS = 0x0400; /** Client change bit mask: allocation information updated */ public static final int CHANGE_METHOD_PROFILING_STATUS = 0x0800; /** Client change bit mask: hprof data updated */ public static final int CHANGE_HPROF = 0x1000; /** Client change bit mask: combination of {@link Client#CHANGE_NAME}, * {@link Client#CHANGE_DEBUGGER_STATUS}, and {@link Client#CHANGE_PORT}. */ public static final int CHANGE_INFO = CHANGE_NAME | CHANGE_DEBUGGER_STATUS | CHANGE_PORT; private SocketChannel mChan; // debugger we're associated with, if any private Debugger mDebugger; private int mDebuggerListenPort; // chunk handlers stash state data in here private ClientData mClientData; // User interface state. Changing the value causes a message to be // sent to the client. private boolean mThreadUpdateEnabled; private boolean mHeapInfoUpdateEnabled; private boolean mHeapSegmentUpdateEnabled; /* * Read/write buffers. We can get large quantities of data from the * client, e.g. the response to a "give me the list of all known classes" * request from the debugger. Requests from the debugger, and from us, * are much smaller. * * Pass-through debugger traffic is sent without copying. "mWriteBuffer" * is only used for data generated within Client. */ private static final int INITIAL_BUF_SIZE = 2*1024; private static final int MAX_BUF_SIZE = 800*1024*1024; private ByteBuffer mReadBuffer; private Device mDevice; private int mConnState; private static final int ST_INIT = 1; private static final int ST_NOT_JDWP = 2; private static final int ST_AWAIT_SHAKE = 10; private static final int ST_NEED_DDM_PKT = 11; private static final int ST_NOT_DDM = 12; private static final int ST_READY = 13; private static final int ST_ERROR = 20; private static final int ST_DISCONNECTED = 21; /** * Create an object for a new client connection. * * @param device the device this client belongs to * @param chan the connected {@link SocketChannel}. * @param pid the client pid. */ Client(Device device, SocketChannel chan, int pid) { super(new JdwpProtocol()); mDevice = device; mChan = chan; mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE); mConnState = ST_INIT; mClientData = new ClientData(pid); mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate(); mHeapInfoUpdateEnabled = DdmPreferences.getInitialHeapUpdate(); mHeapSegmentUpdateEnabled = DdmPreferences.getInitialHeapUpdate(); } /** * Returns a string representation of the {@link Client} object. */ @Override public String toString() { return "[Client pid: " + mClientData.getPid() + "]"; } /** * Returns the {@link IDevice} on which this Client is running. */ public IDevice getDevice() { return mDevice; } /** Returns the {@link Device} on which this Client is running. */ Device getDeviceImpl() { return mDevice; } /** * Returns the debugger port for this client. */ public int getDebuggerListenPort() { return mDebuggerListenPort; } /** * Returns true if the client VM is DDM-aware. * * Calling here is only allowed after the connection has been * established. */ public boolean isDdmAware() { switch (mConnState) { case ST_INIT: case ST_NOT_JDWP: case ST_AWAIT_SHAKE: case ST_NEED_DDM_PKT: case ST_NOT_DDM: case ST_ERROR: case ST_DISCONNECTED: return false; case ST_READY: return true; default: assert false; return false; } } /** * Returns true if a debugger is currently attached to the client. */ public boolean isDebuggerAttached() { return mDebugger.isDebuggerAttached(); } /** * Return the Debugger object associated with this client. */ public Debugger getDebugger() { return mDebugger; } /** * Returns the {@link ClientData} object containing this client information. */ @NonNull public ClientData getClientData() { return mClientData; } /** * Forces the client to execute its garbage collector. */ public void executeGarbageCollector() { try { HandleHeap.sendHPGC(this); } catch (IOException ioe) { Log.w("ddms", "Send of HPGC message failed"); // ignore } } /** * Makes the VM dump an HPROF file */ public void dumpHprof() { boolean canStream = mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING); try { if (canStream) { HandleHeap.sendHPDS(this); } else { String file = "/sdcard/" + mClientData.getClientDescription().replaceAll( "\\:.*", "") + ".hprof"; HandleHeap.sendHPDU(this, file); } } catch (IOException e) { Log.w("ddms", "Send of HPDU message failed"); // ignore } } /** * Toggles method profiling state. * @deprecated Use {@link #startMethodTracer()}, {@link #stopMethodTracer()}, * {@link #startSamplingProfiler(int, java.util.concurrent.TimeUnit)} or * {@link #stopSamplingProfiler()} instead. */ @Deprecated public void toggleMethodProfiling() { try { switch (mClientData.getMethodProfilingStatus()) { case TRACER_ON: stopMethodTracer(); break; case SAMPLER_ON: stopSamplingProfiler(); break; case OFF: startMethodTracer(); break; } } catch (IOException e) { Log.w("ddms", "Toggle method profiling failed"); // ignore } } private int getProfileBufferSize() { return DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024; } public void startMethodTracer() throws IOException { boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING); int bufferSize = getProfileBufferSize(); if (canStream) { HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/); } else { String file = "/sdcard/" + mClientData.getClientDescription().replaceAll("\\:.*", "") + DdmConstants.DOT_TRACE; HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/); } } public void stopMethodTracer() throws IOException { boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING); if (canStream) { HandleProfiling.sendMPSE(this); } else { HandleProfiling.sendMPRE(this); } } public void startSamplingProfiler(int samplingInterval, TimeUnit timeUnit) throws IOException { int bufferSize = getProfileBufferSize(); HandleProfiling.sendSPSS(this, bufferSize, samplingInterval, timeUnit); } public void stopSamplingProfiler() throws IOException { HandleProfiling.sendSPSE(this); } public boolean startOpenGlTracing() { boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING); if (!canTraceOpenGl) { return false; } try { HandleViewDebug.sendStartGlTracing(this); return true; } catch (IOException e) { Log.w("ddms", "Start OpenGL Tracing failed"); return false; } } public boolean stopOpenGlTracing() { boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING); if (!canTraceOpenGl) { return false; } try { HandleViewDebug.sendStopGlTracing(this); return true; } catch (IOException e) { Log.w("ddms", "Stop OpenGL Tracing failed"); return false; } } /** * Sends a request to the VM to send the enable status of the method profiling. * This is asynchronous. *

The allocation status can be accessed by {@link ClientData#getAllocationStatus()}. * The notification that the new status is available will be received through * {@link IClientChangeListener#clientChanged(Client, int)} with a changeMask * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}. */ public void requestMethodProfilingStatus() { try { HandleHeap.sendREAQ(this); } catch (IOException e) { Log.e("ddmlib", e); } } /** * Enables or disables the thread update. *

If true the VM will be able to send thread information. Thread information * must be requested with {@link #requestThreadUpdate()}. * @param enabled the enable flag. */ public void setThreadUpdateEnabled(boolean enabled) { mThreadUpdateEnabled = enabled; if (!enabled) { mClientData.clearThreads(); } try { HandleThread.sendTHEN(this, enabled); } catch (IOException ioe) { // ignore it here; client will clean up shortly ioe.printStackTrace(); } update(CHANGE_THREAD_MODE); } /** * Returns whether the thread update is enabled. */ public boolean isThreadUpdateEnabled() { return mThreadUpdateEnabled; } /** * Sends a thread update request. This is asynchronous. *

The thread info can be accessed by {@link ClientData#getThreads()}. The notification * that the new data is available will be received through * {@link IClientChangeListener#clientChanged(Client, int)} with a changeMask * containing the mask {@link #CHANGE_THREAD_DATA}. */ public void requestThreadUpdate() { HandleThread.requestThreadUpdate(this); } /** * Sends a thread stack trace update request. This is asynchronous. *

The thread info can be accessed by {@link ClientData#getThreads()} and * {@link ThreadInfo#getStackTrace()}. *

The notification that the new data is available * will be received through {@link IClientChangeListener#clientChanged(Client, int)} * with a changeMask containing the mask {@link #CHANGE_THREAD_STACKTRACE}. */ public void requestThreadStackTrace(int threadId) { HandleThread.requestThreadStackCallRefresh(this, threadId); } /** * Enables or disables the heap update. *

If true, any GC will cause the client to send its heap information. *

The heap information can be accessed by {@link ClientData#getVmHeapData()}. *

The notification that the new data is available * will be received through {@link IClientChangeListener#clientChanged(Client, int)} * with a changeMask containing the value {@link #CHANGE_HEAP_DATA}. * @param enabled the enable flag */ public void setHeapUpdateEnabled(boolean enabled) { setHeapInfoUpdateEnabled(enabled); setHeapSegmentUpdateEnabled(enabled); } public void setHeapInfoUpdateEnabled(boolean enabled) { mHeapInfoUpdateEnabled = enabled; try { HandleHeap.sendHPIF(this, enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER); } catch (IOException ioe) { // ignore it here; client will clean up shortly } update(CHANGE_HEAP_MODE); } public void setHeapSegmentUpdateEnabled(boolean enabled) { mHeapSegmentUpdateEnabled = enabled; try { HandleHeap.sendHPSG(this, enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE, HandleHeap.WHAT_MERGE); } catch (IOException ioe) { // ignore it here; client will clean up shortly } update(CHANGE_HEAP_MODE); } void initializeHeapUpdateStatus() throws IOException { setHeapInfoUpdateEnabled(mHeapInfoUpdateEnabled); } /** * Fires a single heap update. */ public void updateHeapInfo() { try { HandleHeap.sendHPIF(this, HandleHeap.HPIF_WHEN_NOW); } catch (IOException ioe) { // ignore it here; client will clean up shortly } } /** * Returns whether any heap update is enabled. * @see #setHeapUpdateEnabled(boolean) */ public boolean isHeapUpdateEnabled() { return mHeapInfoUpdateEnabled || mHeapSegmentUpdateEnabled; } /** * Sends a native heap update request. this is asynchronous. *

The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}. * The notification that the new data is available will be received through * {@link IClientChangeListener#clientChanged(Client, int)} with a changeMask * containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}. */ public boolean requestNativeHeapInformation() { try { HandleNativeHeap.sendNHGT(this); return true; } catch (IOException e) { Log.e("ddmlib", e); } return false; } /** * Enables or disables the Allocation tracker for this client. *

If enabled, the VM will start tracking allocation information. A call to * {@link #requestAllocationDetails()} will make the VM sends the information about all the * allocations that happened between the enabling and the request. * @param enable * @see #requestAllocationDetails() */ public void enableAllocationTracker(boolean enable) { try { HandleHeap.sendREAE(this, enable); } catch (IOException e) { Log.e("ddmlib", e); } } /** * Sends a request to the VM to send the enable status of the allocation tracking. * This is asynchronous. *

The allocation status can be accessed by {@link ClientData#getAllocationStatus()}. * The notification that the new status is available will be received through * {@link IClientChangeListener#clientChanged(Client, int)} with a changeMask * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}. */ public void requestAllocationStatus() { try { HandleHeap.sendREAQ(this); } catch (IOException e) { Log.w("ddmlib", "IO Error while obtaining allocation status"); } } /** * Sends a request to the VM to send the information about all the allocations that have * happened since the call to {@link #enableAllocationTracker(boolean)} with enable * set to null. This is asynchronous. *

The allocation information can be accessed by {@link ClientData#getAllocations()}. * The notification that the new data is available will be received through * {@link IClientChangeListener#clientChanged(Client, int)} with a changeMask * containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}. */ public void requestAllocationDetails() { try { HandleHeap.sendREAL(this); } catch (IOException e) { Log.e("ddmlib", e); } } /** * Sends a kill message to the VM. */ public void kill() { try { HandleExit.sendEXIT(this, 1); } catch (IOException ioe) { Log.w("ddms", "Send of EXIT message failed"); // ignore } } /** * Registers the client with a Selector. */ void register(Selector sel) throws IOException { if (mChan != null) { mChan.register(sel, SelectionKey.OP_READ, this); } } /** * Sets the client to accept debugger connection on the "selected debugger port". * * @see AndroidDebugBridge#setSelectedClient(Client) * @see DdmPreferences#setSelectedDebugPort(int) */ public void setAsSelectedClient() { MonitorThread monitorThread = MonitorThread.getInstance(); if (monitorThread != null) { monitorThread.setSelectedClient(this); } } /** * Returns whether this client is the current selected client, accepting debugger connection * on the "selected debugger port". * * @see #setAsSelectedClient() * @see AndroidDebugBridge#setSelectedClient(Client) * @see DdmPreferences#setSelectedDebugPort(int) */ public boolean isSelectedClient() { MonitorThread monitorThread = MonitorThread.getInstance(); if (monitorThread != null) { return monitorThread.getSelectedClient() == this; } return false; } /** * Tell the client to open a server socket channel and listen for * connections on the specified port. */ void listenForDebugger(int listenPort) throws IOException { mDebuggerListenPort = listenPort; mDebugger = new Debugger(this, listenPort); } /** * Initiate the JDWP handshake. * * On failure, closes the socket and returns false. */ boolean sendHandshake() { ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpHandshake.HANDSHAKE_LEN); try { // assume write buffer can hold 14 bytes JdwpHandshake.putHandshake(tempBuffer); int expectedLen = tempBuffer.position(); tempBuffer.flip(); if (mChan.write(tempBuffer) != expectedLen) throw new IOException("partial handshake write"); } catch (IOException ioe) { Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage()); mConnState = ST_ERROR; close(true /* notify */); return false; } mConnState = ST_AWAIT_SHAKE; return true; } /** * Send a DDM packet to the client. * * 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. */ @Override protected void send(@NonNull JdwpPacket packet) throws IOException { // Fix to avoid a race condition on mChan. This should be better synchronized // but just capturing the channel here, avoids a NPE. SocketChannel chan = mChan; if (chan == null) { // can happen for e.g. THST packets Log.v("ddms", "Not sending packet -- client is closed"); return; } // Synchronizing on this variable is still useful as we do not want to threads // reading at the same time from the same channel, and the only change that // can happen to this channel is to be closed and mChan become null. //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (chan) { try { packet.write(chan); } catch (IOException ioe) { removeReplyInterceptor(packet.getId()); throw ioe; } } } /** * 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, BufferOverflowException { int count; if (mReadBuffer.position() == mReadBuffer.capacity()) { if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) { Log.e("ddms", "Exceeded MAX_BUF_SIZE!"); throw new BufferOverflowException(); } Log.d("ddms", "Expanding read buffer to " + mReadBuffer.capacity() * 2); ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2); // copy entire buffer to new buffer mReadBuffer.position(0); newBuffer.put(mReadBuffer); // leaves "position" at end of copied mReadBuffer = newBuffer; } count = mChan.read(mReadBuffer); if (count < 0) throw new IOException("read failed"); if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this); //Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(), // mReadBuffer.arrayOffset(), mReadBuffer.position()); } /** * 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. Upon receipt * we send out the "HELO" message, 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) { /* * The first thing we get from the client is a response to our * handshake. It doesn't look like a packet, so we have to * handle it specially. */ int result; result = JdwpHandshake.findHandshake(mReadBuffer); //Log.v("ddms", "findHand: " + result); switch (result) { case JdwpHandshake.HANDSHAKE_GOOD: Log.d("ddms", "Good handshake from client, sending HELO to " + mClientData.getPid()); JdwpHandshake.consumeHandshake(mReadBuffer); mConnState = ST_NEED_DDM_PKT; HandleHello.sendHelloCommands(this, SERVER_PROTOCOL_VERSION); // see if we have another packet in the buffer return getJdwpPacket(); case JdwpHandshake.HANDSHAKE_BAD: Log.d("ddms", "Bad handshake from client"); if (MonitorThread.getInstance().getRetryOnBadHandshake()) { // we should drop the client, but also attempt to reopen it. // This is done by the DeviceMonitor. mDevice.getMonitor().addClientToDropAndReopen(this, IDebugPortProvider.NO_STATIC_PORT); } else { // mark it as bad, close the socket, and don't retry mConnState = ST_NOT_JDWP; close(true /* notify */); } break; case JdwpHandshake.HANDSHAKE_NOTYET: Log.d("ddms", "No handshake from client yet."); break; default: Log.e("ddms", "Unknown packet while waiting for client handshake"); } return null; } else if (mConnState == ST_NEED_DDM_PKT || mConnState == ST_NOT_DDM || mConnState == ST_READY) { /* * Normal packet traffic. */ if (mReadBuffer.position() != 0) { if (Log.Config.LOGV) Log.v("ddms", "Checking " + mReadBuffer.position() + " bytes"); } return JdwpPacket.findPacket(mReadBuffer); } else { /* * Not expecting data when in this state. */ Log.e("ddms", "Receiving data in state = " + mConnState); } return null; } /** * An earlier request resulted in a failure. This is the expected * response to a HELO message when talking to a non-DDM client. */ void packetFailed(JdwpPacket reply) { if (mConnState == ST_NEED_DDM_PKT) { Log.d("ddms", "Marking " + this + " as non-DDM client"); mConnState = ST_NOT_DDM; } else if (mConnState != ST_NOT_DDM) { Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req"); } } /** * The MonitorThread calls this when it sees a DDM request or reply. * If we haven't seen a DDM packet before, we advance the state to * ST_READY and return "false". Otherwise, just return true. * * The idea is to let the MonitorThread know when we first see a DDM * packet, so we can send a broadcast to the handlers when a client * connection is made. This method is synchronized so that we only * send the broadcast once. */ synchronized boolean ddmSeen() { if (mConnState == ST_NEED_DDM_PKT) { mConnState = ST_READY; return false; } else if (mConnState != ST_READY) { Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState); } return true; } /** * Close the client socket channel. If there is a debugger associated * with us, close that too. * * Closing a channel automatically unregisters it from the selector. * However, we have to iterate through the selector loop before it * actually lets them go and allows the file descriptors to close. * The caller is expected to manage that. * @param notify Whether or not to notify the listeners of a change. */ void close(boolean notify) { Log.d("ddms", "Closing " + this.toString()); clear(); try { if (mChan != null) { mChan.close(); mChan = null; } if (mDebugger != null) { mDebugger.close(); mDebugger = null; } } catch (IOException ioe) { Log.w("ddms", "failed to close " + this); // swallow it -- not much else to do } mDevice.removeClient(this, notify); } /** * Returns whether this {@link Client} has a valid connection to the application VM. */ public boolean isValid() { return mChan != null; } void update(int changeMask) { mDevice.update(this, changeMask); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy