3 import java.io.IOException;
4 import java.util.HashMap;
5 import java.util.Iterator;
9 import com.sun.jdi.ThreadReference;
10 import com.sun.jdi.VirtualMachine;
11 import com.sun.jdi.connect.AttachingConnector;
12 import com.sun.jdi.connect.Connector;
13 import com.sun.jdi.connect.IllegalConnectorArgumentsException;
14 import com.sun.jdi.connect.LaunchingConnector;
15 import com.sun.jdi.connect.ListeningConnector;
16 import com.sun.jdi.connect.VMStartException;
17 import com.sun.jdi.request.ClassPrepareRequest;
18 import com.sun.jdi.request.EventRequest;
19 import com.sun.jdi.request.EventRequestManager;
20 import jde.debugger.command.CommandHandler;
21 import jde.debugger.command.ProcessCommandHandler;
22 import jde.debugger.gui.GUI;
23 import jde.debugger.spec.EventRequestSpecList;
27 * The main class for debugging a specific process. A Debugger instance
28 * handles the following tasks:
30 * <li>Executing commands sent from Emacs (through the
31 * ProcessCommandHandler in {@link #m_handler m_handler})</li>
32 * <li>Handling events from the VM (through the EventHandler in
33 * {@link #m_eventHandler m_eventHandler})</li>
34 * <li>Keeping track of requested event specifications that haven't
35 * yet been resolved ({@link #m_eventRequestSpecList m_eventRequestSpecList})</li>
36 * <li>Keeping track of objects and the ID that is used as a
37 * reference in the Emacs/Java communication. ({@link #m_objectStore m_objectStore})</li>
38 * <li>Connecting the standard input/output/error streams of an
39 * application that was launched through the debugger to Emacs. This
40 * is done through {@link #m_sio m_sio}</li>
43 * Created: Tue Jan 08 12:24:36 2002
45 * @author Petter Måhlén
46 * @version $Revision: 1.3 $
49 public class Debugger implements Protocol {
50 private ProcessCommandHandler m_handler;
51 private VirtualMachine m_vm;
52 private boolean m_vmAlive;
53 private EventRequestSpecList m_eventRequestSpecList;
54 private Integer m_procID;
55 private DebuggeeSIO m_sio;
56 private EventHandler m_eventHandler;
57 private ObjectStore m_objectStore;
58 private boolean m_useGUI;
62 * This map stores the event requests that are NOT specs. storing
63 * it here allows the user to cancel them easily: they just specify the
64 * id, that gets reverse-looked up here, uniquely identifying the actual
67 * Of course, the id is sent back to the user when the actual command is
68 * responded to, so that the handle is available to jde in the first
71 protected Map m_identifiableEventRequests;
74 * Creates a new <code>Debugger</code> instance. Before the
75 * instance can be used, the following things must happen:
77 * <li>The VM connection must be set up. This is done through
78 * either {@link #launchVM launchVM()}, {@link #attachVMShmem
79 * attachVMShmem()}, {@link #attachVMSocket attachVMSocket()},
80 * {@link #listenShmem listenShmem()}, or {@link
81 * #listenSocket listenSocket()}.</li>
82 * <li>The Debugger instance must have been started. This is done
83 * through the {@link #start start} method.</li>
86 * @param procID an <code>Integer</code> value identifying this
87 * process in the communication with Emacs.
89 public Debugger(Integer procID, boolean useGUI) {
90 JDE.debug(EVENTS, "creating debugger with id: " + procID);
93 m_identifiableEventRequests = new HashMap();
94 m_handler = new ProcessCommandHandler(this);
95 m_eventRequestSpecList = new EventRequestSpecList(this);
96 m_sio = new DebuggeeSIO(this);
97 m_eventHandler = new EventHandler(this);
98 m_objectStore = new ObjectStore(this);
102 m_gui = new GUI(this);
108 * Starts up the threads that make the debugger go. Also makes sure that
109 * a ClassPrepareRequest is sent to the VM, so that it's possible to resolve
112 * @exception JDEException if an error occurs
114 public void start() throws JDEException {
115 JDE.debug(EVENTS, "starting debugger: " + m_procID);
118 throw new JDEException("INTERNAL ERROR: attempted to start debugger " + m_procID + " without a VM");
122 m_eventHandler.start();
125 // we need to raise all class prepare events to
126 // make sure we resolve the corresponding specs.
127 ClassPrepareRequest cprequest =
128 m_vm.eventRequestManager().createClassPrepareRequest();
130 // this (hack?) is used to identify if the user itself specified
131 // a class prepare request, or the event was raised because of
133 cprequest.putProperty("default", "default");
134 cprequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
136 // XXX we don't want to get ClassPrepareEvents for the standard
137 // java classes, since there's no point in debugging those. At least
138 // I don't think there is. / Petter
139 cprequest.addClassExclusionFilter("java.*");
140 cprequest.addClassExclusionFilter("javax.*");
141 cprequest.addClassExclusionFilter("sun.*");
147 * Tells the debugger to stop executing, meaning that the VM is shut down.
148 * The actual execution of the threads is not stopped until the shutdown()
149 * method is called, which is only done when a VM disconnect event is sent
152 * @see EventHandler#vmDisconnectEvent
154 public void stopExecution() {
155 VMUtil.shutdown(m_vm);
160 * Shuts the debugger down and deregisters it from the SessionManager.
162 * @exception JDEException if an error occurs
163 * @see SessionManager#deregisterDebugger
165 public void shutdown() throws JDEException {
166 JDE.debug(EVENTS, "debugger " + m_procID + " shutting down");
169 // the VM will not have been properly shut down if the application runs
170 // to the end and the VM disconnects because of that. On the other hand,
171 // if the Emacs side issues a Finish command, the VM will be shut down,
172 // which is the reason for the m_vmAlive variable.
173 VMUtil.shutdown(m_vm);
177 if (m_handler != null) {
178 m_handler.requestStop();
181 if (m_eventHandler != null) {
182 m_eventHandler.shutdown();
192 // XXX - stop the rest of the stuff as well, when that has been added.
194 // Invalidate this object
196 m_eventHandler = null;
201 // this debugger should no longer be available to the session manager
202 SessionManager.deregisterDebugger(this);
208 * Launches a virtual machine for the process to be debugged, and
209 * sets up the standard in/out/err streams for the process.
211 * @param cmdID an <code>Integer</code> value used for setting up
213 * @param args a <code>List</code> value
214 * @exception JDEException if an error occurs
216 public void launchVM(Integer cmdID, List args) throws JDEException {
218 // this is the connector that launches a debuggee vm
219 String connectSpec = "com.sun.jdi.CommandLineLaunch";
221 // check if this kind of connector is, indeed,
222 // available. if not, throw an exception.
223 LaunchingConnector connector = (LaunchingConnector) VMUtil.getConnector(connectSpec);
224 if (connector == null)
225 throw new JDEException("No such connector is available: "+connectSpec);
228 // first set up the argument map. a table that describes the
229 // different keys should be in the public jpda documentation.
230 Map argumentMap = connector.defaultArguments();
232 Connector.Argument mainArg =
233 (Connector.Argument)argumentMap.get("main");
235 // compose the command line
236 String commandLine = "";
237 String quote =((Connector.Argument)argumentMap.get("quote")).value();
239 // check if there are special launch options we need to process
240 if (args.size() == 0)
241 throw new JDEException("Insufficient arguments");
243 // XXX - not sure if it's a brilliant idea to hard-code the
244 // java executable name here, but that's the way it was
245 // done. Anyway, if it becomes a problem, it is now flagged. / Petter
246 String executable = "java";
248 // be careful with the loop here....
249 while ((args.size() >0)
250 && args.get(0).toString().startsWith("-")) {
251 String origArg = args.remove(0).toString();
252 String arg = origArg.toLowerCase();
253 if (arg.equals("-vmexec")) {
254 if (args.size() == 0)
255 throw new JDEException("Missing argument to 'use_executable'");
256 executable = args.remove(0).toString();
257 Connector.Argument vmexecArg =
258 (Connector.Argument)argumentMap.get("vmexec");
259 vmexecArg.setValue(executable);
261 else if (arg.equals("-home")) {
262 if (args.size() == 0)
263 throw new JDEException("Missing argument to 'home'");
264 String home = args.remove(0).toString();
265 Connector.Argument homeArg = (Connector.Argument) argumentMap.get("home");
266 homeArg.setValue(home);
270 args.add(0, origArg);
275 if (args.size() == 0)
276 throw new JDEException("Missing arguments: no class specified?");
278 // take care of spaces too! so quote everything.
279 Iterator iterator = args.iterator();
280 while(iterator.hasNext()) {
281 // commandLine += quote + iterator.next() + quote + " ";
282 String arg = (String)iterator.next();
283 if (arg.equalsIgnoreCase("-classic")) {
284 Connector.Argument optionsArg =
285 (Connector.Argument)argumentMap.get("options");
286 String options = optionsArg.value();
287 options = "-classic" + " " + options;
288 optionsArg.setValue(options);
289 JDE.signal(m_procID, MESSAGE, "VM options: '" + options + "'", QUOTE);
292 commandLine += quote + arg + quote + " ";
294 mainArg.setValue(commandLine);
299 m_vm = connector.launch(argumentMap);
300 JDE.signal(m_procID, MESSAGE, "Launched VM " + m_vm.description(), QUOTE);
303 // If we're launching the application, the standard in/out/err needs to be connected
305 m_sio.initConnect(cmdID);
306 } catch (IOException ex) {
307 JDE.debug(EXCEPTION, "Exception launching VM: " + ex.toString());
308 throw new JDEException("Unable to launch: " + ex.toString().replace('\\','/'));
309 } catch (IllegalConnectorArgumentsException ex) {
310 throw new JDEException("Invalid or inconsistent connector arguments for connector '"+connector+"'");
311 } catch (VMStartException ex) {
312 throw new JDEException(ex.getMessage().toString().replace('\\','/'));
318 * Attaches to a currently running VM through shared memory. The
319 * JPDA framework currently only supports that on Windows systems.
321 * @param args a <code>List</code> value
322 * @exception JDEException if an error occurs
324 public void attachVMShmem(List args) throws JDEException {
327 throw new JDEException("Missing name");
329 // the attaching connector...
330 String connectSpec = null;
331 connectSpec = "com.sun.jdi.SharedMemoryAttach";
333 AttachingConnector connector = (AttachingConnector) VMUtil.getConnector(connectSpec);
334 if (connector == null)
335 throw new JDEException("No such connector is available: "+connectSpec);
338 Map argumentMap = connector.defaultArguments();
340 Connector.Argument nameArg =
341 (Connector.Argument) argumentMap.get("name");
342 nameArg.setValue(args.remove(0).toString());
344 m_vm = connector.attach(argumentMap);
347 JDE.signal(m_procID, MESSAGE, "Attached VM (shmem) " + m_vm.description(), QUOTE);
349 } catch (IOException ex) {
350 JDE.debug(EXCEPTION, ex.toString());
351 throw new JDEException("Error attempting to attach to process via shared memory.");
352 } catch (IllegalConnectorArgumentsException ex) {
353 throw new JDEException("Illegal connector arguments for connector '"+connector);
358 * Attaches to a currently running VM through socket
359 * communication. Works for all platforms, but is slower than
362 * @param args a <code>List</code> value
363 * @exception JDEException if an error occurs
365 public void attachVMSocket(List args) throws JDEException {
368 throw new JDEException("Missing arguments: specify at least the port");
370 // the attaching connector...
371 String connectSpec = null;
372 connectSpec = "com.sun.jdi.SocketAttach";
374 AttachingConnector connector = (AttachingConnector) VMUtil.getConnector(connectSpec);
375 if (connector == null)
376 throw new JDEException("No such connector is available: " + connectSpec);
379 Map argumentMap = connector.defaultArguments();
381 while ((args.size() > 0) && args.get(0).toString().startsWith("-")) {
382 String arg = args.remove(0).toString().toLowerCase();
383 if (arg.equals("-host")) {
384 if (args.size() == 0)
385 throw new JDEException("Missing argument to 'host'");
386 String host = args.remove(0).toString();
387 Connector.Argument hostArg =
388 (Connector.Argument)argumentMap.get("hostname");
389 hostArg.setValue(host);
390 } else if (arg.equals("-port")) {
391 if (args.size() == 0)
392 throw new JDEException("Missing argument to 'port'");
393 String port = args.remove(0).toString();
394 Connector.Argument portArg =
395 (Connector.Argument)argumentMap.get("port");
396 portArg.setValue(port);
403 m_vm = connector.attach(argumentMap);
406 JDE.signal(m_procID, MESSAGE, "Attached VM (socket) " + m_vm.description(), QUOTE);
408 } catch (IOException ex) {
409 JDE.debug(EXCEPTION, ex.toString());
410 throw new JDEException("I/O error occurred while attempting to attach process.");
411 } catch (IllegalConnectorArgumentsException ex) {
412 throw new JDEException("Illegal connector arguments for connector '"+connector);
418 * Starts a thread that waits for a VM to be launched and connect
419 * to a given address using shared memory. Executing this in a
420 * separate thread means that the command handler can go on
421 * waiting for new commands, without freezing up. The new thread
422 * dies as soon as the VM connects.
424 * @param address a <code>String</code> value
425 * @exception JDEException if an error occurs
427 public void listenShmem(final String address) throws JDEException {
428 String connectSpec = "com.sun.jdi.SharedMemoryListen";
430 final ListeningConnector connector = (ListeningConnector) VMUtil.getConnector(connectSpec);
431 final Debugger thisAsLocal = this;
433 if (connector == null)
434 throw new JDEException("No such connector is available: "+connectSpec);
436 Thread thread = new Thread("Listen on shared memory channel.") {
441 Map argumentMap = connector.defaultArguments();
443 Connector.Argument nameArg =
444 (Connector.Argument)argumentMap.get("name");
445 nameArg.setValue(address);
447 connector.startListening(argumentMap);
448 m_vm = connector.accept(argumentMap);
449 connector.stopListening(argumentMap);
451 JDE.signal(m_procID, MESSAGE, "Attached VM (shmem) " + m_vm.description(), QUOTE);
454 } catch (IOException ex) {
455 JDE.debug(EXCEPTION, ex.toString());
456 JDE.signal(m_procID, MESSAGE,
457 "I/O error occurred while listening at shared memory address:"
461 SessionManager.deregisterDebugger(thisAsLocal);
463 catch (JDEException e) { /* FALLTHROUGH */ }
464 } catch (IllegalConnectorArgumentsException ex) {
465 JDE.debug(EXCEPTION, ex.toString());
466 JDE.signal(m_procID, MESSAGE,
467 "Illegal argument error occurred while listening " +
468 "at shared memory address: " + address,
471 SessionManager.deregisterDebugger(thisAsLocal);
473 catch (JDEException e) { /* FALLTHROUGH */ }
474 } catch (JDEException ex) {
475 JDE.debug(EXCEPTION, ex.toString());
476 JDE.signal(m_procID, MESSAGE,
477 "Error starting up debugger: " + ex,
480 SessionManager.deregisterDebugger(thisAsLocal);
482 catch (JDEException e) { /* FALLTHROUGH */ }
487 JDE.signal(m_procID, MESSAGE,
488 "Listening at shared memory address: " + address,
495 * Starts a thread that waits for a VM to be launched and connect
496 * to a given address using socket communication. Executing this in a
497 * separate thread means that the command handler can go on
498 * waiting for new commands, without freezing up. The new thread
499 * dies as soon as the VM connects.
501 * @param address a <code>String</code> value
502 * @exception JDEException if an error occurs
504 public void listenSocket(final String address) throws JDEException {
505 String connectSpec = "com.sun.jdi.SocketListen";
507 final ListeningConnector connector = (ListeningConnector) VMUtil.getConnector(connectSpec);
508 final Debugger thisAsLocal = this;
510 if (connector == null)
511 throw new JDEException("No such connector is available: "+connectSpec);
513 Thread thread = new Thread("Listen on socket.") {
517 Map argumentMap = connector.defaultArguments();
519 Connector.Argument portArg =
520 (Connector.Argument)argumentMap.get("port");
521 portArg.setValue(address);
523 connector.startListening(argumentMap);
524 m_vm = connector.accept(argumentMap);
525 connector.stopListening(argumentMap);
527 JDE.signal(m_procID, MESSAGE,
528 "Attached VM (socket) " + m_vm.description(),
532 } catch (IOException ex) {
533 JDE.debug(EXCEPTION, ex.toString());
534 JDE.signal(m_procID, MESSAGE,
535 "Error occurred when listening on socket: " + ex,
538 SessionManager.deregisterDebugger(thisAsLocal);
540 catch (JDEException e) { /* FALLTHROUGH */ }
541 } catch(IllegalConnectorArgumentsException ex) {
542 JDE.signal(m_procID, MESSAGE,
543 "Illegal connector arguments for connector '"+connector + " " + ex,
546 SessionManager.deregisterDebugger(thisAsLocal);
548 catch (JDEException e) { /* FALLTHROUGH */ }
549 } catch(JDEException ex) {
550 JDE.signal(m_procID, MESSAGE,
551 "Error starting up debugger: " + ex,
554 SessionManager.deregisterDebugger(thisAsLocal);
556 catch (JDEException e) { /* FALLTHROUGH */ }
561 JDE.signal(m_procID, MESSAGE,
562 "Listening at socket address: " + address, QUOTE);
567 public EventRequestSpecList getEventRequestSpecList() {
568 return m_eventRequestSpecList;
571 public CommandHandler getCommandHandler() {
575 public Integer getProcID() {
579 public ObjectStore getStore() {
580 return m_objectStore;
583 public void signalCommandResult(Integer cmdID, String message, boolean success) {
584 JDE.commandResult(cmdID, message, success, NOQUOTE);
587 public void signalCommandResult(Integer cmdID, String message, boolean success, boolean quote) {
588 JDE.commandResult(cmdID, message, success, quote);
591 public VirtualMachine getVM() {
595 public GUI getGUI() {
600 * Returns true if this is a valid debugger. A debugger is valid if
601 * the start() method has been called, but not the shutdown()
602 * method. XXX - actually not correct at the moment, but it
603 * doesn't matter. The method returns true from the moment the
604 * Debugger instance has been created until the shutdown() method
607 * @return a <code>boolean</code> value
609 public boolean isValid() {
610 // Am using the command handler to indicate whether this is a live
612 return m_handler != null;
616 * Returns the thread corresponding to a given name, or null if
617 * there is no such thread.
621 public ThreadReference getThreadReference(String name) {
623 List list = m_vm.allThreads();
624 Iterator it = list.iterator();
626 ThreadReference thread;
627 while (it.hasNext()) {
628 thread = (ThreadReference)it.next();
629 if (thread.name().equals(name)) return thread;
637 * Adds an event request to the identifiable events, for future
638 * reference. Also enables the event.
640 * @return an identifier for the request
642 public Long addIdentifiableRequest(EventRequest e) {
643 Long id = SessionManager.generateObjectID();
644 synchronized (m_identifiableEventRequests) {
645 m_identifiableEventRequests.put(id, e);
654 * Removes an event request. Also disables/deletes from the vm.
656 public void deleteIdentifiableRequest(Long id) throws JDEException {
658 EventRequestManager erm = getVM().eventRequestManager();
660 synchronized (m_identifiableEventRequests) {
661 if (!m_identifiableEventRequests.containsKey(id)) {
662 throw new JDEException("Invalid request ID");
664 Object e = m_identifiableEventRequests.remove(id);
666 throw new JDEException("No such event request");
667 } else if (e instanceof EventRequest) {
668 ((EventRequest)e).disable();
669 erm.deleteEventRequest((EventRequest)e);
671 throw new JDEException("INTERNAL ERROR: Not an event request : " + e.toString());
677 /** Add an EventSetListener. If the listener is already in the
678 * list, nothing is done.<p>
680 * This is handled by the eventHandler, but there is no public
683 public void addEventSetListener(EventSetListener listener) {
684 m_eventHandler.addEventSetListener(listener);
687 /** Remove an EventSetListener. If the listener is already in the
688 * list, nothing is done */
689 public void removeEventSetListener(EventSetListener listener) {
690 m_eventHandler.removeEventSetListener(listener);
693 /** Add an CommandListener. If the listener is already in the
694 * list, nothing is done.<p>
697 public void addCommandListener(CommandListener listener) {
698 m_handler.addCommandListener(listener);
701 /** Remove an CommandListener. If the listener is already in the
702 * list, nothing is done */
703 public void removeCommandListener(CommandListener listener) {
704 m_handler.removeCommandListener(listener);
711 * $Log: Debugger.java,v $
712 * Revision 1.3 2003/04/29 16:51:56 troy
713 * Initial version of GUI. Includes display of local variables.
715 * Revision 1.2 2003/01/15 05:50:51 paulk
718 * Revision 1.1 2003/01/08 07:16:45 paulk
723 // End of Debugger.java