| /* |
| * Copyright 2002 Sun Microsystems, Inc. All Rights Reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| * |
| */ |
| |
| package sun.jvm.hotspot.livejvm; |
| |
| import sun.jvm.hotspot.debugger.*; |
| import sun.jvm.hotspot.oops.*; |
| import sun.jvm.hotspot.runtime.*; |
| |
| /** Provides Java programming language-level interaction with a live |
| Java HotSpot VM via the use of the SA's JVMDI module. This is an |
| experimental mechanism. The BugSpot debugger should be converted |
| to use the JVMDI/JDWP-based JDI implementation for live process |
| interaction once the JDI binding for the SA is complete. */ |
| |
| public class ServiceabilityAgentJVMDIModule { |
| private Debugger dbg; |
| private String[] saLibNames; |
| private String saLibName; |
| private boolean attached; |
| |
| private boolean suspended; |
| |
| private static final int JVMDI_EVENT_BREAKPOINT = 2; |
| private static final int JVMDI_EVENT_EXCEPTION = 4; |
| |
| private static long timeoutMillis = 3000; |
| |
| // Values in target process |
| // Events sent from VM to SA |
| private CIntegerAccessor saAttached; |
| private CIntegerAccessor saEventPending; |
| private CIntegerAccessor saEventKind; |
| // Exception events |
| private JNIHandleAccessor saExceptionThread; |
| private JNIHandleAccessor saExceptionClass; |
| private JNIid saExceptionMethod; |
| private CIntegerAccessor saExceptionLocation; |
| private JNIHandleAccessor saExceptionException; |
| private JNIHandleAccessor saExceptionCatchClass; |
| private JNIid saExceptionCatchMethod; |
| private CIntegerAccessor saExceptionCatchLocation; |
| // Breakpoint events |
| private JNIHandleAccessor saBreakpointThread; |
| private JNIHandleAccessor saBreakpointClass; |
| private JNIid saBreakpointMethod; |
| private CIntegerAccessor saBreakpointLocation; |
| // Commands sent by the SA to the VM |
| private int SA_CMD_SUSPEND_ALL; |
| private int SA_CMD_RESUME_ALL; |
| private int SA_CMD_TOGGLE_BREAKPOINT; |
| private int SA_CMD_BUF_SIZE; |
| private CIntegerAccessor saCmdPending; |
| private CIntegerAccessor saCmdType; |
| private CIntegerAccessor saCmdResult; |
| private CStringAccessor saCmdResultErrMsg; |
| // Toggle breakpoint command arguments |
| private CStringAccessor saCmdBkptSrcFileName; |
| private CStringAccessor saCmdBkptPkgName; |
| private CIntegerAccessor saCmdBkptLineNumber; |
| private CIntegerAccessor saCmdBkptResWasError; |
| private CIntegerAccessor saCmdBkptResLineNumber; |
| private CIntegerAccessor saCmdBkptResBCI; |
| private CIntegerAccessor saCmdBkptResWasSet; |
| private CStringAccessor saCmdBkptResMethodName; |
| private CStringAccessor saCmdBkptResMethodSig; |
| |
| public ServiceabilityAgentJVMDIModule(Debugger dbg, String[] saLibNames) { |
| this.dbg = dbg; |
| this.saLibNames = saLibNames; |
| } |
| |
| /** Indicates whether a call to attach() should complete without an |
| exception. */ |
| public boolean canAttach() { |
| return setupLookup("SA_CMD_SUSPEND_ALL"); |
| } |
| |
| /** Attempt to initiate a connection with the JVMDI module in the |
| target VM. */ |
| public void attach() throws DebuggerException { |
| if (!canAttach()) { |
| throw new DebuggerException("Unable to initiate symbol lookup in SA's JVMDI module"); |
| } |
| |
| if (attached) { |
| throw new DebuggerException("Already attached"); |
| } |
| |
| // Attempt to look up well-known symbols in the target VM. |
| SA_CMD_SUSPEND_ALL = lookupConstInt("SA_CMD_SUSPEND_ALL"); |
| SA_CMD_RESUME_ALL = lookupConstInt("SA_CMD_RESUME_ALL"); |
| SA_CMD_TOGGLE_BREAKPOINT = lookupConstInt("SA_CMD_TOGGLE_BREAKPOINT"); |
| SA_CMD_BUF_SIZE = lookupConstInt("SA_CMD_BUF_SIZE"); |
| |
| saAttached = lookupCInt("saAttached"); |
| saEventPending = lookupCInt("saEventPending"); |
| saEventKind = lookupCInt("saEventKind"); |
| saCmdPending = lookupCInt("saCmdPending"); |
| saCmdType = lookupCInt("saCmdType"); |
| saCmdResult = lookupCInt("saCmdResult"); |
| saCmdResultErrMsg = lookupCString("saCmdResultErrMsg", SA_CMD_BUF_SIZE); |
| // Toggling of breakpoints |
| saCmdBkptSrcFileName = lookupCString("saCmdBkptSrcFileName", SA_CMD_BUF_SIZE); |
| saCmdBkptPkgName = lookupCString("saCmdBkptPkgName", SA_CMD_BUF_SIZE); |
| saCmdBkptLineNumber = lookupCInt("saCmdBkptLineNumber"); |
| saCmdBkptResWasError = lookupCInt("saCmdBkptResWasError"); |
| saCmdBkptResLineNumber = lookupCInt("saCmdBkptResLineNumber"); |
| saCmdBkptResBCI = lookupCInt("saCmdBkptResBCI"); |
| saCmdBkptResWasSet = lookupCInt("saCmdBkptResWasSet"); |
| saCmdBkptResMethodName = lookupCString("saCmdBkptResMethodName", SA_CMD_BUF_SIZE); |
| saCmdBkptResMethodSig = lookupCString("saCmdBkptResMethodSig", SA_CMD_BUF_SIZE); |
| |
| // Check for existence of symbols needed later |
| // FIXME: should probably cache these since we can't support the |
| // -Xrun module or the VM getting unloaded anyway |
| lookup("saExceptionThread"); |
| lookup("saExceptionClass"); |
| lookup("saExceptionMethod"); |
| lookup("saExceptionLocation"); |
| lookup("saExceptionException"); |
| lookup("saExceptionCatchClass"); |
| lookup("saExceptionCatchMethod"); |
| lookup("saExceptionCatchLocation"); |
| lookup("saBreakpointThread"); |
| lookup("saBreakpointClass"); |
| lookup("saBreakpointMethod"); |
| lookup("saBreakpointLocation"); |
| |
| saAttached.setValue(1); |
| attached = true; |
| } |
| |
| public void detach() { |
| saAttached.setValue(0); |
| attached = false; |
| saLibName = null; |
| } |
| |
| /** Set the timeout value (in milliseconds) for the VM to reply to |
| commands. Once this timeout has elapsed, the VM is assumed to |
| have disconnected. Defaults to 3000 milliseconds (3 seconds). */ |
| public void setCommandTimeout(long millis) { |
| timeoutMillis = millis; |
| } |
| |
| /** Get the timeout value (in milliseconds) for the VM to reply to |
| commands. Once this timeout has elapsed, the VM is assumed to |
| have disconnected. Defaults to 3000 milliseconds (3 seconds). */ |
| public long getCommandTimeout() { |
| return timeoutMillis; |
| } |
| |
| /** Indicates whether a Java debug event is pending */ |
| public boolean eventPending() { |
| return (saEventPending.getValue() != 0); |
| } |
| |
| /** Poll for event; returns null if none pending. */ |
| public Event eventPoll() { |
| if (saEventPending.getValue() == 0) { |
| return null; |
| } |
| |
| int kind = (int) saEventKind.getValue(); |
| switch (kind) { |
| case JVMDI_EVENT_EXCEPTION: { |
| JNIHandleAccessor thread = lookupJNIHandle("saExceptionThread"); |
| JNIHandleAccessor clazz = lookupJNIHandle("saExceptionClass"); |
| JNIid method = lookupJNIid("saExceptionMethod"); |
| CIntegerAccessor location = lookupCInt("saExceptionLocation"); |
| JNIHandleAccessor exception = lookupJNIHandle("saExceptionException"); |
| JNIHandleAccessor catchClass = lookupJNIHandle("saExceptionCatchClass"); |
| JNIid catchMethod = lookupJNIid("saExceptionCatchMethod"); |
| CIntegerAccessor catchLocation = lookupCInt("saExceptionCatchLocation"); |
| return new ExceptionEvent(thread.getValue(), clazz.getValue(), method, |
| (int) location.getValue(), exception.getValue(), |
| catchClass.getValue(), catchMethod, (int) catchLocation.getValue()); |
| } |
| |
| case JVMDI_EVENT_BREAKPOINT: { |
| JNIHandleAccessor thread = lookupJNIHandle("saBreakpointThread"); |
| JNIHandleAccessor clazz = lookupJNIHandle("saBreakpointClass"); |
| JNIid method = lookupJNIid("saBreakpointMethod"); |
| CIntegerAccessor location = lookupCInt("saBreakpointLocation"); |
| return new BreakpointEvent(thread.getValue(), clazz.getValue(), |
| method, (int) location.getValue()); |
| } |
| |
| default: |
| throw new DebuggerException("Unsupported event type " + kind); |
| } |
| } |
| |
| /** Continue past current event */ |
| public void eventContinue() { |
| saEventPending.setValue(0); |
| } |
| |
| /** Suspend all Java threads in the target VM. Throws |
| DebuggerException if the VM disconnected. */ |
| public void suspend() { |
| saCmdType.setValue(SA_CMD_SUSPEND_ALL); |
| saCmdPending.setValue(1); |
| waitForCommandCompletion(); |
| suspended = true; |
| } |
| |
| /** Resume all Java threads in the target VM. Throws |
| DebuggerException if the VM disconnected. */ |
| public void resume() { |
| saCmdType.setValue(SA_CMD_RESUME_ALL); |
| saCmdPending.setValue(1); |
| waitForCommandCompletion(); |
| suspended = false; |
| } |
| |
| /** Indicates whether all Java threads have been suspended via this |
| interface. */ |
| public boolean isSuspended() { |
| return suspended; |
| } |
| |
| /** Information about toggling of breakpoints */ |
| public static class BreakpointToggleResult { |
| private boolean success; |
| private String errMsg; |
| private int lineNumber; |
| private int bci; |
| private boolean wasSet; |
| private String methodName; |
| private String methodSig; |
| |
| /** Success constructor */ |
| public BreakpointToggleResult(int lineNumber, int bci, boolean wasSet, |
| String methodName, String methodSig) { |
| this.lineNumber = lineNumber; |
| this.bci = bci; |
| this.wasSet = wasSet; |
| this.methodName = methodName; |
| this.methodSig = methodSig; |
| success = true; |
| } |
| |
| /** Failure constructor */ |
| public BreakpointToggleResult(String errMsg) { |
| this.errMsg = errMsg; |
| success = false; |
| } |
| |
| /** Indicates whether this represents a successful return or not */ |
| public boolean getSuccess() { return success; } |
| |
| /** Valid only if getSuccess() returns false */ |
| public String getErrMsg() { return errMsg; } |
| |
| /** Line number at which breakpoint toggle occurred; valid only if |
| getSuccess() returns true. */ |
| public int getLineNumber() { return lineNumber; } |
| |
| /** BCI at which breakpoint toggle occurred; valid only if |
| getSuccess() returns true. */ |
| public int getBCI() { return bci; } |
| |
| /** Indicates whether the breakpoint toggle was the set of a |
| breakpoint or not; valid only if getSuccess() returns true. */ |
| public boolean getWasSet() { return wasSet; } |
| |
| /** Method name in which the breakpoint toggle occurred; valid |
| only if getSuccess() returns true. */ |
| public String getMethodName() { return methodName; } |
| |
| /** Method signature in which the breakpoint toggle occurred; |
| valid only if getSuccess() returns true. */ |
| public String getMethodSignature() { return methodSig; } |
| } |
| |
| /** Toggle a breakpoint. Throws DebuggerException if a real error |
| occurred; otherwise returns non-null BreakpointToggleResult. The |
| work of scanning the loaded classes is done in the target VM |
| because it turns out to be significantly faster than scanning |
| through the system dictionary from the SA, and interactivity |
| when setting breakpoints is important. */ |
| public BreakpointToggleResult toggleBreakpoint(String srcFileName, |
| String pkgName, |
| int lineNo) { |
| saCmdBkptSrcFileName.setValue(srcFileName); |
| saCmdBkptPkgName.setValue(pkgName); |
| saCmdBkptLineNumber.setValue(lineNo); |
| saCmdType.setValue(SA_CMD_TOGGLE_BREAKPOINT); |
| saCmdPending.setValue(1); |
| if (waitForCommandCompletion(true)) { |
| return new BreakpointToggleResult((int) saCmdBkptResLineNumber.getValue(), |
| (int) saCmdBkptResBCI.getValue(), |
| (saCmdBkptResWasSet.getValue() != 0), |
| saCmdBkptResMethodName.getValue(), |
| saCmdBkptResMethodSig.getValue()); |
| } else { |
| return new BreakpointToggleResult(saCmdResultErrMsg.getValue()); |
| } |
| } |
| |
| |
| //---------------------------------------------------------------------- |
| // Internals only below this point |
| // |
| |
| private CIntegerAccessor lookupCInt(String symbolName) { |
| return new CIntegerAccessor(lookup(symbolName), 4, false); |
| } |
| |
| private CStringAccessor lookupCString(String symbolName, int bufLen) { |
| return new CStringAccessor(lookup(symbolName), bufLen); |
| } |
| |
| private JNIHandleAccessor lookupJNIHandle(String symbolName) { |
| return new JNIHandleAccessor(lookup(symbolName), VM.getVM().getObjectHeap()); |
| } |
| |
| private JNIid lookupJNIid(String symbolName) { |
| Address idAddr = lookup(symbolName).getAddressAt(0); |
| if (idAddr == null) { |
| return null; |
| } |
| return new JNIid(idAddr, VM.getVM().getObjectHeap()); |
| } |
| |
| private int lookupConstInt(String symbolName) { |
| Address addr = lookup(symbolName); |
| return (int) addr.getCIntegerAt(0, 4, false); |
| } |
| |
| private boolean setupLookup(String symbolName) { |
| if (saLibName == null) { |
| for (int i = 0; i < saLibNames.length; i++) { |
| Address addr = dbg.lookup(saLibNames[i], symbolName); |
| if (addr != null) { |
| saLibName = saLibNames[i]; |
| return true; |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| private Address lookup(String symbolName) { |
| if (saLibName == null) { |
| for (int i = 0; i < saLibNames.length; i++) { |
| Address addr = dbg.lookup(saLibNames[i], symbolName); |
| if (addr != null) { |
| saLibName = saLibNames[i]; |
| return addr; |
| } |
| } |
| throw new DebuggerException("Unable to find symbol " + symbolName + " in any of the known names for the SA"); |
| } |
| |
| Address addr = dbg.lookup(saLibName, symbolName); |
| if (addr == null) { |
| throw new DebuggerException("Unable to find symbol " + symbolName + " in " + saLibName); |
| } |
| return addr; |
| } |
| |
| private void waitForCommandCompletion() { |
| waitForCommandCompletion(false); |
| } |
| |
| /** Returns true if command succeeded, false if not */ |
| private boolean waitForCommandCompletion(boolean forBreakpoint) { |
| long start = System.currentTimeMillis(); |
| long cur = start; |
| while ((saCmdPending.getValue() != 0) && |
| (cur - start < timeoutMillis)) { |
| try { |
| java.lang.Thread.currentThread().sleep(10); |
| } catch (InterruptedException e) { |
| } |
| cur = System.currentTimeMillis(); |
| } |
| if (saCmdPending.getValue() != 0) { |
| detach(); |
| throw new DebuggerException("VM appears to have died"); |
| } |
| boolean succeeded = saCmdResult.getValue() == 0; |
| if (!succeeded && |
| (!forBreakpoint || saCmdBkptResWasError.getValue() != 0)) { |
| String err = saCmdResultErrMsg.getValue(); |
| throw new DebuggerException("Error executing JVMDI command: " + err); |
| } |
| return succeeded; |
| } |
| } |