J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2001 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. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | package com.sun.tools.example.trace; |
| 27 | |
| 28 | import com.sun.jdi.*; |
| 29 | import com.sun.jdi.request.*; |
| 30 | import com.sun.jdi.event.*; |
| 31 | |
| 32 | import java.util.*; |
| 33 | import java.io.PrintWriter; |
| 34 | |
| 35 | /** |
| 36 | * This class processes incoming JDI events and displays them |
| 37 | * |
| 38 | * @author Robert Field |
| 39 | */ |
| 40 | public class EventThread extends Thread { |
| 41 | |
| 42 | private final VirtualMachine vm; // Running VM |
| 43 | private final String[] excludes; // Packages to exclude |
| 44 | private final PrintWriter writer; // Where output goes |
| 45 | |
| 46 | static String nextBaseIndent = ""; // Starting indent for next thread |
| 47 | |
| 48 | private boolean connected = true; // Connected to VM |
| 49 | private boolean vmDied = true; // VMDeath occurred |
| 50 | |
| 51 | // Maps ThreadReference to ThreadTrace instances |
| 52 | private Map traceMap = new HashMap(); |
| 53 | |
| 54 | EventThread(VirtualMachine vm, String[] excludes, PrintWriter writer) { |
| 55 | super("event-handler"); |
| 56 | this.vm = vm; |
| 57 | this.excludes = excludes; |
| 58 | this.writer = writer; |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * Run the event handling thread. |
| 63 | * As long as we are connected, get event sets off |
| 64 | * the queue and dispatch the events within them. |
| 65 | */ |
| 66 | public void run() { |
| 67 | EventQueue queue = vm.eventQueue(); |
| 68 | while (connected) { |
| 69 | try { |
| 70 | EventSet eventSet = queue.remove(); |
| 71 | EventIterator it = eventSet.eventIterator(); |
| 72 | while (it.hasNext()) { |
| 73 | handleEvent(it.nextEvent()); |
| 74 | } |
| 75 | eventSet.resume(); |
| 76 | } catch (InterruptedException exc) { |
| 77 | // Ignore |
| 78 | } catch (VMDisconnectedException discExc) { |
| 79 | handleDisconnectedException(); |
| 80 | break; |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * Create the desired event requests, and enable |
| 87 | * them so that we will get events. |
| 88 | * @param excludes Class patterns for which we don't want events |
| 89 | * @param watchFields Do we want to watch assignments to fields |
| 90 | */ |
| 91 | void setEventRequests(boolean watchFields) { |
| 92 | EventRequestManager mgr = vm.eventRequestManager(); |
| 93 | |
| 94 | // want all exceptions |
| 95 | ExceptionRequest excReq = mgr.createExceptionRequest(null, |
| 96 | true, true); |
| 97 | // suspend so we can step |
| 98 | excReq.setSuspendPolicy(EventRequest.SUSPEND_ALL); |
| 99 | excReq.enable(); |
| 100 | |
| 101 | MethodEntryRequest menr = mgr.createMethodEntryRequest(); |
| 102 | for (int i=0; i<excludes.length; ++i) { |
| 103 | menr.addClassExclusionFilter(excludes[i]); |
| 104 | } |
| 105 | menr.setSuspendPolicy(EventRequest.SUSPEND_NONE); |
| 106 | menr.enable(); |
| 107 | |
| 108 | MethodExitRequest mexr = mgr.createMethodExitRequest(); |
| 109 | for (int i=0; i<excludes.length; ++i) { |
| 110 | mexr.addClassExclusionFilter(excludes[i]); |
| 111 | } |
| 112 | mexr.setSuspendPolicy(EventRequest.SUSPEND_NONE); |
| 113 | mexr.enable(); |
| 114 | |
| 115 | ThreadDeathRequest tdr = mgr.createThreadDeathRequest(); |
| 116 | // Make sure we sync on thread death |
| 117 | tdr.setSuspendPolicy(EventRequest.SUSPEND_ALL); |
| 118 | tdr.enable(); |
| 119 | |
| 120 | if (watchFields) { |
| 121 | ClassPrepareRequest cpr = mgr.createClassPrepareRequest(); |
| 122 | for (int i=0; i<excludes.length; ++i) { |
| 123 | cpr.addClassExclusionFilter(excludes[i]); |
| 124 | } |
| 125 | cpr.setSuspendPolicy(EventRequest.SUSPEND_ALL); |
| 126 | cpr.enable(); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * This class keeps context on events in one thread. |
| 132 | * In this implementation, context is the indentation prefix. |
| 133 | */ |
| 134 | class ThreadTrace { |
| 135 | final ThreadReference thread; |
| 136 | final String baseIndent; |
| 137 | static final String threadDelta = " "; |
| 138 | StringBuffer indent; |
| 139 | |
| 140 | ThreadTrace(ThreadReference thread) { |
| 141 | this.thread = thread; |
| 142 | this.baseIndent = nextBaseIndent; |
| 143 | indent = new StringBuffer(baseIndent); |
| 144 | nextBaseIndent += threadDelta; |
| 145 | println("====== " + thread.name() + " ======"); |
| 146 | } |
| 147 | |
| 148 | private void println(String str) { |
| 149 | writer.print(indent); |
| 150 | writer.println(str); |
| 151 | } |
| 152 | |
| 153 | void methodEntryEvent(MethodEntryEvent event) { |
| 154 | println(event.method().name() + " -- " |
| 155 | + event.method().declaringType().name()); |
| 156 | indent.append("| "); |
| 157 | } |
| 158 | |
| 159 | void methodExitEvent(MethodExitEvent event) { |
| 160 | indent.setLength(indent.length()-2); |
| 161 | } |
| 162 | |
| 163 | void fieldWatchEvent(ModificationWatchpointEvent event) { |
| 164 | Field field = event.field(); |
| 165 | Value value = event.valueToBe(); |
| 166 | println(" " + field.name() + " = " + value); |
| 167 | } |
| 168 | |
| 169 | void exceptionEvent(ExceptionEvent event) { |
| 170 | println("Exception: " + event.exception() + |
| 171 | " catch: " + event.catchLocation()); |
| 172 | |
| 173 | // Step to the catch |
| 174 | EventRequestManager mgr = vm.eventRequestManager(); |
| 175 | StepRequest req = mgr.createStepRequest(thread, |
| 176 | StepRequest.STEP_MIN, |
| 177 | StepRequest.STEP_INTO); |
| 178 | req.addCountFilter(1); // next step only |
| 179 | req.setSuspendPolicy(EventRequest.SUSPEND_ALL); |
| 180 | req.enable(); |
| 181 | } |
| 182 | |
| 183 | // Step to exception catch |
| 184 | void stepEvent(StepEvent event) { |
| 185 | // Adjust call depth |
| 186 | int cnt = 0; |
| 187 | indent = new StringBuffer(baseIndent); |
| 188 | try { |
| 189 | cnt = thread.frameCount(); |
| 190 | } catch (IncompatibleThreadStateException exc) { |
| 191 | } |
| 192 | while (cnt-- > 0) { |
| 193 | indent.append("| "); |
| 194 | } |
| 195 | |
| 196 | EventRequestManager mgr = vm.eventRequestManager(); |
| 197 | mgr.deleteEventRequest(event.request()); |
| 198 | } |
| 199 | |
| 200 | void threadDeathEvent(ThreadDeathEvent event) { |
| 201 | indent = new StringBuffer(baseIndent); |
| 202 | println("====== " + thread.name() + " end ======"); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | /** |
| 207 | * Returns the ThreadTrace instance for the specified thread, |
| 208 | * creating one if needed. |
| 209 | */ |
| 210 | ThreadTrace threadTrace(ThreadReference thread) { |
| 211 | ThreadTrace trace = (ThreadTrace)traceMap.get(thread); |
| 212 | if (trace == null) { |
| 213 | trace = new ThreadTrace(thread); |
| 214 | traceMap.put(thread, trace); |
| 215 | } |
| 216 | return trace; |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Dispatch incoming events |
| 221 | */ |
| 222 | private void handleEvent(Event event) { |
| 223 | if (event instanceof ExceptionEvent) { |
| 224 | exceptionEvent((ExceptionEvent)event); |
| 225 | } else if (event instanceof ModificationWatchpointEvent) { |
| 226 | fieldWatchEvent((ModificationWatchpointEvent)event); |
| 227 | } else if (event instanceof MethodEntryEvent) { |
| 228 | methodEntryEvent((MethodEntryEvent)event); |
| 229 | } else if (event instanceof MethodExitEvent) { |
| 230 | methodExitEvent((MethodExitEvent)event); |
| 231 | } else if (event instanceof StepEvent) { |
| 232 | stepEvent((StepEvent)event); |
| 233 | } else if (event instanceof ThreadDeathEvent) { |
| 234 | threadDeathEvent((ThreadDeathEvent)event); |
| 235 | } else if (event instanceof ClassPrepareEvent) { |
| 236 | classPrepareEvent((ClassPrepareEvent)event); |
| 237 | } else if (event instanceof VMStartEvent) { |
| 238 | vmStartEvent((VMStartEvent)event); |
| 239 | } else if (event instanceof VMDeathEvent) { |
| 240 | vmDeathEvent((VMDeathEvent)event); |
| 241 | } else if (event instanceof VMDisconnectEvent) { |
| 242 | vmDisconnectEvent((VMDisconnectEvent)event); |
| 243 | } else { |
| 244 | throw new Error("Unexpected event type"); |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | /*** |
| 249 | * A VMDisconnectedException has happened while dealing with |
| 250 | * another event. We need to flush the event queue, dealing only |
| 251 | * with exit events (VMDeath, VMDisconnect) so that we terminate |
| 252 | * correctly. |
| 253 | */ |
| 254 | synchronized void handleDisconnectedException() { |
| 255 | EventQueue queue = vm.eventQueue(); |
| 256 | while (connected) { |
| 257 | try { |
| 258 | EventSet eventSet = queue.remove(); |
| 259 | EventIterator iter = eventSet.eventIterator(); |
| 260 | while (iter.hasNext()) { |
| 261 | Event event = iter.nextEvent(); |
| 262 | if (event instanceof VMDeathEvent) { |
| 263 | vmDeathEvent((VMDeathEvent)event); |
| 264 | } else if (event instanceof VMDisconnectEvent) { |
| 265 | vmDisconnectEvent((VMDisconnectEvent)event); |
| 266 | } |
| 267 | } |
| 268 | eventSet.resume(); // Resume the VM |
| 269 | } catch (InterruptedException exc) { |
| 270 | // ignore |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | private void vmStartEvent(VMStartEvent event) { |
| 276 | writer.println("-- VM Started --"); |
| 277 | } |
| 278 | |
| 279 | // Forward event for thread specific processing |
| 280 | private void methodEntryEvent(MethodEntryEvent event) { |
| 281 | threadTrace(event.thread()).methodEntryEvent(event); |
| 282 | } |
| 283 | |
| 284 | // Forward event for thread specific processing |
| 285 | private void methodExitEvent(MethodExitEvent event) { |
| 286 | threadTrace(event.thread()).methodExitEvent(event); |
| 287 | } |
| 288 | |
| 289 | // Forward event for thread specific processing |
| 290 | private void stepEvent(StepEvent event) { |
| 291 | threadTrace(event.thread()).stepEvent(event); |
| 292 | } |
| 293 | |
| 294 | // Forward event for thread specific processing |
| 295 | private void fieldWatchEvent(ModificationWatchpointEvent event) { |
| 296 | threadTrace(event.thread()).fieldWatchEvent(event); |
| 297 | } |
| 298 | |
| 299 | void threadDeathEvent(ThreadDeathEvent event) { |
| 300 | ThreadTrace trace = (ThreadTrace)traceMap.get(event.thread()); |
| 301 | if (trace != null) { // only want threads we care about |
| 302 | trace.threadDeathEvent(event); // Forward event |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * A new class has been loaded. |
| 308 | * Set watchpoints on each of its fields |
| 309 | */ |
| 310 | private void classPrepareEvent(ClassPrepareEvent event) { |
| 311 | EventRequestManager mgr = vm.eventRequestManager(); |
| 312 | List fields = event.referenceType().visibleFields(); |
| 313 | for (Iterator it = fields.iterator(); it.hasNext(); ) { |
| 314 | Field field = (Field)it.next(); |
| 315 | ModificationWatchpointRequest req = |
| 316 | mgr.createModificationWatchpointRequest(field); |
| 317 | for (int i=0; i<excludes.length; ++i) { |
| 318 | req.addClassExclusionFilter(excludes[i]); |
| 319 | } |
| 320 | req.setSuspendPolicy(EventRequest.SUSPEND_NONE); |
| 321 | req.enable(); |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | private void exceptionEvent(ExceptionEvent event) { |
| 326 | ThreadTrace trace = (ThreadTrace)traceMap.get(event.thread()); |
| 327 | if (trace != null) { // only want threads we care about |
| 328 | trace.exceptionEvent(event); // Forward event |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | public void vmDeathEvent(VMDeathEvent event) { |
| 333 | vmDied = true; |
| 334 | writer.println("-- The application exited --"); |
| 335 | } |
| 336 | |
| 337 | public void vmDisconnectEvent(VMDisconnectEvent event) { |
| 338 | connected = false; |
| 339 | if (!vmDied) { |
| 340 | writer.println("-- The application has been disconnected --"); |
| 341 | } |
| 342 | } |
| 343 | } |