blob: 66bb719e5cead3f77f109d7fdbddeb4c9c8bdec1 [file] [log] [blame]
/*
* 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");
}
}
}
}
}
}
}