1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package th.co.edge.jseq;
24
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import com.sun.jdi.AbsentInformationException;
30 import com.sun.jdi.IncompatibleThreadStateException;
31 import com.sun.jdi.Location;
32 import com.sun.jdi.Method;
33 import com.sun.jdi.ReferenceType;
34 import com.sun.jdi.ThreadReference;
35 import com.sun.jdi.VMDisconnectedException;
36 import com.sun.jdi.VirtualMachine;
37 import com.sun.jdi.event.BreakpointEvent;
38 import com.sun.jdi.event.ClassPrepareEvent;
39 import com.sun.jdi.event.Event;
40 import com.sun.jdi.event.EventIterator;
41 import com.sun.jdi.event.EventQueue;
42 import com.sun.jdi.event.EventSet;
43 import com.sun.jdi.event.ExceptionEvent;
44 import com.sun.jdi.event.LocatableEvent;
45 import com.sun.jdi.event.MethodEntryEvent;
46 import com.sun.jdi.event.MethodExitEvent;
47 import com.sun.jdi.event.StepEvent;
48 import com.sun.jdi.event.ThreadDeathEvent;
49 import com.sun.jdi.event.VMDeathEvent;
50 import com.sun.jdi.event.VMDisconnectEvent;
51 import com.sun.jdi.event.VMStartEvent;
52 import com.sun.jdi.request.BreakpointRequest;
53 import com.sun.jdi.request.ClassPrepareRequest;
54 import com.sun.jdi.request.EventRequest;
55 import com.sun.jdi.request.EventRequestManager;
56 import com.sun.jdi.request.ExceptionRequest;
57 import com.sun.jdi.request.MethodEntryRequest;
58 import com.sun.jdi.request.MethodExitRequest;
59 import com.sun.jdi.request.StepRequest;
60 import com.sun.jdi.request.ThreadDeathRequest;
61
62
63
64
65
66
67
68
69
70 public class EventThread extends Thread {
71
72 private final VirtualMachine vm;
73 private final ActivationList rootActivations;
74 private final List<String> includes;
75 private final List<String> excludes;
76 private final List<String> boundaryMethods;
77 private final boolean trace;
78
79 private String startClassName;
80 private String startMethodName;
81
82 private boolean connected = true;
83 private boolean vmDied = true;
84
85 private Map<ThreadReference, ThreadTrace> traceMap =
86 new HashMap<ThreadReference, ThreadTrace>();
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 public EventThread(VirtualMachine vm, ActivationList rootActivations,
146 List<String> includes, List<String> excludes,
147 List<String> boundaryMethods, boolean trace) {
148 super("event-handler");
149 this.vm = vm;
150 this.rootActivations = rootActivations;
151 this.includes = includes;
152 this.excludes = excludes;
153 this.trace = trace;
154 this.boundaryMethods = boundaryMethods;
155 }
156
157
158
159
160
161
162 @Override
163 public void run() {
164 EventQueue queue = vm.eventQueue();
165 while (connected) {
166 try {
167 EventSet eventSet = queue.remove();
168 EventIterator it = eventSet.eventIterator();
169 while (it.hasNext()) {
170 Event event = it.nextEvent();
171 handleEvent(event);
172 }
173 eventSet.resume();
174 } catch (InterruptedException exc) {
175 System.err.println(exc);
176 } catch (VMDisconnectedException discExc) {
177 handleDisconnectedException();
178 break;
179 } catch (NoSuchMethodException e) {
180 e.printStackTrace();
181 }
182 }
183 }
184
185
186
187
188
189
190
191
192
193
194 public void setEventRequests(String startMethod) {
195 EventRequestManager mgr = vm.eventRequestManager();
196
197 if (startMethod != null) {
198 setStartMethodBreakpoints(startMethod);
199 } else {
200
201
202
203
204
205
206 if (includes.isEmpty()) {
207 MethodEntryRequest menr = mgr.createMethodEntryRequest();
208 for (String excludePattern : excludes) {
209 menr.addClassExclusionFilter(excludePattern);
210 }
211 enableRequest(menr);
212 } else {
213 for (String includePattern : includes) {
214 MethodEntryRequest menr = mgr.createMethodEntryRequest();
215 menr.addClassFilter(includePattern);
216 for (String excludePattern : excludes) {
217 menr.addClassExclusionFilter(excludePattern);
218 }
219 enableRequest(menr);
220 }
221 }
222
223 if (includes.isEmpty()) {
224 MethodExitRequest mexr = mgr.createMethodExitRequest();
225 for (String excludePattern : excludes) {
226 mexr.addClassExclusionFilter(excludePattern);
227 }
228 enableRequest(mexr);
229 } else {
230 for (String includePattern : includes) {
231 MethodExitRequest mexr = mgr.createMethodExitRequest();
232 mexr.addClassFilter(includePattern);
233 for (String excludePattern : excludes) {
234 mexr.addClassExclusionFilter(excludePattern);
235 }
236 enableRequest(mexr);
237 }
238 }
239
240 if (includes.isEmpty()) {
241 ExceptionRequest exr =
242 mgr.createExceptionRequest(null, true, true);
243 for (String excludePattern : excludes) {
244 exr.addClassExclusionFilter(excludePattern);
245 }
246 enableRequest(exr);
247 } else {
248 for (String includePattern : includes) {
249 ExceptionRequest exr =
250 mgr.createExceptionRequest(null, true, true);
251 exr.addClassFilter(includePattern);
252 for (String excludePattern : excludes) {
253 exr.addClassExclusionFilter(excludePattern);
254 }
255 enableRequest(exr);
256 }
257 }
258
259 ThreadDeathRequest tdr = mgr.createThreadDeathRequest();
260 enableRequest(tdr);
261 }
262 }
263
264 private void enableRequest(EventRequest request) {
265 request.setSuspendPolicy(EventRequest.SUSPEND_ALL);
266 request.enable();
267 }
268
269 private void setStartMethodBreakpoints(String qualifiedMethodName) {
270 int lastDot = qualifiedMethodName.lastIndexOf('.');
271 if (lastDot < 0) {
272 String message =
273 "Not a qualified method name: \"" + qualifiedMethodName +
274 "\"";
275 throw new IllegalArgumentException(message);
276 }
277 startClassName = qualifiedMethodName.substring(0, lastDot);
278 startMethodName = qualifiedMethodName.substring(lastDot + 1);
279
280 List<ReferenceType> startClasses = vm.classesByName(startClassName);
281 boolean methodFound = false;
282 for (ReferenceType refType : startClasses) {
283 List<Method> methods = refType.methodsByName(startMethodName);
284 if (!methods.isEmpty()) {
285 setMethodBreakpoints(methods);
286 methodFound = true;
287 }
288 }
289 if (!methodFound) {
290 System.err.println("Start method not found: " +
291 qualifiedMethodName +
292 ". Waiting to see if the class will be loaded.");
293 EventRequestManager mgr = vm.eventRequestManager();
294 ClassPrepareRequest cpr = mgr.createClassPrepareRequest();
295 enableRequest(cpr);
296 }
297 }
298
299 private void setMethodBreakpoints(List<Method> methods) {
300 EventRequestManager mgr = vm.eventRequestManager();
301 for (Method method : methods) {
302 try {
303 Location location = method.allLineLocations().get(0);
304 BreakpointRequest bpr = mgr.createBreakpointRequest(location);
305 enableRequest(bpr);
306 } catch (AbsentInformationException e) {
307 e.printStackTrace();
308 }
309 }
310 }
311
312 private void deleteAllEventRequests() {
313 EventRequestManager mgr = vm.eventRequestManager();
314 mgr.deleteEventRequests(mgr.methodEntryRequests());
315 mgr.deleteEventRequests(mgr.methodExitRequests());
316 mgr.deleteEventRequests(mgr.exceptionRequests());
317 }
318
319 private class ThreadTrace {
320 private final ThreadReference thread;
321
322 private Activation currentActivation = null;
323
324 private String currentBoundaryMethod;
325
326 private boolean stopWhenActivationDone = false;
327
328 public ThreadTrace(ThreadReference thread) {
329 this.thread = thread;
330 trace("====== " + thread.name() + " ======");
331 }
332
333 private void classPrepareEvent(ClassPrepareEvent event)
334 throws NoSuchMethodException {
335 if (event.referenceType().name().equals(startClassName)) {
336 List<Method> methods =
337 event.referenceType().methodsByName(startMethodName);
338 if (methods.isEmpty()) {
339 throw new NoSuchMethodException(startClassName + "." +
340 startMethodName);
341 }
342 setMethodBreakpoints(methods);
343 }
344 }
345
346 private void methodEntryEvent(MethodEntryEvent event) {
347 String className = getClassName(event);
348 Method method = event.method();
349 List<String> argumentTypeNames = event.method().argumentTypeNames();
350 if (isBoundaryMethod(event)) {
351 deleteAllEventRequests();
352 currentBoundaryMethod = className + "." + method.name();
353 EventRequestManager mgr = vm.eventRequestManager();
354 MethodExitRequest methodExitRequest =
355 mgr.createMethodExitRequest();
356 enableRequest(methodExitRequest);
357 }
358 methodEntry(className, method, argumentTypeNames);
359 }
360
361 private boolean isBoundaryMethod(MethodEntryEvent event) {
362 String className = getClassName(event);
363 String methodName = event.method().name();
364 String qualifiedMethodName = className + "." + methodName;
365 for (String boundaryMethod : boundaryMethods) {
366 if (qualifiedMethodName.equals(boundaryMethod)) {
367 return true;
368 }
369 }
370 return false;
371 }
372
373 private void methodEntry(String className, Method method,
374 List<String> argumentTypeNames) {
375 trace(className + "." + method.name() + " " + argumentTypeNames);
376 int frameCount = -1;
377 try {
378 frameCount = thread.frameCount();
379 } catch (IncompatibleThreadStateException e) {
380 e.printStackTrace();
381
382 }
383 Activation activation =
384 new Activation(currentActivation, className, method,
385 frameCount);
386 if (currentActivation == null) {
387 rootActivations.add(activation);
388 }
389 currentActivation = activation;
390 }
391
392 private void methodExitEvent(MethodExitEvent event) {
393 if (currentActivation == null) {
394 return;
395 }
396
397 String className = getClassName(event);
398 String methodName = event.method().name();
399 String qualifiedMethodName = className + "." + methodName;
400
401 if (className.equals(currentActivation.getClassName()) &&
402 methodName.equals(currentActivation.getMethod().name())) {
403 currentActivation = currentActivation.getParent();
404 }
405 if (currentActivation == null && stopWhenActivationDone) {
406 deleteAllEventRequests();
407 }
408 if (currentBoundaryMethod != null &&
409 currentBoundaryMethod.equals(qualifiedMethodName)) {
410 setEventRequests(null);
411 }
412 }
413
414 private void exceptionEvent(ExceptionEvent event) {
415 EventRequestManager mgr = vm.eventRequestManager();
416 StepRequest request =
417 mgr.createStepRequest(thread, StepRequest.STEP_MIN,
418 StepRequest.STEP_INTO);
419 request.addCountFilter(1);
420 enableRequest(request);
421 }
422
423 private void stepEvent(StepEvent event) {
424 EventRequestManager mgr = vm.eventRequestManager();
425 mgr.deleteEventRequest(event.request());
426
427 int frameCount = -1;
428 try {
429 frameCount = thread.frameCount();
430 } catch (IncompatibleThreadStateException e) {
431 System.err.println(e);
432 }
433 while (currentActivation.getParent() != null) {
434 if (currentActivation.getFrameCount() == frameCount) {
435 break;
436 }
437 currentActivation = currentActivation.getParent();
438 }
439 }
440
441 private void breakpointEvent(BreakpointEvent event) {
442 String methodName = event.location().method().name();
443 if (!methodName.equals(startMethodName)) {
444 String message =
445 "Unexpected breakpoint. Should be in method " +
446 startMethodName + ", but was in method " +
447 methodName + ". Event=" + event;
448 throw new IllegalArgumentException(message);
449 }
450 methodEntry(startClassName, event.location().method(), event
451 .location().method().argumentTypeNames());
452 EventRequestManager mgr = vm.eventRequestManager();
453 mgr.deleteEventRequest(event.request());
454 setEventRequests(null);
455 stopWhenActivationDone = true;
456 }
457
458 private void threadDeathEvent(ThreadDeathEvent event) {
459 trace("====== " + thread.name() + " end ======");
460 }
461
462 private String getClassName(LocatableEvent event) {
463 String className = getDeclaringType(event).name();
464 try {
465 className = getInstanceType(event).name();
466 } catch (IncompatibleThreadStateException itse) {
467 System.err.println("cannot identify instance type for " +
468 "method call: " + event.location().method().name() +
469 "; using declaring type: " + className);
470 }
471 return className;
472 }
473
474 private ReferenceType getDeclaringType(LocatableEvent event) {
475 return event.location().method().declaringType();
476 }
477
478 private ReferenceType getInstanceType(LocatableEvent event)
479 throws IncompatibleThreadStateException {
480 try {
481 return event.thread().frame(0).thisObject().referenceType();
482 } catch (Exception e) {
483 throw new IncompatibleThreadStateException(e.getMessage());
484 }
485 }
486 }
487
488 private ThreadTrace threadTrace(ThreadReference thread) {
489 ThreadTrace threadTrace = traceMap.get(thread);
490 if (threadTrace == null) {
491 threadTrace = new ThreadTrace(thread);
492 traceMap.put(thread, threadTrace);
493 }
494 return threadTrace;
495 }
496
497 private void handleEvent(Event event) throws NoSuchMethodException {
498 if (event instanceof ClassPrepareEvent) {
499 classPrepareEvent((ClassPrepareEvent) event);
500 } else if (event instanceof MethodEntryEvent) {
501 methodEntryEvent((MethodEntryEvent) event);
502 } else if (event instanceof MethodExitEvent) {
503 methodExitEvent((MethodExitEvent) event);
504 } else if (event instanceof ExceptionEvent) {
505 exceptionEvent((ExceptionEvent) event);
506 } else if (event instanceof StepEvent) {
507 stepEvent((StepEvent) event);
508 } else if (event instanceof BreakpointEvent) {
509 breakpointEvent((BreakpointEvent) event);
510 } else if (event instanceof ThreadDeathEvent) {
511 threadDeathEvent((ThreadDeathEvent) event);
512 } else if (event instanceof VMStartEvent) {
513 vmStartEvent((VMStartEvent) event);
514 } else if (event instanceof VMDeathEvent) {
515 vmDeathEvent((VMDeathEvent) event);
516 } else if (event instanceof VMDisconnectEvent) {
517 vmDisconnectEvent((VMDisconnectEvent) event);
518 } else {
519 throw new Error("Internal error: Unexpected event type");
520 }
521 }
522
523 private synchronized void handleDisconnectedException() {
524 EventQueue queue = vm.eventQueue();
525 while (connected) {
526 try {
527 EventSet eventSet = queue.remove();
528 EventIterator iter = eventSet.eventIterator();
529 while (iter.hasNext()) {
530 Event event = iter.nextEvent();
531 if (event instanceof VMDeathEvent) {
532 vmDeathEvent((VMDeathEvent) event);
533 } else if (event instanceof VMDisconnectEvent) {
534 vmDisconnectEvent((VMDisconnectEvent) event);
535 }
536 }
537 eventSet.resume();
538 } catch (InterruptedException e) {
539 System.err.println(e);
540 }
541 }
542 }
543
544 private void vmStartEvent(VMStartEvent event) {
545 trace("-- VM Started --");
546 }
547
548 private void classPrepareEvent(ClassPrepareEvent event)
549 throws NoSuchMethodException {
550 threadTrace(event.thread()).classPrepareEvent(event);
551 }
552
553 private void methodEntryEvent(MethodEntryEvent event) {
554 threadTrace(event.thread()).methodEntryEvent(event);
555 }
556
557 private void methodExitEvent(MethodExitEvent event) {
558 threadTrace(event.thread()).methodExitEvent(event);
559 }
560
561 private void stepEvent(StepEvent event) {
562 threadTrace(event.thread()).stepEvent(event);
563 }
564
565 private void breakpointEvent(BreakpointEvent event) {
566 threadTrace(event.thread()).breakpointEvent(event);
567 }
568
569 private void threadDeathEvent(ThreadDeathEvent event) {
570 ThreadTrace threadTrace = traceMap.get(event.thread());
571 if (threadTrace != null) {
572 threadTrace.threadDeathEvent(event);
573 }
574 }
575
576 private void exceptionEvent(ExceptionEvent event) {
577 ThreadTrace threadTrace = traceMap.get(event.thread());
578 if (threadTrace != null) {
579 threadTrace.exceptionEvent(event);
580 }
581 }
582
583 private void vmDeathEvent(VMDeathEvent event) {
584 vmDied = true;
585 trace("-- The application exited --");
586 }
587
588 private void vmDisconnectEvent(VMDisconnectEvent event) {
589 connected = false;
590 if (!vmDied) {
591 trace("-- The application has been disconnected --");
592 }
593 }
594
595 private void trace(String s) {
596 if (trace) {
597 System.err.println(s);
598 }
599 }
600 }