1 /* |
|
2 * Copyright (c) 2002, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * 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 } |
|