Mercurial > hg > truffle
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"); + } + } + } + } + } + } +}