| /* | |
| * Copyright (c) 2000, 2024, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| package sun.jvm.hotspot; | |
| import java.io.*; | |
| import java.awt.*; | |
| import java.awt.event.*; | |
| import javax.swing.*; | |
| import java.util.*; | |
| import sun.jvm.hotspot.code.*; | |
| import sun.jvm.hotspot.compiler.*; | |
| import sun.jvm.hotspot.debugger.*; | |
| import sun.jvm.hotspot.gc.epsilon.*; | |
| import sun.jvm.hotspot.gc.parallel.*; | |
| import sun.jvm.hotspot.gc.serial.*; | |
| import sun.jvm.hotspot.gc.shared.*; | |
| import sun.jvm.hotspot.gc.shenandoah.*; | |
| import sun.jvm.hotspot.gc.g1.*; | |
| import sun.jvm.hotspot.gc.z.*; | |
| import sun.jvm.hotspot.interpreter.*; | |
| import sun.jvm.hotspot.oops.*; | |
| import sun.jvm.hotspot.runtime.*; | |
| import sun.jvm.hotspot.ui.*; | |
| import sun.jvm.hotspot.ui.tree.*; | |
| import sun.jvm.hotspot.ui.classbrowser.*; | |
| import sun.jvm.hotspot.utilities.*; | |
| import sun.jvm.hotspot.utilities.Observable; | |
| import sun.jvm.hotspot.utilities.Observer; | |
| /** The top-level HotSpot Debugger. FIXME: make this an embeddable | |
| component! (Among other things, figure out what to do with the | |
| menu bar...) */ | |
| public class HSDB implements ObjectHistogramPanel.Listener, SAListener { | |
| public static void main(String[] args) { | |
| new HSDB(args).run(); | |
| } | |
| //-------------------------------------------------------------------------------- | |
| // Internals only below this point | |
| // | |
| private HotSpotAgent agent; | |
| private JVMDebugger jvmDebugger; | |
| private JDesktopPane desktop; | |
| private boolean attached; | |
| private boolean argError; | |
| private JFrame frame; | |
| private java.util.List<JMenuItem> attachMenuItems; | |
| private java.util.List<JMenuItem> detachMenuItems; | |
| private JMenu toolsMenu; | |
| private JMenuItem showDbgConsoleMenuItem; | |
| private JMenuItem computeRevPtrsMenuItem; | |
| private JInternalFrame attachWaitDialog; | |
| private JInternalFrame threadsFrame; | |
| private JInternalFrame consoleFrame; | |
| private WorkerThread workerThread; | |
| // These had to be made data members because they are referenced in inner classes. | |
| private int pid; | |
| private String execPath; | |
| private String coreFilename; | |
| private String debugServerName; | |
| private void doUsage() { | |
| System.out.println("Usage: java HSDB [[pid] | [path-to-java-executable [path-to-corefile]] | help ]"); | |
| System.out.println(" pid: attach to the process whose id is 'pid'"); | |
| System.out.println(" path-to-java-executable: Debug a core file produced by this program"); | |
| System.out.println(" path-to-corefile: Debug this corefile. The default is 'core'"); | |
| System.out.println(" If no arguments are specified, you can select what to do from the GUI.\n"); | |
| HotSpotAgent.showUsage(); | |
| argError = true; | |
| } | |
| public HSDB(JVMDebugger d) { | |
| pid = -1; | |
| execPath = null; | |
| coreFilename = null; | |
| debugServerName = null; | |
| jvmDebugger = d; | |
| } | |
| private HSDB(String[] args) { | |
| pid = -1; | |
| execPath = null; | |
| coreFilename = null; | |
| debugServerName = null; | |
| switch (args.length) { | |
| case (0): | |
| break; | |
| case (1): | |
| if (args[0].equals("help") || args[0].equals("-help")) { | |
| doUsage(); | |
| } | |
| try { | |
| // Attempt to attach as a PID | |
| pid = Integer.parseInt(args[0]); | |
| } catch (NumberFormatException e) { | |
| // Attempt to connect to remote debug server | |
| debugServerName = args[0]; | |
| } | |
| break; | |
| case (2): | |
| execPath = args[0]; | |
| coreFilename = args[1]; | |
| break; | |
| default: | |
| System.out.println("HSDB Error: Too many options specified"); | |
| doUsage(); | |
| } | |
| } | |
| private class CloseUI extends WindowAdapter { | |
| @Override | |
| public void windowClosing(WindowEvent e) { | |
| workerThread.shutdown(); | |
| frame.dispose(); | |
| } | |
| } | |
| public void run() { | |
| // Don't start the UI if there were bad arguments. | |
| if (argError) { | |
| return; | |
| } | |
| // Create frame first, to catch any GUI creation issues | |
| // before we initialize agent | |
| frame = new JFrame("HSDB - HotSpot Debugger"); | |
| frame.setSize(800, 600); | |
| frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | |
| frame.addWindowListener(new CloseUI()); | |
| agent = new HotSpotAgent(); | |
| workerThread = new WorkerThread(); | |
| attachMenuItems = new java.util.ArrayList<>(); | |
| detachMenuItems = new java.util.ArrayList<>(); | |
| JMenuBar menuBar = new JMenuBar(); | |
| // | |
| // File menu | |
| // | |
| JMenu menu = new JMenu("File"); | |
| menu.setMnemonic(KeyEvent.VK_F); | |
| JMenuItem item; | |
| item = createMenuItem("Attach to HotSpot process...", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showAttachDialog(); | |
| } | |
| }); | |
| item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.ALT_MASK)); | |
| item.setMnemonic(KeyEvent.VK_A); | |
| menu.add(item); | |
| attachMenuItems.add(item); | |
| item = createMenuItem("Open HotSpot core file...", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showOpenCoreFileDialog(); | |
| } | |
| }); | |
| item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK)); | |
| item.setMnemonic(KeyEvent.VK_O); | |
| menu.add(item); | |
| attachMenuItems.add(item); | |
| item = createMenuItem("Connect to debug server...", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showConnectDialog(); | |
| } | |
| }); | |
| item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK)); | |
| item.setMnemonic(KeyEvent.VK_S); | |
| menu.add(item); | |
| attachMenuItems.add(item); | |
| item = createMenuItem("Detach", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| detach(); | |
| } | |
| }); | |
| item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, ActionEvent.ALT_MASK)); | |
| item.setMnemonic(KeyEvent.VK_S); | |
| menu.add(item); | |
| detachMenuItems.add(item); | |
| // Disable detach menu items at first | |
| setMenuItemsEnabled(detachMenuItems, false); | |
| menu.addSeparator(); | |
| item = createMenuItem("Exit", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| workerThread.shutdown(); | |
| frame.dispose(); | |
| } | |
| }); | |
| item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.ALT_MASK)); | |
| item.setMnemonic(KeyEvent.VK_X); | |
| menu.add(item); | |
| menuBar.add(menu); | |
| // | |
| // Tools menu | |
| // | |
| toolsMenu = new JMenu("Tools"); | |
| toolsMenu.setMnemonic(KeyEvent.VK_T); | |
| item = createMenuItem("Class Browser", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showClassBrowser(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_B); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Code Viewer", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showCodeViewer(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_C); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Compute Reverse Ptrs", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| fireComputeReversePtrs(); | |
| } | |
| }); | |
| computeRevPtrsMenuItem = item; | |
| item.setMnemonic(KeyEvent.VK_M); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Deadlock Detection", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showDeadlockDetectionPanel(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_D); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Find Pointer", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showFindPanel(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_P); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Find Value In Heap", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showFindInHeapPanel(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_V); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Find Value In Code Cache", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showFindInCodeCachePanel(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_A); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Heap Parameters", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showHeapParametersPanel(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_H); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Inspector", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showInspector(null); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_R); | |
| item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK)); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Memory Viewer", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showMemoryViewer(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_M); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Annotated Memory Viewer", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showAnnotatedMemoryViewer(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_W); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Monitor Cache Dump", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showMonitorCacheDumpPanel(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_D); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Object Histogram", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showObjectHistogram(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_O); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Show System Properties", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showSystemProperties(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_S); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Show VM Version", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showVMVersion(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_M); | |
| toolsMenu.add(item); | |
| item = createMenuItem("Show -XX flags", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showCommandLineFlags(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_X); | |
| toolsMenu.add(item); | |
| toolsMenu.setEnabled(false); | |
| menuBar.add(toolsMenu); | |
| // | |
| // Windows menu | |
| // | |
| JMenu windowsMenu = new JMenu("Windows"); | |
| windowsMenu.setMnemonic(KeyEvent.VK_W); | |
| item = createMenuItem("Console", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showConsole(); | |
| } | |
| }); | |
| item.setMnemonic(KeyEvent.VK_C); | |
| windowsMenu.add(item); | |
| showDbgConsoleMenuItem = createMenuItem("Debugger Console", | |
| new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| showDebuggerConsole(); | |
| } | |
| }); | |
| showDbgConsoleMenuItem.setMnemonic(KeyEvent.VK_D); | |
| windowsMenu.add(showDbgConsoleMenuItem); | |
| showDbgConsoleMenuItem.setEnabled(false); | |
| menuBar.add(windowsMenu); | |
| frame.setJMenuBar(menuBar); | |
| desktop = new JDesktopPane(); | |
| frame.getContentPane().add(desktop); | |
| GraphicsUtilities.reshapeToAspectRatio(frame, 4.0f/3.0f, 0.75f, Toolkit.getDefaultToolkit().getScreenSize()); | |
| GraphicsUtilities.centerInContainer(frame, Toolkit.getDefaultToolkit().getScreenSize()); | |
| frame.setVisible(true); | |
| Runtime.getRuntime().addShutdownHook(new java.lang.Thread() { | |
| public void run() { | |
| detachDebugger(); | |
| } | |
| }); | |
| // If jvmDebugger is already set, we have been given a JVMDebugger. | |
| // Otherwise, if pid != -1 we are supposed to attach to it. | |
| // If execPath != null, it is the path of a jdk/bin/java | |
| // and coreFilename is the pathname of a core file we are | |
| // supposed to attach to. | |
| // Finally, if debugServerName != null, we are supposed to | |
| // connect to remote debug server. | |
| if (jvmDebugger != null) { | |
| attach(jvmDebugger); | |
| } else if (pid != -1) { | |
| attach(pid); | |
| } else if (execPath != null) { | |
| attach(execPath, coreFilename); | |
| } else if (debugServerName != null) { | |
| connect(debugServerName); | |
| } | |
| } | |
| // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog | |
| private void showAttachDialog() { | |
| // FIXME: create filtered text field which only accepts numbers | |
| setMenuItemsEnabled(attachMenuItems, false); | |
| final JInternalFrame attachDialog = new JInternalFrame("Attach to HotSpot process"); | |
| attachDialog.getContentPane().setLayout(new BorderLayout()); | |
| JPanel panel = new JPanel(); | |
| panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); | |
| panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
| attachDialog.setBackground(panel.getBackground()); | |
| panel.add(new JLabel("Enter process ID:")); | |
| final JTextField pidTextField = new JTextField(10); | |
| ActionListener attacher = new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| attachDialog.setVisible(false); | |
| desktop.remove(attachDialog); | |
| workerThread.invokeLater(new Runnable() { | |
| public void run() { | |
| attach(Integer.parseInt(pidTextField.getText())); | |
| } | |
| }); | |
| } | |
| }; | |
| pidTextField.addActionListener(attacher); | |
| panel.add(pidTextField); | |
| attachDialog.getContentPane().add(panel, BorderLayout.NORTH); | |
| Box vbox = Box.createVerticalBox(); | |
| panel = new JPanel(); | |
| panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); | |
| panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); | |
| JTextArea ta = new JTextArea( | |
| "Enter the process ID of a currently-running HotSpot process. On " + | |
| "most Unix operating systems, this can be determined by " + | |
| "typing \"ps -u <your username> | grep java\"; the process ID is the " + | |
| "first number which appears on the resulting line. On Windows, the " + | |
| "process ID is present in the Task Manager, which can be brought up " + | |
| "while logged on to the desktop by pressing Ctrl-Alt-Delete."); | |
| ta.setLineWrap(true); | |
| ta.setWrapStyleWord(true); | |
| ta.setEditable(false); | |
| ta.setBackground(panel.getBackground()); | |
| panel.add(ta); | |
| vbox.add(panel); | |
| 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) { | |
| attachDialog.setVisible(false); | |
| desktop.remove(attachDialog); | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| } | |
| }); | |
| hbox.add(button); | |
| hbox.add(Box.createGlue()); | |
| panel = new JPanel(); | |
| panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
| panel.add(hbox); | |
| vbox.add(panel); | |
| attachDialog.getContentPane().add(vbox, BorderLayout.SOUTH); | |
| desktop.add(attachDialog); | |
| attachDialog.setSize(400, 300); | |
| GraphicsUtilities.centerInContainer(attachDialog); | |
| attachDialog.show(); | |
| pidTextField.requestFocus(); | |
| } | |
| // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog | |
| private void showOpenCoreFileDialog() { | |
| setMenuItemsEnabled(attachMenuItems, false); | |
| final JInternalFrame dialog = new JInternalFrame("Open Core File"); | |
| dialog.getContentPane().setLayout(new BorderLayout()); | |
| JPanel panel = new JPanel(); | |
| panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); | |
| panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
| dialog.setBackground(panel.getBackground()); | |
| Box hbox = Box.createHorizontalBox(); | |
| Box vbox = Box.createVerticalBox(); | |
| vbox.add(new JLabel("Path to core file:")); | |
| vbox.add(new JLabel("Path to Java executable:")); | |
| hbox.add(vbox); | |
| vbox = Box.createVerticalBox(); | |
| final JTextField corePathField = new JTextField(40); | |
| final JTextField execPathField = new JTextField(40); | |
| vbox.add(corePathField); | |
| vbox.add(execPathField); | |
| hbox.add(vbox); | |
| final JButton browseCorePath = new JButton("Browse .."); | |
| final JButton browseExecPath = new JButton("Browse .."); | |
| browseCorePath.addActionListener(new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| JFileChooser fileChooser = new JFileChooser(new File(".")); | |
| int retVal = fileChooser.showOpenDialog(dialog); | |
| if (retVal == JFileChooser.APPROVE_OPTION) { | |
| corePathField.setText(fileChooser.getSelectedFile().getPath()); | |
| } | |
| } | |
| }); | |
| browseExecPath.addActionListener(new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| JFileChooser fileChooser = new JFileChooser(new File(".")); | |
| int retVal = fileChooser.showOpenDialog(dialog); | |
| if (retVal == JFileChooser.APPROVE_OPTION) { | |
| execPathField.setText(fileChooser.getSelectedFile().getPath()); | |
| } | |
| } | |
| }); | |
| vbox = Box.createVerticalBox(); | |
| vbox.add(browseCorePath); | |
| vbox.add(browseExecPath); | |
| hbox.add(vbox); | |
| panel.add(hbox); | |
| dialog.getContentPane().add(panel, BorderLayout.NORTH); | |
| ActionListener attacher = new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| dialog.setVisible(false); | |
| desktop.remove(dialog); | |
| workerThread.invokeLater(new Runnable() { | |
| public void run() { | |
| attach(execPathField.getText(), corePathField.getText()); | |
| } | |
| }); | |
| } | |
| }; | |
| corePathField.addActionListener(attacher); | |
| execPathField.addActionListener(attacher); | |
| vbox = Box.createVerticalBox(); | |
| panel = new JPanel(); | |
| panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); | |
| panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); | |
| JTextArea ta = new JTextArea( | |
| "Enter the full path names to the core file from a HotSpot process " + | |
| "and the Java executable from which it came. The latter is typically " + | |
| "located in the JDK/JRE directory under the directory " + | |
| "jre/bin/<arch>/native_threads."); | |
| ta.setLineWrap(true); | |
| ta.setWrapStyleWord(true); | |
| ta.setEditable(false); | |
| ta.setBackground(panel.getBackground()); | |
| panel.add(ta); | |
| vbox.add(panel); | |
| 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) { | |
| dialog.setVisible(false); | |
| desktop.remove(dialog); | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| } | |
| }); | |
| hbox.add(button); | |
| hbox.add(Box.createGlue()); | |
| panel = new JPanel(); | |
| panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
| panel.add(hbox); | |
| vbox.add(panel); | |
| dialog.getContentPane().add(vbox, BorderLayout.SOUTH); | |
| desktop.add(dialog); | |
| dialog.setSize(500, 300); | |
| GraphicsUtilities.centerInContainer(dialog); | |
| dialog.show(); | |
| corePathField.requestFocus(); | |
| } | |
| // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog | |
| private void showConnectDialog() { | |
| // FIXME: create filtered text field which only accepts numbers | |
| setMenuItemsEnabled(attachMenuItems, false); | |
| final JInternalFrame dialog = new JInternalFrame("Connect to HotSpot Debug Server"); | |
| dialog.getContentPane().setLayout(new BorderLayout()); | |
| JPanel panel = new JPanel(); | |
| panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); | |
| panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
| dialog.setBackground(panel.getBackground()); | |
| panel.add(new JLabel("Enter machine name:")); | |
| final JTextField pidTextField = new JTextField(40); | |
| ActionListener attacher = new ActionListener() { | |
| public void actionPerformed(ActionEvent e) { | |
| dialog.setVisible(false); | |
| desktop.remove(dialog); | |
| workerThread.invokeLater(new Runnable() { | |
| public void run() { | |
| connect(pidTextField.getText()); | |
| } | |
| }); | |
| } | |
| }; | |
| pidTextField.addActionListener(attacher); | |
| panel.add(pidTextField); | |
| dialog.getContentPane().add(panel, BorderLayout.NORTH); | |
| Box vbox = Box.createVerticalBox(); | |
| panel = new JPanel(); | |
| panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); | |
| panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); | |
| JTextArea ta = new JTextArea( | |
| "Enter the name of a machine on which the HotSpot \"Debug Server\" is " + | |
| "running and is attached to a process or core file."); | |
| ta.setLineWrap(true); | |
| ta.setWrapStyleWord(true); | |
| ta.setEditable(false); | |
| ta.setBackground(panel.getBackground()); | |
| panel.add(ta); | |
| vbox.add(panel); | |
| 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) { | |
| dialog.setVisible(false); | |
| desktop.remove(dialog); | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| } | |
| }); | |
| hbox.add(button); | |
| hbox.add(Box.createGlue()); | |
| panel = new JPanel(); | |
| panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
| panel.add(hbox); | |
| vbox.add(panel); | |
| dialog.getContentPane().add(vbox, BorderLayout.SOUTH); | |
| desktop.add(dialog); | |
| dialog.setSize(400, 300); | |
| GraphicsUtilities.centerInContainer(dialog); | |
| dialog.show(); | |
| pidTextField.requestFocus(); | |
| } | |
| public void showThreadOopInspector(JavaThread thread) { | |
| showInspector(new OopTreeNodeAdapter(thread.getThreadObj(), null)); | |
| } | |
| public void showInspector(SimpleTreeNode adapter) { | |
| showPanel("Inspector", new Inspector(adapter), 1.0f, 0.65f); | |
| } | |
| public void showLiveness(Oop oop, LivenessPathList liveness) { | |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| PrintStream tty = new PrintStream(bos); | |
| int numPaths = liveness.size(); | |
| for (int i = 0; i < numPaths; i++) { | |
| tty.println("Path " + (i + 1) + " of " + numPaths + ":"); | |
| liveness.get(i).printOn(tty); | |
| } | |
| JTextArea ta = new JTextArea(bos.toString()); | |
| ta.setLineWrap(true); | |
| ta.setWrapStyleWord(true); | |
| ta.setEditable(false); | |
| JPanel panel = new JPanel(); | |
| panel.setLayout(new BorderLayout()); | |
| JScrollPane scroller = new JScrollPane(); | |
| scroller.getViewport().add(ta); | |
| panel.add(scroller, BorderLayout.CENTER); | |
| bos = new ByteArrayOutputStream(); | |
| tty = new PrintStream(bos); | |
| tty.print("Liveness result for "); | |
| Oop.printOopValueOn(oop, tty); | |
| JInternalFrame frame = new JInternalFrame(bos.toString()); | |
| frame.setResizable(true); | |
| frame.setClosable(true); | |
| frame.setIconifiable(true); | |
| frame.getContentPane().setLayout(new BorderLayout()); | |
| frame.getContentPane().add(panel, BorderLayout.CENTER); | |
| frame.pack(); | |
| desktop.add(frame); | |
| GraphicsUtilities.reshapeToAspectRatio(frame, 0.5f / 0.2f, 0.5f, frame.getParent().getSize()); | |
| frame.show(); | |
| } | |
| private void fireComputeReversePtrs() { | |
| // Possible this might have been computed elsewhere | |
| if (VM.getVM().getRevPtrs() != null) { | |
| computeRevPtrsMenuItem.setEnabled(false); | |
| return; | |
| } | |
| workerThread.invokeLater(new Runnable() { | |
| public void run() { | |
| HeapProgress progress = new HeapProgress("Reverse Pointers Analysis"); | |
| try { | |
| ReversePtrsAnalysis analysis = new ReversePtrsAnalysis(); | |
| analysis.setHeapProgressThunk(progress); | |
| analysis.run(); | |
| computeRevPtrsMenuItem.setEnabled(false); | |
| } catch (OutOfMemoryError e) { | |
| final String errMsg = formatMessage(e.toString(), 80); | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JOptionPane.showInternalMessageDialog(desktop, | |
| "Error computing reverse pointers:" + errMsg, | |
| "Error", | |
| JOptionPane.WARNING_MESSAGE); | |
| } | |
| }); | |
| } finally { | |
| // make sure the progress bar goes away | |
| progress.heapIterationComplete(); | |
| } | |
| } | |
| }); | |
| } | |
| // Simple struct containing signal information | |
| class SignalInfo { | |
| public int sigNum; | |
| public String sigName; | |
| } | |
| // Need to have mutable vframe as well as visible memory panel | |
| abstract class StackWalker implements Runnable { | |
| protected JavaVFrame vf; | |
| protected AnnotatedMemoryPanel annoPanel; | |
| StackWalker(JavaVFrame vf, AnnotatedMemoryPanel annoPanel) { | |
| this.vf = vf; | |
| this.annoPanel = annoPanel; | |
| } | |
| } | |
| public void showThreadStackMemory(final JavaThread thread) { | |
| // dumpStack(thread); | |
| JavaVFrame vframe = getLastJavaVFrame(thread); | |
| if (vframe == null) { | |
| JOptionPane.showInternalMessageDialog(desktop, | |
| "Thread \"" + thread.getThreadName() + | |
| "\" has no Java frames on its stack", | |
| "Show Stack Memory", | |
| JOptionPane.INFORMATION_MESSAGE); | |
| return; | |
| } | |
| JInternalFrame stackFrame = new JInternalFrame("Stack Memory for " + thread.getThreadName()); | |
| stackFrame.getContentPane().setLayout(new BorderLayout()); | |
| stackFrame.setResizable(true); | |
| stackFrame.setClosable(true); | |
| stackFrame.setIconifiable(true); | |
| final long addressSize = agent.getTypeDataBase().getAddressSize(); | |
| boolean is64Bit = (addressSize == 8); | |
| // This is somewhat of a hack to guess a thread's stack limits since the | |
| // JavaThread doesn't support this functionality. However it is nice in that | |
| // it locks us into the active region of the thread's stack and not its | |
| // theoretical limits. | |
| // | |
| sun.jvm.hotspot.runtime.Frame tmpFrame = thread.getCurrentFrameGuess(); | |
| Address sp = tmpFrame.getSP(); | |
| Address starting = sp; | |
| Address maxSP = starting; | |
| Address minSP = starting; | |
| RegisterMap tmpMap = thread.newRegisterMap(false); | |
| while ((tmpFrame != null) && (!tmpFrame.isFirstFrame())) { | |
| tmpFrame = tmpFrame.sender(tmpMap); | |
| if (tmpFrame != null) { | |
| sp = tmpFrame.getSP(); | |
| if (sp != null) { | |
| maxSP = AddressOps.max(maxSP, sp); | |
| minSP = AddressOps.min(minSP, sp); | |
| } | |
| } | |
| } | |
| // It is useful to be able to see say +/- 8K on the current stack range | |
| AnnotatedMemoryPanel annoMemPanel = new AnnotatedMemoryPanel(agent.getDebugger(), is64Bit, starting, | |
| minSP.addOffsetTo(-8192), | |
| maxSP.addOffsetTo( 8192)); | |
| stackFrame.getContentPane().add(annoMemPanel, BorderLayout.CENTER); | |
| desktop.add(stackFrame); | |
| GraphicsUtilities.reshapeToAspectRatio(stackFrame, 4.0f / 3.0f, 0.85f, stackFrame.getParent().getSize()); | |
| stackFrame.show(); | |
| // Stackmap computation for interpreted frames is expensive; do | |
| // all stackwalking work in another thread for better GUI | |
| // responsiveness | |
| workerThread.invokeLater(new StackWalker(vframe, annoMemPanel) { | |
| public void run() { | |
| Address startAddr = null; | |
| // As this is a debugger, we want to provide potential crash | |
| // information to the user, i.e., by marking signal handler frames | |
| // on the stack. Since this system is currently targeted at | |
| // annotating the Java frames (interpreted or compiled) on the | |
| // stack and not, for example, "external" frames (note the current | |
| // absence of a PC-to-symbol lookup mechanism at the Debugger | |
| // level), we want to mark any Java frames which were interrupted | |
| // by a signal. We do this by making two passes over the stack, | |
| // one which finds signal handler frames and puts the parent | |
| // frames in a table and one which finds Java frames and if they | |
| // are in the table indicates that they were interrupted by a signal. | |
| Map<sun.jvm.hotspot.runtime.Frame, SignalInfo> interruptedFrameMap = new HashMap<>(); | |
| { | |
| sun.jvm.hotspot.runtime.Frame tmpFrame = thread.getCurrentFrameGuess(); | |
| RegisterMap tmpMap = thread.newRegisterMap(false); | |
| while ((tmpFrame != null) && (!tmpFrame.isFirstFrame())) { | |
| if (tmpFrame.isSignalHandlerFrameDbg()) { | |
| // Add some information to the map that we can extract later | |
| sun.jvm.hotspot.runtime.Frame interruptedFrame = tmpFrame.sender(tmpMap); | |
| SignalInfo info = new SignalInfo(); | |
| info.sigNum = tmpFrame.getSignalNumberDbg(); | |
| info.sigName = tmpFrame.getSignalNameDbg(); | |
| interruptedFrameMap.put(interruptedFrame, info); | |
| } | |
| tmpFrame = tmpFrame.sender(tmpMap); | |
| } | |
| } | |
| while (vf != null) { | |
| String anno = null; | |
| JavaVFrame curVFrame = vf; | |
| sun.jvm.hotspot.runtime.Frame curFrame = curVFrame.getFrame(); | |
| Method interpreterFrameMethod = null; | |
| if (curVFrame.isInterpretedFrame()) { | |
| anno = "Interpreted frame"; | |
| } else { | |
| anno = "Compiled frame"; | |
| if (curVFrame.isDeoptimized()) { | |
| anno += " (deoptimized)"; | |
| } | |
| } | |
| if (curVFrame.mayBeImpreciseDbg()) { | |
| anno += "; information may be imprecise"; | |
| } | |
| if (curVFrame.isInterpretedFrame()) { | |
| // Find the codelet | |
| InterpreterCodelet codelet = VM.getVM().getInterpreter().getCodeletContaining(curFrame.getPC()); | |
| String description = null; | |
| if (codelet != null) { | |
| description = codelet.getDescription(); | |
| } | |
| if (description == null) { | |
| anno += "\n(Unknown interpreter codelet)"; | |
| } else { | |
| anno += "\nExecuting in codelet \"" + description + "\" at PC = " + curFrame.getPC(); | |
| } | |
| } else if (curVFrame.isCompiledFrame()) { | |
| anno += "\nExecuting at PC = " + curFrame.getPC(); | |
| } | |
| if (startAddr == null) { | |
| startAddr = curFrame.getSP(); | |
| } | |
| // FIXME: some compiled frames with empty oop map sets have been | |
| // found (for example, Vector's inner Enumeration class, method | |
| // "hasMoreElements"). Not sure yet why these cases are showing | |
| // up -- should be possible (though unlikely) for safepoint code | |
| // to patch the return instruction of these methods and then | |
| // later attempt to get an oop map for that instruction. For | |
| // now, we warn if we find such a method. | |
| boolean shouldSkipOopMaps = false; | |
| if (curVFrame.isCompiledFrame()) { | |
| CodeBlob cb = VM.getVM().getCodeCache().findBlob(curFrame.getPC()); | |
| ImmutableOopMapSet maps = cb.getOopMaps(); | |
| if ((maps == null) || (maps.getCount() == 0)) { | |
| shouldSkipOopMaps = true; | |
| } | |
| } | |
| // Add signal information to annotation if necessary | |
| SignalInfo sigInfo = interruptedFrameMap.get(curFrame); | |
| if (sigInfo != null) { | |
| // This frame took a signal and we need to report it. | |
| anno = anno + "\n*** INTERRUPTED BY SIGNAL " + sigInfo.sigNum + | |
| " (" + sigInfo.sigName + ")"; | |
| } | |
| JavaVFrame nextVFrame = curVFrame; | |
| sun.jvm.hotspot.runtime.Frame nextFrame = curFrame; | |
| do { | |
| curVFrame = nextVFrame; | |
| curFrame = nextFrame; | |
| try { | |
| Method method = curVFrame.getMethod(); | |
| if (interpreterFrameMethod == null && curVFrame.isInterpretedFrame()) { | |
| interpreterFrameMethod = method; | |
| } | |
| int bci = curVFrame.getBCI(); | |
| String lineNumberAnno = ""; | |
| if (method.hasLineNumberTable()) { | |
| if ((bci == DebugInformationRecorder.SYNCHRONIZATION_ENTRY_BCI) || | |
| (bci >= 0 && bci < method.getCodeSize())) { | |
| lineNumberAnno = ", line " + method.getLineNumberFromBCI(bci); | |
| } else { | |
| lineNumberAnno = " (INVALID BCI)"; | |
| } | |
| } | |
| anno += "\n" + method.getMethodHolder().getName().asString() + "." + | |
| method.getName().asString() + method.getSignature().asString() + | |
| "\n@bci " + bci + lineNumberAnno; | |
| } catch (Exception e) { | |
| anno += "\n(ERROR while iterating vframes for frame " + curFrame + ")"; | |
| } | |
| nextVFrame = curVFrame.javaSender(); | |
| if (nextVFrame != null) { | |
| nextFrame = nextVFrame.getFrame(); | |
| } | |
| } while (nextVFrame != null && nextFrame.equals(curFrame)); | |
| if (shouldSkipOopMaps) { | |
| anno = anno + "\nNOTE: null or empty ImmutableOopMapSet found for this CodeBlob"; | |
| } | |
| if (curFrame.getFP() != null) { | |
| annoPanel.addAnnotation(new Annotation(curFrame.getSP(), | |
| curFrame.getFP(), | |
| anno)); | |
| } else { | |
| // For C2, which has null frame pointers on x86/amd64/aarch64 | |
| CodeBlob cb = VM.getVM().getCodeCache().findBlob(curFrame.getPC()); | |
| Address sp = curFrame.getSP(); | |
| if (Assert.ASSERTS_ENABLED) { | |
| Assert.that(cb.getFrameSize() > 0, "CodeBlob must have non-zero frame size"); | |
| } | |
| annoPanel.addAnnotation(new Annotation(sp, | |
| sp.addOffsetTo(cb.getFrameSize()), | |
| anno)); | |
| } | |
| // Add interpreter frame annotations | |
| if (curFrame.isInterpretedFrame()) { | |
| annoPanel.addAnnotation(new Annotation(curFrame.addressOfInterpreterFrameExpressionStack(), | |
| curFrame.addressOfInterpreterFrameTOS(), | |
| "Interpreter expression stack")); | |
| Address monBegin = curFrame.interpreterFrameMonitorBegin().address(); | |
| Address monEnd = curFrame.interpreterFrameMonitorEnd().address(); | |
| if (!monBegin.equals(monEnd)) { | |
| annoPanel.addAnnotation(new Annotation(monBegin, monEnd, | |
| "BasicObjectLocks")); | |
| } | |
| if (interpreterFrameMethod != null) { | |
| // The offset is just to get the right stack slots highlighted in the output | |
| int offset = 1; | |
| annoPanel.addAnnotation(new Annotation(curFrame.addressOfInterpreterFrameLocal(offset), | |
| curFrame.addressOfInterpreterFrameLocal((int) interpreterFrameMethod.getMaxLocals() + offset), | |
| "Interpreter locals area for frame with SP = " + curFrame.getSP())); | |
| } | |
| String methodAnno = "Interpreter frame Method*"; | |
| if (interpreterFrameMethod == null) { | |
| methodAnno += " (BAD OOP)"; | |
| } | |
| Address a = curFrame.addressOfInterpreterFrameMethod(); | |
| annoPanel.addAnnotation(new Annotation(a, a.addOffsetTo(addressSize), methodAnno)); | |
| a = curFrame.addressOfInterpreterFrameCPCache(); | |
| annoPanel.addAnnotation(new Annotation(a, a.addOffsetTo(addressSize), "Interpreter constant pool cache")); | |
| } | |
| RegisterMap rm = (RegisterMap) vf.getRegisterMap().clone(); | |
| if (!shouldSkipOopMaps) { | |
| try { | |
| curFrame.oopsDo(new AddressVisitor() { | |
| public void visitAddress(Address addr) { | |
| if (Assert.ASSERTS_ENABLED) { | |
| Assert.that(addr.andWithMask(VM.getVM().getAddressSize() - 1) == null, | |
| "Address " + addr + "should have been aligned"); | |
| } | |
| OopHandle handle = addr.getOopHandleAt(0); | |
| addAnnotation(addr, handle); | |
| } | |
| public void visitCompOopAddress(Address addr) { | |
| if (Assert.ASSERTS_ENABLED) { | |
| Assert.that(addr.andWithMask(VM.getVM().getAddressSize() - 1) == null, | |
| "Address " + addr + "should have been aligned"); | |
| } | |
| OopHandle handle = addr.getCompOopHandleAt(0); | |
| addAnnotation(addr, handle); | |
| } | |
| public void addAnnotation(Address addr, OopHandle handle) { | |
| // Check contents | |
| String anno = "null oop"; | |
| if (handle != null) { | |
| // Find location | |
| CollectedHeap collHeap = VM.getVM().getUniverse().heap(); | |
| boolean bad = true; | |
| anno = "BAD OOP"; | |
| if (collHeap instanceof SerialHeap) { | |
| SerialHeap heap = (SerialHeap) collHeap; | |
| if (heap.youngGen().isIn(handle)) { | |
| anno = "NewGen "; | |
| bad = false; | |
| } else if (heap.oldGen().isIn(handle)) { | |
| anno = "OldGen "; | |
| bad = false; | |
| } | |
| } else if (collHeap instanceof G1CollectedHeap) { | |
| G1CollectedHeap heap = (G1CollectedHeap)collHeap; | |
| G1HeapRegion region = heap.hrm().getByAddress(handle); | |
| if (region == null) { | |
| // intentionally skip | |
| } else if (region.isFree()) { | |
| anno = "Free "; | |
| bad = false; | |
| } else if (region.isYoung()) { | |
| anno = "Young "; | |
| bad = false; | |
| } else if (region.isHumongous()) { | |
| anno = "Humongous "; | |
| bad = false; | |
| } else if (region.isOld()) { | |
| anno = "Old "; | |
| bad = false; | |
| } | |
| if (!bad && region.isPinned()) { | |
| anno += "Pinned "; | |
| } | |
| } else if (collHeap instanceof ParallelScavengeHeap) { | |
| ParallelScavengeHeap heap = (ParallelScavengeHeap) collHeap; | |
| if (heap.youngGen().isIn(handle)) { | |
| anno = "PSYoungGen "; | |
| bad = false; | |
| } else if (heap.oldGen().isIn(handle)) { | |
| anno = "PSOldGen "; | |
| bad = false; | |
| } | |
| } else if (collHeap instanceof EpsilonHeap) { | |
| anno = "Epsilon "; | |
| bad = false; | |
| } else if (collHeap instanceof ShenandoahHeap) { | |
| ShenandoahHeap heap = (ShenandoahHeap) collHeap; | |
| anno = "ShenandoahHeap "; | |
| bad = false; | |
| } else if (collHeap instanceof ZCollectedHeap) { | |
| ZCollectedHeap heap = (ZCollectedHeap) collHeap; | |
| anno = "ZHeap "; | |
| bad = false; | |
| } else { | |
| // Optimistically assume the oop isn't bad | |
| anno = "[Unknown generation] "; | |
| bad = false; | |
| } | |
| if (!bad) { | |
| try { | |
| Oop oop = VM.getVM().getObjectHeap().newOop(handle); | |
| if (oop instanceof Instance) { | |
| // Java-level objects always have workable names | |
| anno = anno + oop.getKlass().getName().asString(); | |
| } else { | |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| Oop.printOopValueOn(oop, new PrintStream(bos)); | |
| anno = anno + bos.toString(); | |
| } | |
| } | |
| catch (AddressException e) { | |
| anno += "CORRUPT OOP"; | |
| } | |
| catch (NullPointerException e) { | |
| anno += "CORRUPT OOP (null pointer)"; | |
| } | |
| } | |
| } | |
| annoPanel.addAnnotation(new Annotation(addr, addr.addOffsetTo(addressSize), anno)); | |
| } | |
| }, rm); | |
| } catch (Exception e) { | |
| System.err.println("Error while performing oopsDo for frame " + curFrame); | |
| e.printStackTrace(); | |
| } | |
| } | |
| vf = nextVFrame; | |
| } | |
| // This used to paint as we walked the frames. This caused the display to be refreshed | |
| // enough to be annoying on remote displays. It also would cause the annotations to | |
| // be displayed in varying order which caused some annotations to overwrite others | |
| // depending on the races between painting and adding annotations. This latter problem | |
| // still exists to some degree but moving this code here definitely seems to reduce it | |
| annoPanel.makeVisible(startAddr); | |
| annoPanel.repaint(); | |
| } | |
| }); | |
| } | |
| // Attach to existing JVMDebugger, which should be already attached to a core/process. | |
| private void attach(JVMDebugger d) { | |
| attached = true; | |
| showThreadsDialog(); | |
| } | |
| /** NOTE we are in a different thread here than either the main | |
| thread or the Swing/AWT event handler thread, so we must be very | |
| careful when creating or removing widgets */ | |
| private void attach(int pid) { | |
| this.pid = pid; | |
| // Try to attach to this process | |
| Runnable remover = new Runnable() { | |
| public void run() { | |
| attachWaitDialog.setVisible(false); | |
| desktop.remove(attachWaitDialog); | |
| attachWaitDialog = null; | |
| } | |
| }; | |
| try { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JOptionPane pane = new JOptionPane("Attaching to process " + pid + ", please wait...", JOptionPane.INFORMATION_MESSAGE); | |
| pane.setOptions(new Object[] {}); | |
| attachWaitDialog = pane.createInternalFrame(desktop, "Attaching to Process"); | |
| attachWaitDialog.show(); | |
| } | |
| }); | |
| // FIXME: display exec'd debugger's output messages during this | |
| // lengthy call | |
| agent.attach(pid); | |
| if (agent.getDebugger().hasConsole()) { | |
| showDbgConsoleMenuItem.setEnabled(true); | |
| } | |
| attached = true; | |
| SwingUtilities.invokeLater(remover); | |
| } | |
| catch (DebuggerException e) { | |
| SwingUtilities.invokeLater(remover); | |
| final String errMsg = formatMessage(e.getMessage(), 80); | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| JOptionPane.showInternalMessageDialog(desktop, | |
| "Unable to connect to process ID " + pid + ":\n\n" + errMsg, | |
| "Unable to Connect", | |
| JOptionPane.WARNING_MESSAGE); | |
| } | |
| }); | |
| agent.detach(); | |
| return; | |
| } | |
| // OK, the VM should be available. Create the Threads dialog. | |
| showThreadsDialog(); | |
| } | |
| /** NOTE we are in a different thread here than either the main | |
| thread or the Swing/AWT event handler thread, so we must be very | |
| careful when creating or removing widgets */ | |
| private void attach(final String executablePath, final String corePath) { | |
| // Try to open this core file | |
| Runnable remover = new Runnable() { | |
| public void run() { | |
| attachWaitDialog.setVisible(false); | |
| desktop.remove(attachWaitDialog); | |
| attachWaitDialog = null; | |
| } | |
| }; | |
| try { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JOptionPane pane = new JOptionPane("Opening core file, please wait...", JOptionPane.INFORMATION_MESSAGE); | |
| pane.setOptions(new Object[] {}); | |
| attachWaitDialog = pane.createInternalFrame(desktop, "Opening Core File"); | |
| attachWaitDialog.show(); | |
| } | |
| }); | |
| // FIXME: display exec'd debugger's output messages during this | |
| // lengthy call | |
| agent.attach(executablePath, corePath); | |
| if (agent.getDebugger().hasConsole()) { | |
| showDbgConsoleMenuItem.setEnabled(true); | |
| } | |
| attached = true; | |
| SwingUtilities.invokeLater(remover); | |
| } | |
| catch (DebuggerException e) { | |
| SwingUtilities.invokeLater(remover); | |
| final String errMsg = formatMessage(e.getMessage(), 80); | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| JOptionPane.showInternalMessageDialog(desktop, | |
| "Unable to open core file\n" + corePath + ":\n\n" + errMsg, | |
| "Unable to Open Core File", | |
| JOptionPane.WARNING_MESSAGE); | |
| } | |
| }); | |
| agent.detach(); | |
| return; | |
| } | |
| // OK, the VM should be available. Create the Threads dialog. | |
| showThreadsDialog(); | |
| } | |
| /** NOTE we are in a different thread here than either the main | |
| thread or the Swing/AWT event handler thread, so we must be very | |
| careful when creating or removing widgets */ | |
| private void connect(final String debugServerName) { | |
| // Try to open this core file | |
| Runnable remover = new Runnable() { | |
| public void run() { | |
| attachWaitDialog.setVisible(false); | |
| desktop.remove(attachWaitDialog); | |
| attachWaitDialog = null; | |
| } | |
| }; | |
| try { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JOptionPane pane = new JOptionPane("Connecting to debug server, please wait...", JOptionPane.INFORMATION_MESSAGE); | |
| pane.setOptions(new Object[] {}); | |
| attachWaitDialog = pane.createInternalFrame(desktop, "Connecting to Debug Server"); | |
| attachWaitDialog.show(); | |
| } | |
| }); | |
| agent.attach(debugServerName); | |
| if (agent.getDebugger().hasConsole()) { | |
| showDbgConsoleMenuItem.setEnabled(true); | |
| } | |
| attached = true; | |
| SwingUtilities.invokeLater(remover); | |
| } | |
| catch (DebuggerException e) { | |
| SwingUtilities.invokeLater(remover); | |
| final String errMsg = formatMessage(e.getMessage(), 80); | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| JOptionPane.showInternalMessageDialog(desktop, | |
| "Unable to connect to machine \"" + debugServerName + "\":\n\n" + errMsg, | |
| "Unable to Connect", | |
| JOptionPane.WARNING_MESSAGE); | |
| } | |
| }); | |
| agent.detach(); | |
| return; | |
| } | |
| // OK, the VM should be available. Create the Threads dialog. | |
| showThreadsDialog(); | |
| } | |
| private void detachDebugger() { | |
| if (!attached) { | |
| return; | |
| } | |
| agent.detach(); | |
| attached = false; | |
| } | |
| private void detach() { | |
| detachDebugger(); | |
| attachWaitDialog = null; | |
| threadsFrame = null; | |
| consoleFrame = null; | |
| setMenuItemsEnabled(attachMenuItems, true); | |
| setMenuItemsEnabled(detachMenuItems, false); | |
| toolsMenu.setEnabled(false); | |
| showDbgConsoleMenuItem.setEnabled(false); | |
| // 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(); | |
| } | |
| /** NOTE that this is called from another thread than the main or | |
| Swing thread and we have to be careful about synchronization */ | |
| private void showThreadsDialog() { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| threadsFrame = new JInternalFrame("Java Threads"); | |
| threadsFrame.setResizable(true); | |
| threadsFrame.setIconifiable(true); | |
| JavaThreadsPanel threadsPanel = new JavaThreadsPanel(); | |
| threadsPanel.addPanelListener(HSDB.this); | |
| threadsFrame.getContentPane().add(threadsPanel); | |
| threadsFrame.setSize(500, 300); | |
| threadsFrame.pack(); | |
| desktop.add(threadsFrame); | |
| GraphicsUtilities.moveToInContainer(threadsFrame, 0.75f, 0.25f, 0, 20); | |
| threadsFrame.show(); | |
| setMenuItemsEnabled(attachMenuItems, false); | |
| setMenuItemsEnabled(detachMenuItems, true); | |
| toolsMenu.setEnabled(true); | |
| VM.registerVMInitializedObserver(new Observer() { | |
| public void update(Observable o, Object data) { | |
| computeRevPtrsMenuItem.setEnabled(true); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| private void showObjectHistogram() { | |
| sun.jvm.hotspot.oops.ObjectHistogram histo = new sun.jvm.hotspot.oops.ObjectHistogram(); | |
| ObjectHistogramCleanupThunk cleanup = | |
| new ObjectHistogramCleanupThunk(histo); | |
| doHeapIteration("Object Histogram", | |
| "Generating histogram...", | |
| histo, | |
| cleanup); | |
| } | |
| class ObjectHistogramCleanupThunk implements CleanupThunk { | |
| sun.jvm.hotspot.oops.ObjectHistogram histo; | |
| ObjectHistogramCleanupThunk(sun.jvm.hotspot.oops.ObjectHistogram histo) { | |
| this.histo = histo; | |
| } | |
| public void heapIterationComplete() { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JInternalFrame histoFrame = new JInternalFrame("Object Histogram"); | |
| histoFrame.setResizable(true); | |
| histoFrame.setClosable(true); | |
| histoFrame.setIconifiable(true); | |
| histoFrame.getContentPane().setLayout(new BorderLayout()); | |
| ObjectHistogramPanel panel = new ObjectHistogramPanel(histo); | |
| panel.addPanelListener(HSDB.this); | |
| histoFrame.getContentPane().add(panel); | |
| desktop.add(histoFrame); | |
| GraphicsUtilities.reshapeToAspectRatio(histoFrame, 4.0f / 3.0f, 0.6f, | |
| histoFrame.getParent().getSize()); | |
| GraphicsUtilities.centerInContainer(histoFrame); | |
| histoFrame.show(); | |
| } | |
| }); | |
| } | |
| } | |
| public void showObjectsOfType(Klass type) { | |
| FindObjectByType finder = new FindObjectByType(type); | |
| FindObjectByTypeCleanupThunk cleanup = | |
| new FindObjectByTypeCleanupThunk(finder); | |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| type.printValueOn(new PrintStream(bos)); | |
| String typeName = bos.toString(); | |
| doHeapIteration("Show Objects Of Type", | |
| "Finding instances of \"" + typeName + "\"", | |
| finder, | |
| cleanup); | |
| } | |
| class FindObjectByTypeCleanupThunk implements CleanupThunk { | |
| FindObjectByType finder; | |
| FindObjectByTypeCleanupThunk(FindObjectByType finder) { | |
| this.finder = finder; | |
| } | |
| public void heapIterationComplete() { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JInternalFrame finderFrame = new JInternalFrame("Show Objects of Type"); | |
| finderFrame.getContentPane().setLayout(new BorderLayout()); | |
| finderFrame.setResizable(true); | |
| finderFrame.setClosable(true); | |
| finderFrame.setIconifiable(true); | |
| ObjectListPanel panel = new ObjectListPanel(finder.getResults(), | |
| new HeapProgress("Reverse Pointers Analysis")); | |
| panel.addPanelListener(HSDB.this); | |
| finderFrame.getContentPane().add(panel); | |
| desktop.add(finderFrame); | |
| GraphicsUtilities.reshapeToAspectRatio(finderFrame, 4.0f / 3.0f, 0.6f, | |
| finderFrame.getParent().getSize()); | |
| GraphicsUtilities.centerInContainer(finderFrame); | |
| finderFrame.show(); | |
| } | |
| }); | |
| } | |
| } | |
| private void showDebuggerConsole() { | |
| if (consoleFrame == null) { | |
| consoleFrame = new JInternalFrame("Debugger Console"); | |
| consoleFrame.setResizable(true); | |
| consoleFrame.setClosable(true); | |
| consoleFrame.setIconifiable(true); | |
| consoleFrame.getContentPane().setLayout(new BorderLayout()); | |
| consoleFrame.getContentPane().add(new DebuggerConsolePanel(agent.getDebugger()), BorderLayout.CENTER); | |
| GraphicsUtilities.reshapeToAspectRatio(consoleFrame, 5.0f, 0.9f, desktop.getSize()); | |
| } | |
| if (consoleFrame.getParent() == null) { | |
| desktop.add(consoleFrame); | |
| } | |
| consoleFrame.setVisible(true); | |
| consoleFrame.show(); | |
| consoleFrame.getContentPane().getComponent(0).requestFocus(); | |
| } | |
| private void showConsole() { | |
| CommandProcessor.DebuggerInterface di = new CommandProcessor.DebuggerInterface() { | |
| public HotSpotAgent getAgent() { | |
| return agent; | |
| } | |
| public boolean isAttached() { | |
| return attached; | |
| } | |
| public void attach(int pid) { | |
| HSDB.this.attach(pid); | |
| } | |
| public void attach(String java, String core) { | |
| } | |
| public void attach(String debugServerName) { | |
| HSDB.this.connect(debugServerName); | |
| } | |
| public void detach() { | |
| detachDebugger(); | |
| } | |
| public void reattach() { | |
| if (attached) { | |
| detachDebugger(); | |
| } | |
| if (pid != -1) { | |
| attach(pid); | |
| } else if (debugServerName != null) { | |
| connect(debugServerName); | |
| } else { | |
| attach(execPath, coreFilename); | |
| } | |
| } | |
| }; | |
| showPanel("Command Line", new CommandProcessorPanel(new CommandProcessor(di, null, null, null))); | |
| } | |
| private void showFindPanel() { | |
| showPanel("Find Pointer", new FindPanel()); | |
| } | |
| private void showFindInHeapPanel() { | |
| showPanel("Find Address In Heap", new FindInHeapPanel()); | |
| } | |
| private void showFindInCodeCachePanel() { | |
| showPanel("Find Address In Code Cache", new FindInCodeCachePanel()); | |
| } | |
| private void showHeapParametersPanel() { | |
| showPanel("Heap Parameters", new HeapParametersPanel()); | |
| } | |
| public void showThreadInfo(final JavaThread thread) { | |
| showPanel("Info for " + thread.getThreadName(), new ThreadInfoPanel(thread)); | |
| } | |
| public void showJavaStackTrace(final JavaThread thread) { | |
| JavaStackTracePanel jstp = new JavaStackTracePanel(); | |
| showPanel("Java stack trace for " + thread.getThreadName(), jstp); | |
| jstp.setJavaThread(thread); | |
| } | |
| private void showDeadlockDetectionPanel() { | |
| showPanel("Deadlock Detection", new DeadlockDetectionPanel()); | |
| } | |
| private void showMonitorCacheDumpPanel() { | |
| showPanel("Monitor Cache Dump", new MonitorCacheDumpPanel()); | |
| } | |
| public void showClassBrowser() { | |
| final JInternalFrame progressFrame = new JInternalFrame("Class Browser"); | |
| progressFrame.setResizable(true); | |
| progressFrame.setClosable(true); | |
| progressFrame.setIconifiable(true); | |
| progressFrame.getContentPane().setLayout(new BorderLayout()); | |
| final ProgressBarPanel bar = new ProgressBarPanel("Generating class list .."); | |
| bar.setIndeterminate(true); | |
| progressFrame.getContentPane().add(bar, BorderLayout.CENTER); | |
| desktop.add(progressFrame); | |
| progressFrame.pack(); | |
| GraphicsUtilities.centerInContainer(progressFrame); | |
| progressFrame.show(); | |
| workerThread.invokeLater(new Runnable() { | |
| public void run() { | |
| HTMLGenerator htmlGen = new HTMLGenerator(); | |
| InstanceKlass[] klasses = SystemDictionaryHelper.getAllInstanceKlasses(); | |
| final String htmlText = htmlGen.genHTMLForKlassNames(klasses); | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| JInternalFrame cbFrame = new JInternalFrame("Class Browser"); | |
| cbFrame.getContentPane().setLayout(new BorderLayout()); | |
| cbFrame.setResizable(true); | |
| cbFrame.setClosable(true); | |
| cbFrame.setIconifiable(true); | |
| ClassBrowserPanel cbPanel = new ClassBrowserPanel(); | |
| cbFrame.getContentPane().add(cbPanel, BorderLayout.CENTER); | |
| desktop.remove(progressFrame); | |
| desktop.repaint(); | |
| desktop.add(cbFrame); | |
| GraphicsUtilities.reshapeToAspectRatio(cbFrame, 1.25f, 0.85f, | |
| cbFrame.getParent().getSize()); | |
| cbFrame.show(); | |
| cbPanel.setClassesText(htmlText); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| public void showCodeViewer() { | |
| showPanel("Code Viewer", new CodeViewerPanel(), 1.25f, 0.85f); | |
| } | |
| public void showCodeViewer(final Address address) { | |
| final CodeViewerPanel panel = new CodeViewerPanel(); | |
| showPanel("Code Viewer", panel, 1.25f, 0.85f); | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| panel.viewAddress(address); | |
| } | |
| }); | |
| } | |
| public void showMemoryViewer() { | |
| showPanel("Memory Viewer", new MemoryViewer(agent.getDebugger(), false, agent.getTypeDataBase().getAddressSize() == 8)); | |
| } | |
| public void showAnnotatedMemoryViewer() { | |
| showPanel("Annotated Memory Viewer", new MemoryViewer(agent.getDebugger(), true, agent.getTypeDataBase().getAddressSize() == 8)); | |
| } | |
| public void showCommandLineFlags() { | |
| showPanel("Command Line Flags", new VMFlagsPanel()); | |
| } | |
| public void showVMVersion() { | |
| showPanel("VM Version Info", new VMVersionInfoPanel()); | |
| } | |
| public void showSystemProperties() { | |
| showPanel("System Properties", new SysPropsPanel()); | |
| } | |
| private void showPanel(String name, JPanel panel) { | |
| showPanel(name, panel, 5.0f / 3.0f, 0.4f); | |
| } | |
| private void showPanel(String name, JPanel panel, float aspectRatio, float fillRatio) { | |
| JInternalFrame frame = new JInternalFrame(name); | |
| frame.getContentPane().setLayout(new BorderLayout()); | |
| frame.setResizable(true); | |
| frame.setClosable(true); | |
| frame.setIconifiable(true); | |
| frame.setMaximizable(true); | |
| frame.getContentPane().add(panel, BorderLayout.CENTER); | |
| desktop.add(frame); | |
| GraphicsUtilities.reshapeToAspectRatio(frame, aspectRatio, fillRatio, frame.getParent().getSize()); | |
| GraphicsUtilities.randomLocation(frame); | |
| frame.show(); | |
| if (panel instanceof SAPanel) { | |
| ((SAPanel)panel).addPanelListener(this); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------- | |
| // Framework for heap iteration with progress bar | |
| // | |
| interface CleanupThunk { | |
| public void heapIterationComplete(); | |
| } | |
| class HeapProgress implements HeapProgressThunk { | |
| private JInternalFrame frame; | |
| private ProgressBarPanel bar; | |
| private String windowTitle; | |
| private String progressBarTitle; | |
| private CleanupThunk cleanup; | |
| HeapProgress(String windowTitle) { | |
| this(windowTitle, "Percentage of heap visited", null); | |
| } | |
| HeapProgress(String windowTitle, String progressBarTitle) { | |
| this(windowTitle, progressBarTitle, null); | |
| } | |
| HeapProgress(String windowTitle, String progressBarTitle, CleanupThunk cleanup) { | |
| this.windowTitle = windowTitle; | |
| this.progressBarTitle = progressBarTitle; | |
| this.cleanup = cleanup; | |
| } | |
| public void heapIterationFractionUpdate(final double fractionOfHeapVisited) { | |
| if (frame == null) { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| frame = new JInternalFrame(windowTitle); | |
| frame.setResizable(true); | |
| frame.setIconifiable(true); | |
| frame.getContentPane().setLayout(new BorderLayout()); | |
| bar = new ProgressBarPanel(progressBarTitle); | |
| frame.getContentPane().add(bar, BorderLayout.CENTER); | |
| desktop.add(frame); | |
| frame.pack(); | |
| GraphicsUtilities.constrainToSize(frame, frame.getParent().getSize()); | |
| GraphicsUtilities.centerInContainer(frame); | |
| frame.show(); | |
| } | |
| }); | |
| } | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| bar.setValue(fractionOfHeapVisited); | |
| } | |
| }); | |
| } | |
| public void heapIterationComplete() { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| desktop.remove(frame); | |
| desktop.repaint(); | |
| if (VM.getVM().getRevPtrs() != null) { | |
| // Ended up computing reverse pointers as a side-effect | |
| computeRevPtrsMenuItem.setEnabled(false); | |
| } | |
| } | |
| }); | |
| if (cleanup != null) { | |
| cleanup.heapIterationComplete(); | |
| } | |
| } | |
| } | |
| class VisitHeap implements Runnable { | |
| HeapVisitor visitor; | |
| VisitHeap(HeapVisitor visitor) { | |
| this.visitor = visitor; | |
| } | |
| public void run() { | |
| VM.getVM().getObjectHeap().iterate(visitor); | |
| } | |
| } | |
| private void doHeapIteration(String frameTitle, | |
| String progressBarText, | |
| HeapVisitor visitor, | |
| CleanupThunk cleanup) { | |
| HeapProgress progress = new HeapProgress(frameTitle, | |
| progressBarText, | |
| cleanup); | |
| HeapVisitor progVisitor = new ProgressiveHeapVisitor(visitor, progress); | |
| workerThread.invokeLater(new VisitHeap(progVisitor)); | |
| } | |
| //-------------------------------------------------------------------------------- | |
| // Stack trace helper | |
| // | |
| private static JavaVFrame getLastJavaVFrame(JavaThread cur) { | |
| RegisterMap regMap = cur.newRegisterMap(true); | |
| sun.jvm.hotspot.runtime.Frame f = cur.getCurrentFrameGuess(); | |
| if (f == null) return null; | |
| boolean imprecise = true; | |
| if (f.isInterpretedFrame() && !f.isInterpretedFrameValid()) { | |
| System.err.println("Correcting for invalid interpreter frame"); | |
| f = f.sender(regMap); | |
| imprecise = false; | |
| } | |
| VFrame vf = VFrame.newVFrame(f, regMap, cur, true, imprecise); | |
| if (vf == null) { | |
| System.err.println(" (Unable to create vframe for topmost frame guess)"); | |
| return null; | |
| } | |
| if (vf.isJavaFrame()) { | |
| return (JavaVFrame) vf; | |
| } | |
| return vf.javaSender(); | |
| } | |
| // Internal routine for debugging | |
| private static void dumpStack(JavaThread cur) { | |
| RegisterMap regMap = cur.newRegisterMap(true); | |
| sun.jvm.hotspot.runtime.Frame f = cur.getCurrentFrameGuess(); | |
| PrintStream tty = System.err; | |
| while (f != null) { | |
| tty.print("Found "); | |
| if (f.isInterpretedFrame()) { tty.print("interpreted"); } | |
| else if (f.isCompiledFrame()) { tty.print("compiled"); } | |
| else if (f.isEntryFrame()) { tty.print("entry"); } | |
| else if (f.isNativeFrame()) { tty.print("native"); } | |
| else if (f.isRuntimeFrame()) { tty.print("runtime"); } | |
| else { tty.print("external"); } | |
| tty.print(" frame with PC = " + f.getPC() + ", SP = " + f.getSP() + ", FP = " + f.getFP()); | |
| if (f.isSignalHandlerFrameDbg()) { | |
| tty.print(" (SIGNAL HANDLER)"); | |
| } | |
| tty.println(); | |
| if (!f.isFirstFrame()) { | |
| f = f.sender(regMap); | |
| } else { | |
| f = null; | |
| } | |
| } | |
| } | |
| //-------------------------------------------------------------------------------- | |
| // Component utilities | |
| // | |
| private static JMenuItem createMenuItem(String name, ActionListener l) { | |
| JMenuItem item = new JMenuItem(name); | |
| item.addActionListener(l); | |
| 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 String formatMessage(String message, int charsPerLine) { | |
| StringBuilder buf = new StringBuilder(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<JMenuItem> items, boolean enabled) { | |
| for (Iterator<JMenuItem> iter = items.iterator(); iter.hasNext(); ) { | |
| iter.next().setEnabled(enabled); | |
| } | |
| } | |
| } |