| /* |
| * 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"); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |