1 /*
2  * Copyright (c) 1998, 2011, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.jdi;
27 
28 import com.sun.jdi.*;
29 import com.sun.jdi.event.*;
30 import com.sun.jdi.connect.spi.Connection;
31 import com.sun.jdi.event.EventSet;
32 
33 import java.util.*;
34 import java.io.IOException;
35 
36 public class TargetVM implements Runnable {
37     private Map<String, Packet> waitingQueue = new HashMap<String, Packet>(32,0.75f);
38     private boolean shouldListen = true;
39     private List<EventQueue> eventQueues = Collections.synchronizedList(new ArrayList<EventQueue>(2));
40     private VirtualMachineImpl vm;
41     private Connection connection;
42     private Thread readerThread;
43     private EventController eventController = null;
44     private boolean eventsHeld = false;
45 
46     /*
47      * TO DO: The limit numbers below are somewhat arbitrary and should
48      * be configurable in the future.
49      */
50     static private final int OVERLOADED_QUEUE = 2000;
51     static private final int UNDERLOADED_QUEUE = 100;
52 
TargetVM(VirtualMachineImpl vm, Connection connection)53     TargetVM(VirtualMachineImpl vm, Connection connection) {
54         this.vm = vm;
55         this.connection = connection;
56         this.readerThread = new Thread(vm.threadGroupForJDI(),
57                                        this, "JDI Target VM Interface");
58         this.readerThread.setDaemon(true);
59     }
60 
start()61     void start() {
62         readerThread.start();
63     }
64 
dumpPacket(Packet packet, boolean sending)65     private void dumpPacket(Packet packet, boolean sending) {
66         String direction = sending ? "Sending" : "Receiving";
67         if (sending) {
68             vm.printTrace(direction + " Command. id=" + packet.id +
69                           ", length=" + packet.data.length +
70                           ", commandSet=" + packet.cmdSet +
71                           ", command=" + packet.cmd +
72                           ", flags=" + packet.flags);
73         } else {
74             String type = (packet.flags & Packet.Reply) != 0 ?
75                           "Reply" : "Event";
76             vm.printTrace(direction + " " + type + ". id=" + packet.id +
77                           ", length=" + packet.data.length +
78                           ", errorCode=" + packet.errorCode +
79                           ", flags=" + packet.flags);
80         }
81         StringBuffer line = new StringBuffer(80);
82         line.append("0000: ");
83         for (int i = 0; i < packet.data.length; i++) {
84             if ((i > 0) && (i % 16 == 0)) {
85                 vm.printTrace(line.toString());
86                 line.setLength(0);
87                 line.append(String.valueOf(i));
88                 line.append(": ");
89                 int len = line.length();
90                 for (int j = 0; j < 6 - len; j++) {
91                     line.insert(0, '0');
92                 }
93             }
94             int val = 0xff & packet.data[i];
95             String str = Integer.toHexString(val);
96             if (str.length() == 1) {
97                 line.append('0');
98             }
99             line.append(str);
100             line.append(' ');
101         }
102         if (line.length() > 6) {
103             vm.printTrace(line.toString());
104         }
105     }
106 
run()107     public void run() {
108         if ((vm.traceFlags & VirtualMachine.TRACE_SENDS) != 0) {
109             vm.printTrace("Target VM interface thread running");
110         }
111         Packet p=null,p2;
112         String idString;
113 
114         while(shouldListen) {
115 
116             boolean done = false;
117             try {
118                 byte b[] = connection.readPacket();
119                 if (b.length == 0) {
120                     done = true;
121                 }
122                 p = Packet.fromByteArray(b);
123             } catch (IOException e) {
124                 done = true;
125             }
126 
127             if (done) {
128                 shouldListen = false;
129                 try {
130                     connection.close();
131                 } catch (IOException ioe) { }
132                 break;
133             }
134 
135             if ((vm.traceFlags & VirtualMachineImpl.TRACE_RAW_RECEIVES) != 0)  {
136                 dumpPacket(p, false);
137             }
138 
139             if((p.flags & Packet.Reply) == 0) {
140                 // It's a command
141                 handleVMCommand(p);
142             } else {
143                 /*if(p.errorCode != Packet.ReplyNoError) {
144                     System.err.println("Packet " + p.id + " returned failure = " + p.errorCode);
145                 }*/
146 
147                 vm.state().notifyCommandComplete(p.id);
148                 idString = String.valueOf(p.id);
149 
150                 synchronized(waitingQueue) {
151                     p2 = waitingQueue.get(idString);
152 
153                     if (p2 != null)
154                         waitingQueue.remove(idString);
155                 }
156 
157                 if(p2 == null) {
158                     // Whoa! a reply without a sender. Problem.
159                     // FIX ME! Need to post an error.
160 
161                     System.err.println("Recieved reply with no sender!");
162                     continue;
163                 }
164                 p2.errorCode = p.errorCode;
165                 p2.data = p.data;
166                 p2.replied = true;
167 
168                 synchronized(p2) {
169                     p2.notify();
170                 }
171             }
172         }
173 
174         // inform the VM mamager that this VM is history
175         vm.vmManager.disposeVirtualMachine(vm);
176 
177         if (eventController != null) {
178             eventController.release();
179         }
180 
181         // close down all the event queues
182         // Closing a queue causes a VMDisconnectEvent to
183         // be put onto the queue.
184         synchronized(eventQueues) {
185             Iterator<EventQueue> iter = eventQueues.iterator();
186             while (iter.hasNext()) {
187                 ((EventQueueImpl)iter.next()).close();
188             }
189         }
190 
191         // indirectly throw VMDisconnectedException to
192         // command requesters.
193         synchronized(waitingQueue) {
194             Iterator<Packet> iter = waitingQueue.values().iterator();
195             while (iter.hasNext()) {
196                 Packet packet = iter.next();
197                 synchronized(packet) {
198                     packet.notify();
199                 }
200             }
201             waitingQueue.clear();
202         }
203 
204         if ((vm.traceFlags & VirtualMachine.TRACE_SENDS) != 0) {
205             vm.printTrace("Target VM interface thread exiting");
206         }
207     }
208 
handleVMCommand(Packet p)209     protected void handleVMCommand(Packet p) {
210         switch (p.cmdSet) {
211             case JDWP.Event.COMMAND_SET:
212                 handleEventCmdSet(p);
213                 break;
214 
215             default:
216                 System.err.println("Ignoring cmd " + p.id + "/" +
217                                    p.cmdSet + "/" + p.cmd + " from the VM");
218                 return;
219         }
220     }
221 
222     /* Events should not be constructed on this thread (the thread
223      * which reads all data from the transport). This means that the
224      * packet cannot be converted to real JDI objects as that may
225      * involve further communications with the back end which would
226      * deadlock.
227      *
228      * Instead the whole packet is passed for lazy eval by a queue
229      * reading thread.
230      */
handleEventCmdSet(Packet p)231     protected void handleEventCmdSet(Packet p) {
232         EventSet eventSet = new EventSetImpl(vm, p);
233 
234         if (eventSet != null) {
235             queueEventSet(eventSet);
236         }
237     }
238 
eventController()239     private EventController eventController() {
240         if (eventController == null) {
241             eventController = new EventController(vm);
242         }
243         return eventController;
244     }
245 
controlEventFlow(int maxQueueSize)246     private synchronized void controlEventFlow(int maxQueueSize) {
247         if (!eventsHeld && (maxQueueSize > OVERLOADED_QUEUE)) {
248             eventController().hold();
249             eventsHeld = true;
250         } else if (eventsHeld && (maxQueueSize < UNDERLOADED_QUEUE)) {
251             eventController().release();
252             eventsHeld = false;
253         }
254     }
255 
notifyDequeueEventSet()256     void notifyDequeueEventSet() {
257         int maxQueueSize = 0;
258         synchronized(eventQueues) {
259             Iterator<EventQueue> iter = eventQueues.iterator();
260             while (iter.hasNext()) {
261                 EventQueueImpl queue = (EventQueueImpl)iter.next();
262                 maxQueueSize = Math.max(maxQueueSize, queue.size());
263             }
264         }
265         controlEventFlow(maxQueueSize);
266     }
267 
queueEventSet(EventSet eventSet)268     private void queueEventSet(EventSet eventSet) {
269         int maxQueueSize = 0;
270 
271         synchronized(eventQueues) {
272             Iterator<EventQueue> iter = eventQueues.iterator();
273             while (iter.hasNext()) {
274                 EventQueueImpl queue = (EventQueueImpl)iter.next();
275                 queue.enqueue(eventSet);
276                 maxQueueSize = Math.max(maxQueueSize, queue.size());
277             }
278         }
279 
280         controlEventFlow(maxQueueSize);
281     }
282 
send(Packet packet)283     void send(Packet packet) {
284         String id = String.valueOf(packet.id);
285 
286         synchronized(waitingQueue) {
287             waitingQueue.put(id, packet);
288         }
289 
290         if ((vm.traceFlags & VirtualMachineImpl.TRACE_RAW_SENDS) != 0) {
291             dumpPacket(packet, true);
292         }
293 
294         try {
295             connection.writePacket(packet.toByteArray());
296         } catch (IOException e) {
297             throw new VMDisconnectedException(e.getMessage());
298         }
299     }
300 
waitForReply(Packet packet)301     void waitForReply(Packet packet) {
302         synchronized(packet) {
303             while ((!packet.replied) && shouldListen) {
304                 try { packet.wait(); } catch (InterruptedException e) {;}
305             }
306 
307             if (!packet.replied) {
308                 throw new VMDisconnectedException();
309             }
310         }
311     }
312 
addEventQueue(EventQueueImpl queue)313     void addEventQueue(EventQueueImpl queue) {
314         if ((vm.traceFlags & VirtualMachine.TRACE_EVENTS) != 0) {
315             vm.printTrace("New event queue added");
316         }
317         eventQueues.add(queue);
318     }
319 
stopListening()320     void stopListening() {
321         if ((vm.traceFlags & VirtualMachine.TRACE_EVENTS) != 0) {
322             vm.printTrace("Target VM i/f closing event queues");
323         }
324         shouldListen = false;
325         try {
326             connection.close();
327         } catch (IOException ioe) { }
328     }
329 
330     private class EventController extends Thread {
331         int controlRequest = 0;
332 
EventController(VirtualMachineImpl vm)333         EventController(VirtualMachineImpl vm) {
334             super(vm.threadGroupForJDI(), "JDI Event Control Thread");
335             setDaemon(true);
336             setPriority((MAX_PRIORITY + NORM_PRIORITY)/2);
337             super.start();
338         }
339 
hold()340         synchronized void hold() {
341             controlRequest++;
342             notifyAll();
343         }
344 
release()345         synchronized void release() {
346             controlRequest--;
347             notifyAll();
348         }
349 
run()350         public void run() {
351             while(true) {
352                 int currentRequest;
353                 synchronized(this) {
354                     while (controlRequest == 0) {
355                         try {wait();} catch (InterruptedException e) {}
356                         if (!shouldListen) return;
357                     }
358                     currentRequest = controlRequest;
359                     controlRequest = 0;
360                 }
361                 try {
362                     if (currentRequest > 0) {
363                         JDWP.VirtualMachine.HoldEvents.process(vm);
364                     } else {
365                         JDWP.VirtualMachine.ReleaseEvents.process(vm);
366                     }
367                 } catch (JDWPException e) {
368                     /*
369                      * Don't want to terminate the thread, so the
370                      * stack trace is printed and we continue.
371                      */
372                     e.toJDIException().printStackTrace(System.err);
373                 }
374             }
375         }
376     }
377 
378 }
379