1 /*
   2  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  *
  23  */
  24 
  25 package sun.jvm.hotspot;
  26 
  27 import java.io.*;
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.math.*;
  31 import javax.swing.*;
  32 import javax.swing.tree.*;
  33 import java.util.*;
  34 
  35 import sun.jvm.hotspot.code.*;
  36 import sun.jvm.hotspot.compiler.*;
  37 import sun.jvm.hotspot.debugger.*;
  38 import sun.jvm.hotspot.gc_implementation.parallelScavenge.*;

  39 import sun.jvm.hotspot.gc_interface.*;
  40 import sun.jvm.hotspot.interpreter.*;
  41 import sun.jvm.hotspot.memory.*;
  42 import sun.jvm.hotspot.oops.*;
  43 import sun.jvm.hotspot.runtime.*;
  44 import sun.jvm.hotspot.ui.*;
  45 import sun.jvm.hotspot.ui.tree.*;
  46 import sun.jvm.hotspot.ui.classbrowser.*;
  47 import sun.jvm.hotspot.utilities.*;
  48 
  49 /** The top-level HotSpot Debugger. FIXME: make this an embeddable
  50     component! (Among other things, figure out what to do with the
  51     menu bar...) */
  52 
  53 public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
  54   public static void main(String[] args) {
  55     new HSDB(args).run();
  56   }
  57 
  58   //--------------------------------------------------------------------------------
  59   // Internals only below this point
  60   //
  61   private HotSpotAgent agent;
  62   private JVMDebugger jvmDebugger;
  63   private JDesktopPane desktop;
  64   private boolean      attached;
  65   private boolean      argError;
  66   private JFrame frame;
  67   /** List <JMenuItem> */
  68   private java.util.List attachMenuItems;
  69   /** List <JMenuItem> */
  70   private java.util.List detachMenuItems;
  71   private JMenu toolsMenu;
  72   private JMenuItem showDbgConsoleMenuItem;
  73   private JMenuItem computeRevPtrsMenuItem;
  74   private JInternalFrame attachWaitDialog;
  75   private JInternalFrame threadsFrame;
  76   private JInternalFrame consoleFrame;
  77   private WorkerThread workerThread;
  78   // These had to be made data members because they are referenced in inner classes.
  79   private String pidText;
  80   private int pid;
  81   private String execPath;
  82   private String coreFilename;
  83 
  84   private void doUsage() {
  85     // With JDK-8059038 launchers for this class exist. Print usage for those launchers.
  86     System.out.println("Usage:  hsdb [[pid] | [path-to-java-executable [path-to-corefile]] | help | -help ]");
  87     System.out.println("           pid:                     attach to the process whose id is 'pid'");
  88     System.out.println("           path-to-java-executable: Debug a core file produced by this program");
  89     System.out.println("           path-to-corefile:        Debug this corefile.  The default is 'core'");
  90     System.out.println("        If no arguments are specified, you can select what to do from the GUI.\n");
  91     HotSpotAgent.showUsage();
  92     argError = true;
  93   }
  94 
  95   public HSDB(JVMDebugger d) {
  96     jvmDebugger = d;
  97   }
  98 
  99   private HSDB(String[] args) {
 100     switch (args.length) {
 101     case (0):
 102       break;
 103 
 104     case (1):
 105       if (args[0].equals("help") || args[0].equals("-help")) {
 106         doUsage();
 107       }
 108       // If all numbers, it is a PID to attach to
 109       // Else, it is a pathname to a .../bin/java for a core file.
 110       try {
 111         int unused = Integer.parseInt(args[0]);
 112         // If we get here, we have a PID and not a core file name
 113         pidText = args[0];
 114       } catch (NumberFormatException e) {
 115         execPath = args[0];
 116         coreFilename = "core";
 117       }
 118       break;
 119 
 120     case (2):
 121       execPath = args[0];
 122       coreFilename = args[1];
 123       break;
 124 
 125     default:
 126       System.out.println("HSDB Error: Too many options specified");
 127       doUsage();
 128     }
 129   }
 130 
 131   private class CloseUI extends WindowAdapter {
 132 
 133       @Override
 134       public void windowClosing(WindowEvent e) {
 135           workerThread.shutdown();
 136           frame.dispose();
 137       }
 138 
 139   }
 140 
 141   public void run() {
 142     // Don't start the UI if there were bad arguments.
 143     if (argError) {
 144         return;
 145     }
 146 
 147     agent = new HotSpotAgent();
 148     workerThread = new WorkerThread();
 149     attachMenuItems = new java.util.ArrayList();
 150     detachMenuItems = new java.util.ArrayList();
 151 
 152     frame = new JFrame("HSDB - HotSpot Debugger");
 153     frame.setSize(800, 600);
 154     frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
 155     frame.addWindowListener(new CloseUI());
 156 
 157     JMenuBar menuBar = new JMenuBar();
 158 
 159     //
 160     // File menu
 161     //
 162 
 163     JMenu menu = new JMenu("File");
 164     menu.setMnemonic(KeyEvent.VK_F);
 165     JMenuItem item;
 166     item = createMenuItem("Attach to HotSpot process...",
 167                           new ActionListener() {
 168                               public void actionPerformed(ActionEvent e) {
 169                                 showAttachDialog();
 170                               }
 171                             });
 172     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.ALT_MASK));
 173     item.setMnemonic(KeyEvent.VK_A);
 174     menu.add(item);
 175     attachMenuItems.add(item);
 176 
 177     item = createMenuItem("Open HotSpot core file...",
 178                           new ActionListener() {
 179                               public void actionPerformed(ActionEvent e) {
 180                                 showOpenCoreFileDialog();
 181                               }
 182                             });
 183     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
 184     item.setMnemonic(KeyEvent.VK_O);
 185     menu.add(item);
 186     attachMenuItems.add(item);
 187 
 188     item = createMenuItem("Connect to debug server...",
 189                           new ActionListener() {
 190                               public void actionPerformed(ActionEvent e) {
 191                                 showConnectDialog();
 192                               }
 193                             });
 194     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK));
 195     item.setMnemonic(KeyEvent.VK_S);
 196     menu.add(item);
 197     attachMenuItems.add(item);
 198 
 199     item = createMenuItem("Detach",
 200                           new ActionListener() {
 201                               public void actionPerformed(ActionEvent e) {
 202                                 detach();
 203                               }
 204                             });
 205     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, ActionEvent.ALT_MASK));
 206     item.setMnemonic(KeyEvent.VK_S);
 207     menu.add(item);
 208     detachMenuItems.add(item);
 209 
 210     // Disable detach menu items at first
 211     setMenuItemsEnabled(detachMenuItems, false);
 212 
 213     menu.addSeparator();
 214 
 215     item = createMenuItem("Exit",
 216                             new ActionListener() {
 217                                 public void actionPerformed(ActionEvent e) {
 218                                   workerThread.shutdown();
 219                                   frame.dispose();
 220                                 }
 221                               });
 222     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.ALT_MASK));
 223     item.setMnemonic(KeyEvent.VK_X);
 224     menu.add(item);
 225     menuBar.add(menu);
 226 
 227     //
 228     // Tools menu
 229     //
 230 
 231     toolsMenu = new JMenu("Tools");
 232     toolsMenu.setMnemonic(KeyEvent.VK_T);
 233 
 234     item = createMenuItem("Class Browser",
 235                           new ActionListener() {
 236                              public void actionPerformed(ActionEvent e) {
 237                                 showClassBrowser();
 238                              }
 239                           });
 240     item.setMnemonic(KeyEvent.VK_B);
 241 
 242     toolsMenu.add(item);
 243 
 244     item = createMenuItem("Code Viewer",
 245                           new ActionListener() {
 246                              public void actionPerformed(ActionEvent e) {
 247                                 showCodeViewer();
 248                              }
 249                           });
 250     item.setMnemonic(KeyEvent.VK_C);
 251 
 252     toolsMenu.add(item);
 253 
 254 
 255     item = createMenuItem("Compute Reverse Ptrs",
 256                           new ActionListener() {
 257                               public void actionPerformed(ActionEvent e) {
 258                                 fireComputeReversePtrs();
 259                               }
 260                             });
 261     computeRevPtrsMenuItem = item;
 262     item.setMnemonic(KeyEvent.VK_M);
 263     toolsMenu.add(item);
 264 
 265     item = createMenuItem("Deadlock Detection",
 266                           new ActionListener() {
 267                               public void actionPerformed(ActionEvent e) {
 268                                 showDeadlockDetectionPanel();
 269                               }
 270                             });
 271     item.setMnemonic(KeyEvent.VK_D);
 272     toolsMenu.add(item);
 273 
 274     item = createMenuItem("Find Object by Query",
 275                           new ActionListener() {
 276                               public void actionPerformed(ActionEvent e) {
 277                                 showFindByQueryPanel();
 278                               }
 279                             });
 280     item.setMnemonic(KeyEvent.VK_Q);
 281     toolsMenu.add(item);
 282 
 283 
 284     item = createMenuItem("Find Pointer",
 285                           new ActionListener() {
 286                               public void actionPerformed(ActionEvent e) {
 287                                 showFindPanel();
 288                               }
 289                             });
 290     item.setMnemonic(KeyEvent.VK_P);
 291     toolsMenu.add(item);
 292 
 293     item = createMenuItem("Find Value In Heap",
 294                           new ActionListener() {
 295                               public void actionPerformed(ActionEvent e) {
 296                                 showFindInHeapPanel();
 297                               }
 298                             });
 299     item.setMnemonic(KeyEvent.VK_V);
 300     toolsMenu.add(item);
 301 
 302     item = createMenuItem("Find Value In Code Cache",
 303                           new ActionListener() {
 304                               public void actionPerformed(ActionEvent e) {
 305                                 showFindInCodeCachePanel();
 306                               }
 307                             });
 308     item.setMnemonic(KeyEvent.VK_A);
 309     toolsMenu.add(item);
 310 
 311     item = createMenuItem("Heap Parameters",
 312                           new ActionListener() {
 313                               public void actionPerformed(ActionEvent e) {
 314                                 showHeapParametersPanel();
 315                               }
 316                             });
 317     item.setMnemonic(KeyEvent.VK_H);
 318     toolsMenu.add(item);
 319 
 320     item = createMenuItem("Inspector",
 321                           new ActionListener() {
 322                               public void actionPerformed(ActionEvent e) {
 323                                 showInspector(null);
 324                               }
 325                             });
 326     item.setMnemonic(KeyEvent.VK_R);
 327     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
 328     toolsMenu.add(item);
 329 
 330     item = createMenuItem("Memory Viewer",
 331                           new ActionListener() {
 332                              public void actionPerformed(ActionEvent e) {
 333                                 showMemoryViewer();
 334                              }
 335                           });
 336     item.setMnemonic(KeyEvent.VK_M);
 337     toolsMenu.add(item);
 338 
 339     item = createMenuItem("Monitor Cache Dump",
 340                           new ActionListener() {
 341                               public void actionPerformed(ActionEvent e) {
 342                                 showMonitorCacheDumpPanel();
 343                               }
 344                             });
 345     item.setMnemonic(KeyEvent.VK_D);
 346     toolsMenu.add(item);
 347 
 348     item = createMenuItem("Object Histogram",
 349                           new ActionListener() {
 350                               public void actionPerformed(ActionEvent e) {
 351                                 showObjectHistogram();
 352                               }
 353                             });
 354     item.setMnemonic(KeyEvent.VK_O);
 355     toolsMenu.add(item);
 356 
 357     item = createMenuItem("Show System Properties",
 358                           new ActionListener() {
 359                              public void actionPerformed(ActionEvent e) {
 360                                 showSystemProperties();
 361                              }
 362                           });
 363     item.setMnemonic(KeyEvent.VK_S);
 364     toolsMenu.add(item);
 365 
 366     item = createMenuItem("Show VM Version",
 367                           new ActionListener() {
 368                              public void actionPerformed(ActionEvent e) {
 369                                 showVMVersion();
 370                              }
 371                           });
 372     item.setMnemonic(KeyEvent.VK_M);
 373     toolsMenu.add(item);
 374 
 375     item = createMenuItem("Show -XX flags",
 376                           new ActionListener() {
 377                              public void actionPerformed(ActionEvent e) {
 378                                 showCommandLineFlags();
 379                              }
 380                           });
 381     item.setMnemonic(KeyEvent.VK_X);
 382     toolsMenu.add(item);
 383 
 384     toolsMenu.setEnabled(false);
 385     menuBar.add(toolsMenu);
 386 
 387     //
 388     // Windows menu
 389     //
 390 
 391     JMenu windowsMenu = new JMenu("Windows");
 392     windowsMenu.setMnemonic(KeyEvent.VK_W);
 393     item = createMenuItem("Console",
 394                           new ActionListener() {
 395                              public void actionPerformed(ActionEvent e) {
 396                                  showConsole();
 397                              }
 398                           });
 399     item.setMnemonic(KeyEvent.VK_C);
 400     windowsMenu.add(item);
 401     showDbgConsoleMenuItem = createMenuItem("Debugger Console",
 402                                          new ActionListener() {
 403                                              public void actionPerformed(ActionEvent e) {
 404                                                showDebuggerConsole();
 405                                              }
 406                                            });
 407     showDbgConsoleMenuItem.setMnemonic(KeyEvent.VK_D);
 408     windowsMenu.add(showDbgConsoleMenuItem);
 409     showDbgConsoleMenuItem.setEnabled(false);
 410 
 411     menuBar.add(windowsMenu);
 412 
 413 
 414     frame.setJMenuBar(menuBar);
 415 
 416     desktop = new JDesktopPane();
 417     frame.getContentPane().add(desktop);
 418     GraphicsUtilities.reshapeToAspectRatio(frame, 4.0f/3.0f, 0.75f, Toolkit.getDefaultToolkit().getScreenSize());
 419     GraphicsUtilities.centerInContainer(frame, Toolkit.getDefaultToolkit().getScreenSize());
 420     frame.setVisible(true);
 421 
 422     Runtime.getRuntime().addShutdownHook(new java.lang.Thread() {
 423         public void run() {
 424           detachDebugger();
 425         }
 426       });
 427 
 428     // If jvmDebugger is already set, we have been given a JVMDebugger.
 429     // Otherwise, if pidText != null we are supposed to attach to it.
 430     // Finally, if execPath != null, it is the path of a jdk/bin/java
 431     // and coreFilename is the pathname of a core file we are
 432     // supposed to attach to.
 433 
 434     if (jvmDebugger != null) {
 435       attach(jvmDebugger);
 436     } else if (pidText != null) {
 437       attach(pidText);
 438     } else if (execPath != null) {
 439       attach(execPath, coreFilename);
 440     }
 441   }
 442 
 443   // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog
 444   private void showAttachDialog() {
 445     // FIXME: create filtered text field which only accepts numbers
 446     setMenuItemsEnabled(attachMenuItems, false);
 447     final JInternalFrame attachDialog = new JInternalFrame("Attach to HotSpot process");
 448     attachDialog.getContentPane().setLayout(new BorderLayout());
 449 
 450     JPanel panel = new JPanel();
 451     panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
 452     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 453     attachDialog.setBackground(panel.getBackground());
 454 
 455     panel.add(new JLabel("Enter process ID:"));
 456     final JTextField pidTextField = new JTextField(10);
 457     ActionListener attacher = new ActionListener() {
 458         public void actionPerformed(ActionEvent e) {
 459           attachDialog.setVisible(false);
 460           desktop.remove(attachDialog);
 461           workerThread.invokeLater(new Runnable() {
 462               public void run() {
 463                 attach(pidTextField.getText());
 464               }
 465             });
 466         }
 467       };
 468 
 469     pidTextField.addActionListener(attacher);
 470     panel.add(pidTextField);
 471     attachDialog.getContentPane().add(panel, BorderLayout.NORTH);
 472 
 473     Box vbox = Box.createVerticalBox();
 474     panel = new JPanel();
 475     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
 476     panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
 477     JTextArea ta = new JTextArea(
 478                                  "Enter the process ID of a currently-running HotSpot process. On " +
 479                                  "Solaris and most Unix operating systems, this can be determined by " +
 480                                  "typing \"ps -u <your username> | grep java\"; the process ID is the " +
 481                                  "first number which appears on the resulting line. On Windows, the " +
 482                                  "process ID is present in the Task Manager, which can be brought up " +
 483                                  "while logged on to the desktop by pressing Ctrl-Alt-Delete.");
 484     ta.setLineWrap(true);
 485     ta.setWrapStyleWord(true);
 486     ta.setEditable(false);
 487     ta.setBackground(panel.getBackground());
 488     panel.add(ta);
 489     vbox.add(panel);
 490 
 491     Box hbox = Box.createHorizontalBox();
 492     hbox.add(Box.createGlue());
 493     JButton button = new JButton("OK");
 494     button.addActionListener(attacher);
 495     hbox.add(button);
 496     hbox.add(Box.createHorizontalStrut(20));
 497     button = new JButton("Cancel");
 498     button.addActionListener(new ActionListener() {
 499         public void actionPerformed(ActionEvent e) {
 500           attachDialog.setVisible(false);
 501           desktop.remove(attachDialog);
 502           setMenuItemsEnabled(attachMenuItems, true);
 503         }
 504       });
 505     hbox.add(button);
 506     hbox.add(Box.createGlue());
 507     panel = new JPanel();
 508     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 509     panel.add(hbox);
 510     vbox.add(panel);
 511 
 512     attachDialog.getContentPane().add(vbox, BorderLayout.SOUTH);
 513 
 514     desktop.add(attachDialog);
 515     attachDialog.setSize(400, 300);
 516     GraphicsUtilities.centerInContainer(attachDialog);
 517     attachDialog.show();
 518     pidTextField.requestFocus();
 519   }
 520 
 521   // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog
 522   private void showOpenCoreFileDialog() {
 523     setMenuItemsEnabled(attachMenuItems, false);
 524     final JInternalFrame dialog = new JInternalFrame("Open Core File");
 525     dialog.getContentPane().setLayout(new BorderLayout());
 526 
 527     JPanel panel = new JPanel();
 528     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
 529     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 530     dialog.setBackground(panel.getBackground());
 531 
 532     Box hbox = Box.createHorizontalBox();
 533     Box vbox = Box.createVerticalBox();
 534     vbox.add(new JLabel("Path to core file:"));
 535     vbox.add(new JLabel("Path to Java executable:"));
 536     hbox.add(vbox);
 537 
 538     vbox = Box.createVerticalBox();
 539     final JTextField corePathField = new JTextField(40);
 540     final JTextField execPathField = new JTextField(40);
 541     vbox.add(corePathField);
 542     vbox.add(execPathField);
 543     hbox.add(vbox);
 544 
 545     final JButton browseCorePath = new JButton("Browse ..");
 546     final JButton browseExecPath = new JButton("Browse ..");
 547     browseCorePath.addActionListener(new ActionListener() {
 548                                         public void actionPerformed(ActionEvent e) {
 549                                            JFileChooser fileChooser = new JFileChooser(new File("."));
 550                                            int retVal = fileChooser.showOpenDialog(dialog);
 551                                            if (retVal == JFileChooser.APPROVE_OPTION) {
 552                                               corePathField.setText(fileChooser.getSelectedFile().getPath());
 553                                            }
 554                                         }
 555                                      });
 556     browseExecPath.addActionListener(new ActionListener() {
 557                                         public void actionPerformed(ActionEvent e) {
 558                                            JFileChooser fileChooser = new JFileChooser(new File("."));
 559                                            int retVal = fileChooser.showOpenDialog(dialog);
 560                                            if (retVal == JFileChooser.APPROVE_OPTION) {
 561                                               execPathField.setText(fileChooser.getSelectedFile().getPath());
 562                                            }
 563                                         }
 564                                      });
 565     vbox = Box.createVerticalBox();
 566     vbox.add(browseCorePath);
 567     vbox.add(browseExecPath);
 568     hbox.add(vbox);
 569 
 570     panel.add(hbox);
 571     dialog.getContentPane().add(panel, BorderLayout.NORTH);
 572 
 573     ActionListener attacher = new ActionListener() {
 574         public void actionPerformed(ActionEvent e) {
 575           dialog.setVisible(false);
 576           desktop.remove(dialog);
 577           workerThread.invokeLater(new Runnable() {
 578               public void run() {
 579                 attach(execPathField.getText(), corePathField.getText());
 580               }
 581             });
 582         }
 583       };
 584     corePathField.addActionListener(attacher);
 585     execPathField.addActionListener(attacher);
 586 
 587     vbox = Box.createVerticalBox();
 588     panel = new JPanel();
 589     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
 590     panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
 591     JTextArea ta = new JTextArea(
 592                                  "Enter the full path names to the core file from a HotSpot process " +
 593                                  "and the Java executable from which it came. The latter is typically " +
 594                                  "located in the JDK/JRE directory under the directory " +
 595                                  "jre/bin/<arch>/native_threads.");
 596     ta.setLineWrap(true);
 597     ta.setWrapStyleWord(true);
 598     ta.setEditable(false);
 599     ta.setBackground(panel.getBackground());
 600     panel.add(ta);
 601     vbox.add(panel);
 602 
 603     hbox = Box.createHorizontalBox();
 604     hbox.add(Box.createGlue());
 605     JButton button = new JButton("OK");
 606     button.addActionListener(attacher);
 607     hbox.add(button);
 608     hbox.add(Box.createHorizontalStrut(20));
 609     button = new JButton("Cancel");
 610     button.addActionListener(new ActionListener() {
 611         public void actionPerformed(ActionEvent e) {
 612           dialog.setVisible(false);
 613           desktop.remove(dialog);
 614           setMenuItemsEnabled(attachMenuItems, true);
 615         }
 616       });
 617     hbox.add(button);
 618     hbox.add(Box.createGlue());
 619     panel = new JPanel();
 620     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 621     panel.add(hbox);
 622     vbox.add(panel);
 623 
 624     dialog.getContentPane().add(vbox, BorderLayout.SOUTH);
 625 
 626     desktop.add(dialog);
 627     dialog.setSize(500, 300);
 628     GraphicsUtilities.centerInContainer(dialog);
 629     dialog.show();
 630     corePathField.requestFocus();
 631   }
 632 
 633   // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog
 634   private void showConnectDialog() {
 635     // FIXME: create filtered text field which only accepts numbers
 636     setMenuItemsEnabled(attachMenuItems, false);
 637     final JInternalFrame dialog = new JInternalFrame("Connect to HotSpot Debug Server");
 638     dialog.getContentPane().setLayout(new BorderLayout());
 639 
 640     JPanel panel = new JPanel();
 641     panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
 642     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 643     dialog.setBackground(panel.getBackground());
 644 
 645     panel.add(new JLabel("Enter machine name:"));
 646     final JTextField pidTextField = new JTextField(40);
 647     ActionListener attacher = new ActionListener() {
 648         public void actionPerformed(ActionEvent e) {
 649           dialog.setVisible(false);
 650           desktop.remove(dialog);
 651           workerThread.invokeLater(new Runnable() {
 652               public void run() {
 653                 connect(pidTextField.getText());
 654               }
 655             });
 656         }
 657       };
 658 
 659     pidTextField.addActionListener(attacher);
 660     panel.add(pidTextField);
 661     dialog.getContentPane().add(panel, BorderLayout.NORTH);
 662 
 663     Box vbox = Box.createVerticalBox();
 664     panel = new JPanel();
 665     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
 666     panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
 667     JTextArea ta = new JTextArea(
 668                                  "Enter the name of a machine on which the HotSpot \"Debug Server\" is " +
 669                                  "running and is attached to a process or core file.");
 670     ta.setLineWrap(true);
 671     ta.setWrapStyleWord(true);
 672     ta.setEditable(false);
 673     ta.setBackground(panel.getBackground());
 674     panel.add(ta);
 675     vbox.add(panel);
 676 
 677     Box hbox = Box.createHorizontalBox();
 678     hbox.add(Box.createGlue());
 679     JButton button = new JButton("OK");
 680     button.addActionListener(attacher);
 681     hbox.add(button);
 682     hbox.add(Box.createHorizontalStrut(20));
 683     button = new JButton("Cancel");
 684     button.addActionListener(new ActionListener() {
 685         public void actionPerformed(ActionEvent e) {
 686           dialog.setVisible(false);
 687           desktop.remove(dialog);
 688           setMenuItemsEnabled(attachMenuItems, true);
 689         }
 690       });
 691     hbox.add(button);
 692     hbox.add(Box.createGlue());
 693     panel = new JPanel();
 694     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 695     panel.add(hbox);
 696     vbox.add(panel);
 697 
 698     dialog.getContentPane().add(vbox, BorderLayout.SOUTH);
 699 
 700     desktop.add(dialog);
 701     dialog.setSize(400, 300);
 702     GraphicsUtilities.centerInContainer(dialog);
 703     dialog.show();
 704     pidTextField.requestFocus();
 705   }
 706 
 707   public void showThreadOopInspector(JavaThread thread) {
 708     showInspector(new OopTreeNodeAdapter(thread.getThreadObj(), null));
 709   }
 710 
 711   public void showInspector(SimpleTreeNode adapter) {
 712     showPanel("Inspector", new Inspector(adapter), 1.0f, 0.65f);
 713   }
 714 
 715   public void showLiveness(Oop oop, LivenessPathList liveness) {
 716     ByteArrayOutputStream bos = new ByteArrayOutputStream();
 717     PrintStream tty = new PrintStream(bos);
 718     int numPaths = liveness.size();
 719     for (int i = 0; i < numPaths; i++) {
 720       tty.println("Path " + (i + 1) + " of " + numPaths + ":");
 721       liveness.get(i).printOn(tty);
 722     }
 723     JTextArea ta = new JTextArea(bos.toString());
 724     ta.setLineWrap(true);
 725     ta.setWrapStyleWord(true);
 726     ta.setEditable(false);
 727 
 728     JPanel panel = new JPanel();
 729     panel.setLayout(new BorderLayout());
 730 
 731     JScrollPane scroller = new JScrollPane();
 732     scroller.getViewport().add(ta);
 733 
 734     panel.add(scroller, BorderLayout.CENTER);
 735 
 736     bos = new ByteArrayOutputStream();
 737     tty = new PrintStream(bos);
 738     tty.print("Liveness result for ");
 739     Oop.printOopValueOn(oop, tty);
 740 
 741     JInternalFrame frame = new JInternalFrame(bos.toString());
 742     frame.setResizable(true);
 743     frame.setClosable(true);
 744     frame.setIconifiable(true);
 745     frame.getContentPane().setLayout(new BorderLayout());
 746     frame.getContentPane().add(panel, BorderLayout.CENTER);
 747     frame.pack();
 748     desktop.add(frame);
 749     GraphicsUtilities.reshapeToAspectRatio(frame, 0.5f / 0.2f, 0.5f, frame.getParent().getSize());
 750     frame.show();
 751   }
 752 
 753   private void fireComputeReversePtrs() {
 754     // Possible this might have been computed elsewhere
 755     if (VM.getVM().getRevPtrs() != null) {
 756       computeRevPtrsMenuItem.setEnabled(false);
 757       return;
 758     }
 759 
 760     workerThread.invokeLater(new Runnable() {
 761         public void run() {
 762           HeapProgress progress = new HeapProgress("Reverse Pointers Analysis");
 763           try {
 764             ReversePtrsAnalysis analysis = new ReversePtrsAnalysis();
 765             analysis.setHeapProgressThunk(progress);
 766             analysis.run();
 767             computeRevPtrsMenuItem.setEnabled(false);
 768           } catch (OutOfMemoryError e) {
 769             final String errMsg = formatMessage(e.toString(), 80);
 770             SwingUtilities.invokeLater(new Runnable() {
 771                 public void run() {
 772                   JOptionPane.showInternalMessageDialog(desktop,
 773                                                         "Error computing reverse pointers:" + errMsg,
 774                                                         "Error",
 775                                                         JOptionPane.WARNING_MESSAGE);
 776                 }
 777               });
 778           } finally {
 779             // make sure the progress bar goes away
 780             progress.heapIterationComplete();
 781           }
 782         }
 783       });
 784   }
 785 
 786   // Simple struct containing signal information
 787   class SignalInfo {
 788     public int sigNum;
 789     public String sigName;
 790   }
 791 
 792   // Need to have mutable vframe as well as visible memory panel
 793   abstract class StackWalker implements Runnable {
 794     protected JavaVFrame vf;
 795     protected AnnotatedMemoryPanel annoPanel;
 796 
 797     StackWalker(JavaVFrame vf, AnnotatedMemoryPanel annoPanel) {
 798       this.vf = vf;
 799       this.annoPanel = annoPanel;
 800     }
 801   }
 802 
 803   public void showThreadStackMemory(final JavaThread thread) {
 804     // dumpStack(thread);
 805     JavaVFrame vframe = getLastJavaVFrame(thread);
 806     if (vframe == null) {
 807       JOptionPane.showInternalMessageDialog(desktop,
 808                                             "Thread \"" + thread.getThreadName() +
 809                                             "\" has no Java frames on its stack",
 810                                             "Show Stack Memory",
 811                                             JOptionPane.INFORMATION_MESSAGE);
 812       return;
 813     }
 814 
 815     JInternalFrame stackFrame = new JInternalFrame("Stack Memory for " + thread.getThreadName());
 816     stackFrame.getContentPane().setLayout(new BorderLayout());
 817     stackFrame.setResizable(true);
 818     stackFrame.setClosable(true);
 819     stackFrame.setIconifiable(true);
 820     final long addressSize = agent.getTypeDataBase().getAddressSize();
 821     boolean is64Bit = (addressSize == 8);
 822     // This is somewhat of a  hack to guess a thread's stack limits since the
 823     // JavaThread doesn't support this functionality. However it is nice in that
 824     // it locks us into the active region of the thread's stack and not its
 825     // theoretical limits.
 826     //
 827     sun.jvm.hotspot.runtime.Frame tmpFrame = thread.getCurrentFrameGuess();
 828     Address sp = tmpFrame.getSP();
 829     Address starting = sp;
 830     Address maxSP = starting;
 831     Address minSP = starting;
 832     RegisterMap tmpMap = thread.newRegisterMap(false);
 833     while ((tmpFrame != null) && (!tmpFrame.isFirstFrame())) {
 834         tmpFrame = tmpFrame.sender(tmpMap);
 835         if (tmpFrame != null) {
 836           sp = tmpFrame.getSP();
 837           if (sp != null) {
 838             maxSP = AddressOps.max(maxSP, sp);
 839             minSP = AddressOps.min(minSP, sp);
 840           }
 841         }
 842 
 843     }
 844     // It is useful to be able to see say +/- 8K on the current stack range
 845     AnnotatedMemoryPanel annoMemPanel = new AnnotatedMemoryPanel(agent.getDebugger(), is64Bit, starting,
 846                                                                  minSP.addOffsetTo(-8192),
 847                                                                  maxSP.addOffsetTo( 8192));
 848 
 849     stackFrame.getContentPane().add(annoMemPanel, BorderLayout.CENTER);
 850     desktop.add(stackFrame);
 851     GraphicsUtilities.reshapeToAspectRatio(stackFrame, 4.0f / 3.0f, 0.85f, stackFrame.getParent().getSize());
 852     stackFrame.show();
 853 
 854     // Stackmap computation for interpreted frames is expensive; do
 855     // all stackwalking work in another thread for better GUI
 856     // responsiveness
 857     workerThread.invokeLater(new StackWalker(vframe, annoMemPanel) {
 858         public void run() {
 859           Address startAddr = null;
 860 
 861           // As this is a debugger, we want to provide potential crash
 862           // information to the user, i.e., by marking signal handler frames
 863           // on the stack. Since this system is currently targeted at
 864           // annotating the Java frames (interpreted or compiled) on the
 865           // stack and not, for example, "external" frames (note the current
 866           // absence of a PC-to-symbol lookup mechanism at the Debugger
 867           // level), we want to mark any Java frames which were interrupted
 868           // by a signal. We do this by making two passes over the stack,
 869           // one which finds signal handler frames and puts the parent
 870           // frames in a table and one which finds Java frames and if they
 871           // are in the table indicates that they were interrupted by a signal.
 872 
 873           Map interruptedFrameMap = new HashMap();
 874           {
 875             sun.jvm.hotspot.runtime.Frame tmpFrame = thread.getCurrentFrameGuess();
 876             RegisterMap tmpMap = thread.newRegisterMap(false);
 877             while ((tmpFrame != null) && (!tmpFrame.isFirstFrame())) {
 878               if (tmpFrame.isSignalHandlerFrameDbg()) {
 879                 // Add some information to the map that we can extract later
 880                 sun.jvm.hotspot.runtime.Frame interruptedFrame = tmpFrame.sender(tmpMap);
 881                 SignalInfo info = new SignalInfo();
 882                 info.sigNum  = tmpFrame.getSignalNumberDbg();
 883                 info.sigName = tmpFrame.getSignalNameDbg();
 884                 interruptedFrameMap.put(interruptedFrame, info);
 885               }
 886               tmpFrame = tmpFrame.sender(tmpMap);
 887             }
 888           }
 889 
 890           while (vf != null) {
 891             String anno = null;
 892             JavaVFrame curVFrame = vf;
 893             sun.jvm.hotspot.runtime.Frame curFrame = curVFrame.getFrame();
 894             Method interpreterFrameMethod = null;
 895 
 896             if (curVFrame.isInterpretedFrame()) {
 897               anno = "Interpreted frame";
 898             } else {
 899               anno = "Compiled frame";
 900               if (curVFrame.isDeoptimized()) {
 901                 anno += " (deoptimized)";
 902               }
 903             }
 904             if (curVFrame.mayBeImpreciseDbg()) {
 905               anno += "; information may be imprecise";
 906             }
 907 
 908             if (curVFrame.isInterpretedFrame()) {
 909               // Find the codelet
 910               InterpreterCodelet codelet = VM.getVM().getInterpreter().getCodeletContaining(curFrame.getPC());
 911               String description = null;
 912               if (codelet != null) {
 913                 description = codelet.getDescription();
 914               }
 915               if (description == null) {
 916                 anno += "\n(Unknown interpreter codelet)";
 917               } else {
 918                 anno += "\nExecuting in codelet \"" + description + "\" at PC = " + curFrame.getPC();
 919               }
 920             } else if (curVFrame.isCompiledFrame()) {
 921               anno += "\nExecuting at PC = " + curFrame.getPC();
 922             }
 923 
 924             if (startAddr == null) {
 925               startAddr = curFrame.getSP();
 926             }
 927 
 928             // FIXME: some compiled frames with empty oop map sets have been
 929             // found (for example, Vector's inner Enumeration class, method
 930             // "hasMoreElements"). Not sure yet why these cases are showing
 931             // up -- should be possible (though unlikely) for safepoint code
 932             // to patch the return instruction of these methods and then
 933             // later attempt to get an oop map for that instruction. For
 934             // now, we warn if we find such a method.
 935             boolean shouldSkipOopMaps = false;
 936             if (curVFrame.isCompiledFrame()) {
 937               CodeBlob cb = VM.getVM().getCodeCache().findBlob(curFrame.getPC());
 938               OopMapSet maps = cb.getOopMaps();
 939               if ((maps == null) || (maps.getSize() == 0)) {
 940                 shouldSkipOopMaps = true;
 941               }
 942             }
 943 
 944             // Add signal information to annotation if necessary
 945             SignalInfo sigInfo = (SignalInfo) interruptedFrameMap.get(curFrame);
 946             if (sigInfo != null) {
 947               // This frame took a signal and we need to report it.
 948               anno = (anno + "\n*** INTERRUPTED BY SIGNAL " + Integer.toString(sigInfo.sigNum) +
 949                       " (" + sigInfo.sigName + ")");
 950             }
 951 
 952             JavaVFrame nextVFrame = curVFrame;
 953             sun.jvm.hotspot.runtime.Frame nextFrame = curFrame;
 954             do {
 955               curVFrame = nextVFrame;
 956               curFrame = nextFrame;
 957 
 958               try {
 959                 Method method = curVFrame.getMethod();
 960                 if (interpreterFrameMethod == null && curVFrame.isInterpretedFrame()) {
 961                   interpreterFrameMethod = method;
 962                 }
 963                 int bci = curVFrame.getBCI();
 964                 String lineNumberAnno = "";
 965                 if (method.hasLineNumberTable()) {
 966                   if ((bci == DebugInformationRecorder.SYNCHRONIZATION_ENTRY_BCI) ||
 967                       (bci >= 0 && bci < method.getCodeSize())) {
 968                     lineNumberAnno = ", line " + method.getLineNumberFromBCI(bci);
 969                   } else {
 970                     lineNumberAnno = " (INVALID BCI)";
 971                   }
 972                 }
 973                 anno += "\n" + method.getMethodHolder().getName().asString() + "." +
 974                                method.getName().asString() + method.getSignature().asString() +
 975                                "\n@bci " + bci + lineNumberAnno;
 976               } catch (Exception e) {
 977                 anno += "\n(ERROR while iterating vframes for frame " + curFrame + ")";
 978               }
 979 
 980               nextVFrame = curVFrame.javaSender();
 981               if (nextVFrame != null) {
 982                 nextFrame = nextVFrame.getFrame();
 983               }
 984             } while (nextVFrame != null && nextFrame.equals(curFrame));
 985 
 986             if (shouldSkipOopMaps) {
 987               anno = anno + "\nNOTE: null or empty OopMapSet found for this CodeBlob";
 988             }
 989 
 990             if (curFrame.getFP() != null) {
 991               annoPanel.addAnnotation(new Annotation(curFrame.getSP(),
 992                                                      curFrame.getFP(),
 993                                                      anno));
 994             } else {
 995               if (VM.getVM().getCPU().equals("x86") || VM.getVM().getCPU().equals("amd64") ||
 996                   VM.getVM().getCPU().equals("aarch64")) {
 997                 // For C2, which has null frame pointers on x86/amd64
 998                 CodeBlob cb = VM.getVM().getCodeCache().findBlob(curFrame.getPC());
 999                 Address sp = curFrame.getSP();
1000                 if (Assert.ASSERTS_ENABLED) {
1001                   Assert.that(cb.getFrameSize() > 0, "CodeBlob must have non-zero frame size");
1002                 }
1003                 annoPanel.addAnnotation(new Annotation(sp,
1004                                                        sp.addOffsetTo(cb.getFrameSize()),
1005                                                        anno));
1006               } else {
1007                 Assert.that(VM.getVM().getCPU().equals("ia64"), "only ia64 should reach here");
1008               }
1009             }
1010 
1011             // Add interpreter frame annotations
1012             if (curFrame.isInterpretedFrame()) {
1013               annoPanel.addAnnotation(new Annotation(curFrame.addressOfInterpreterFrameExpressionStack(),
1014                                                      curFrame.addressOfInterpreterFrameTOS(),
1015                                                      "Interpreter expression stack"));
1016               Address monBegin = curFrame.interpreterFrameMonitorBegin().address();
1017               Address monEnd = curFrame.interpreterFrameMonitorEnd().address();
1018               if (!monBegin.equals(monEnd)) {
1019                   annoPanel.addAnnotation(new Annotation(monBegin, monEnd,
1020                                                          "BasicObjectLocks"));
1021               }
1022               if (interpreterFrameMethod != null) {
1023                 // The offset is just to get the right stack slots highlighted in the output
1024                 int offset = 1;
1025                 annoPanel.addAnnotation(new Annotation(curFrame.addressOfInterpreterFrameLocal(offset),
1026                                                        curFrame.addressOfInterpreterFrameLocal((int) interpreterFrameMethod.getMaxLocals() + offset),
1027                                                        "Interpreter locals area for frame with SP = " + curFrame.getSP()));
1028               }
1029               String methodAnno = "Interpreter frame Method*";
1030               if (interpreterFrameMethod == null) {
1031                 methodAnno += " (BAD OOP)";
1032               }
1033               Address a = curFrame.addressOfInterpreterFrameMethod();
1034               annoPanel.addAnnotation(new Annotation(a, a.addOffsetTo(addressSize), methodAnno));
1035               a = curFrame.addressOfInterpreterFrameCPCache();
1036               annoPanel.addAnnotation(new Annotation(a, a.addOffsetTo(addressSize), "Interpreter constant pool cache"));
1037             }
1038 
1039             RegisterMap rm = (RegisterMap) vf.getRegisterMap().clone();
1040             if (!shouldSkipOopMaps) {
1041               try {
1042                 curFrame.oopsDo(new AddressVisitor() {
1043                     public void visitAddress(Address addr) {
1044                       if (Assert.ASSERTS_ENABLED) {
1045                         Assert.that(addr.andWithMask(VM.getVM().getAddressSize() - 1) == null,
1046                                     "Address " + addr + "should have been aligned");
1047                       }
1048                       OopHandle handle = addr.getOopHandleAt(0);
1049                       addAnnotation(addr, handle);
1050                     }
1051 
1052                     public void visitCompOopAddress(Address addr) {
1053                       if (Assert.ASSERTS_ENABLED) {
1054                         Assert.that(addr.andWithMask(VM.getVM().getAddressSize() - 1) == null,
1055                                     "Address " + addr + "should have been aligned");
1056                       }
1057                       OopHandle handle = addr.getCompOopHandleAt(0);
1058                       addAnnotation(addr, handle);
1059                     }
1060 
1061                     public void addAnnotation(Address addr, OopHandle handle) {
1062                       // Check contents
1063                       String anno = "null oop";
1064                       if (handle != null) {
1065                         // Find location
1066                         CollectedHeap collHeap = VM.getVM().getUniverse().heap();
1067                         boolean bad = true;
1068                         anno = "BAD OOP";
1069                         if (collHeap instanceof GenCollectedHeap) {
1070                           GenCollectedHeap heap = (GenCollectedHeap) collHeap;
1071                           for (int i = 0; i < heap.nGens(); i++) {
1072                             if (heap.getGen(i).isIn(handle)) {
1073                               if (i == 0) {
1074                                 anno = "NewGen ";
1075                               } else if (i == 1) {
1076                                 anno = "OldGen ";
1077                               } else {
1078                                 anno = "Gen " + i + " ";
1079                               }
1080                               bad = false;
1081                               break;
1082                             }
1083                           }
1084 
1085                         } else if (collHeap instanceof ParallelScavengeHeap) {
1086                           ParallelScavengeHeap heap = (ParallelScavengeHeap) collHeap;
1087                           if (heap.youngGen().isIn(handle)) {
1088                             anno = "PSYoungGen ";
1089                             bad = false;
1090                           } else if (heap.oldGen().isIn(handle)) {
1091                             anno = "PSOldGen ";
1092                             bad = false;
1093                           }




1094                         } else {
1095                           // Optimistically assume the oop isn't bad
1096                           anno = "[Unknown generation] ";
1097                           bad = false;
1098                         }
1099 
1100                         if (!bad) {
1101                           try {
1102                             Oop oop = VM.getVM().getObjectHeap().newOop(handle);
1103                             if (oop instanceof Instance) {
1104                                 // Java-level objects always have workable names
1105                               anno = anno + oop.getKlass().getName().asString();
1106                             } else {
1107                               ByteArrayOutputStream bos = new ByteArrayOutputStream();
1108                               Oop.printOopValueOn(oop, new PrintStream(bos));
1109                               anno = anno + bos.toString();
1110                             }
1111                           }
1112                           catch (AddressException e) {
1113                             anno += "CORRUPT OOP";
1114                           }
1115                           catch (NullPointerException e) {
1116                             anno += "CORRUPT OOP (null pointer)";
1117                           }
1118                         }
1119                       }
1120 
1121                       annoPanel.addAnnotation(new Annotation(addr, addr.addOffsetTo(addressSize), anno));
1122                     }
1123                   }, rm);
1124               } catch (Exception e) {
1125                 System.err.println("Error while performing oopsDo for frame " + curFrame);
1126                 e.printStackTrace();
1127               }
1128             }
1129 
1130             vf = nextVFrame;
1131           }
1132 
1133           // This used to paint as we walked the frames. This caused the display to be refreshed
1134           // enough to be annoying on remote displays. It also would cause the annotations to
1135           // be displayed in varying order which caused some annotations to overwrite others
1136           // depending on the races between painting and adding annotations. This latter problem
1137           // still exists to some degree but moving this code here definitely seems to reduce it
1138           annoPanel.makeVisible(startAddr);
1139           annoPanel.repaint();
1140         }
1141       });
1142   }
1143 
1144   // Attach to existing JVMDebugger, which should be already attached to a core/process.
1145   private void attach(JVMDebugger d) {
1146     attached = true;
1147     showThreadsDialog();
1148   }
1149 
1150   /** NOTE we are in a different thread here than either the main
1151       thread or the Swing/AWT event handler thread, so we must be very
1152       careful when creating or removing widgets */
1153   private void attach(String pidText) {
1154       try {
1155       this.pidText = pidText;
1156       pid = Integer.parseInt(pidText);
1157     }
1158     catch (NumberFormatException e) {
1159       SwingUtilities.invokeLater(new Runnable() {
1160           public void run() {
1161             setMenuItemsEnabled(attachMenuItems, true);
1162             JOptionPane.showInternalMessageDialog(desktop,
1163                                                   "Unable to parse process ID \"" + HSDB.this.pidText + "\".\nPlease enter a number.",
1164                                                   "Parse error",
1165                                                   JOptionPane.WARNING_MESSAGE);
1166           }
1167         });
1168       return;
1169     }
1170 
1171     // Try to attach to this process
1172     Runnable remover = new Runnable() {
1173           public void run() {
1174             attachWaitDialog.setVisible(false);
1175             desktop.remove(attachWaitDialog);
1176             attachWaitDialog = null;
1177           }
1178       };
1179 
1180     try {
1181       SwingUtilities.invokeLater(new Runnable() {
1182           public void run() {
1183             JOptionPane pane = new JOptionPane("Attaching to process " + pid + ", please wait...", JOptionPane.INFORMATION_MESSAGE);
1184             pane.setOptions(new Object[] {});
1185             attachWaitDialog = pane.createInternalFrame(desktop, "Attaching to Process");
1186             attachWaitDialog.show();
1187           }
1188         });
1189 
1190       // FIXME: display exec'd debugger's output messages during this
1191       // lengthy call
1192       agent.attach(pid);
1193       if (agent.getDebugger().hasConsole()) {
1194         showDbgConsoleMenuItem.setEnabled(true);
1195       }
1196       attached = true;
1197       SwingUtilities.invokeLater(remover);
1198     }
1199     catch (DebuggerException e) {
1200       SwingUtilities.invokeLater(remover);
1201       final String errMsg = formatMessage(e.getMessage(), 80);
1202       SwingUtilities.invokeLater(new Runnable() {
1203           public void run() {
1204             setMenuItemsEnabled(attachMenuItems, true);
1205             JOptionPane.showInternalMessageDialog(desktop,
1206                                                   "Unable to connect to process ID " + pid + ":\n\n" + errMsg,
1207                                                   "Unable to Connect",
1208                                                   JOptionPane.WARNING_MESSAGE);
1209           }
1210         });
1211       agent.detach();
1212       return;
1213     }
1214 
1215     // OK, the VM should be available. Create the Threads dialog.
1216     showThreadsDialog();
1217   }
1218 
1219   /** NOTE we are in a different thread here than either the main
1220       thread or the Swing/AWT event handler thread, so we must be very
1221       careful when creating or removing widgets */
1222   private void attach(final String executablePath, final String corePath) {
1223     // Try to open this core file
1224     Runnable remover = new Runnable() {
1225           public void run() {
1226             attachWaitDialog.setVisible(false);
1227             desktop.remove(attachWaitDialog);
1228             attachWaitDialog = null;
1229           }
1230       };
1231 
1232     try {
1233       SwingUtilities.invokeLater(new Runnable() {
1234           public void run() {
1235             JOptionPane pane = new JOptionPane("Opening core file, please wait...", JOptionPane.INFORMATION_MESSAGE);
1236             pane.setOptions(new Object[] {});
1237             attachWaitDialog = pane.createInternalFrame(desktop, "Opening Core File");
1238             attachWaitDialog.show();
1239           }
1240         });
1241 
1242       // FIXME: display exec'd debugger's output messages during this
1243       // lengthy call
1244       agent.attach(executablePath, corePath);
1245       if (agent.getDebugger().hasConsole()) {
1246         showDbgConsoleMenuItem.setEnabled(true);
1247       }
1248       attached = true;
1249       SwingUtilities.invokeLater(remover);
1250     }
1251     catch (DebuggerException e) {
1252       SwingUtilities.invokeLater(remover);
1253       final String errMsg = formatMessage(e.getMessage(), 80);
1254       SwingUtilities.invokeLater(new Runnable() {
1255           public void run() {
1256             setMenuItemsEnabled(attachMenuItems, true);
1257             JOptionPane.showInternalMessageDialog(desktop,
1258                                                   "Unable to open core file\n" + corePath + ":\n\n" + errMsg,
1259                                                   "Unable to Open Core File",
1260                                                   JOptionPane.WARNING_MESSAGE);
1261           }
1262         });
1263       agent.detach();
1264       return;
1265     }
1266 
1267     // OK, the VM should be available. Create the Threads dialog.
1268     showThreadsDialog();
1269   }
1270 
1271   /** NOTE we are in a different thread here than either the main
1272       thread or the Swing/AWT event handler thread, so we must be very
1273       careful when creating or removing widgets */
1274   private void connect(final String remoteMachineName) {
1275     // Try to open this core file
1276     Runnable remover = new Runnable() {
1277           public void run() {
1278             attachWaitDialog.setVisible(false);
1279             desktop.remove(attachWaitDialog);
1280             attachWaitDialog = null;
1281           }
1282       };
1283 
1284     try {
1285       SwingUtilities.invokeLater(new Runnable() {
1286           public void run() {
1287             JOptionPane pane = new JOptionPane("Connecting to debug server, please wait...", JOptionPane.INFORMATION_MESSAGE);
1288             pane.setOptions(new Object[] {});
1289             attachWaitDialog = pane.createInternalFrame(desktop, "Connecting to Debug Server");
1290             attachWaitDialog.show();
1291           }
1292         });
1293 
1294       agent.attach(remoteMachineName);
1295       if (agent.getDebugger().hasConsole()) {
1296         showDbgConsoleMenuItem.setEnabled(true);
1297       }
1298       attached = true;
1299       SwingUtilities.invokeLater(remover);
1300     }
1301     catch (DebuggerException e) {
1302       SwingUtilities.invokeLater(remover);
1303       final String errMsg = formatMessage(e.getMessage(), 80);
1304       SwingUtilities.invokeLater(new Runnable() {
1305           public void run() {
1306             setMenuItemsEnabled(attachMenuItems, true);
1307             JOptionPane.showInternalMessageDialog(desktop,
1308                                                   "Unable to connect to machine \"" + remoteMachineName + "\":\n\n" + errMsg,
1309                                                   "Unable to Connect",
1310                                                   JOptionPane.WARNING_MESSAGE);
1311           }
1312         });
1313       agent.detach();
1314       return;
1315     }
1316 
1317     // OK, the VM should be available. Create the Threads dialog.
1318     showThreadsDialog();
1319   }
1320 
1321   private void detachDebugger() {
1322     if (!attached) {
1323       return;
1324     }
1325     agent.detach();
1326     attached = false;
1327   }
1328 
1329   private void detach() {
1330     detachDebugger();
1331     attachWaitDialog = null;
1332     threadsFrame = null;
1333     consoleFrame = null;
1334     setMenuItemsEnabled(attachMenuItems, true);
1335     setMenuItemsEnabled(detachMenuItems, false);
1336     toolsMenu.setEnabled(false);
1337     showDbgConsoleMenuItem.setEnabled(false);
1338     // FIXME: is this sufficient, or will I have to do anything else
1339     // to the components to kill them off? What about WorkerThreads?
1340     desktop.removeAll();
1341     desktop.invalidate();
1342     desktop.validate();
1343     desktop.repaint();
1344   }
1345 
1346   /** NOTE that this is called from another thread than the main or
1347       Swing thread and we have to be careful about synchronization */
1348   private void showThreadsDialog() {
1349     SwingUtilities.invokeLater(new Runnable() {
1350         public void run() {
1351           threadsFrame = new JInternalFrame("Java Threads");
1352           threadsFrame.setResizable(true);
1353           threadsFrame.setIconifiable(true);
1354           JavaThreadsPanel threadsPanel = new JavaThreadsPanel();
1355           threadsPanel.addPanelListener(HSDB.this);
1356           threadsFrame.getContentPane().add(threadsPanel);
1357           threadsFrame.setSize(500, 300);
1358           threadsFrame.pack();
1359           desktop.add(threadsFrame);
1360           GraphicsUtilities.moveToInContainer(threadsFrame, 0.75f, 0.25f, 0, 20);
1361           threadsFrame.show();
1362           setMenuItemsEnabled(attachMenuItems, false);
1363           setMenuItemsEnabled(detachMenuItems, true);
1364           toolsMenu.setEnabled(true);
1365           VM.registerVMInitializedObserver(new Observer() {
1366               public void update(Observable o, Object data) {
1367                 computeRevPtrsMenuItem.setEnabled(true);
1368               }
1369             });
1370         }
1371       });
1372   }
1373 
1374   private void showObjectHistogram() {
1375     sun.jvm.hotspot.oops.ObjectHistogram histo = new sun.jvm.hotspot.oops.ObjectHistogram();
1376     ObjectHistogramCleanupThunk cleanup =
1377       new ObjectHistogramCleanupThunk(histo);
1378     doHeapIteration("Object Histogram",
1379                     "Generating histogram...",
1380                     histo,
1381                     cleanup);
1382   }
1383 
1384   class ObjectHistogramCleanupThunk implements CleanupThunk {
1385     sun.jvm.hotspot.oops.ObjectHistogram histo;
1386 
1387     ObjectHistogramCleanupThunk(sun.jvm.hotspot.oops.ObjectHistogram histo) {
1388       this.histo = histo;
1389     }
1390 
1391     public void heapIterationComplete() {
1392       SwingUtilities.invokeLater(new Runnable() {
1393           public void run() {
1394             JInternalFrame histoFrame = new JInternalFrame("Object Histogram");
1395             histoFrame.setResizable(true);
1396             histoFrame.setClosable(true);
1397             histoFrame.setIconifiable(true);
1398             histoFrame.getContentPane().setLayout(new BorderLayout());
1399             ObjectHistogramPanel panel = new ObjectHistogramPanel(histo);
1400             panel.addPanelListener(HSDB.this);
1401             histoFrame.getContentPane().add(panel);
1402             desktop.add(histoFrame);
1403             GraphicsUtilities.reshapeToAspectRatio(histoFrame, 4.0f / 3.0f, 0.6f,
1404                                        histoFrame.getParent().getSize());
1405             GraphicsUtilities.centerInContainer(histoFrame);
1406             histoFrame.show();
1407           }
1408         });
1409     }
1410   }
1411 
1412   public void showObjectsOfType(Klass type) {
1413     FindObjectByType finder = new FindObjectByType(type);
1414     FindObjectByTypeCleanupThunk cleanup =
1415       new FindObjectByTypeCleanupThunk(finder);
1416     ByteArrayOutputStream bos = new ByteArrayOutputStream();
1417     type.printValueOn(new PrintStream(bos));
1418     String typeName = bos.toString();
1419     doHeapIteration("Show Objects Of Type",
1420                     "Finding instances of \"" + typeName + "\"",
1421                     finder,
1422                     cleanup);
1423   }
1424 
1425   class FindObjectByTypeCleanupThunk implements CleanupThunk {
1426     FindObjectByType finder;
1427 
1428     FindObjectByTypeCleanupThunk(FindObjectByType finder) {
1429       this.finder = finder;
1430     }
1431 
1432     public void heapIterationComplete() {
1433       SwingUtilities.invokeLater(new Runnable() {
1434           public void run() {
1435             JInternalFrame finderFrame = new JInternalFrame("Show Objects of Type");
1436             finderFrame.getContentPane().setLayout(new BorderLayout());
1437             finderFrame.setResizable(true);
1438             finderFrame.setClosable(true);
1439             finderFrame.setIconifiable(true);
1440             ObjectListPanel panel = new ObjectListPanel(finder.getResults(),
1441                                                         new HeapProgress("Reverse Pointers Analysis"));
1442             panel.addPanelListener(HSDB.this);
1443             finderFrame.getContentPane().add(panel);
1444             desktop.add(finderFrame);
1445             GraphicsUtilities.reshapeToAspectRatio(finderFrame, 4.0f / 3.0f, 0.6f,
1446                                        finderFrame.getParent().getSize());
1447             GraphicsUtilities.centerInContainer(finderFrame);
1448             finderFrame.show();
1449           }
1450         });
1451     }
1452   }
1453 
1454   private void showDebuggerConsole() {
1455     if (consoleFrame == null) {
1456       consoleFrame = new JInternalFrame("Debugger Console");
1457       consoleFrame.setResizable(true);
1458       consoleFrame.setClosable(true);
1459       consoleFrame.setIconifiable(true);
1460       consoleFrame.getContentPane().setLayout(new BorderLayout());
1461       consoleFrame.getContentPane().add(new DebuggerConsolePanel(agent.getDebugger()), BorderLayout.CENTER);
1462       GraphicsUtilities.reshapeToAspectRatio(consoleFrame, 5.0f, 0.9f, desktop.getSize());
1463     }
1464     if (consoleFrame.getParent() == null) {
1465       desktop.add(consoleFrame);
1466     }
1467     consoleFrame.setVisible(true);
1468     consoleFrame.show();
1469     consoleFrame.getContentPane().getComponent(0).requestFocus();
1470   }
1471 
1472   private void showConsole() {
1473       CommandProcessor.DebuggerInterface di = new CommandProcessor.DebuggerInterface() {
1474               public HotSpotAgent getAgent() {
1475                   return agent;
1476               }
1477               public boolean isAttached() {
1478                   return attached;
1479               }
1480               public void attach(String pid) {
1481                   attach(pid);
1482               }
1483               public void attach(String java, String core) {
1484               }
1485               public void detach() {
1486                   detachDebugger();
1487               }
1488               public void reattach() {
1489                   if (attached) {
1490                       detachDebugger();
1491                   }
1492                   if (pidText != null) {
1493                       attach(pidText);
1494                   } else {
1495                       attach(execPath, coreFilename);
1496                   }
1497               }
1498           };
1499 
1500       showPanel("Command Line", new CommandProcessorPanel(new CommandProcessor(di, null, null, null)));
1501   }
1502 
1503   private void showFindByQueryPanel() {
1504     showPanel("Find Object by Query", new FindByQueryPanel());
1505   }
1506 
1507   private void showFindPanel() {
1508     showPanel("Find Pointer", new FindPanel());
1509   }
1510 
1511   private void showFindInHeapPanel() {
1512     showPanel("Find Address In Heap", new FindInHeapPanel());
1513   }
1514 
1515   private void showFindInCodeCachePanel() {
1516     showPanel("Find Address In Code Cache", new FindInCodeCachePanel());
1517   }
1518 
1519   private void showHeapParametersPanel() {
1520     showPanel("Heap Parameters", new HeapParametersPanel());
1521   }
1522 
1523   public void showThreadInfo(final JavaThread thread) {
1524     showPanel("Info for " + thread.getThreadName(), new ThreadInfoPanel(thread));
1525   }
1526 
1527   public void showJavaStackTrace(final JavaThread thread) {
1528     JavaStackTracePanel jstp = new JavaStackTracePanel();
1529     showPanel("Java stack trace for " + thread.getThreadName(), jstp);
1530     jstp.setJavaThread(thread);
1531   }
1532 
1533   private void showDeadlockDetectionPanel() {
1534     showPanel("Deadlock Detection", new DeadlockDetectionPanel());
1535   }
1536 
1537   private void showMonitorCacheDumpPanel() {
1538     showPanel("Monitor Cache Dump", new MonitorCacheDumpPanel());
1539   }
1540 
1541   public void showClassBrowser() {
1542     final JInternalFrame progressFrame = new JInternalFrame("Class Browser");
1543     progressFrame.setResizable(true);
1544     progressFrame.setClosable(true);
1545     progressFrame.setIconifiable(true);
1546     progressFrame.getContentPane().setLayout(new BorderLayout());
1547     final ProgressBarPanel bar = new ProgressBarPanel("Generating class list ..");
1548     bar.setIndeterminate(true);
1549     progressFrame.getContentPane().add(bar, BorderLayout.CENTER);
1550     desktop.add(progressFrame);
1551     progressFrame.pack();
1552     GraphicsUtilities.centerInContainer(progressFrame);
1553     progressFrame.show();
1554 
1555     workerThread.invokeLater(new Runnable() {
1556                                 public void run() {
1557                                    HTMLGenerator htmlGen = new HTMLGenerator();
1558                                    InstanceKlass[] klasses = SystemDictionaryHelper.getAllInstanceKlasses();
1559                                    final String htmlText = htmlGen.genHTMLForKlassNames(klasses);
1560                                    SwingUtilities.invokeLater(new Runnable() {
1561                                       public void run() {
1562                                          JInternalFrame cbFrame = new JInternalFrame("Class Browser");
1563                                          cbFrame.getContentPane().setLayout(new BorderLayout());
1564                                          cbFrame.setResizable(true);
1565                                          cbFrame.setClosable(true);
1566                                          cbFrame.setIconifiable(true);
1567                                          ClassBrowserPanel cbPanel = new ClassBrowserPanel();
1568                                          cbFrame.getContentPane().add(cbPanel, BorderLayout.CENTER);
1569                                          desktop.remove(progressFrame);
1570                                          desktop.repaint();
1571                                          desktop.add(cbFrame);
1572                                          GraphicsUtilities.reshapeToAspectRatio(cbFrame, 1.25f, 0.85f,
1573                                                                       cbFrame.getParent().getSize());
1574                                          cbFrame.show();
1575                                          cbPanel.setClassesText(htmlText);
1576                                       }
1577                                    });
1578                                 }
1579                              });
1580   }
1581 
1582   public void showCodeViewer() {
1583     showPanel("Code Viewer", new CodeViewerPanel(), 1.25f, 0.85f);
1584   }
1585 
1586   public void showCodeViewer(final Address address) {
1587     final CodeViewerPanel panel = new CodeViewerPanel();
1588     showPanel("Code Viewer", panel, 1.25f, 0.85f);
1589     SwingUtilities.invokeLater(new Runnable() {
1590         public void run() {
1591           panel.viewAddress(address);
1592         }
1593       });
1594 
1595   }
1596 
1597   public void showMemoryViewer() {
1598     showPanel("Memory Viewer", new MemoryViewer(agent.getDebugger(), agent.getTypeDataBase().getAddressSize() == 8));
1599   }
1600 
1601   public void showCommandLineFlags() {
1602     showPanel("Command Line Flags", new VMFlagsPanel());
1603   }
1604 
1605   public void showVMVersion() {
1606     showPanel("VM Version Info", new VMVersionInfoPanel());
1607   }
1608 
1609   public void showSystemProperties() {
1610     showPanel("System Properties", new SysPropsPanel());
1611   }
1612 
1613   private void showPanel(String name, JPanel panel) {
1614     showPanel(name, panel, 5.0f / 3.0f, 0.4f);
1615   }
1616 
1617   private void showPanel(String name, JPanel panel, float aspectRatio, float fillRatio) {
1618     JInternalFrame frame = new JInternalFrame(name);
1619     frame.getContentPane().setLayout(new BorderLayout());
1620     frame.setResizable(true);
1621     frame.setClosable(true);
1622     frame.setIconifiable(true);
1623     frame.setMaximizable(true);
1624     frame.getContentPane().add(panel, BorderLayout.CENTER);
1625     desktop.add(frame);
1626     GraphicsUtilities.reshapeToAspectRatio(frame, aspectRatio, fillRatio, frame.getParent().getSize());
1627     GraphicsUtilities.randomLocation(frame);
1628     frame.show();
1629     if (panel instanceof SAPanel) {
1630       ((SAPanel)panel).addPanelListener(this);
1631     }
1632   }
1633 
1634   //--------------------------------------------------------------------------------
1635   // Framework for heap iteration with progress bar
1636   //
1637 
1638   interface CleanupThunk {
1639     public void heapIterationComplete();
1640   }
1641 
1642   class HeapProgress implements HeapProgressThunk {
1643     private JInternalFrame frame;
1644     private ProgressBarPanel bar;
1645     private String windowTitle;
1646     private String progressBarTitle;
1647     private CleanupThunk cleanup;
1648 
1649     HeapProgress(String windowTitle) {
1650       this(windowTitle, "Percentage of heap visited", null);
1651     }
1652 
1653     HeapProgress(String windowTitle, String progressBarTitle) {
1654       this(windowTitle, progressBarTitle, null);
1655     }
1656 
1657     HeapProgress(String windowTitle, String progressBarTitle, CleanupThunk cleanup) {
1658       this.windowTitle = windowTitle;
1659       this.progressBarTitle = progressBarTitle;
1660       this.cleanup = cleanup;
1661     }
1662 
1663     public void heapIterationFractionUpdate(final double fractionOfHeapVisited) {
1664       if (frame == null) {
1665         SwingUtilities.invokeLater(new Runnable() {
1666             public void run() {
1667               frame = new JInternalFrame(windowTitle);
1668               frame.setResizable(true);
1669               frame.setIconifiable(true);
1670               frame.getContentPane().setLayout(new BorderLayout());
1671               bar = new ProgressBarPanel(progressBarTitle);
1672               frame.getContentPane().add(bar, BorderLayout.CENTER);
1673               desktop.add(frame);
1674               frame.pack();
1675               GraphicsUtilities.constrainToSize(frame, frame.getParent().getSize());
1676               GraphicsUtilities.centerInContainer(frame);
1677               frame.show();
1678             }
1679           });
1680       }
1681 
1682       SwingUtilities.invokeLater(new Runnable() {
1683           public void run() {
1684             bar.setValue(fractionOfHeapVisited);
1685           }
1686         });
1687     }
1688 
1689     public void heapIterationComplete() {
1690       SwingUtilities.invokeLater(new Runnable() {
1691           public void run() {
1692             desktop.remove(frame);
1693             desktop.repaint();
1694             if (VM.getVM().getRevPtrs() != null) {
1695               // Ended up computing reverse pointers as a side-effect
1696               computeRevPtrsMenuItem.setEnabled(false);
1697             }
1698           }
1699         });
1700 
1701       if (cleanup != null) {
1702         cleanup.heapIterationComplete();
1703       }
1704     }
1705   }
1706 
1707   class VisitHeap implements Runnable {
1708     HeapVisitor visitor;
1709 
1710     VisitHeap(HeapVisitor visitor) {
1711       this.visitor = visitor;
1712     }
1713 
1714     public void run() {
1715       VM.getVM().getObjectHeap().iterate(visitor);
1716     }
1717   }
1718 
1719   private void doHeapIteration(String frameTitle,
1720                                String progressBarText,
1721                                HeapVisitor visitor,
1722                                CleanupThunk cleanup) {
1723     sun.jvm.hotspot.oops.ObjectHistogram histo = new sun.jvm.hotspot.oops.ObjectHistogram();
1724     HeapProgress progress = new HeapProgress(frameTitle,
1725                                              progressBarText,
1726                                              cleanup);
1727     HeapVisitor progVisitor = new ProgressiveHeapVisitor(visitor, progress);
1728     workerThread.invokeLater(new VisitHeap(progVisitor));
1729   }
1730 
1731   //--------------------------------------------------------------------------------
1732   // Stack trace helper
1733   //
1734 
1735   private static JavaVFrame getLastJavaVFrame(JavaThread cur) {
1736     RegisterMap regMap = cur.newRegisterMap(true);
1737     sun.jvm.hotspot.runtime.Frame f = cur.getCurrentFrameGuess();
1738     if (f == null) return null;
1739     boolean imprecise = true;
1740     if (f.isInterpretedFrame() && !f.isInterpretedFrameValid()) {
1741       System.err.println("Correcting for invalid interpreter frame");
1742       f = f.sender(regMap);
1743       imprecise = false;
1744     }
1745     VFrame vf = VFrame.newVFrame(f, regMap, cur, true, imprecise);
1746     if (vf == null) {
1747       System.err.println(" (Unable to create vframe for topmost frame guess)");
1748       return null;
1749     }
1750     if (vf.isJavaFrame()) {
1751       return (JavaVFrame) vf;
1752     }
1753     return (JavaVFrame) vf.javaSender();
1754   }
1755 
1756   // Internal routine for debugging
1757   private static void dumpStack(JavaThread cur) {
1758     RegisterMap regMap = cur.newRegisterMap(true);
1759     sun.jvm.hotspot.runtime.Frame f = cur.getCurrentFrameGuess();
1760     PrintStream tty = System.err;
1761     while (f != null) {
1762       tty.print("Found ");
1763            if (f.isInterpretedFrame()) { tty.print("interpreted"); }
1764       else if (f.isCompiledFrame())    { tty.print("compiled"); }
1765       else if (f.isEntryFrame())       { tty.print("entry"); }
1766       else if (f.isNativeFrame())      { tty.print("native"); }
1767       else if (f.isRuntimeFrame())     { tty.print("runtime"); }
1768       else { tty.print("external"); }
1769       tty.print(" frame with PC = " + f.getPC() + ", SP = " + f.getSP() + ", FP = " + f.getFP());
1770       if (f.isSignalHandlerFrameDbg()) {
1771         tty.print(" (SIGNAL HANDLER)");
1772       }
1773       tty.println();
1774 
1775       if (!f.isFirstFrame()) {
1776         f = f.sender(regMap);
1777       } else {
1778         f = null;
1779       }
1780     }
1781   }
1782 
1783   //--------------------------------------------------------------------------------
1784   // Component utilities
1785   //
1786 
1787   private static JMenuItem createMenuItem(String name, ActionListener l) {
1788     JMenuItem item = new JMenuItem(name);
1789     item.addActionListener(l);
1790     return item;
1791   }
1792 
1793   /** Punctuates the given string with \n's where necessary to not
1794       exceed the given number of characters per line. Strips
1795       extraneous whitespace. */
1796   private String formatMessage(String message, int charsPerLine) {
1797     StringBuffer buf = new StringBuffer(message.length());
1798     StringTokenizer tokenizer = new StringTokenizer(message);
1799     int curLineLength = 0;
1800     while (tokenizer.hasMoreTokens()) {
1801       String tok = tokenizer.nextToken();
1802       if (curLineLength + tok.length() > charsPerLine) {
1803         buf.append('\n');
1804         curLineLength = 0;
1805       } else {
1806         if (curLineLength != 0) {
1807           buf.append(' ');
1808           ++curLineLength;
1809         }
1810       }
1811       buf.append(tok);
1812       curLineLength += tok.length();
1813     }
1814     return buf.toString();
1815   }
1816 
1817   private void setMenuItemsEnabled(java.util.List items, boolean enabled) {
1818     for (Iterator iter = items.iterator(); iter.hasNext(); ) {
1819       ((JMenuItem) iter.next()).setEnabled(enabled);
1820     }
1821   }
1822 }
--- EOF ---