0
|
1 /*
|
|
2 * Copyright 2002 Sun Microsystems, Inc. All Rights Reserved.
|
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 *
|
|
5 * This code is free software; you can redistribute it and/or modify it
|
|
6 * under the terms of the GNU General Public License version 2 only, as
|
|
7 * published by the Free Software Foundation.
|
|
8 *
|
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT
|
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
12 * version 2 for more details (a copy is included in the LICENSE file that
|
|
13 * accompanied this code).
|
|
14 *
|
|
15 * You should have received a copy of the GNU General Public License version
|
|
16 * 2 along with this work; if not, write to the Free Software Foundation,
|
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
18 *
|
|
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
20 * CA 95054 USA or visit www.sun.com if you need additional information or
|
|
21 * have any questions.
|
|
22 *
|
|
23 */
|
|
24
|
|
25 package sun.jvm.hotspot.livejvm;
|
|
26
|
|
27 import sun.jvm.hotspot.debugger.*;
|
|
28 import sun.jvm.hotspot.oops.*;
|
|
29 import sun.jvm.hotspot.runtime.*;
|
|
30
|
|
31 /** Provides Java programming language-level interaction with a live
|
|
32 Java HotSpot VM via the use of the SA's JVMDI module. This is an
|
|
33 experimental mechanism. The BugSpot debugger should be converted
|
|
34 to use the JVMDI/JDWP-based JDI implementation for live process
|
|
35 interaction once the JDI binding for the SA is complete. */
|
|
36
|
|
37 public class ServiceabilityAgentJVMDIModule {
|
|
38 private Debugger dbg;
|
|
39 private String[] saLibNames;
|
|
40 private String saLibName;
|
|
41 private boolean attached;
|
|
42
|
|
43 private boolean suspended;
|
|
44
|
|
45 private static final int JVMDI_EVENT_BREAKPOINT = 2;
|
|
46 private static final int JVMDI_EVENT_EXCEPTION = 4;
|
|
47
|
|
48 private static long timeoutMillis = 3000;
|
|
49
|
|
50 // Values in target process
|
|
51 // Events sent from VM to SA
|
|
52 private CIntegerAccessor saAttached;
|
|
53 private CIntegerAccessor saEventPending;
|
|
54 private CIntegerAccessor saEventKind;
|
|
55 // Exception events
|
|
56 private JNIHandleAccessor saExceptionThread;
|
|
57 private JNIHandleAccessor saExceptionClass;
|
|
58 private JNIid saExceptionMethod;
|
|
59 private CIntegerAccessor saExceptionLocation;
|
|
60 private JNIHandleAccessor saExceptionException;
|
|
61 private JNIHandleAccessor saExceptionCatchClass;
|
|
62 private JNIid saExceptionCatchMethod;
|
|
63 private CIntegerAccessor saExceptionCatchLocation;
|
|
64 // Breakpoint events
|
|
65 private JNIHandleAccessor saBreakpointThread;
|
|
66 private JNIHandleAccessor saBreakpointClass;
|
|
67 private JNIid saBreakpointMethod;
|
|
68 private CIntegerAccessor saBreakpointLocation;
|
|
69 // Commands sent by the SA to the VM
|
|
70 private int SA_CMD_SUSPEND_ALL;
|
|
71 private int SA_CMD_RESUME_ALL;
|
|
72 private int SA_CMD_TOGGLE_BREAKPOINT;
|
|
73 private int SA_CMD_BUF_SIZE;
|
|
74 private CIntegerAccessor saCmdPending;
|
|
75 private CIntegerAccessor saCmdType;
|
|
76 private CIntegerAccessor saCmdResult;
|
|
77 private CStringAccessor saCmdResultErrMsg;
|
|
78 // Toggle breakpoint command arguments
|
|
79 private CStringAccessor saCmdBkptSrcFileName;
|
|
80 private CStringAccessor saCmdBkptPkgName;
|
|
81 private CIntegerAccessor saCmdBkptLineNumber;
|
|
82 private CIntegerAccessor saCmdBkptResWasError;
|
|
83 private CIntegerAccessor saCmdBkptResLineNumber;
|
|
84 private CIntegerAccessor saCmdBkptResBCI;
|
|
85 private CIntegerAccessor saCmdBkptResWasSet;
|
|
86 private CStringAccessor saCmdBkptResMethodName;
|
|
87 private CStringAccessor saCmdBkptResMethodSig;
|
|
88
|
|
89 public ServiceabilityAgentJVMDIModule(Debugger dbg, String[] saLibNames) {
|
|
90 this.dbg = dbg;
|
|
91 this.saLibNames = saLibNames;
|
|
92 }
|
|
93
|
|
94 /** Indicates whether a call to attach() should complete without an
|
|
95 exception. */
|
|
96 public boolean canAttach() {
|
|
97 return setupLookup("SA_CMD_SUSPEND_ALL");
|
|
98 }
|
|
99
|
|
100 /** Attempt to initiate a connection with the JVMDI module in the
|
|
101 target VM. */
|
|
102 public void attach() throws DebuggerException {
|
|
103 if (!canAttach()) {
|
|
104 throw new DebuggerException("Unable to initiate symbol lookup in SA's JVMDI module");
|
|
105 }
|
|
106
|
|
107 if (attached) {
|
|
108 throw new DebuggerException("Already attached");
|
|
109 }
|
|
110
|
|
111 // Attempt to look up well-known symbols in the target VM.
|
|
112 SA_CMD_SUSPEND_ALL = lookupConstInt("SA_CMD_SUSPEND_ALL");
|
|
113 SA_CMD_RESUME_ALL = lookupConstInt("SA_CMD_RESUME_ALL");
|
|
114 SA_CMD_TOGGLE_BREAKPOINT = lookupConstInt("SA_CMD_TOGGLE_BREAKPOINT");
|
|
115 SA_CMD_BUF_SIZE = lookupConstInt("SA_CMD_BUF_SIZE");
|
|
116
|
|
117 saAttached = lookupCInt("saAttached");
|
|
118 saEventPending = lookupCInt("saEventPending");
|
|
119 saEventKind = lookupCInt("saEventKind");
|
|
120 saCmdPending = lookupCInt("saCmdPending");
|
|
121 saCmdType = lookupCInt("saCmdType");
|
|
122 saCmdResult = lookupCInt("saCmdResult");
|
|
123 saCmdResultErrMsg = lookupCString("saCmdResultErrMsg", SA_CMD_BUF_SIZE);
|
|
124 // Toggling of breakpoints
|
|
125 saCmdBkptSrcFileName = lookupCString("saCmdBkptSrcFileName", SA_CMD_BUF_SIZE);
|
|
126 saCmdBkptPkgName = lookupCString("saCmdBkptPkgName", SA_CMD_BUF_SIZE);
|
|
127 saCmdBkptLineNumber = lookupCInt("saCmdBkptLineNumber");
|
|
128 saCmdBkptResWasError = lookupCInt("saCmdBkptResWasError");
|
|
129 saCmdBkptResLineNumber = lookupCInt("saCmdBkptResLineNumber");
|
|
130 saCmdBkptResBCI = lookupCInt("saCmdBkptResBCI");
|
|
131 saCmdBkptResWasSet = lookupCInt("saCmdBkptResWasSet");
|
|
132 saCmdBkptResMethodName = lookupCString("saCmdBkptResMethodName", SA_CMD_BUF_SIZE);
|
|
133 saCmdBkptResMethodSig = lookupCString("saCmdBkptResMethodSig", SA_CMD_BUF_SIZE);
|
|
134
|
|
135 // Check for existence of symbols needed later
|
|
136 // FIXME: should probably cache these since we can't support the
|
|
137 // -Xrun module or the VM getting unloaded anyway
|
|
138 lookup("saExceptionThread");
|
|
139 lookup("saExceptionClass");
|
|
140 lookup("saExceptionMethod");
|
|
141 lookup("saExceptionLocation");
|
|
142 lookup("saExceptionException");
|
|
143 lookup("saExceptionCatchClass");
|
|
144 lookup("saExceptionCatchMethod");
|
|
145 lookup("saExceptionCatchLocation");
|
|
146 lookup("saBreakpointThread");
|
|
147 lookup("saBreakpointClass");
|
|
148 lookup("saBreakpointMethod");
|
|
149 lookup("saBreakpointLocation");
|
|
150
|
|
151 saAttached.setValue(1);
|
|
152 attached = true;
|
|
153 }
|
|
154
|
|
155 public void detach() {
|
|
156 saAttached.setValue(0);
|
|
157 attached = false;
|
|
158 saLibName = null;
|
|
159 }
|
|
160
|
|
161 /** Set the timeout value (in milliseconds) for the VM to reply to
|
|
162 commands. Once this timeout has elapsed, the VM is assumed to
|
|
163 have disconnected. Defaults to 3000 milliseconds (3 seconds). */
|
|
164 public void setCommandTimeout(long millis) {
|
|
165 timeoutMillis = millis;
|
|
166 }
|
|
167
|
|
168 /** Get the timeout value (in milliseconds) for the VM to reply to
|
|
169 commands. Once this timeout has elapsed, the VM is assumed to
|
|
170 have disconnected. Defaults to 3000 milliseconds (3 seconds). */
|
|
171 public long getCommandTimeout() {
|
|
172 return timeoutMillis;
|
|
173 }
|
|
174
|
|
175 /** Indicates whether a Java debug event is pending */
|
|
176 public boolean eventPending() {
|
|
177 return (saEventPending.getValue() != 0);
|
|
178 }
|
|
179
|
|
180 /** Poll for event; returns null if none pending. */
|
|
181 public Event eventPoll() {
|
|
182 if (saEventPending.getValue() == 0) {
|
|
183 return null;
|
|
184 }
|
|
185
|
|
186 int kind = (int) saEventKind.getValue();
|
|
187 switch (kind) {
|
|
188 case JVMDI_EVENT_EXCEPTION: {
|
|
189 JNIHandleAccessor thread = lookupJNIHandle("saExceptionThread");
|
|
190 JNIHandleAccessor clazz = lookupJNIHandle("saExceptionClass");
|
|
191 JNIid method = lookupJNIid("saExceptionMethod");
|
|
192 CIntegerAccessor location = lookupCInt("saExceptionLocation");
|
|
193 JNIHandleAccessor exception = lookupJNIHandle("saExceptionException");
|
|
194 JNIHandleAccessor catchClass = lookupJNIHandle("saExceptionCatchClass");
|
|
195 JNIid catchMethod = lookupJNIid("saExceptionCatchMethod");
|
|
196 CIntegerAccessor catchLocation = lookupCInt("saExceptionCatchLocation");
|
|
197 return new ExceptionEvent(thread.getValue(), clazz.getValue(), method,
|
|
198 (int) location.getValue(), exception.getValue(),
|
|
199 catchClass.getValue(), catchMethod, (int) catchLocation.getValue());
|
|
200 }
|
|
201
|
|
202 case JVMDI_EVENT_BREAKPOINT: {
|
|
203 JNIHandleAccessor thread = lookupJNIHandle("saBreakpointThread");
|
|
204 JNIHandleAccessor clazz = lookupJNIHandle("saBreakpointClass");
|
|
205 JNIid method = lookupJNIid("saBreakpointMethod");
|
|
206 CIntegerAccessor location = lookupCInt("saBreakpointLocation");
|
|
207 return new BreakpointEvent(thread.getValue(), clazz.getValue(),
|
|
208 method, (int) location.getValue());
|
|
209 }
|
|
210
|
|
211 default:
|
|
212 throw new DebuggerException("Unsupported event type " + kind);
|
|
213 }
|
|
214 }
|
|
215
|
|
216 /** Continue past current event */
|
|
217 public void eventContinue() {
|
|
218 saEventPending.setValue(0);
|
|
219 }
|
|
220
|
|
221 /** Suspend all Java threads in the target VM. Throws
|
|
222 DebuggerException if the VM disconnected. */
|
|
223 public void suspend() {
|
|
224 saCmdType.setValue(SA_CMD_SUSPEND_ALL);
|
|
225 saCmdPending.setValue(1);
|
|
226 waitForCommandCompletion();
|
|
227 suspended = true;
|
|
228 }
|
|
229
|
|
230 /** Resume all Java threads in the target VM. Throws
|
|
231 DebuggerException if the VM disconnected. */
|
|
232 public void resume() {
|
|
233 saCmdType.setValue(SA_CMD_RESUME_ALL);
|
|
234 saCmdPending.setValue(1);
|
|
235 waitForCommandCompletion();
|
|
236 suspended = false;
|
|
237 }
|
|
238
|
|
239 /** Indicates whether all Java threads have been suspended via this
|
|
240 interface. */
|
|
241 public boolean isSuspended() {
|
|
242 return suspended;
|
|
243 }
|
|
244
|
|
245 /** Information about toggling of breakpoints */
|
|
246 public static class BreakpointToggleResult {
|
|
247 private boolean success;
|
|
248 private String errMsg;
|
|
249 private int lineNumber;
|
|
250 private int bci;
|
|
251 private boolean wasSet;
|
|
252 private String methodName;
|
|
253 private String methodSig;
|
|
254
|
|
255 /** Success constructor */
|
|
256 public BreakpointToggleResult(int lineNumber, int bci, boolean wasSet,
|
|
257 String methodName, String methodSig) {
|
|
258 this.lineNumber = lineNumber;
|
|
259 this.bci = bci;
|
|
260 this.wasSet = wasSet;
|
|
261 this.methodName = methodName;
|
|
262 this.methodSig = methodSig;
|
|
263 success = true;
|
|
264 }
|
|
265
|
|
266 /** Failure constructor */
|
|
267 public BreakpointToggleResult(String errMsg) {
|
|
268 this.errMsg = errMsg;
|
|
269 success = false;
|
|
270 }
|
|
271
|
|
272 /** Indicates whether this represents a successful return or not */
|
|
273 public boolean getSuccess() { return success; }
|
|
274
|
|
275 /** Valid only if getSuccess() returns false */
|
|
276 public String getErrMsg() { return errMsg; }
|
|
277
|
|
278 /** Line number at which breakpoint toggle occurred; valid only if
|
|
279 getSuccess() returns true. */
|
|
280 public int getLineNumber() { return lineNumber; }
|
|
281
|
|
282 /** BCI at which breakpoint toggle occurred; valid only if
|
|
283 getSuccess() returns true. */
|
|
284 public int getBCI() { return bci; }
|
|
285
|
|
286 /** Indicates whether the breakpoint toggle was the set of a
|
|
287 breakpoint or not; valid only if getSuccess() returns true. */
|
|
288 public boolean getWasSet() { return wasSet; }
|
|
289
|
|
290 /** Method name in which the breakpoint toggle occurred; valid
|
|
291 only if getSuccess() returns true. */
|
|
292 public String getMethodName() { return methodName; }
|
|
293
|
|
294 /** Method signature in which the breakpoint toggle occurred;
|
|
295 valid only if getSuccess() returns true. */
|
|
296 public String getMethodSignature() { return methodSig; }
|
|
297 }
|
|
298
|
|
299 /** Toggle a breakpoint. Throws DebuggerException if a real error
|
|
300 occurred; otherwise returns non-null BreakpointToggleResult. The
|
|
301 work of scanning the loaded classes is done in the target VM
|
|
302 because it turns out to be significantly faster than scanning
|
|
303 through the system dictionary from the SA, and interactivity
|
|
304 when setting breakpoints is important. */
|
|
305 public BreakpointToggleResult toggleBreakpoint(String srcFileName,
|
|
306 String pkgName,
|
|
307 int lineNo) {
|
|
308 saCmdBkptSrcFileName.setValue(srcFileName);
|
|
309 saCmdBkptPkgName.setValue(pkgName);
|
|
310 saCmdBkptLineNumber.setValue(lineNo);
|
|
311 saCmdType.setValue(SA_CMD_TOGGLE_BREAKPOINT);
|
|
312 saCmdPending.setValue(1);
|
|
313 if (waitForCommandCompletion(true)) {
|
|
314 return new BreakpointToggleResult((int) saCmdBkptResLineNumber.getValue(),
|
|
315 (int) saCmdBkptResBCI.getValue(),
|
|
316 (saCmdBkptResWasSet.getValue() != 0),
|
|
317 saCmdBkptResMethodName.getValue(),
|
|
318 saCmdBkptResMethodSig.getValue());
|
|
319 } else {
|
|
320 return new BreakpointToggleResult(saCmdResultErrMsg.getValue());
|
|
321 }
|
|
322 }
|
|
323
|
|
324
|
|
325 //----------------------------------------------------------------------
|
|
326 // Internals only below this point
|
|
327 //
|
|
328
|
|
329 private CIntegerAccessor lookupCInt(String symbolName) {
|
|
330 return new CIntegerAccessor(lookup(symbolName), 4, false);
|
|
331 }
|
|
332
|
|
333 private CStringAccessor lookupCString(String symbolName, int bufLen) {
|
|
334 return new CStringAccessor(lookup(symbolName), bufLen);
|
|
335 }
|
|
336
|
|
337 private JNIHandleAccessor lookupJNIHandle(String symbolName) {
|
|
338 return new JNIHandleAccessor(lookup(symbolName), VM.getVM().getObjectHeap());
|
|
339 }
|
|
340
|
|
341 private JNIid lookupJNIid(String symbolName) {
|
|
342 Address idAddr = lookup(symbolName).getAddressAt(0);
|
|
343 if (idAddr == null) {
|
|
344 return null;
|
|
345 }
|
|
346 return new JNIid(idAddr, VM.getVM().getObjectHeap());
|
|
347 }
|
|
348
|
|
349 private int lookupConstInt(String symbolName) {
|
|
350 Address addr = lookup(symbolName);
|
|
351 return (int) addr.getCIntegerAt(0, 4, false);
|
|
352 }
|
|
353
|
|
354 private boolean setupLookup(String symbolName) {
|
|
355 if (saLibName == null) {
|
|
356 for (int i = 0; i < saLibNames.length; i++) {
|
|
357 Address addr = dbg.lookup(saLibNames[i], symbolName);
|
|
358 if (addr != null) {
|
|
359 saLibName = saLibNames[i];
|
|
360 return true;
|
|
361 }
|
|
362 }
|
|
363 return false;
|
|
364 }
|
|
365 return true;
|
|
366 }
|
|
367
|
|
368 private Address lookup(String symbolName) {
|
|
369 if (saLibName == null) {
|
|
370 for (int i = 0; i < saLibNames.length; i++) {
|
|
371 Address addr = dbg.lookup(saLibNames[i], symbolName);
|
|
372 if (addr != null) {
|
|
373 saLibName = saLibNames[i];
|
|
374 return addr;
|
|
375 }
|
|
376 }
|
|
377 throw new DebuggerException("Unable to find symbol " + symbolName + " in any of the known names for the SA");
|
|
378 }
|
|
379
|
|
380 Address addr = dbg.lookup(saLibName, symbolName);
|
|
381 if (addr == null) {
|
|
382 throw new DebuggerException("Unable to find symbol " + symbolName + " in " + saLibName);
|
|
383 }
|
|
384 return addr;
|
|
385 }
|
|
386
|
|
387 private void waitForCommandCompletion() {
|
|
388 waitForCommandCompletion(false);
|
|
389 }
|
|
390
|
|
391 /** Returns true if command succeeded, false if not */
|
|
392 private boolean waitForCommandCompletion(boolean forBreakpoint) {
|
|
393 long start = System.currentTimeMillis();
|
|
394 long cur = start;
|
|
395 while ((saCmdPending.getValue() != 0) &&
|
|
396 (cur - start < timeoutMillis)) {
|
|
397 try {
|
|
398 java.lang.Thread.currentThread().sleep(10);
|
|
399 } catch (InterruptedException e) {
|
|
400 }
|
|
401 cur = System.currentTimeMillis();
|
|
402 }
|
|
403 if (saCmdPending.getValue() != 0) {
|
|
404 detach();
|
|
405 throw new DebuggerException("VM appears to have died");
|
|
406 }
|
|
407 boolean succeeded = saCmdResult.getValue() == 0;
|
|
408 if (!succeeded &&
|
|
409 (!forBreakpoint || saCmdBkptResWasError.getValue() != 0)) {
|
|
410 String err = saCmdResultErrMsg.getValue();
|
|
411 throw new DebuggerException("Error executing JVMDI command: " + err);
|
|
412 }
|
|
413 return succeeded;
|
|
414 }
|
|
415 }
|