diff agent/src/share/classes/sun/jvm/hotspot/bugspot/BugSpot.java @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children bc32f286fae0
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/share/classes/sun/jvm/hotspot/bugspot/BugSpot.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1536 @@
+/*
+ * Copyright 2001-2003 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+package sun.jvm.hotspot.bugspot;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.filechooser.*;
+import sun.jvm.hotspot.debugger.*;
+import sun.jvm.hotspot.debugger.cdbg.*;
+import sun.jvm.hotspot.debugger.posix.*;
+import sun.jvm.hotspot.debugger.win32.*;
+import sun.jvm.hotspot.livejvm.*;
+import sun.jvm.hotspot.memory.*;
+import sun.jvm.hotspot.oops.*;
+import sun.jvm.hotspot.runtime.*;
+import sun.jvm.hotspot.ui.*;
+import sun.jvm.hotspot.utilities.*;
+
+/** The BugSpot component. This is embeddable in an application by
+    virtue of its being a JComponent. It (currently) requires the use
+    of a menu bar which can be fetched via getMenuBar(). This is
+    intended ultimately to replace HSDB. */
+
+public class BugSpot extends JPanel {
+  public BugSpot() {
+    super();
+    Runtime.getRuntime().addShutdownHook(new java.lang.Thread() {
+        public void run() {
+          detachDebugger();
+        }
+      });
+  }
+
+  /** Turn on or off MDI (Multiple Document Interface) mode. When MDI
+      is enabled, the BugSpot component contains a JDesktopPane and all
+      windows are JInternalFrames. When disabled, only the menu bar is
+      relevant. */
+  public void setMDIMode(boolean onOrOff) {
+    mdiMode = onOrOff;
+  }
+
+  /** Indicates whether MDI mode is enabled. */
+  public boolean getMDIMode() {
+    return mdiMode;
+  }
+
+  /** Build user interface widgets. This must be called before adding
+      the BugSpot component to its parent. */
+  public void build() {
+    setLayout(new BorderLayout());
+
+    menuBar = new JMenuBar();
+
+    attachMenuItems = new java.util.ArrayList();
+    detachMenuItems = new java.util.ArrayList();
+    debugMenuItems  = new java.util.ArrayList();
+    suspendDebugMenuItems = new java.util.ArrayList();
+    resumeDebugMenuItems = new java.util.ArrayList();
+
+    //
+    // File menu
+    //
+
+    JMenu menu = createMenu("File", 'F', 0);
+    JMenuItem item;
+    item = createMenuItem("Open source file...",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                openSourceFile();
+                              }
+                            },
+                          KeyEvent.VK_O, InputEvent.CTRL_MASK,
+                          'O', 0);
+    menu.add(item);
+    detachMenuItems.add(item);
+
+    menu.addSeparator();
+
+    item = createMenuItem("Attach to process...",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                showAttachDialog();
+                              }
+                            },
+                          'A', 0);
+    menu.add(item);
+    attachMenuItems.add(item);
+
+    item = createMenuItem("Detach",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                detach();
+                              }
+                            },
+                          'D', 0);
+    menu.add(item);
+    detachMenuItems.add(item);
+
+    // Disable detach menu items at first
+    setMenuItemsEnabled(detachMenuItems, false);
+
+    menu.addSeparator();
+
+    menu.add(createMenuItem("Exit",
+                            new ActionListener() {
+                                public void actionPerformed(ActionEvent e) {
+                                  detach();
+                                  System.exit(0);
+                                }
+                              },
+                            'x', 1));
+
+    menuBar.add(menu);
+
+    //
+    // Debug menu
+    //
+
+    debugMenu = createMenu("Debug", 'D', 0);
+    item = createMenuItem("Go",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                if (!attached) return;
+                                if (!isSuspended()) return;
+                                resume();
+                              }
+                            },
+                          KeyEvent.VK_F5, 0,
+                          'G', 0);
+    debugMenu.add(item);
+    resumeDebugMenuItems.add(item);
+
+    item = createMenuItem("Break",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                if (!attached) {
+                                  System.err.println("Not attached");
+                                  return;
+                                }
+                                if (isSuspended()) {
+                                  System.err.println("Already suspended");
+                                  return;
+                                }
+                                suspend();
+                              }
+                            },
+                          'B', 0);
+    debugMenu.add(item);
+    suspendDebugMenuItems.add(item);
+
+    debugMenu.addSeparator();
+
+    item = createMenuItem("Threads...",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                showThreadsDialog();
+                              }
+                            },
+                          'T', 0);
+    debugMenu.add(item);
+    debugMenuItems.add(item);
+    // FIXME: belongs under "View -> Debug Windows"
+    item = createMenuItem("Memory",
+                          new ActionListener() {
+                              public void actionPerformed(ActionEvent e) {
+                                showMemoryDialog();
+                              }
+                            },
+                          'M', 0);
+    debugMenu.add(item);
+    debugMenuItems.add(item);
+
+    debugMenu.setEnabled(false);
+    menuBar.add(debugMenu);
+
+    if (mdiMode) {
+      desktop = new JDesktopPane();
+      add(desktop, BorderLayout.CENTER);
+    }
+
+    fixedWidthFont = GraphicsUtilities.lookupFont("Courier");
+
+    debugEventTimer = new javax.swing.Timer(100, new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          pollForDebugEvent();
+        }
+      });
+  }
+
+  public JMenuBar getMenuBar() {
+    return menuBar;
+  }
+
+  public void showAttachDialog() {
+    setMenuItemsEnabled(attachMenuItems, false);
+    final FrameWrapper attachDialog = newFrame("Attach to process");
+    attachDialog.getContentPane().setLayout(new BorderLayout());
+    attachDialog.setClosable(true);
+    attachDialog.setResizable(true);
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new BorderLayout());
+    panel.setBorder(GraphicsUtilities.newBorder(5));
+    attachDialog.setBackground(panel.getBackground());
+
+    JPanel listPanel = new JPanel();
+    listPanel.setLayout(new BorderLayout());
+    final ProcessListPanel plist = new ProcessListPanel(getLocalDebugger());
+    panel.add(plist, BorderLayout.CENTER);
+    JCheckBox check = new JCheckBox("Update list continuously");
+    check.addItemListener(new ItemListener() {
+        public void itemStateChanged(ItemEvent e) {
+          if (e.getStateChange() == ItemEvent.SELECTED) {
+            plist.start();
+          } else {
+            plist.stop();
+          }
+        }
+      });
+    listPanel.add(plist, BorderLayout.CENTER);
+    listPanel.add(check, BorderLayout.SOUTH);
+    panel.add(listPanel, BorderLayout.CENTER);
+    attachDialog.getContentPane().add(panel, BorderLayout.CENTER);
+    attachDialog.setClosingActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          plist.stop();
+          setMenuItemsEnabled(attachMenuItems, true);
+        }
+      });
+
+    ActionListener attacher = new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          plist.stop();
+          attachDialog.setVisible(false);
+          removeFrame(attachDialog);
+          ProcessInfo info = plist.getSelectedProcess();
+          if (info != null) {
+            attach(info.getPid());
+          }
+        }
+      };
+
+    Box hbox = Box.createHorizontalBox();
+    hbox.add(Box.createGlue());
+    JButton button = new JButton("OK");
+    button.addActionListener(attacher);
+    hbox.add(button);
+    hbox.add(Box.createHorizontalStrut(20));
+    button = new JButton("Cancel");
+    button.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          plist.stop();
+          attachDialog.setVisible(false);
+          removeFrame(attachDialog);
+          setMenuItemsEnabled(attachMenuItems, true);
+        }
+      });
+    hbox.add(button);
+    hbox.add(Box.createGlue());
+    panel = new JPanel();
+    panel.setBorder(GraphicsUtilities.newBorder(5));
+    panel.add(hbox);
+
+    attachDialog.getContentPane().add(panel, BorderLayout.SOUTH);
+
+    addFrame(attachDialog);
+    attachDialog.pack();
+    attachDialog.setSize(400, 300);
+    GraphicsUtilities.centerInContainer(attachDialog.getComponent(),
+                                        getParentDimension(attachDialog.getComponent()));
+    attachDialog.show();
+  }
+
+  public void showThreadsDialog() {
+    final FrameWrapper threadsDialog = newFrame("Threads");
+    threadsDialog.getContentPane().setLayout(new BorderLayout());
+    threadsDialog.setClosable(true);
+    threadsDialog.setResizable(true);
+
+    ThreadListPanel threads = new ThreadListPanel(getCDebugger(), getAgent().isJavaMode());
+    threads.addListener(new ThreadListPanel.Listener() {
+        public void setFocus(ThreadProxy thread, JavaThread jthread) {
+          setCurrentThread(thread);
+          // FIXME: print this to GUI, bring some windows to foreground
+          System.err.println("Focus changed to thread " + thread);
+        }
+      });
+    threads.setBorder(GraphicsUtilities.newBorder(5));
+    threadsDialog.getContentPane().add(threads);
+    addFrame(threadsDialog);
+    threadsDialog.pack();
+    GraphicsUtilities.reshapeToAspectRatio(threadsDialog.getComponent(),
+                                           3.0f,
+                                           0.9f,
+                                           getParentDimension(threadsDialog.getComponent()));
+    GraphicsUtilities.centerInContainer(threadsDialog.getComponent(),
+                                        getParentDimension(threadsDialog.getComponent()));
+    threadsDialog.show();
+  }
+
+  public void showMemoryDialog() {
+    final FrameWrapper memoryDialog = newFrame("Memory");
+    memoryDialog.getContentPane().setLayout(new BorderLayout());
+    memoryDialog.setClosable(true);
+    memoryDialog.setResizable(true);
+
+    memoryDialog.getContentPane().add(new MemoryViewer(getDebugger(),
+                                                       (getDebugger().getMachineDescription().getAddressSize() == 8)),
+                                      BorderLayout.CENTER);
+    addFrame(memoryDialog);
+    memoryDialog.pack();
+    GraphicsUtilities.reshapeToAspectRatio(memoryDialog.getComponent(),
+                                           1.0f,
+                                           0.7f,
+                                           getParentDimension(memoryDialog.getComponent()));
+    GraphicsUtilities.centerInContainer(memoryDialog.getComponent(),
+                                        getParentDimension(memoryDialog.getComponent()));
+    memoryDialog.show();
+  }
+
+  /** Changes the editor factory this debugger uses to display source
+      code. Specified factory may be null, in which case the default
+      factory is used. */
+  public void setEditorFactory(EditorFactory fact) {
+    if (fact != null) {
+      editorFact = fact;
+    } else {
+      editorFact = new DefaultEditorFactory();
+    }
+  }
+
+  //----------------------------------------------------------------------
+  // Internals only below this point
+  //
+
+  private WorkerThread    workerThread;
+  private boolean         mdiMode;
+  private JVMDebugger     localDebugger;
+  private BugSpotAgent    agent = new BugSpotAgent();
+  private JMenuBar        menuBar;
+  /** List <JMenuItem> */
+  private java.util.List  attachMenuItems;
+  private java.util.List  detachMenuItems;
+  private java.util.List  debugMenuItems;
+  private java.util.List  suspendDebugMenuItems;
+  private java.util.List  resumeDebugMenuItems;
+  private FrameWrapper    stackFrame;
+  private VariablePanel   localsPanel;
+  private StackTracePanel stackTracePanel;
+  private FrameWrapper    registerFrame;
+  private RegisterPanel   registerPanel;
+  // Used for mixed-language stack traces
+  private Map             threadToJavaThreadMap;
+
+  private JMenu debugMenu;
+
+  // MDI mode only: desktop pane
+  private JDesktopPane desktop;
+
+  // Attach/detach state
+  private boolean attached;
+
+  // Suspension (combined Java/C++) state
+  private boolean suspended;
+
+  // Fixed-width font
+  private Font fixedWidthFont;
+
+  // Breakpoint setting
+  // Maps Strings to List/*<LineNumberInfo>*/
+  private Map sourceFileToLineNumberInfoMap;
+  // Maps Strings (file names) to Sets of Integers (line numbers)
+  private Map fileToBreakpointMap;
+
+  // Debug events
+  private javax.swing.Timer debugEventTimer;
+
+  // Java debug events
+  private boolean javaEventPending;
+
+  static class BreakpointResult {
+    private boolean success;
+    private boolean set;
+    private int lineNo;
+    private String why;
+
+    /** For positive results */
+    BreakpointResult(boolean success, boolean set, int lineNo) {
+      this(success, set, lineNo, null);
+    }
+
+    /** For negative results */
+    BreakpointResult(boolean success, boolean set, int lineNo, String why) {
+      this.success = success;
+      this.set = set;
+      this.lineNo = lineNo;
+      this.why = why;
+    }
+
+    public boolean succeeded() {
+      return success;
+    }
+
+    public boolean set() {
+      return set;
+    }
+
+    /** Line at which the breakpoint was actually set; only valid if
+        succeeded() returns true */
+    public int getLine() {
+      return lineNo;
+    }
+
+    public String getWhy() {
+      return why;
+    }
+  }
+
+
+  // Editors for source code. File name-to-Editor mapping.
+  private Map editors;
+  private EditorFactory editorFact = new DefaultEditorFactory();
+  private EditorCommands editorComm = new EditorCommands() {
+      public void windowClosed(Editor editor) {
+        editors.remove(editor.getSourceFileName());
+      }
+
+      public void toggleBreakpointAtLine(Editor editor, int lineNumber) {
+        // FIXME: handle "lazy" breakpoints where the source file has
+        // been opened with some other mechanism (File -> Open) and we
+        // don't have debug information pointing to that file yet
+        // FIXME: NOT FINISHED
+
+        BreakpointResult res =
+          handleBreakpointToggle(editor, lineNumber);
+        if (res.succeeded()) {
+          if (res.set()) {
+            editor.showBreakpointAtLine(res.getLine());
+          } else {
+            editor.clearBreakpointAtLine(res.getLine());
+          }
+        } else {
+          String why = res.getWhy();
+          if (why == null) {
+            why = "";
+          } else {
+            why = ": " + why;
+          }
+          showMessageDialog("Unable to toggle breakpoint" + why,
+                            "Unable to toggle breakpoint",
+                            JOptionPane.WARNING_MESSAGE);
+        }
+      }
+    };
+
+  private void attach(final int pid) {
+    try {
+      getAgent().attach(pid);
+      setMenuItemsEnabled(detachMenuItems, true);
+      setMenuItemsEnabled(suspendDebugMenuItems, false);
+      setMenuItemsEnabled(resumeDebugMenuItems, true);
+      debugMenu.setEnabled(true);
+      attached = true;
+      suspended = true;
+
+      if (getAgent().isJavaMode()) {
+        System.err.println("Java HotSpot(TM) virtual machine detected.");
+      } else {
+        System.err.println("(No Java(TM) virtual machine detected)");
+      }
+
+      // Set up editor map
+      editors = new HashMap();
+
+      // Initialize breakpoints
+      fileToBreakpointMap = new HashMap();
+
+      // Create combined stack trace and local variable panel
+      JPanel framePanel = new JPanel();
+      framePanel.setLayout(new BorderLayout());
+      framePanel.setBorder(GraphicsUtilities.newBorder(5));
+      localsPanel = new VariablePanel();
+      JTabbedPane tab = new JTabbedPane();
+      tab.addTab("Locals", localsPanel);
+      tab.setTabPlacement(JTabbedPane.BOTTOM);
+      framePanel.add(tab, BorderLayout.CENTER);
+      JPanel stackPanel = new JPanel();
+      stackPanel.setLayout(new BoxLayout(stackPanel, BoxLayout.X_AXIS));
+      stackPanel.add(new JLabel("Context:"));
+      stackPanel.add(Box.createHorizontalStrut(5));
+      stackTracePanel = new StackTracePanel();
+      stackTracePanel.addListener(new StackTracePanel.Listener() {
+          public void frameChanged(CFrame fr, JavaVFrame jfr) {
+            setCurrentFrame(fr, jfr);
+          }
+        });
+      stackPanel.add(stackTracePanel);
+      framePanel.add(stackPanel, BorderLayout.NORTH);
+      stackFrame = newFrame("Stack");
+      stackFrame.getContentPane().setLayout(new BorderLayout());
+      stackFrame.getContentPane().add(framePanel, BorderLayout.CENTER);
+      stackFrame.setResizable(true);
+      stackFrame.setClosable(false);
+      addFrame(stackFrame);
+      stackFrame.setSize(400, 200);
+      GraphicsUtilities.moveToInContainer(stackFrame.getComponent(), 0.0f, 1.0f, 0, 20);
+      stackFrame.show();
+
+      // Create register panel
+      registerPanel = new RegisterPanel();
+      registerPanel.setFont(fixedWidthFont);
+      registerFrame = newFrame("Registers");
+      registerFrame.getContentPane().setLayout(new BorderLayout());
+      registerFrame.getContentPane().add(registerPanel, BorderLayout.CENTER);
+      addFrame(registerFrame);
+      registerFrame.setResizable(true);
+      registerFrame.setClosable(false);
+      registerFrame.setSize(225, 200);
+      GraphicsUtilities.moveToInContainer(registerFrame.getComponent(),
+                                          1.0f, 0.0f, 0, 0);
+      registerFrame.show();
+
+      resetCurrentThread();
+    } catch (DebuggerException e) {
+      final String errMsg = formatMessage(e.getMessage(), 80);
+      setMenuItemsEnabled(attachMenuItems, true);
+      showMessageDialog("Unable to connect to process ID " + pid + ":\n\n" + errMsg,
+                        "Unable to Connect",
+                        JOptionPane.WARNING_MESSAGE);
+      getAgent().detach();
+    }
+  }
+
+  private synchronized void detachDebugger() {
+    if (!attached) {
+      return;
+    }
+    if (isSuspended()) {
+      resume(); // Necessary for JVMDI resumption
+    }
+    getAgent().detach();
+    // FIXME: clear out breakpoints (both Java and C/C++) from target
+    // process
+    sourceFileToLineNumberInfoMap = null;
+    fileToBreakpointMap = null;
+    threadToJavaThreadMap = null;
+    editors = null;
+    attached = false;
+  }
+
+  private synchronized void detach() {
+    detachDebugger();
+    setMenuItemsEnabled(attachMenuItems, true);
+    setMenuItemsEnabled(detachMenuItems, false);
+    debugMenu.setEnabled(false);
+    if (mdiMode) {
+      // FIXME: is this sufficient, or will I have to do anything else
+      // to the components to kill them off? What about WorkerThreads?
+      desktop.removeAll();
+      desktop.invalidate();
+      desktop.validate();
+      desktop.repaint();
+    }
+    // FIXME: keep track of all windows and close them even in non-MDI
+    // mode
+    debugEventTimer.stop();
+  }
+
+  // Returns a Debugger for processes on the local machine. This is
+  // only used to fetch the process list.
+  private Debugger getLocalDebugger() {
+    if (localDebugger == null) {
+      String os  = PlatformInfo.getOS();
+      String cpu = PlatformInfo.getCPU();
+
+      if (os.equals("win32")) {
+        if (!cpu.equals("x86")) {
+          throw new DebuggerException("Unsupported CPU \"" + cpu + "\" for Windows");
+        }
+
+        localDebugger = new Win32DebuggerLocal(new MachineDescriptionIntelX86(), true);
+      } else if (os.equals("linux")) {
+        if (!cpu.equals("x86")) {
+          throw new DebuggerException("Unsupported CPU \"" + cpu + "\" for Linux");
+        }
+
+        // FIXME: figure out how to specify path to debugger module
+        throw new RuntimeException("FIXME: figure out how to specify path to debugger module");
+        //        localDebugger = new PosixDebuggerLocal(new MachineDescriptionIntelX86(), true);
+      } else {
+        // FIXME: port to Solaris
+        throw new DebuggerException("Unsupported OS \"" + os + "\"");
+      }
+
+      // FIXME: we require that the primitive type sizes be configured
+      // in order to use basic functionality in class Address such as
+      // the fetching of floating-point values. There are a lot of
+      // assumptions in the current code that Java floats and doubles
+      // are of equivalent size to C values. The configurability of the
+      // primitive type sizes hasn't seemed necessary and in this kind
+      // of debugging scenario (namely, debugging arbitrary C++
+      // processes) it appears difficult to support that kind of
+      // flexibility.
+      localDebugger.configureJavaPrimitiveTypeSizes(1, 1, 2, 8, 4, 4, 8, 2);
+    }
+
+    return localDebugger;
+  }
+
+  private BugSpotAgent getAgent() {
+    return agent;
+  }
+
+  private Debugger getDebugger() {
+    return getAgent().getDebugger();
+  }
+
+  private CDebugger getCDebugger() {
+    return getAgent().getCDebugger();
+  }
+
+  private void resetCurrentThread() {
+    setCurrentThread((ThreadProxy) getCDebugger().getThreadList().get(0));
+  }
+
+  private void setCurrentThread(ThreadProxy t) {
+    // Create stack trace
+    // FIXME: add ability to intermix C/Java frames
+    java.util.List trace = new ArrayList();
+    CFrame fr = getCDebugger().topFrameForThread(t);
+    while (fr != null) {
+      trace.add(new StackTraceEntry(fr, getCDebugger()));
+      try {
+        fr = fr.sender();
+      } catch (AddressException e) {
+        e.printStackTrace();
+        showMessageDialog("Error while walking stack; stack trace will be truncated\n(see console for details)",
+                          "Error walking stack",
+                          JOptionPane.WARNING_MESSAGE);
+        fr = null;
+      }
+    }
+    JavaThread jthread = javaThreadForProxy(t);
+    if (jthread != null) {
+      // Java mode, and we have a Java thread.
+      // Find all Java frames on the stack. We currently do this in a
+      // manner which involves minimal interaction between the Java
+      // and C/C++ debugging systems: any C frame which has a PC in an
+      // unknown location (i.e., not in any DSO) is assumed to be a
+      // Java frame. We merge stack segments of unknown frames with
+      // segments of Java frames beginning with native methods.
+      java.util.List javaTrace = new ArrayList();
+      VFrame vf = jthread.getLastJavaVFrameDbg();
+      while (vf != null) {
+        if (vf.isJavaFrame()) {
+          javaTrace.add(new StackTraceEntry((JavaVFrame) vf));
+          vf = vf.sender();
+        }
+      }
+      // Merge stack traces
+      java.util.List mergedTrace = new ArrayList();
+      int c = 0;
+      int j = 0;
+      while (c < trace.size()) {
+        StackTraceEntry entry = (StackTraceEntry) trace.get(c);
+        if (entry.isUnknownCFrame()) {
+          boolean gotJavaFrame = false;
+          while (j < javaTrace.size()) {
+            StackTraceEntry javaEntry = (StackTraceEntry) javaTrace.get(j);
+            JavaVFrame jvf = javaEntry.getJavaFrame();
+            Method m = jvf.getMethod();
+            if (!m.isNative() || !gotJavaFrame) {
+              gotJavaFrame = true;
+              mergedTrace.add(javaEntry);
+              ++j;
+            } else {
+              break; // Reached native method; have intervening C frames
+            }
+          }
+          if (gotJavaFrame) {
+            // Skip this sequence of unknown frames, as we've
+            // successfully identified it as Java frames
+            while (c < trace.size() && entry.isUnknownCFrame()) {
+              ++c;
+              if (c < trace.size()) {
+                entry = (StackTraceEntry) trace.get(c);
+              }
+            }
+            continue;
+          }
+        }
+        // If we get here, we either have an unknown frame we didn't
+        // know how to categorize or we have a known C frame. Add it
+        // to the trace.
+        mergedTrace.add(entry);
+        ++c;
+      }
+      trace = mergedTrace;
+    }
+    stackTracePanel.setTrace(trace);
+
+    registerPanel.update(t);
+  }
+
+  private void setCurrentFrame(CFrame fr, JavaVFrame jfr) {
+    localsPanel.clear();
+
+    if (fr != null) {
+      localsPanel.update(fr);
+
+      // FIXME: load source file if we can find it, otherwise display disassembly
+      LoadObject lo = getCDebugger().loadObjectContainingPC(fr.pc());
+      if (lo != null) {
+        CDebugInfoDataBase db = lo.getDebugInfoDataBase();
+        if (db != null) {
+          LineNumberInfo info = db.lineNumberForPC(fr.pc());
+          if (info != null) {
+            System.err.println("PC " + fr.pc() + ": Source file \"" +
+                               info.getSourceFileName() +
+                               "\", line number " +
+                               info.getLineNumber() +
+                               ", PC range [" +
+                               info.getStartPC() +
+                               ", " +
+                               info.getEndPC() +
+                               ")");
+            // OK, here we go...
+            showLineNumber(null, info.getSourceFileName(), info.getLineNumber());
+          } else {
+            System.err.println("(No line number information for PC " + fr.pc() + ")");
+            // Dump line number information for database
+            db.iterate(new LineNumberVisitor() {
+                public void doLineNumber(LineNumberInfo info) {
+                  System.err.println("  Source file \"" +
+                                     info.getSourceFileName() +
+                                     "\", line number " +
+                                     info.getLineNumber() +
+                                     ", PC range [" +
+                                     info.getStartPC() +
+                                     ", " +
+                                     info.getEndPC() +
+                                     ")");
+                }
+              });
+          }
+        }
+      }
+    } else {
+      if (Assert.ASSERTS_ENABLED) {
+        Assert.that(jfr != null, "Must have either C or Java frame");
+      }
+      localsPanel.update(jfr);
+      // See whether we can locate source file and line number
+      // FIXME: infer pathmap entries from user's locating of this
+      // source file
+      // FIXME: figure out what to do for native methods. Possible to
+      // go to line number for the native method declaration?
+      Method m = jfr.getMethod();
+      Symbol sfn = ((InstanceKlass) m.getMethodHolder()).getSourceFileName();
+      if (sfn != null) {
+        int bci = jfr.getBCI();
+        int lineNo = m.getLineNumberFromBCI(bci);
+        if (lineNo >= 0) {
+          // FIXME: show disassembly otherwise
+          showLineNumber(packageName(m.getMethodHolder().getName().asString()),
+                         sfn.asString(), lineNo);
+        }
+      }
+    }
+  }
+
+  private String packageName(String str) {
+    int idx = str.lastIndexOf('/');
+    if (idx < 0) {
+      return "";
+    }
+    return str.substring(0, idx).replace('/', '.');
+  }
+
+  private JavaThread javaThreadForProxy(ThreadProxy t) {
+    if (!getAgent().isJavaMode()) {
+      return null;
+    }
+    if (threadToJavaThreadMap == null) {
+      threadToJavaThreadMap = new HashMap();
+      Threads threads = VM.getVM().getThreads();
+      for (JavaThread thr = threads.first(); thr != null; thr = thr.next()) {
+        threadToJavaThreadMap.put(thr.getThreadProxy(), thr);
+      }
+    }
+    return (JavaThread) threadToJavaThreadMap.get(t);
+  }
+
+  private static JMenu createMenu(String name, char mnemonic, int mnemonicPos) {
+    JMenu menu = new JMenu(name);
+    menu.setMnemonic(mnemonic);
+    menu.setDisplayedMnemonicIndex(mnemonicPos);
+    return menu;
+  }
+
+  private static JMenuItem createMenuItem(String name, ActionListener l) {
+    JMenuItem item = new JMenuItem(name);
+    item.addActionListener(l);
+    return item;
+  }
+
+  private static JMenuItem createMenuItemInternal(String name, ActionListener l, int accelerator, int modifiers) {
+    JMenuItem item = createMenuItem(name, l);
+    item.setAccelerator(KeyStroke.getKeyStroke(accelerator, modifiers));
+    return item;
+  }
+
+  private static JMenuItem createMenuItem(String name, ActionListener l, int accelerator) {
+    return createMenuItemInternal(name, l, accelerator, 0);
+  }
+
+  private static JMenuItem createMenuItem(String name, ActionListener l, char mnemonic, int mnemonicPos) {
+    JMenuItem item = createMenuItem(name, l);
+    item.setMnemonic(mnemonic);
+    item.setDisplayedMnemonicIndex(mnemonicPos);
+    return item;
+  }
+
+  private static JMenuItem createMenuItem(String name,
+                                          ActionListener l,
+                                          int accelerator,
+                                          int acceleratorMods,
+                                          char mnemonic,
+                                          int mnemonicPos) {
+    JMenuItem item = createMenuItemInternal(name, l, accelerator, acceleratorMods);
+    item.setMnemonic(mnemonic);
+    item.setDisplayedMnemonicIndex(mnemonicPos);
+    return item;
+  }
+
+  /** Punctuates the given string with \n's where necessary to not
+      exceed the given number of characters per line. Strips
+      extraneous whitespace. */
+  private static String formatMessage(String message, int charsPerLine) {
+    StringBuffer buf = new StringBuffer(message.length());
+    StringTokenizer tokenizer = new StringTokenizer(message);
+    int curLineLength = 0;
+    while (tokenizer.hasMoreTokens()) {
+      String tok = tokenizer.nextToken();
+      if (curLineLength + tok.length() > charsPerLine) {
+        buf.append('\n');
+        curLineLength = 0;
+      } else {
+        if (curLineLength != 0) {
+          buf.append(' ');
+          ++curLineLength;
+        }
+      }
+      buf.append(tok);
+      curLineLength += tok.length();
+    }
+    return buf.toString();
+  }
+
+  private void setMenuItemsEnabled(java.util.List items, boolean enabled) {
+    for (Iterator iter = items.iterator(); iter.hasNext(); ) {
+      ((JMenuItem) iter.next()).setEnabled(enabled);
+    }
+  }
+
+  private void showMessageDialog(final String message, final String title, final int jOptionPaneKind) {
+    SwingUtilities.invokeLater(new Runnable() {
+        public void run() {
+          if (mdiMode) {
+            JOptionPane.showInternalMessageDialog(desktop, message, title, jOptionPaneKind);
+          } else {
+            JOptionPane.showMessageDialog(null, message, title, jOptionPaneKind);
+          }
+        }
+      });
+  }
+
+  private FrameWrapper newFrame(String title) {
+    if (mdiMode) {
+      return new JInternalFrameWrapper(new JInternalFrame(title));
+    } else {
+      return new JFrameWrapper(new JFrame(title));
+    }
+  }
+
+  private void addFrame(FrameWrapper frame) {
+    if (mdiMode) {
+      desktop.add(frame.getComponent());
+    }
+  }
+
+  private void removeFrame(FrameWrapper frame) {
+    if (mdiMode) {
+      desktop.remove(frame.getComponent());
+      desktop.invalidate();
+      desktop.validate();
+      desktop.repaint();
+    }
+    // FIXME: do something when not in MDI mode
+  }
+
+  private Dimension getParentDimension(Component c) {
+    if (mdiMode) {
+      return desktop.getSize();
+    } else {
+      return Toolkit.getDefaultToolkit().getScreenSize();
+    }
+  }
+
+  // Default editor implementation
+  class DefaultEditor implements Editor {
+    private DefaultEditorFactory factory;
+    private FrameWrapper    editorFrame;
+    private String          filename;
+    private SourceCodePanel code;
+    private boolean         shown;
+    private Object          userData;
+
+    public DefaultEditor(DefaultEditorFactory fact, String filename, final EditorCommands comm) {
+      this.filename = filename;
+      this.factory = fact;
+      editorFrame = newFrame(filename);
+      code = new SourceCodePanel();
+      // FIXME: when font changes, change font in editors as well
+      code.setFont(fixedWidthFont);
+      editorFrame.getContentPane().add(code);
+      editorFrame.setClosable(true);
+      editorFrame.setResizable(true);
+      editorFrame.setClosingActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            comm.windowClosed(DefaultEditor.this);
+            removeFrame(editorFrame);
+            editorFrame.dispose();
+            factory.editorClosed(DefaultEditor.this);
+          }
+        });
+      editorFrame.setActivatedActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            factory.makeEditorCurrent(DefaultEditor.this);
+            code.requestFocus();
+          }
+        });
+      code.setEditorCommands(comm, this);
+    }
+
+    public boolean openFile()                        { return code.openFile(filename);     }
+    public String  getSourceFileName()               { return filename;                    }
+    public int     getCurrentLineNumber()            { return code.getCurrentLineNumber(); }
+    public void showLineNumber(int lineNo) {
+      if (!shown) {
+        addFrame(editorFrame);
+        GraphicsUtilities.reshapeToAspectRatio(editorFrame.getComponent(),
+                                               1.0f,
+                                               0.85f,
+                                               getParentDimension(editorFrame.getComponent()));
+        editorFrame.show();
+        shown = true;
+      }
+      code.showLineNumber(lineNo);
+      editorFrame.toFront();
+    }
+    public void    highlightLineNumber(int lineNo)   { code.highlightLineNumber(lineNo);        }
+    public void    showBreakpointAtLine(int lineNo)  { code.showBreakpointAtLine(lineNo);       }
+    public boolean hasBreakpointAtLine(int lineNo)   { return code.hasBreakpointAtLine(lineNo); }
+    public void    clearBreakpointAtLine(int lineNo) { code.clearBreakpointAtLine(lineNo);      }
+    public void    clearBreakpoints()                { code.clearBreakpoints();                 }
+    public void    setUserData(Object o)             { userData = o;                            }
+    public Object  getUserData()                     { return userData;                         }
+    public void    toFront()                         { editorFrame.toFront();
+                                                       factory.makeEditorCurrent(this);         }
+  }
+
+  class DefaultEditorFactory implements EditorFactory {
+    private LinkedList/*<Editor>*/ editors = new LinkedList();
+
+    public Editor openFile(String filename, EditorCommands commands) {
+      DefaultEditor editor = new DefaultEditor(this, filename, editorComm);
+      if (!editor.openFile()) {
+        return null;
+      }
+      return editor;
+    }
+
+    public Editor getCurrentEditor() {
+      if (editors.isEmpty()) {
+        return null;
+      }
+      return (Editor) editors.getFirst();
+    }
+
+    void editorClosed(Editor editor) {
+      editors.remove(editor);
+    }
+
+    void makeEditorCurrent(Editor editor) {
+      editors.remove(editor);
+      editors.addFirst(editor);
+    }
+  }
+
+  // Helper class for loading .java files; show only those with
+  // correct file name which are also in the correct package
+  static class JavaFileFilter extends javax.swing.filechooser.FileFilter {
+    private String packageName;
+    private String fileName;
+
+    JavaFileFilter(String packageName, String fileName) {
+      this.packageName = packageName;
+      this.fileName = fileName;
+    }
+
+    public boolean accept(File f) {
+      if (f.isDirectory()) {
+        return true;
+      }
+      // This rejects most files
+      if (!f.getName().equals(fileName)) {
+        return false;
+      }
+      // Ensure selected file is in the correct package
+      PackageScanner scanner = new PackageScanner();
+      String pkg = scanner.scan(f);
+      if (!pkg.equals(packageName)) {
+        return false;
+      }
+      return true;
+    }
+
+    public String getDescription() { return "Java source files"; }
+  }
+
+  // Auxiliary information used only for Java source files
+  static class JavaUserData {
+    private String packageName; // External format
+    private String sourceFileName;
+
+    /** Source file name is equivalent to that found in the .java
+        file; i.e., not a full path */
+    JavaUserData(String packageName, String sourceFileName) {
+      this.packageName = packageName;
+      this.sourceFileName = sourceFileName;
+    }
+
+    String packageName()    { return packageName; }
+    String sourceFileName() { return sourceFileName; }
+  }
+
+  // Opens a source file. This makes it available for the setting of
+  // lazy breakpoints.
+  private void openSourceFile() {
+    JFileChooser chooser = new JFileChooser();
+    chooser.setDialogTitle("Open source code file");
+    chooser.setMultiSelectionEnabled(false);
+    if (chooser.showOpenDialog(null) != JFileChooser.APPROVE_OPTION) {
+      return;
+    }
+    File chosen = chooser.getSelectedFile();
+    if (chosen == null) {
+      return;
+    }
+
+    // See whether we have a Java source file. If so, derive a package
+    // name for it.
+    String path = chosen.getPath();
+    String name = null;
+    JavaUserData data = null;
+    if (path.endsWith(".java")) {
+      PackageScanner scanner = new PackageScanner();
+      String pkg = scanner.scan(chosen);
+      // Now knowing both the package name and file name, we can put
+      // this in the editor map and use it for setting breakpoints
+      // later
+      String fileName = chosen.getName();
+      name = pkg + "." + fileName;
+      data = new JavaUserData(pkg, fileName);
+    } else {
+      // FIXME: need pathmap mechanism
+      name = path;
+    }
+    Editor editor = (Editor) editors.get(name);
+    if (editor == null) {
+      editor = editorFact.openFile(path, editorComm);
+      if (editor == null) {
+        showMessageDialog("Unable to open file \"" + path + "\" -- unexpected error.",
+                          "Unable to open file",
+                          JOptionPane.WARNING_MESSAGE);
+        return;
+      }
+      editors.put(name, editor);
+      if (data != null) {
+        editor.setUserData(data);
+      }
+    } else {
+      editor.toFront();
+    }
+    editor.showLineNumber(1);
+    // Show breakpoints as well if we have any for this file
+    Set set = (Set) fileToBreakpointMap.get(editor.getSourceFileName());
+    if (set != null) {
+      for (Iterator iter = set.iterator(); iter.hasNext(); ) {
+        editor.showBreakpointAtLine(((Integer) iter.next()).intValue());
+      }
+    }
+  }
+
+  // Package name may be null, in which case the file is assumed to be
+  // a C source file. Otherwise it is assumed to be a Java source file
+  // and certain filtering rules will be applied.
+  private void showLineNumber(String packageName, String fileName, int lineNumber) {
+    String name;
+    if (packageName == null) {
+      name = fileName;
+    } else {
+      name = packageName + "." + fileName;
+    }
+    Editor editor = (Editor) editors.get(name);
+    if (editor == null) {
+      // See whether file exists
+      File file = new File(fileName);
+      String realFileName = fileName;
+      if (!file.exists()) {
+        // User must specify path to file
+        JFileChooser chooser = new JFileChooser();
+        chooser.setDialogTitle("Please locate " + fileName);
+        chooser.setMultiSelectionEnabled(false);
+        if (packageName != null) {
+          chooser.setFileFilter(new JavaFileFilter(packageName, fileName));
+        }
+        int res = chooser.showOpenDialog(null);
+        if (res != JFileChooser.APPROVE_OPTION) {
+          // FIXME: show disassembly instead
+          return;
+        }
+        // FIXME: would like to infer more from the selection; i.e.,
+        // a pathmap leading up to this file
+        File chosen = chooser.getSelectedFile();
+        if (chosen == null) {
+          return;
+        }
+        realFileName = chosen.getPath();
+      }
+      // Now instruct editor factory to open file
+      editor = editorFact.openFile(realFileName, editorComm);
+      if (editor == null) {
+        showMessageDialog("Unable to open file \"" + realFileName + "\" -- unexpected error.",
+                          "Unable to open file",
+                          JOptionPane.WARNING_MESSAGE);
+        return;
+      }
+      // Got an editor; put it in map
+      editors.put(name, editor);
+      // If Java source file, add additional information for later
+      if (packageName != null) {
+        editor.setUserData(new JavaUserData(packageName, fileName));
+      }
+    }
+    // Got editor; show line
+    editor.showLineNumber(lineNumber);
+    editor.highlightLineNumber(lineNumber);
+    // Show breakpoints as well if we have any for this file
+    Set set = (Set) fileToBreakpointMap.get(editor.getSourceFileName());
+    if (set != null) {
+      for (Iterator iter = set.iterator(); iter.hasNext(); ) {
+        editor.showBreakpointAtLine(((Integer) iter.next()).intValue());
+      }
+    }
+  }
+
+  //
+  // Suspend/resume
+  //
+
+  private boolean isSuspended() {
+    return suspended;
+  }
+
+  private synchronized void suspend() {
+    setMenuItemsEnabled(resumeDebugMenuItems, true);
+    setMenuItemsEnabled(suspendDebugMenuItems, false);
+    BugSpotAgent agent = getAgent();
+    if (agent.canInteractWithJava() && !agent.isJavaSuspended()) {
+      agent.suspendJava();
+    }
+    agent.suspend();
+    // FIXME: call VM.getVM().fireVMSuspended()
+    resetCurrentThread();
+    debugEventTimer.stop();
+    suspended = true;
+  }
+
+  private synchronized void resume() {
+    // Note: we don't wipe out the cached state like the
+    // sourceFileToLineNumberInfoMap since it is too expensive to
+    // recompute. Instead we recompute it if any DLLs are loaded or
+    // unloaded.
+    threadToJavaThreadMap = null;
+    setMenuItemsEnabled(resumeDebugMenuItems, false);
+    setMenuItemsEnabled(suspendDebugMenuItems, true);
+    registerPanel.clear();
+    // FIXME: call VM.getVM().fireVMResumed()
+    BugSpotAgent agent = getAgent();
+    agent.resume();
+    if (agent.canInteractWithJava()) {
+      if (agent.isJavaSuspended()) {
+        agent.resumeJava();
+      }
+      if (javaEventPending) {
+        javaEventPending = false;
+        // Clear it out before resuming polling for events
+        agent.javaEventContinue();
+      }
+    }
+    agent.enableJavaInteraction();
+    suspended = false;
+    debugEventTimer.start();
+  }
+
+  //
+  // Breakpoints
+  //
+
+  private synchronized BreakpointResult handleBreakpointToggle(Editor editor, int lineNumber) {
+    // Currently we only use user data in editors to indicate Java
+    // source files. If this changes then this code will need to
+    // change.
+    JavaUserData data = (JavaUserData) editor.getUserData();
+    String filename = editor.getSourceFileName();
+    if (data == null) {
+      // C/C++ code
+      // FIXME: as noted above in EditorCommands.toggleBreakpointAtLine,
+      // this needs more work to handle "lazy" breakpoints in files
+      // which we don't know about in the debug information yet
+      CDebugger dbg = getCDebugger();
+      ProcessControl prctl = dbg.getProcessControl();
+      if (prctl == null) {
+        return new BreakpointResult(false, false, 0, "Process control not enabled");
+      }
+      boolean mustSuspendAndResume = (!prctl.isSuspended());
+      try {
+        if (mustSuspendAndResume) {
+          prctl.suspend();
+        }
+        // Search debug info for all DSOs
+        LineNumberInfo info = getLineNumberInfo(filename, lineNumber);
+        if (info != null) {
+          Set bpset = (Set) fileToBreakpointMap.get(filename);
+          if (bpset == null) {
+            bpset = new HashSet();
+            fileToBreakpointMap.put(filename, bpset);
+          }
+          Integer key = new Integer(info.getLineNumber());
+          if (bpset.contains(key)) {
+            // Clear breakpoint at this line's PC
+            prctl.clearBreakpoint(info.getStartPC());
+            bpset.remove(key);
+            return new BreakpointResult(true, false, info.getLineNumber());
+          } else {
+            // Set breakpoint at this line's PC
+            System.err.println("Setting breakpoint at PC " + info.getStartPC());
+            prctl.setBreakpoint(info.getStartPC());
+            bpset.add(key);
+            return new BreakpointResult(true, true, info.getLineNumber());
+          }
+        } else {
+          return new BreakpointResult(false, false, 0, "No debug information for this source file and line");
+        }
+      } finally {
+        if (mustSuspendAndResume) {
+          prctl.resume();
+        }
+      }
+    } else {
+      BugSpotAgent agent = getAgent();
+      if (!agent.canInteractWithJava()) {
+        String why;
+        if (agent.isJavaInteractionDisabled()) {
+          why = "Can not toggle Java breakpoints while stopped because\nof C/C++ debug events (breakpoints, single-stepping)";
+        } else {
+          why = "Could not talk to SA's JVMDI module to enable Java\nprogramming language breakpoints (run with -Xdebug -Xrunsa)";
+        }
+        return new BreakpointResult(false, false, 0, why);
+      }
+      Set bpset = (Set) fileToBreakpointMap.get(filename);
+      if (bpset == null) {
+        bpset = new HashSet();
+        fileToBreakpointMap.put(filename, bpset);
+      }
+      boolean mustResumeAndSuspend = isSuspended();
+      try {
+        if (mustResumeAndSuspend) {
+          agent.resume();
+        }
+        ServiceabilityAgentJVMDIModule.BreakpointToggleResult res =
+          getAgent().toggleJavaBreakpoint(data.sourceFileName(),
+                                          data.packageName(),
+                                          lineNumber);
+        if (res.getSuccess()) {
+          Integer key = new Integer(res.getLineNumber());
+          boolean addRemRes = false;
+          if (res.getWasSet()) {
+            addRemRes = bpset.add(key);
+            System.err.println("Setting breakpoint at " + res.getMethodName() + res.getMethodSignature() +
+                               ", bci " + res.getBCI() + ", line " + res.getLineNumber());
+          } else {
+            addRemRes = bpset.remove(key);
+            System.err.println("Clearing breakpoint at " + res.getMethodName() + res.getMethodSignature() +
+                               ", bci " + res.getBCI() + ", line " + res.getLineNumber());
+          }
+          if (Assert.ASSERTS_ENABLED) {
+            Assert.that(addRemRes, "Inconsistent Java breakpoint state with respect to target process");
+          }
+          return new BreakpointResult(true, res.getWasSet(), res.getLineNumber());
+        } else {
+          return new BreakpointResult(false, false, 0, res.getErrMsg());
+        }
+      } finally {
+        if (mustResumeAndSuspend) {
+          agent.suspend();
+          resetCurrentThread();
+        }
+      }
+    }
+  }
+
+  // Must call only when suspended
+  private LineNumberInfo getLineNumberInfo(String filename, int lineNumber) {
+    Map map = getSourceFileToLineNumberInfoMap();
+    java.util.List infos = (java.util.List) map.get(filename);
+    if (infos == null) {
+      return null;
+    }
+    // Binary search for line number
+    return searchLineNumbers(infos, lineNumber, 0, infos.size());
+  }
+
+  // Must call only when suspended
+  private Map getSourceFileToLineNumberInfoMap() {
+    if (sourceFileToLineNumberInfoMap == null) {
+      // Build from debug info
+      java.util.List loadObjects = getCDebugger().getLoadObjectList();
+      final Map map = new HashMap();
+      for (Iterator iter = loadObjects.iterator(); iter.hasNext(); ) {
+        LoadObject lo = (LoadObject) iter.next();
+        CDebugInfoDataBase db = lo.getDebugInfoDataBase();
+        if (db != null) {
+          db.iterate(new LineNumberVisitor() {
+              public void doLineNumber(LineNumberInfo info) {
+                String name = info.getSourceFileName();
+                if (name != null) {
+                  java.util.List val = (java.util.List) map.get(name);
+                  if (val == null) {
+                    val = new ArrayList();
+                    map.put(name, val);
+                  }
+                  val.add(info);
+                }
+              }
+            });
+        }
+      }
+      // Sort all lists
+      for (Iterator iter = map.values().iterator(); iter.hasNext(); ) {
+        java.util.List list = (java.util.List) iter.next();
+        Collections.sort(list, new Comparator() {
+            public int compare(Object o1, Object o2) {
+              LineNumberInfo l1 = (LineNumberInfo) o1;
+              LineNumberInfo l2 = (LineNumberInfo) o2;
+              int n1 = l1.getLineNumber();
+              int n2 = l2.getLineNumber();
+              if (n1 < n2) return -1;
+              if (n1 == n2) return 0;
+              return 1;
+            }
+          });
+      }
+      sourceFileToLineNumberInfoMap = map;
+    }
+    return sourceFileToLineNumberInfoMap;
+  }
+
+  private LineNumberInfo searchLineNumbers(java.util.List infoList, int lineNo, int lowIdx, int highIdx) {
+    if (highIdx < lowIdx) return null;
+    if (lowIdx == highIdx) {
+      // Base case: see whether start PC is less than or equal to addr
+      if (checkLineNumber(infoList, lineNo, lowIdx)) {
+        return (LineNumberInfo) infoList.get(lowIdx);
+      } else {
+        return null;
+      }
+    } else if (lowIdx == highIdx - 1) {
+      if (checkLineNumber(infoList, lineNo, lowIdx)) {
+        return (LineNumberInfo) infoList.get(lowIdx);
+      } else if (checkLineNumber(infoList, lineNo, highIdx)) {
+        return (LineNumberInfo) infoList.get(highIdx);
+      } else {
+        return null;
+      }
+    }
+    int midIdx = (lowIdx + highIdx) >> 1;
+    LineNumberInfo info = (LineNumberInfo) infoList.get(midIdx);
+    if (lineNo < info.getLineNumber()) {
+      // Always move search down
+      return searchLineNumbers(infoList, lineNo, lowIdx, midIdx);
+    } else if (lineNo == info.getLineNumber()) {
+      return info;
+    } else {
+      // Move search up
+      return searchLineNumbers(infoList, lineNo, midIdx, highIdx);
+    }
+  }
+
+  private boolean checkLineNumber(java.util.List infoList, int lineNo, int idx) {
+    LineNumberInfo info = (LineNumberInfo) infoList.get(idx);
+    return (info.getLineNumber() >= lineNo);
+  }
+
+  //
+  // Debug events
+  //
+
+  private synchronized void pollForDebugEvent() {
+    ProcessControl prctl = getCDebugger().getProcessControl();
+    if (prctl == null) {
+      return;
+    }
+    DebugEvent ev = prctl.debugEventPoll();
+    if (ev != null) {
+      DebugEvent.Type t = ev.getType();
+      if (t == DebugEvent.Type.LOADOBJECT_LOAD ||
+          t == DebugEvent.Type.LOADOBJECT_UNLOAD) {
+        // Conservatively clear cached debug info state
+        sourceFileToLineNumberInfoMap = null;
+        // FIXME: would be very useful to have "stop on load/unload"
+        // events
+        // FIXME: must do work at these events to implement lazy
+        // breakpoints
+        prctl.debugEventContinue();
+      } else if (t == DebugEvent.Type.BREAKPOINT) {
+        // Note: Visual C++ only notifies on breakpoints it doesn't
+        // know about
+
+        // FIXME: put back test
+        //        if (!prctl.isBreakpointSet(ev.getPC())) {
+          showMessageDialog("Breakpoint reached at PC " + ev.getPC(),
+                            "Breakpoint reached",
+                            JOptionPane.INFORMATION_MESSAGE);
+          //        }
+        agent.disableJavaInteraction();
+        suspend();
+        prctl.debugEventContinue();
+      } else if (t == DebugEvent.Type.SINGLE_STEP) {
+        agent.disableJavaInteraction();
+        suspend();
+        prctl.debugEventContinue();
+      } else if (t == DebugEvent.Type.ACCESS_VIOLATION) {
+        showMessageDialog("Access violation attempting to " +
+                          (ev.getWasWrite() ? "write" : "read") +
+                          " address " + ev.getAddress() +
+                          " at PC " + ev.getPC(),
+                          "Access Violation",
+                          JOptionPane.WARNING_MESSAGE);
+        agent.disableJavaInteraction();
+        suspend();
+        prctl.debugEventContinue();
+      } else {
+        String info = "Unknown debug event encountered";
+        if (ev.getUnknownEventDetail() != null) {
+          info = info + ": " + ev.getUnknownEventDetail();
+        }
+        showMessageDialog(info, "Unknown debug event", JOptionPane.INFORMATION_MESSAGE);
+        suspend();
+        prctl.debugEventContinue();
+      }
+      return;
+    }
+
+    // No C++ debug event; poll for Java debug event
+    if (getAgent().canInteractWithJava()) {
+      if (!javaEventPending) {
+        if (getAgent().javaEventPending()) {
+          suspend();
+          // This does a lot of work and we want to have the page
+          // cache available to us as it runs
+          sun.jvm.hotspot.livejvm.Event jev = getAgent().javaEventPoll();
+          if (jev != null) {
+            javaEventPending = true;
+            if (jev.getType() == sun.jvm.hotspot.livejvm.Event.Type.BREAKPOINT) {
+              BreakpointEvent bpev = (BreakpointEvent) jev;
+              showMessageDialog("Breakpoint reached in method\n" +
+                                bpev.methodID().method().externalNameAndSignature() +
+                                ",\nbci " + bpev.location(),
+                                "Breakpoint reached",
+                                JOptionPane.INFORMATION_MESSAGE);
+            } else if (jev.getType() == sun.jvm.hotspot.livejvm.Event.Type.EXCEPTION) {
+              ExceptionEvent exev = (ExceptionEvent) jev;
+              showMessageDialog(exev.exception().getKlass().getName().asString() +
+                                "\nthrown in method\n" +
+                                exev.methodID().method().externalNameAndSignature() +
+                                "\nat BCI " + exev.location(),
+                                "Exception thrown",
+                                JOptionPane.INFORMATION_MESSAGE);
+            } else {
+              Assert.that(false, "Should not reach here");
+            }
+          }
+        }
+      }
+    }
+  }
+}