1 /*
   2  * Copyright 2001-2003 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.
   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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  *
  23  */
  24 
  25 package sun.jvm.hotspot.bugspot;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.io.*;
  30 import java.net.*;
  31 import java.util.*;
  32 import javax.swing.*;
  33 import javax.swing.filechooser.*;
  34 import sun.jvm.hotspot.debugger.*;
  35 import sun.jvm.hotspot.debugger.cdbg.*;
  36 import sun.jvm.hotspot.debugger.posix.*;
  37 import sun.jvm.hotspot.debugger.win32.*;
  38 import sun.jvm.hotspot.livejvm.*;
  39 import sun.jvm.hotspot.memory.*;
  40 import sun.jvm.hotspot.oops.*;
  41 import sun.jvm.hotspot.runtime.*;
  42 import sun.jvm.hotspot.ui.*;
  43 import sun.jvm.hotspot.utilities.*;
  44 
  45 /** The BugSpot component. This is embeddable in an application by
  46     virtue of its being a JComponent. It (currently) requires the use
  47     of a menu bar which can be fetched via getMenuBar(). This is
  48     intended ultimately to replace HSDB. */
  49 
  50 public class BugSpot extends JPanel {
  51   public BugSpot() {
  52     super();
  53     Runtime.getRuntime().addShutdownHook(new java.lang.Thread() {
  54         public void run() {
  55           detachDebugger();
  56         }
  57       });
  58   }
  59 
  60   /** Turn on or off MDI (Multiple Document Interface) mode. When MDI
  61       is enabled, the BugSpot component contains a JDesktopPane and all
  62       windows are JInternalFrames. When disabled, only the menu bar is
  63       relevant. */
  64   public void setMDIMode(boolean onOrOff) {
  65     mdiMode = onOrOff;
  66   }
  67 
  68   /** Indicates whether MDI mode is enabled. */
  69   public boolean getMDIMode() {
  70     return mdiMode;
  71   }
  72 
  73   /** Build user interface widgets. This must be called before adding
  74       the BugSpot component to its parent. */
  75   public void build() {
  76     setLayout(new BorderLayout());
  77 
  78     menuBar = new JMenuBar();
  79 
  80     attachMenuItems = new java.util.ArrayList();
  81     detachMenuItems = new java.util.ArrayList();
  82     debugMenuItems  = new java.util.ArrayList();
  83     suspendDebugMenuItems = new java.util.ArrayList();
  84     resumeDebugMenuItems = new java.util.ArrayList();
  85 
  86     //
  87     // File menu
  88     //
  89 
  90     JMenu menu = createMenu("File", 'F', 0);
  91     JMenuItem item;
  92     item = createMenuItem("Open source file...",
  93                           new ActionListener() {
  94                               public void actionPerformed(ActionEvent e) {
  95                                 openSourceFile();
  96                               }
  97                             },
  98                           KeyEvent.VK_O, InputEvent.CTRL_MASK,
  99                           'O', 0);
 100     menu.add(item);
 101     detachMenuItems.add(item);
 102 
 103     menu.addSeparator();
 104 
 105     item = createMenuItem("Attach to process...",
 106                           new ActionListener() {
 107                               public void actionPerformed(ActionEvent e) {
 108                                 showAttachDialog();
 109                               }
 110                             },
 111                           'A', 0);
 112     menu.add(item);
 113     attachMenuItems.add(item);
 114 
 115     item = createMenuItem("Detach",
 116                           new ActionListener() {
 117                               public void actionPerformed(ActionEvent e) {
 118                                 detach();
 119                               }
 120                             },
 121                           'D', 0);
 122     menu.add(item);
 123     detachMenuItems.add(item);
 124 
 125     // Disable detach menu items at first
 126     setMenuItemsEnabled(detachMenuItems, false);
 127 
 128     menu.addSeparator();
 129 
 130     menu.add(createMenuItem("Exit",
 131                             new ActionListener() {
 132                                 public void actionPerformed(ActionEvent e) {
 133                                   detach();
 134                                   System.exit(0);
 135                                 }
 136                               },
 137                             'x', 1));
 138 
 139     menuBar.add(menu);
 140 
 141     //
 142     // Debug menu
 143     //
 144 
 145     debugMenu = createMenu("Debug", 'D', 0);
 146     item = createMenuItem("Go",
 147                           new ActionListener() {
 148                               public void actionPerformed(ActionEvent e) {
 149                                 if (!attached) return;
 150                                 if (!isSuspended()) return;
 151                                 resume();
 152                               }
 153                             },
 154                           KeyEvent.VK_F5, 0,
 155                           'G', 0);
 156     debugMenu.add(item);
 157     resumeDebugMenuItems.add(item);
 158 
 159     item = createMenuItem("Break",
 160                           new ActionListener() {
 161                               public void actionPerformed(ActionEvent e) {
 162                                 if (!attached) {
 163                                   System.err.println("Not attached");
 164                                   return;
 165                                 }
 166                                 if (isSuspended()) {
 167                                   System.err.println("Already suspended");
 168                                   return;
 169                                 }
 170                                 suspend();
 171                               }
 172                             },
 173                           'B', 0);
 174     debugMenu.add(item);
 175     suspendDebugMenuItems.add(item);
 176 
 177     debugMenu.addSeparator();
 178 
 179     item = createMenuItem("Threads...",
 180                           new ActionListener() {
 181                               public void actionPerformed(ActionEvent e) {
 182                                 showThreadsDialog();
 183                               }
 184                             },
 185                           'T', 0);
 186     debugMenu.add(item);
 187     debugMenuItems.add(item);
 188     // FIXME: belongs under "View -> Debug Windows"
 189     item = createMenuItem("Memory",
 190                           new ActionListener() {
 191                               public void actionPerformed(ActionEvent e) {
 192                                 showMemoryDialog();
 193                               }
 194                             },
 195                           'M', 0);
 196     debugMenu.add(item);
 197     debugMenuItems.add(item);
 198 
 199     debugMenu.setEnabled(false);
 200     menuBar.add(debugMenu);
 201 
 202     if (mdiMode) {
 203       desktop = new JDesktopPane();
 204       add(desktop, BorderLayout.CENTER);
 205     }
 206 
 207     fixedWidthFont = GraphicsUtilities.lookupFont("Courier");
 208 
 209     debugEventTimer = new javax.swing.Timer(100, new ActionListener() {
 210         public void actionPerformed(ActionEvent e) {
 211           pollForDebugEvent();
 212         }
 213       });
 214   }
 215 
 216   public JMenuBar getMenuBar() {
 217     return menuBar;
 218   }
 219 
 220   public void showAttachDialog() {
 221     setMenuItemsEnabled(attachMenuItems, false);
 222     final FrameWrapper attachDialog = newFrame("Attach to process");
 223     attachDialog.getContentPane().setLayout(new BorderLayout());
 224     attachDialog.setClosable(true);
 225     attachDialog.setResizable(true);
 226 
 227     JPanel panel = new JPanel();
 228     panel.setLayout(new BorderLayout());
 229     panel.setBorder(GraphicsUtilities.newBorder(5));
 230     attachDialog.setBackground(panel.getBackground());
 231 
 232     JPanel listPanel = new JPanel();
 233     listPanel.setLayout(new BorderLayout());
 234     final ProcessListPanel plist = new ProcessListPanel(getLocalDebugger());
 235     panel.add(plist, BorderLayout.CENTER);
 236     JCheckBox check = new JCheckBox("Update list continuously");
 237     check.addItemListener(new ItemListener() {
 238         public void itemStateChanged(ItemEvent e) {
 239           if (e.getStateChange() == ItemEvent.SELECTED) {
 240             plist.start();
 241           } else {
 242             plist.stop();
 243           }
 244         }
 245       });
 246     listPanel.add(plist, BorderLayout.CENTER);
 247     listPanel.add(check, BorderLayout.SOUTH);
 248     panel.add(listPanel, BorderLayout.CENTER);
 249     attachDialog.getContentPane().add(panel, BorderLayout.CENTER);
 250     attachDialog.setClosingActionListener(new ActionListener() {
 251         public void actionPerformed(ActionEvent e) {
 252           plist.stop();
 253           setMenuItemsEnabled(attachMenuItems, true);
 254         }
 255       });
 256 
 257     ActionListener attacher = new ActionListener() {
 258         public void actionPerformed(ActionEvent e) {
 259           plist.stop();
 260           attachDialog.setVisible(false);
 261           removeFrame(attachDialog);
 262           ProcessInfo info = plist.getSelectedProcess();
 263           if (info != null) {
 264             attach(info.getPid());
 265           }
 266         }
 267       };
 268 
 269     Box hbox = Box.createHorizontalBox();
 270     hbox.add(Box.createGlue());
 271     JButton button = new JButton("OK");
 272     button.addActionListener(attacher);
 273     hbox.add(button);
 274     hbox.add(Box.createHorizontalStrut(20));
 275     button = new JButton("Cancel");
 276     button.addActionListener(new ActionListener() {
 277         public void actionPerformed(ActionEvent e) {
 278           plist.stop();
 279           attachDialog.setVisible(false);
 280           removeFrame(attachDialog);
 281           setMenuItemsEnabled(attachMenuItems, true);
 282         }
 283       });
 284     hbox.add(button);
 285     hbox.add(Box.createGlue());
 286     panel = new JPanel();
 287     panel.setBorder(GraphicsUtilities.newBorder(5));
 288     panel.add(hbox);
 289 
 290     attachDialog.getContentPane().add(panel, BorderLayout.SOUTH);
 291 
 292     addFrame(attachDialog);
 293     attachDialog.pack();
 294     attachDialog.setSize(400, 300);
 295     GraphicsUtilities.centerInContainer(attachDialog.getComponent(),
 296                                         getParentDimension(attachDialog.getComponent()));
 297     attachDialog.show();
 298   }
 299 
 300   public void showThreadsDialog() {
 301     final FrameWrapper threadsDialog = newFrame("Threads");
 302     threadsDialog.getContentPane().setLayout(new BorderLayout());
 303     threadsDialog.setClosable(true);
 304     threadsDialog.setResizable(true);
 305 
 306     ThreadListPanel threads = new ThreadListPanel(getCDebugger(), getAgent().isJavaMode());
 307     threads.addListener(new ThreadListPanel.Listener() {
 308         public void setFocus(ThreadProxy thread, JavaThread jthread) {
 309           setCurrentThread(thread);
 310           // FIXME: print this to GUI, bring some windows to foreground
 311           System.err.println("Focus changed to thread " + thread);
 312         }
 313       });
 314     threads.setBorder(GraphicsUtilities.newBorder(5));
 315     threadsDialog.getContentPane().add(threads);
 316     addFrame(threadsDialog);
 317     threadsDialog.pack();
 318     GraphicsUtilities.reshapeToAspectRatio(threadsDialog.getComponent(),
 319                                            3.0f,
 320                                            0.9f,
 321                                            getParentDimension(threadsDialog.getComponent()));
 322     GraphicsUtilities.centerInContainer(threadsDialog.getComponent(),
 323                                         getParentDimension(threadsDialog.getComponent()));
 324     threadsDialog.show();
 325   }
 326 
 327   public void showMemoryDialog() {
 328     final FrameWrapper memoryDialog = newFrame("Memory");
 329     memoryDialog.getContentPane().setLayout(new BorderLayout());
 330     memoryDialog.setClosable(true);
 331     memoryDialog.setResizable(true);
 332 
 333     memoryDialog.getContentPane().add(new MemoryViewer(getDebugger(),
 334                                                        (getDebugger().getMachineDescription().getAddressSize() == 8)),
 335                                       BorderLayout.CENTER);
 336     addFrame(memoryDialog);
 337     memoryDialog.pack();
 338     GraphicsUtilities.reshapeToAspectRatio(memoryDialog.getComponent(),
 339                                            1.0f,
 340                                            0.7f,
 341                                            getParentDimension(memoryDialog.getComponent()));
 342     GraphicsUtilities.centerInContainer(memoryDialog.getComponent(),
 343                                         getParentDimension(memoryDialog.getComponent()));
 344     memoryDialog.show();
 345   }
 346 
 347   /** Changes the editor factory this debugger uses to display source
 348       code. Specified factory may be null, in which case the default
 349       factory is used. */
 350   public void setEditorFactory(EditorFactory fact) {
 351     if (fact != null) {
 352       editorFact = fact;
 353     } else {
 354       editorFact = new DefaultEditorFactory();
 355     }
 356   }
 357 
 358   //----------------------------------------------------------------------
 359   // Internals only below this point
 360   //
 361 
 362   private WorkerThread    workerThread;
 363   private boolean         mdiMode;
 364   private JVMDebugger     localDebugger;
 365   private BugSpotAgent    agent = new BugSpotAgent();
 366   private JMenuBar        menuBar;
 367   /** List <JMenuItem> */
 368   private java.util.List  attachMenuItems;
 369   private java.util.List  detachMenuItems;
 370   private java.util.List  debugMenuItems;
 371   private java.util.List  suspendDebugMenuItems;
 372   private java.util.List  resumeDebugMenuItems;
 373   private FrameWrapper    stackFrame;
 374   private VariablePanel   localsPanel;
 375   private StackTracePanel stackTracePanel;
 376   private FrameWrapper    registerFrame;
 377   private RegisterPanel   registerPanel;
 378   // Used for mixed-language stack traces
 379   private Map             threadToJavaThreadMap;
 380 
 381   private JMenu debugMenu;
 382 
 383   // MDI mode only: desktop pane
 384   private JDesktopPane desktop;
 385 
 386   // Attach/detach state
 387   private boolean attached;
 388 
 389   // Suspension (combined Java/C++) state
 390   private boolean suspended;
 391 
 392   // Fixed-width font
 393   private Font fixedWidthFont;
 394 
 395   // Breakpoint setting
 396   // Maps Strings to List/*<LineNumberInfo>*/
 397   private Map sourceFileToLineNumberInfoMap;
 398   // Maps Strings (file names) to Sets of Integers (line numbers)
 399   private Map fileToBreakpointMap;
 400 
 401   // Debug events
 402   private javax.swing.Timer debugEventTimer;
 403 
 404   // Java debug events
 405   private boolean javaEventPending;
 406 
 407   static class BreakpointResult {
 408     private boolean success;
 409     private boolean set;
 410     private int lineNo;
 411     private String why;
 412 
 413     /** For positive results */
 414     BreakpointResult(boolean success, boolean set, int lineNo) {
 415       this(success, set, lineNo, null);
 416     }
 417 
 418     /** For negative results */
 419     BreakpointResult(boolean success, boolean set, int lineNo, String why) {
 420       this.success = success;
 421       this.set = set;
 422       this.lineNo = lineNo;
 423       this.why = why;
 424     }
 425 
 426     public boolean succeeded() {
 427       return success;
 428     }
 429 
 430     public boolean set() {
 431       return set;
 432     }
 433 
 434     /** Line at which the breakpoint was actually set; only valid if
 435         succeeded() returns true */
 436     public int getLine() {
 437       return lineNo;
 438     }
 439 
 440     public String getWhy() {
 441       return why;
 442     }
 443   }
 444 
 445 
 446   // Editors for source code. File name-to-Editor mapping.
 447   private Map editors;
 448   private EditorFactory editorFact = new DefaultEditorFactory();
 449   private EditorCommands editorComm = new EditorCommands() {
 450       public void windowClosed(Editor editor) {
 451         editors.remove(editor.getSourceFileName());
 452       }
 453 
 454       public void toggleBreakpointAtLine(Editor editor, int lineNumber) {
 455         // FIXME: handle "lazy" breakpoints where the source file has
 456         // been opened with some other mechanism (File -> Open) and we
 457         // don't have debug information pointing to that file yet
 458         // FIXME: NOT FINISHED
 459 
 460         BreakpointResult res =
 461           handleBreakpointToggle(editor, lineNumber);
 462         if (res.succeeded()) {
 463           if (res.set()) {
 464             editor.showBreakpointAtLine(res.getLine());
 465           } else {
 466             editor.clearBreakpointAtLine(res.getLine());
 467           }
 468         } else {
 469           String why = res.getWhy();
 470           if (why == null) {
 471             why = "";
 472           } else {
 473             why = ": " + why;
 474           }
 475           showMessageDialog("Unable to toggle breakpoint" + why,
 476                             "Unable to toggle breakpoint",
 477                             JOptionPane.WARNING_MESSAGE);
 478         }
 479       }
 480     };
 481 
 482   private void attach(final int pid) {
 483     try {
 484       getAgent().attach(pid);
 485       setMenuItemsEnabled(detachMenuItems, true);
 486       setMenuItemsEnabled(suspendDebugMenuItems, false);
 487       setMenuItemsEnabled(resumeDebugMenuItems, true);
 488       debugMenu.setEnabled(true);
 489       attached = true;
 490       suspended = true;
 491 
 492       if (getAgent().isJavaMode()) {
 493         System.err.println("Java HotSpot(TM) virtual machine detected.");
 494       } else {
 495         System.err.println("(No Java(TM) virtual machine detected)");
 496       }
 497 
 498       // Set up editor map
 499       editors = new HashMap();
 500 
 501       // Initialize breakpoints
 502       fileToBreakpointMap = new HashMap();
 503 
 504       // Create combined stack trace and local variable panel
 505       JPanel framePanel = new JPanel();
 506       framePanel.setLayout(new BorderLayout());
 507       framePanel.setBorder(GraphicsUtilities.newBorder(5));
 508       localsPanel = new VariablePanel();
 509       JTabbedPane tab = new JTabbedPane();
 510       tab.addTab("Locals", localsPanel);
 511       tab.setTabPlacement(JTabbedPane.BOTTOM);
 512       framePanel.add(tab, BorderLayout.CENTER);
 513       JPanel stackPanel = new JPanel();
 514       stackPanel.setLayout(new BoxLayout(stackPanel, BoxLayout.X_AXIS));
 515       stackPanel.add(new JLabel("Context:"));
 516       stackPanel.add(Box.createHorizontalStrut(5));
 517       stackTracePanel = new StackTracePanel();
 518       stackTracePanel.addListener(new StackTracePanel.Listener() {
 519           public void frameChanged(CFrame fr, JavaVFrame jfr) {
 520             setCurrentFrame(fr, jfr);
 521           }
 522         });
 523       stackPanel.add(stackTracePanel);
 524       framePanel.add(stackPanel, BorderLayout.NORTH);
 525       stackFrame = newFrame("Stack");
 526       stackFrame.getContentPane().setLayout(new BorderLayout());
 527       stackFrame.getContentPane().add(framePanel, BorderLayout.CENTER);
 528       stackFrame.setResizable(true);
 529       stackFrame.setClosable(false);
 530       addFrame(stackFrame);
 531       stackFrame.setSize(400, 200);
 532       GraphicsUtilities.moveToInContainer(stackFrame.getComponent(), 0.0f, 1.0f, 0, 20);
 533       stackFrame.show();
 534 
 535       // Create register panel
 536       registerPanel = new RegisterPanel();
 537       registerPanel.setFont(fixedWidthFont);
 538       registerFrame = newFrame("Registers");
 539       registerFrame.getContentPane().setLayout(new BorderLayout());
 540       registerFrame.getContentPane().add(registerPanel, BorderLayout.CENTER);
 541       addFrame(registerFrame);
 542       registerFrame.setResizable(true);
 543       registerFrame.setClosable(false);
 544       registerFrame.setSize(225, 200);
 545       GraphicsUtilities.moveToInContainer(registerFrame.getComponent(),
 546                                           1.0f, 0.0f, 0, 0);
 547       registerFrame.show();
 548 
 549       resetCurrentThread();
 550     } catch (DebuggerException e) {
 551       final String errMsg = formatMessage(e.getMessage(), 80);
 552       setMenuItemsEnabled(attachMenuItems, true);
 553       showMessageDialog("Unable to connect to process ID " + pid + ":\n\n" + errMsg,
 554                         "Unable to Connect",
 555                         JOptionPane.WARNING_MESSAGE);
 556       getAgent().detach();
 557     }
 558   }
 559 
 560   private synchronized void detachDebugger() {
 561     if (!attached) {
 562       return;
 563     }
 564     if (isSuspended()) {
 565       resume(); // Necessary for JVMDI resumption
 566     }
 567     getAgent().detach();
 568     // FIXME: clear out breakpoints (both Java and C/C++) from target
 569     // process
 570     sourceFileToLineNumberInfoMap = null;
 571     fileToBreakpointMap = null;
 572     threadToJavaThreadMap = null;
 573     editors = null;
 574     attached = false;
 575   }
 576 
 577   private synchronized void detach() {
 578     detachDebugger();
 579     setMenuItemsEnabled(attachMenuItems, true);
 580     setMenuItemsEnabled(detachMenuItems, false);
 581     debugMenu.setEnabled(false);
 582     if (mdiMode) {
 583       // FIXME: is this sufficient, or will I have to do anything else
 584       // to the components to kill them off? What about WorkerThreads?
 585       desktop.removeAll();
 586       desktop.invalidate();
 587       desktop.validate();
 588       desktop.repaint();
 589     }
 590     // FIXME: keep track of all windows and close them even in non-MDI
 591     // mode
 592     debugEventTimer.stop();
 593   }
 594 
 595   // Returns a Debugger for processes on the local machine. This is
 596   // only used to fetch the process list.
 597   private Debugger getLocalDebugger() {
 598     if (localDebugger == null) {
 599       String os  = PlatformInfo.getOS();
 600       String cpu = PlatformInfo.getCPU();
 601 
 602       if (os.equals("win32")) {
 603         if (!cpu.equals("x86")) {
 604           throw new DebuggerException("Unsupported CPU \"" + cpu + "\" for Windows");
 605         }
 606 
 607         localDebugger = new Win32DebuggerLocal(new MachineDescriptionIntelX86(), true);
 608       } else if (os.equals("linux")) {
 609         if (!cpu.equals("x86")) {
 610           throw new DebuggerException("Unsupported CPU \"" + cpu + "\" for Linux");
 611         }
 612 
 613         // FIXME: figure out how to specify path to debugger module
 614         throw new RuntimeException("FIXME: figure out how to specify path to debugger module");
 615         //        localDebugger = new PosixDebuggerLocal(new MachineDescriptionIntelX86(), true);
 616       } else {
 617         // FIXME: port to Solaris
 618         throw new DebuggerException("Unsupported OS \"" + os + "\"");
 619       }
 620 
 621       // FIXME: we require that the primitive type sizes be configured
 622       // in order to use basic functionality in class Address such as
 623       // the fetching of floating-point values. There are a lot of
 624       // assumptions in the current code that Java floats and doubles
 625       // are of equivalent size to C values. The configurability of the
 626       // primitive type sizes hasn't seemed necessary and in this kind
 627       // of debugging scenario (namely, debugging arbitrary C++
 628       // processes) it appears difficult to support that kind of
 629       // flexibility.
 630       localDebugger.configureJavaPrimitiveTypeSizes(1, 1, 2, 8, 4, 4, 8, 2);
 631     }
 632 
 633     return localDebugger;
 634   }
 635 
 636   private BugSpotAgent getAgent() {
 637     return agent;
 638   }
 639 
 640   private Debugger getDebugger() {
 641     return getAgent().getDebugger();
 642   }
 643 
 644   private CDebugger getCDebugger() {
 645     return getAgent().getCDebugger();
 646   }
 647 
 648   private void resetCurrentThread() {
 649     setCurrentThread((ThreadProxy) getCDebugger().getThreadList().get(0));
 650   }
 651 
 652   private void setCurrentThread(ThreadProxy t) {
 653     // Create stack trace
 654     // FIXME: add ability to intermix C/Java frames
 655     java.util.List trace = new ArrayList();
 656     CFrame fr = getCDebugger().topFrameForThread(t);
 657     while (fr != null) {
 658       trace.add(new StackTraceEntry(fr, getCDebugger()));
 659       try {
 660         fr = fr.sender();
 661       } catch (AddressException e) {
 662         e.printStackTrace();
 663         showMessageDialog("Error while walking stack; stack trace will be truncated\n(see console for details)",
 664                           "Error walking stack",
 665                           JOptionPane.WARNING_MESSAGE);
 666         fr = null;
 667       }
 668     }
 669     JavaThread jthread = javaThreadForProxy(t);
 670     if (jthread != null) {
 671       // Java mode, and we have a Java thread.
 672       // Find all Java frames on the stack. We currently do this in a
 673       // manner which involves minimal interaction between the Java
 674       // and C/C++ debugging systems: any C frame which has a PC in an
 675       // unknown location (i.e., not in any DSO) is assumed to be a
 676       // Java frame. We merge stack segments of unknown frames with
 677       // segments of Java frames beginning with native methods.
 678       java.util.List javaTrace = new ArrayList();
 679       VFrame vf = jthread.getLastJavaVFrameDbg();
 680       while (vf != null) {
 681         if (vf.isJavaFrame()) {
 682           javaTrace.add(new StackTraceEntry((JavaVFrame) vf));
 683           vf = vf.sender();
 684         }
 685       }
 686       // Merge stack traces
 687       java.util.List mergedTrace = new ArrayList();
 688       int c = 0;
 689       int j = 0;
 690       while (c < trace.size()) {
 691         StackTraceEntry entry = (StackTraceEntry) trace.get(c);
 692         if (entry.isUnknownCFrame()) {
 693           boolean gotJavaFrame = false;
 694           while (j < javaTrace.size()) {
 695             StackTraceEntry javaEntry = (StackTraceEntry) javaTrace.get(j);
 696             JavaVFrame jvf = javaEntry.getJavaFrame();
 697             Method m = jvf.getMethod();
 698             if (!m.isNative() || !gotJavaFrame) {
 699               gotJavaFrame = true;
 700               mergedTrace.add(javaEntry);
 701               ++j;
 702             } else {
 703               break; // Reached native method; have intervening C frames
 704             }
 705           }
 706           if (gotJavaFrame) {
 707             // Skip this sequence of unknown frames, as we've
 708             // successfully identified it as Java frames
 709             while (c < trace.size() && entry.isUnknownCFrame()) {
 710               ++c;
 711               if (c < trace.size()) {
 712                 entry = (StackTraceEntry) trace.get(c);
 713               }
 714             }
 715             continue;
 716           }
 717         }
 718         // If we get here, we either have an unknown frame we didn't
 719         // know how to categorize or we have a known C frame. Add it
 720         // to the trace.
 721         mergedTrace.add(entry);
 722         ++c;
 723       }
 724       trace = mergedTrace;
 725     }
 726     stackTracePanel.setTrace(trace);
 727 
 728     registerPanel.update(t);
 729   }
 730 
 731   private void setCurrentFrame(CFrame fr, JavaVFrame jfr) {
 732     localsPanel.clear();
 733 
 734     if (fr != null) {
 735       localsPanel.update(fr);
 736 
 737       // FIXME: load source file if we can find it, otherwise display disassembly
 738       LoadObject lo = getCDebugger().loadObjectContainingPC(fr.pc());
 739       if (lo != null) {
 740         CDebugInfoDataBase db = lo.getDebugInfoDataBase();
 741         if (db != null) {
 742           LineNumberInfo info = db.lineNumberForPC(fr.pc());
 743           if (info != null) {
 744             System.err.println("PC " + fr.pc() + ": Source file \"" +
 745                                info.getSourceFileName() +
 746                                "\", line number " +
 747                                info.getLineNumber() +
 748                                ", PC range [" +
 749                                info.getStartPC() +
 750                                ", " +
 751                                info.getEndPC() +
 752                                ")");
 753             // OK, here we go...
 754             showLineNumber(null, info.getSourceFileName(), info.getLineNumber());
 755           } else {
 756             System.err.println("(No line number information for PC " + fr.pc() + ")");
 757             // Dump line number information for database
 758             db.iterate(new LineNumberVisitor() {
 759                 public void doLineNumber(LineNumberInfo info) {
 760                   System.err.println("  Source file \"" +
 761                                      info.getSourceFileName() +
 762                                      "\", line number " +
 763                                      info.getLineNumber() +
 764                                      ", PC range [" +
 765                                      info.getStartPC() +
 766                                      ", " +
 767                                      info.getEndPC() +
 768                                      ")");
 769                 }
 770               });
 771           }
 772         }
 773       }
 774     } else {
 775       if (Assert.ASSERTS_ENABLED) {
 776         Assert.that(jfr != null, "Must have either C or Java frame");
 777       }
 778       localsPanel.update(jfr);
 779       // See whether we can locate source file and line number
 780       // FIXME: infer pathmap entries from user's locating of this
 781       // source file
 782       // FIXME: figure out what to do for native methods. Possible to
 783       // go to line number for the native method declaration?
 784       Method m = jfr.getMethod();
 785       Symbol sfn = ((InstanceKlass) m.getMethodHolder()).getSourceFileName();
 786       if (sfn != null) {
 787         int bci = jfr.getBCI();
 788         int lineNo = m.getLineNumberFromBCI(bci);
 789         if (lineNo >= 0) {
 790           // FIXME: show disassembly otherwise
 791           showLineNumber(packageName(m.getMethodHolder().getName().asString()),
 792                          sfn.asString(), lineNo);
 793         }
 794       }
 795     }
 796   }
 797 
 798   private String packageName(String str) {
 799     int idx = str.lastIndexOf('/');
 800     if (idx < 0) {
 801       return "";
 802     }
 803     return str.substring(0, idx).replace('/', '.');
 804   }
 805 
 806   private JavaThread javaThreadForProxy(ThreadProxy t) {
 807     if (!getAgent().isJavaMode()) {
 808       return null;
 809     }
 810     if (threadToJavaThreadMap == null) {
 811       threadToJavaThreadMap = new HashMap();
 812       Threads threads = VM.getVM().getThreads();
 813       for (JavaThread thr = threads.first(); thr != null; thr = thr.next()) {
 814         threadToJavaThreadMap.put(thr.getThreadProxy(), thr);
 815       }
 816     }
 817     return (JavaThread) threadToJavaThreadMap.get(t);
 818   }
 819 
 820   private static JMenu createMenu(String name, char mnemonic, int mnemonicPos) {
 821     JMenu menu = new JMenu(name);
 822     menu.setMnemonic(mnemonic);
 823     menu.setDisplayedMnemonicIndex(mnemonicPos);
 824     return menu;
 825   }
 826 
 827   private static JMenuItem createMenuItem(String name, ActionListener l) {
 828     JMenuItem item = new JMenuItem(name);
 829     item.addActionListener(l);
 830     return item;
 831   }
 832 
 833   private static JMenuItem createMenuItemInternal(String name, ActionListener l, int accelerator, int modifiers) {
 834     JMenuItem item = createMenuItem(name, l);
 835     item.setAccelerator(KeyStroke.getKeyStroke(accelerator, modifiers));
 836     return item;
 837   }
 838 
 839   private static JMenuItem createMenuItem(String name, ActionListener l, int accelerator) {
 840     return createMenuItemInternal(name, l, accelerator, 0);
 841   }
 842 
 843   private static JMenuItem createMenuItem(String name, ActionListener l, char mnemonic, int mnemonicPos) {
 844     JMenuItem item = createMenuItem(name, l);
 845     item.setMnemonic(mnemonic);
 846     item.setDisplayedMnemonicIndex(mnemonicPos);
 847     return item;
 848   }
 849 
 850   private static JMenuItem createMenuItem(String name,
 851                                           ActionListener l,
 852                                           int accelerator,
 853                                           int acceleratorMods,
 854                                           char mnemonic,
 855                                           int mnemonicPos) {
 856     JMenuItem item = createMenuItemInternal(name, l, accelerator, acceleratorMods);
 857     item.setMnemonic(mnemonic);
 858     item.setDisplayedMnemonicIndex(mnemonicPos);
 859     return item;
 860   }
 861 
 862   /** Punctuates the given string with \n's where necessary to not
 863       exceed the given number of characters per line. Strips
 864       extraneous whitespace. */
 865   private static String formatMessage(String message, int charsPerLine) {
 866     StringBuffer buf = new StringBuffer(message.length());
 867     StringTokenizer tokenizer = new StringTokenizer(message);
 868     int curLineLength = 0;
 869     while (tokenizer.hasMoreTokens()) {
 870       String tok = tokenizer.nextToken();
 871       if (curLineLength + tok.length() > charsPerLine) {
 872         buf.append('\n');
 873         curLineLength = 0;
 874       } else {
 875         if (curLineLength != 0) {
 876           buf.append(' ');
 877           ++curLineLength;
 878         }
 879       }
 880       buf.append(tok);
 881       curLineLength += tok.length();
 882     }
 883     return buf.toString();
 884   }
 885 
 886   private void setMenuItemsEnabled(java.util.List items, boolean enabled) {
 887     for (Iterator iter = items.iterator(); iter.hasNext(); ) {
 888       ((JMenuItem) iter.next()).setEnabled(enabled);
 889     }
 890   }
 891 
 892   private void showMessageDialog(final String message, final String title, final int jOptionPaneKind) {
 893     SwingUtilities.invokeLater(new Runnable() {
 894         public void run() {
 895           if (mdiMode) {
 896             JOptionPane.showInternalMessageDialog(desktop, message, title, jOptionPaneKind);
 897           } else {
 898             JOptionPane.showMessageDialog(null, message, title, jOptionPaneKind);
 899           }
 900         }
 901       });
 902   }
 903 
 904   private FrameWrapper newFrame(String title) {
 905     if (mdiMode) {
 906       return new JInternalFrameWrapper(new JInternalFrame(title));
 907     } else {
 908       return new JFrameWrapper(new JFrame(title));
 909     }
 910   }
 911 
 912   private void addFrame(FrameWrapper frame) {
 913     if (mdiMode) {
 914       desktop.add(frame.getComponent());
 915     }
 916   }
 917 
 918   private void removeFrame(FrameWrapper frame) {
 919     if (mdiMode) {
 920       desktop.remove(frame.getComponent());
 921       desktop.invalidate();
 922       desktop.validate();
 923       desktop.repaint();
 924     }
 925     // FIXME: do something when not in MDI mode
 926   }
 927 
 928   private Dimension getParentDimension(Component c) {
 929     if (mdiMode) {
 930       return desktop.getSize();
 931     } else {
 932       return Toolkit.getDefaultToolkit().getScreenSize();
 933     }
 934   }
 935 
 936   // Default editor implementation
 937   class DefaultEditor implements Editor {
 938     private DefaultEditorFactory factory;
 939     private FrameWrapper    editorFrame;
 940     private String          filename;
 941     private SourceCodePanel code;
 942     private boolean         shown;
 943     private Object          userData;
 944 
 945     public DefaultEditor(DefaultEditorFactory fact, String filename, final EditorCommands comm) {
 946       this.filename = filename;
 947       this.factory = fact;
 948       editorFrame = newFrame(filename);
 949       code = new SourceCodePanel();
 950       // FIXME: when font changes, change font in editors as well
 951       code.setFont(fixedWidthFont);
 952       editorFrame.getContentPane().add(code);
 953       editorFrame.setClosable(true);
 954       editorFrame.setResizable(true);
 955       editorFrame.setClosingActionListener(new ActionListener() {
 956           public void actionPerformed(ActionEvent e) {
 957             comm.windowClosed(DefaultEditor.this);
 958             removeFrame(editorFrame);
 959             editorFrame.dispose();
 960             factory.editorClosed(DefaultEditor.this);
 961           }
 962         });
 963       editorFrame.setActivatedActionListener(new ActionListener() {
 964           public void actionPerformed(ActionEvent e) {
 965             factory.makeEditorCurrent(DefaultEditor.this);
 966             code.requestFocus();
 967           }
 968         });
 969       code.setEditorCommands(comm, this);
 970     }
 971 
 972     public boolean openFile()                        { return code.openFile(filename);     }
 973     public String  getSourceFileName()               { return filename;                    }
 974     public int     getCurrentLineNumber()            { return code.getCurrentLineNumber(); }
 975     public void showLineNumber(int lineNo) {
 976       if (!shown) {
 977         addFrame(editorFrame);
 978         GraphicsUtilities.reshapeToAspectRatio(editorFrame.getComponent(),
 979                                                1.0f,
 980                                                0.85f,
 981                                                getParentDimension(editorFrame.getComponent()));
 982         editorFrame.show();
 983         shown = true;
 984       }
 985       code.showLineNumber(lineNo);
 986       editorFrame.toFront();
 987     }
 988     public void    highlightLineNumber(int lineNo)   { code.highlightLineNumber(lineNo);        }
 989     public void    showBreakpointAtLine(int lineNo)  { code.showBreakpointAtLine(lineNo);       }
 990     public boolean hasBreakpointAtLine(int lineNo)   { return code.hasBreakpointAtLine(lineNo); }
 991     public void    clearBreakpointAtLine(int lineNo) { code.clearBreakpointAtLine(lineNo);      }
 992     public void    clearBreakpoints()                { code.clearBreakpoints();                 }
 993     public void    setUserData(Object o)             { userData = o;                            }
 994     public Object  getUserData()                     { return userData;                         }
 995     public void    toFront()                         { editorFrame.toFront();
 996                                                        factory.makeEditorCurrent(this);         }
 997   }
 998 
 999   class DefaultEditorFactory implements EditorFactory {
1000     private LinkedList/*<Editor>*/ editors = new LinkedList();
1001 
1002     public Editor openFile(String filename, EditorCommands commands) {
1003       DefaultEditor editor = new DefaultEditor(this, filename, editorComm);
1004       if (!editor.openFile()) {
1005         return null;
1006       }
1007       return editor;
1008     }
1009 
1010     public Editor getCurrentEditor() {
1011       if (editors.isEmpty()) {
1012         return null;
1013       }
1014       return (Editor) editors.getFirst();
1015     }
1016 
1017     void editorClosed(Editor editor) {
1018       editors.remove(editor);
1019     }
1020 
1021     void makeEditorCurrent(Editor editor) {
1022       editors.remove(editor);
1023       editors.addFirst(editor);
1024     }
1025   }
1026 
1027   // Helper class for loading .java files; show only those with
1028   // correct file name which are also in the correct package
1029   static class JavaFileFilter extends javax.swing.filechooser.FileFilter {
1030     private String packageName;
1031     private String fileName;
1032 
1033     JavaFileFilter(String packageName, String fileName) {
1034       this.packageName = packageName;
1035       this.fileName = fileName;
1036     }
1037 
1038     public boolean accept(File f) {
1039       if (f.isDirectory()) {
1040         return true;
1041       }
1042       // This rejects most files
1043       if (!f.getName().equals(fileName)) {
1044         return false;
1045       }
1046       // Ensure selected file is in the correct package
1047       PackageScanner scanner = new PackageScanner();
1048       String pkg = scanner.scan(f);
1049       if (!pkg.equals(packageName)) {
1050         return false;
1051       }
1052       return true;
1053     }
1054 
1055     public String getDescription() { return "Java source files"; }
1056   }
1057 
1058   // Auxiliary information used only for Java source files
1059   static class JavaUserData {
1060     private String packageName; // External format
1061     private String sourceFileName;
1062 
1063     /** Source file name is equivalent to that found in the .java
1064         file; i.e., not a full path */
1065     JavaUserData(String packageName, String sourceFileName) {
1066       this.packageName = packageName;
1067       this.sourceFileName = sourceFileName;
1068     }
1069 
1070     String packageName()    { return packageName; }
1071     String sourceFileName() { return sourceFileName; }
1072   }
1073 
1074   // Opens a source file. This makes it available for the setting of
1075   // lazy breakpoints.
1076   private void openSourceFile() {
1077     JFileChooser chooser = new JFileChooser();
1078     chooser.setDialogTitle("Open source code file");
1079     chooser.setMultiSelectionEnabled(false);
1080     if (chooser.showOpenDialog(null) != JFileChooser.APPROVE_OPTION) {
1081       return;
1082     }
1083     File chosen = chooser.getSelectedFile();
1084     if (chosen == null) {
1085       return;
1086     }
1087 
1088     // See whether we have a Java source file. If so, derive a package
1089     // name for it.
1090     String path = chosen.getPath();
1091     String name = null;
1092     JavaUserData data = null;
1093     if (path.endsWith(".java")) {
1094       PackageScanner scanner = new PackageScanner();
1095       String pkg = scanner.scan(chosen);
1096       // Now knowing both the package name and file name, we can put
1097       // this in the editor map and use it for setting breakpoints
1098       // later
1099       String fileName = chosen.getName();
1100       name = pkg + "." + fileName;
1101       data = new JavaUserData(pkg, fileName);
1102     } else {
1103       // FIXME: need pathmap mechanism
1104       name = path;
1105     }
1106     Editor editor = (Editor) editors.get(name);
1107     if (editor == null) {
1108       editor = editorFact.openFile(path, editorComm);
1109       if (editor == null) {
1110         showMessageDialog("Unable to open file \"" + path + "\" -- unexpected error.",
1111                           "Unable to open file",
1112                           JOptionPane.WARNING_MESSAGE);
1113         return;
1114       }
1115       editors.put(name, editor);
1116       if (data != null) {
1117         editor.setUserData(data);
1118       }
1119     } else {
1120       editor.toFront();
1121     }
1122     editor.showLineNumber(1);
1123     // Show breakpoints as well if we have any for this file
1124     Set set = (Set) fileToBreakpointMap.get(editor.getSourceFileName());
1125     if (set != null) {
1126       for (Iterator iter = set.iterator(); iter.hasNext(); ) {
1127         editor.showBreakpointAtLine(((Integer) iter.next()).intValue());
1128       }
1129     }
1130   }
1131 
1132   // Package name may be null, in which case the file is assumed to be
1133   // a C source file. Otherwise it is assumed to be a Java source file
1134   // and certain filtering rules will be applied.
1135   private void showLineNumber(String packageName, String fileName, int lineNumber) {
1136     String name;
1137     if (packageName == null) {
1138       name = fileName;
1139     } else {
1140       name = packageName + "." + fileName;
1141     }
1142     Editor editor = (Editor) editors.get(name);
1143     if (editor == null) {
1144       // See whether file exists
1145       File file = new File(fileName);
1146       String realFileName = fileName;
1147       if (!file.exists()) {
1148         // User must specify path to file
1149         JFileChooser chooser = new JFileChooser();
1150         chooser.setDialogTitle("Please locate " + fileName);
1151         chooser.setMultiSelectionEnabled(false);
1152         if (packageName != null) {
1153           chooser.setFileFilter(new JavaFileFilter(packageName, fileName));
1154         }
1155         int res = chooser.showOpenDialog(null);
1156         if (res != JFileChooser.APPROVE_OPTION) {
1157           // FIXME: show disassembly instead
1158           return;
1159         }
1160         // FIXME: would like to infer more from the selection; i.e.,
1161         // a pathmap leading up to this file
1162         File chosen = chooser.getSelectedFile();
1163         if (chosen == null) {
1164           return;
1165         }
1166         realFileName = chosen.getPath();
1167       }
1168       // Now instruct editor factory to open file
1169       editor = editorFact.openFile(realFileName, editorComm);
1170       if (editor == null) {
1171         showMessageDialog("Unable to open file \"" + realFileName + "\" -- unexpected error.",
1172                           "Unable to open file",
1173                           JOptionPane.WARNING_MESSAGE);
1174         return;
1175       }
1176       // Got an editor; put it in map
1177       editors.put(name, editor);
1178       // If Java source file, add additional information for later
1179       if (packageName != null) {
1180         editor.setUserData(new JavaUserData(packageName, fileName));
1181       }
1182     }
1183     // Got editor; show line
1184     editor.showLineNumber(lineNumber);
1185     editor.highlightLineNumber(lineNumber);
1186     // Show breakpoints as well if we have any for this file
1187     Set set = (Set) fileToBreakpointMap.get(editor.getSourceFileName());
1188     if (set != null) {
1189       for (Iterator iter = set.iterator(); iter.hasNext(); ) {
1190         editor.showBreakpointAtLine(((Integer) iter.next()).intValue());
1191       }
1192     }
1193   }
1194 
1195   //
1196   // Suspend/resume
1197   //
1198 
1199   private boolean isSuspended() {
1200     return suspended;
1201   }
1202 
1203   private synchronized void suspend() {
1204     setMenuItemsEnabled(resumeDebugMenuItems, true);
1205     setMenuItemsEnabled(suspendDebugMenuItems, false);
1206     BugSpotAgent agent = getAgent();
1207     if (agent.canInteractWithJava() && !agent.isJavaSuspended()) {
1208       agent.suspendJava();
1209     }
1210     agent.suspend();
1211     // FIXME: call VM.getVM().fireVMSuspended()
1212     resetCurrentThread();
1213     debugEventTimer.stop();
1214     suspended = true;
1215   }
1216 
1217   private synchronized void resume() {
1218     // Note: we don't wipe out the cached state like the
1219     // sourceFileToLineNumberInfoMap since it is too expensive to
1220     // recompute. Instead we recompute it if any DLLs are loaded or
1221     // unloaded.
1222     threadToJavaThreadMap = null;
1223     setMenuItemsEnabled(resumeDebugMenuItems, false);
1224     setMenuItemsEnabled(suspendDebugMenuItems, true);
1225     registerPanel.clear();
1226     // FIXME: call VM.getVM().fireVMResumed()
1227     BugSpotAgent agent = getAgent();
1228     agent.resume();
1229     if (agent.canInteractWithJava()) {
1230       if (agent.isJavaSuspended()) {
1231         agent.resumeJava();
1232       }
1233       if (javaEventPending) {
1234         javaEventPending = false;
1235         // Clear it out before resuming polling for events
1236         agent.javaEventContinue();
1237       }
1238     }
1239     agent.enableJavaInteraction();
1240     suspended = false;
1241     debugEventTimer.start();
1242   }
1243 
1244   //
1245   // Breakpoints
1246   //
1247 
1248   private synchronized BreakpointResult handleBreakpointToggle(Editor editor, int lineNumber) {
1249     // Currently we only use user data in editors to indicate Java
1250     // source files. If this changes then this code will need to
1251     // change.
1252     JavaUserData data = (JavaUserData) editor.getUserData();
1253     String filename = editor.getSourceFileName();
1254     if (data == null) {
1255       // C/C++ code
1256       // FIXME: as noted above in EditorCommands.toggleBreakpointAtLine,
1257       // this needs more work to handle "lazy" breakpoints in files
1258       // which we don't know about in the debug information yet
1259       CDebugger dbg = getCDebugger();
1260       ProcessControl prctl = dbg.getProcessControl();
1261       if (prctl == null) {
1262         return new BreakpointResult(false, false, 0, "Process control not enabled");
1263       }
1264       boolean mustSuspendAndResume = (!prctl.isSuspended());
1265       try {
1266         if (mustSuspendAndResume) {
1267           prctl.suspend();
1268         }
1269         // Search debug info for all DSOs
1270         LineNumberInfo info = getLineNumberInfo(filename, lineNumber);
1271         if (info != null) {
1272           Set bpset = (Set) fileToBreakpointMap.get(filename);
1273           if (bpset == null) {
1274             bpset = new HashSet();
1275             fileToBreakpointMap.put(filename, bpset);
1276           }
1277           Integer key = new Integer(info.getLineNumber());
1278           if (bpset.contains(key)) {
1279             // Clear breakpoint at this line's PC
1280             prctl.clearBreakpoint(info.getStartPC());
1281             bpset.remove(key);
1282             return new BreakpointResult(true, false, info.getLineNumber());
1283           } else {
1284             // Set breakpoint at this line's PC
1285             System.err.println("Setting breakpoint at PC " + info.getStartPC());
1286             prctl.setBreakpoint(info.getStartPC());
1287             bpset.add(key);
1288             return new BreakpointResult(true, true, info.getLineNumber());
1289           }
1290         } else {
1291           return new BreakpointResult(false, false, 0, "No debug information for this source file and line");
1292         }
1293       } finally {
1294         if (mustSuspendAndResume) {
1295           prctl.resume();
1296         }
1297       }
1298     } else {
1299       BugSpotAgent agent = getAgent();
1300       if (!agent.canInteractWithJava()) {
1301         String why;
1302         if (agent.isJavaInteractionDisabled()) {
1303           why = "Can not toggle Java breakpoints while stopped because\nof C/C++ debug events (breakpoints, single-stepping)";
1304         } else {
1305           why = "Could not talk to SA's JVMDI module to enable Java\nprogramming language breakpoints (run with -Xdebug -Xrunsa)";
1306         }
1307         return new BreakpointResult(false, false, 0, why);
1308       }
1309       Set bpset = (Set) fileToBreakpointMap.get(filename);
1310       if (bpset == null) {
1311         bpset = new HashSet();
1312         fileToBreakpointMap.put(filename, bpset);
1313       }
1314       boolean mustResumeAndSuspend = isSuspended();
1315       try {
1316         if (mustResumeAndSuspend) {
1317           agent.resume();
1318         }
1319         ServiceabilityAgentJVMDIModule.BreakpointToggleResult res =
1320           getAgent().toggleJavaBreakpoint(data.sourceFileName(),
1321                                           data.packageName(),
1322                                           lineNumber);
1323         if (res.getSuccess()) {
1324           Integer key = new Integer(res.getLineNumber());
1325           boolean addRemRes = false;
1326           if (res.getWasSet()) {
1327             addRemRes = bpset.add(key);
1328             System.err.println("Setting breakpoint at " + res.getMethodName() + res.getMethodSignature() +
1329                                ", bci " + res.getBCI() + ", line " + res.getLineNumber());
1330           } else {
1331             addRemRes = bpset.remove(key);
1332             System.err.println("Clearing breakpoint at " + res.getMethodName() + res.getMethodSignature() +
1333                                ", bci " + res.getBCI() + ", line " + res.getLineNumber());
1334           }
1335           if (Assert.ASSERTS_ENABLED) {
1336             Assert.that(addRemRes, "Inconsistent Java breakpoint state with respect to target process");
1337           }
1338           return new BreakpointResult(true, res.getWasSet(), res.getLineNumber());
1339         } else {
1340           return new BreakpointResult(false, false, 0, res.getErrMsg());
1341         }
1342       } finally {
1343         if (mustResumeAndSuspend) {
1344           agent.suspend();
1345           resetCurrentThread();
1346         }
1347       }
1348     }
1349   }
1350 
1351   // Must call only when suspended
1352   private LineNumberInfo getLineNumberInfo(String filename, int lineNumber) {
1353     Map map = getSourceFileToLineNumberInfoMap();
1354     java.util.List infos = (java.util.List) map.get(filename);
1355     if (infos == null) {
1356       return null;
1357     }
1358     // Binary search for line number
1359     return searchLineNumbers(infos, lineNumber, 0, infos.size());
1360   }
1361 
1362   // Must call only when suspended
1363   private Map getSourceFileToLineNumberInfoMap() {
1364     if (sourceFileToLineNumberInfoMap == null) {
1365       // Build from debug info
1366       java.util.List loadObjects = getCDebugger().getLoadObjectList();
1367       final Map map = new HashMap();
1368       for (Iterator iter = loadObjects.iterator(); iter.hasNext(); ) {
1369         LoadObject lo = (LoadObject) iter.next();
1370         CDebugInfoDataBase db = lo.getDebugInfoDataBase();
1371         if (db != null) {
1372           db.iterate(new LineNumberVisitor() {
1373               public void doLineNumber(LineNumberInfo info) {
1374                 String name = info.getSourceFileName();
1375                 if (name != null) {
1376                   java.util.List val = (java.util.List) map.get(name);
1377                   if (val == null) {
1378                     val = new ArrayList();
1379                     map.put(name, val);
1380                   }
1381                   val.add(info);
1382                 }
1383               }
1384             });
1385         }
1386       }
1387       // Sort all lists
1388       for (Iterator iter = map.values().iterator(); iter.hasNext(); ) {
1389         java.util.List list = (java.util.List) iter.next();
1390         Collections.sort(list, new Comparator() {
1391             public int compare(Object o1, Object o2) {
1392               LineNumberInfo l1 = (LineNumberInfo) o1;
1393               LineNumberInfo l2 = (LineNumberInfo) o2;
1394               int n1 = l1.getLineNumber();
1395               int n2 = l2.getLineNumber();
1396               if (n1 < n2) return -1;
1397               if (n1 == n2) return 0;
1398               return 1;
1399             }
1400           });
1401       }
1402       sourceFileToLineNumberInfoMap = map;
1403     }
1404     return sourceFileToLineNumberInfoMap;
1405   }
1406 
1407   private LineNumberInfo searchLineNumbers(java.util.List infoList, int lineNo, int lowIdx, int highIdx) {
1408     if (highIdx < lowIdx) return null;
1409     if (lowIdx == highIdx) {
1410       // Base case: see whether start PC is less than or equal to addr
1411       if (checkLineNumber(infoList, lineNo, lowIdx)) {
1412         return (LineNumberInfo) infoList.get(lowIdx);
1413       } else {
1414         return null;
1415       }
1416     } else if (lowIdx == highIdx - 1) {
1417       if (checkLineNumber(infoList, lineNo, lowIdx)) {
1418         return (LineNumberInfo) infoList.get(lowIdx);
1419       } else if (checkLineNumber(infoList, lineNo, highIdx)) {
1420         return (LineNumberInfo) infoList.get(highIdx);
1421       } else {
1422         return null;
1423       }
1424     }
1425     int midIdx = (lowIdx + highIdx) >> 1;
1426     LineNumberInfo info = (LineNumberInfo) infoList.get(midIdx);
1427     if (lineNo < info.getLineNumber()) {
1428       // Always move search down
1429       return searchLineNumbers(infoList, lineNo, lowIdx, midIdx);
1430     } else if (lineNo == info.getLineNumber()) {
1431       return info;
1432     } else {
1433       // Move search up
1434       return searchLineNumbers(infoList, lineNo, midIdx, highIdx);
1435     }
1436   }
1437 
1438   private boolean checkLineNumber(java.util.List infoList, int lineNo, int idx) {
1439     LineNumberInfo info = (LineNumberInfo) infoList.get(idx);
1440     return (info.getLineNumber() >= lineNo);
1441   }
1442 
1443   //
1444   // Debug events
1445   //
1446 
1447   private synchronized void pollForDebugEvent() {
1448     ProcessControl prctl = getCDebugger().getProcessControl();
1449     if (prctl == null) {
1450       return;
1451     }
1452     DebugEvent ev = prctl.debugEventPoll();
1453     if (ev != null) {
1454       DebugEvent.Type t = ev.getType();
1455       if (t == DebugEvent.Type.LOADOBJECT_LOAD ||
1456           t == DebugEvent.Type.LOADOBJECT_UNLOAD) {
1457         // Conservatively clear cached debug info state
1458         sourceFileToLineNumberInfoMap = null;
1459         // FIXME: would be very useful to have "stop on load/unload"
1460         // events
1461         // FIXME: must do work at these events to implement lazy
1462         // breakpoints
1463         prctl.debugEventContinue();
1464       } else if (t == DebugEvent.Type.BREAKPOINT) {
1465         // Note: Visual C++ only notifies on breakpoints it doesn't
1466         // know about
1467 
1468         // FIXME: put back test
1469         //        if (!prctl.isBreakpointSet(ev.getPC())) {
1470           showMessageDialog("Breakpoint reached at PC " + ev.getPC(),
1471                             "Breakpoint reached",
1472                             JOptionPane.INFORMATION_MESSAGE);
1473           //        }
1474         agent.disableJavaInteraction();
1475         suspend();
1476         prctl.debugEventContinue();
1477       } else if (t == DebugEvent.Type.SINGLE_STEP) {
1478         agent.disableJavaInteraction();
1479         suspend();
1480         prctl.debugEventContinue();
1481       } else if (t == DebugEvent.Type.ACCESS_VIOLATION) {
1482         showMessageDialog("Access violation attempting to " +
1483                           (ev.getWasWrite() ? "write" : "read") +
1484                           " address " + ev.getAddress() +
1485                           " at PC " + ev.getPC(),
1486                           "Access Violation",
1487                           JOptionPane.WARNING_MESSAGE);
1488         agent.disableJavaInteraction();
1489         suspend();
1490         prctl.debugEventContinue();
1491       } else {
1492         String info = "Unknown debug event encountered";
1493         if (ev.getUnknownEventDetail() != null) {
1494           info = info + ": " + ev.getUnknownEventDetail();
1495         }
1496         showMessageDialog(info, "Unknown debug event", JOptionPane.INFORMATION_MESSAGE);
1497         suspend();
1498         prctl.debugEventContinue();
1499       }
1500       return;
1501     }
1502 
1503     // No C++ debug event; poll for Java debug event
1504     if (getAgent().canInteractWithJava()) {
1505       if (!javaEventPending) {
1506         if (getAgent().javaEventPending()) {
1507           suspend();
1508           // This does a lot of work and we want to have the page
1509           // cache available to us as it runs
1510           sun.jvm.hotspot.livejvm.Event jev = getAgent().javaEventPoll();
1511           if (jev != null) {
1512             javaEventPending = true;
1513             if (jev.getType() == sun.jvm.hotspot.livejvm.Event.Type.BREAKPOINT) {
1514               BreakpointEvent bpev = (BreakpointEvent) jev;
1515               showMessageDialog("Breakpoint reached in method\n" +
1516                                 bpev.methodID().method().externalNameAndSignature() +
1517                                 ",\nbci " + bpev.location(),
1518                                 "Breakpoint reached",
1519                                 JOptionPane.INFORMATION_MESSAGE);
1520             } else if (jev.getType() == sun.jvm.hotspot.livejvm.Event.Type.EXCEPTION) {
1521               ExceptionEvent exev = (ExceptionEvent) jev;
1522               showMessageDialog(exev.exception().getKlass().getName().asString() +
1523                                 "\nthrown in method\n" +
1524                                 exev.methodID().method().externalNameAndSignature() +
1525                                 "\nat BCI " + exev.location(),
1526                                 "Exception thrown",
1527                                 JOptionPane.INFORMATION_MESSAGE);
1528             } else {
1529               Assert.that(false, "Should not reach here");
1530             }
1531           }
1532         }
1533       }
1534     }
1535   }
1536 }