1 /*
   2  * Copyright (c) 2000, 2006, 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,
  20  * CA 94065 USA or visit www.oracle.com if you need additional information or
  21  * have any questions.
  22  *
  23  */
  24 
  25 package sun.jvm.hotspot;
  26 
  27 import java.io.PrintStream;
  28 import java.net.*;
  29 import java.rmi.*;
  30 import sun.jvm.hotspot.debugger.*;
  31 import sun.jvm.hotspot.debugger.dbx.*;
  32 import sun.jvm.hotspot.debugger.proc.*;
  33 import sun.jvm.hotspot.debugger.remote.*;
  34 import sun.jvm.hotspot.debugger.win32.*;
  35 import sun.jvm.hotspot.debugger.windbg.*;
  36 import sun.jvm.hotspot.debugger.linux.*;
  37 import sun.jvm.hotspot.memory.*;
  38 import sun.jvm.hotspot.oops.*;
  39 import sun.jvm.hotspot.runtime.*;
  40 import sun.jvm.hotspot.types.*;
  41 import sun.jvm.hotspot.utilities.*;
  42 
  43 /** <P> This class wraps much of the basic functionality and is the
  44  * highest-level factory for VM data structures. It makes it simple
  45  * to start up the debugging system. </P>
  46  *
  47  * <P> FIXME: need to add a way to configure the paths to dbx and the
  48  * DSO from the outside. However, this should work for now for
  49  * internal use. </P>
  50  *
  51  * <P> FIXME: especially with the addition of remote debugging, this
  52  * has turned into a mess; needs rethinking. </P>
  53  */
  54 
  55 public class HotSpotAgent {
  56     private JVMDebugger debugger;
  57     private MachineDescription machDesc;
  58     private TypeDataBase db;
  59 
  60     private String os;
  61     private String cpu;
  62     private String fileSep;
  63 
  64     // The system can work in several ways:
  65     //  - Attaching to local process
  66     //  - Attaching to local core file
  67     //  - Connecting to remote debug server
  68     //  - Starting debug server for process
  69     //  - Starting debug server for core file
  70 
  71     // These are options for the "client" side of things
  72     private static final int PROCESS_MODE   = 0;
  73     private static final int CORE_FILE_MODE = 1;
  74     private static final int REMOTE_MODE    = 2;
  75     private int startupMode;
  76 
  77     // This indicates whether we are really starting a server or not
  78     private boolean isServer;
  79 
  80     // All possible required information for connecting
  81     private int pid;
  82     private String javaExecutableName;
  83     private String coreFileName;
  84     private String debugServerID;
  85 
  86     // All needed information for server side
  87     private String serverID;
  88 
  89     private String[] jvmLibNames;
  90 
  91     // FIXME: make these configurable, i.e., via a dotfile; also
  92     // consider searching within the JDK from which this Java executable
  93     // comes to find them
  94     private static final String defaultDbxPathPrefix                = "/net/jano.sfbay/export/disk05/hotspot/sa";
  95     private static final String defaultDbxSvcAgentDSOPathPrefix     = "/net/jano.sfbay/export/disk05/hotspot/sa";
  96 
  97     static void showUsage() {
  98         System.out.println("    You can also pass these -D options to java to specify where to find dbx and the \n" +
  99         "    Serviceability Agent plugin for dbx:");
 100         System.out.println("       -DdbxPathName=<path-to-dbx-executable>\n" +
 101         "             Default is derived from dbxPathPrefix");
 102         System.out.println("    or");
 103         System.out.println("       -DdbxPathPrefix=<xxx>\n" +
 104         "             where xxx is the path name of a dir structure that contains:\n" +
 105         "                   <os>/<arch>/bin/dbx\n" +
 106         "             The default is " + defaultDbxPathPrefix);
 107         System.out.println("    and");
 108         System.out.println("       -DdbxSvcAgentDSOPathName=<path-to-dbx-serviceability-agent-module>\n" +
 109         "             Default is determined from dbxSvcAgentDSOPathPrefix");
 110         System.out.println("    or");
 111         System.out.println("       -DdbxSvcAgentDSOPathPrefix=<xxx>\n" +
 112         "             where xxx is the pathname of a dir structure that contains:\n" +
 113         "                   <os>/<arch>/bin/lib/libsvc_agent_dbx.so\n" +
 114         "             The default is " + defaultDbxSvcAgentDSOPathPrefix);
 115     }
 116 
 117     public HotSpotAgent() {
 118         // for non-server add shutdown hook to clean-up debugger in case
 119         // of forced exit. For remote server, shutdown hook is added by
 120         // DebugServer.
 121         Runtime.getRuntime().addShutdownHook(new java.lang.Thread(
 122         new Runnable() {
 123             public void run() {
 124                 synchronized (HotSpotAgent.this) {
 125                     if (!isServer) {
 126                         detach();
 127                     }
 128                 }
 129             }
 130         }));
 131     }
 132 
 133     //--------------------------------------------------------------------------------
 134     // Accessors (once the system is set up)
 135     //
 136 
 137     public synchronized Debugger getDebugger() {
 138         return debugger;
 139     }
 140 
 141     public synchronized TypeDataBase getTypeDataBase() {
 142         return db;
 143     }
 144 
 145     //--------------------------------------------------------------------------------
 146     // Client-side operations
 147     //
 148 
 149     /** This attaches to a process running on the local machine. */
 150     public synchronized void attach(int processID)
 151     throws DebuggerException {
 152         if (debugger != null) {
 153             throw new DebuggerException("Already attached");
 154         }
 155         pid = processID;
 156         startupMode = PROCESS_MODE;
 157         isServer = false;
 158         go();
 159     }
 160 
 161     /** This opens a core file on the local machine */
 162     public synchronized void attach(String javaExecutableName, String coreFileName)
 163     throws DebuggerException {
 164         if (debugger != null) {
 165             throw new DebuggerException("Already attached");
 166         }
 167         if ((javaExecutableName == null) || (coreFileName == null)) {
 168             throw new DebuggerException("Both the core file name and Java executable name must be specified");
 169         }
 170         this.javaExecutableName = javaExecutableName;
 171         this.coreFileName = coreFileName;
 172         startupMode = CORE_FILE_MODE;
 173         isServer = false;
 174         go();
 175     }
 176 
 177     /** This attaches to a "debug server" on a remote machine; this
 178       remote server has already attached to a process or opened a
 179       core file and is waiting for RMI calls on the Debugger object to
 180       come in. */
 181     public synchronized void attach(String remoteServerID)
 182     throws DebuggerException {
 183         if (debugger != null) {
 184             throw new DebuggerException("Already attached to a process");
 185         }
 186         if (remoteServerID == null) {
 187             throw new DebuggerException("Debug server id must be specified");
 188         }
 189 
 190         debugServerID = remoteServerID;
 191         startupMode = REMOTE_MODE;
 192         isServer = false;
 193         go();
 194     }
 195 
 196     /** This should only be called by the user on the client machine,
 197       not the server machine */
 198     public synchronized boolean detach() throws DebuggerException {
 199         if (isServer) {
 200             throw new DebuggerException("Should not call detach() for server configuration");
 201         }
 202         return detachInternal();
 203     }
 204 
 205     //--------------------------------------------------------------------------------
 206     // Server-side operations
 207     //
 208 
 209     /** This attaches to a process running on the local machine and
 210       starts a debug server, allowing remote machines to connect and
 211       examine this process. Uses specified name to uniquely identify a
 212       specific debuggee on the server */
 213     public synchronized void startServer(int processID, String uniqueID) {
 214         if (debugger != null) {
 215             throw new DebuggerException("Already attached");
 216         }
 217         pid = processID;
 218         startupMode = PROCESS_MODE;
 219         isServer = true;
 220         serverID = uniqueID;
 221         go();
 222     }
 223 
 224     /** This attaches to a process running on the local machine and
 225       starts a debug server, allowing remote machines to connect and
 226       examine this process. */
 227     public synchronized void startServer(int processID)
 228     throws DebuggerException {
 229         startServer(processID, null);
 230     }
 231 
 232     /** This opens a core file on the local machine and starts a debug
 233       server, allowing remote machines to connect and examine this
 234       core file. Uses supplied uniqueID to uniquely identify a specific
 235       debugee */
 236     public synchronized void startServer(String javaExecutableName,
 237     String coreFileName,
 238     String uniqueID) {
 239         if (debugger != null) {
 240             throw new DebuggerException("Already attached");
 241         }
 242         if ((javaExecutableName == null) || (coreFileName == null)) {
 243             throw new DebuggerException("Both the core file name and Java executable name must be specified");
 244         }
 245         this.javaExecutableName = javaExecutableName;
 246         this.coreFileName = coreFileName;
 247         startupMode = CORE_FILE_MODE;
 248         isServer = true;
 249         serverID = uniqueID;
 250         go();
 251     }
 252 
 253     /** This opens a core file on the local machine and starts a debug
 254       server, allowing remote machines to connect and examine this
 255       core file. */
 256     public synchronized void startServer(String javaExecutableName, String coreFileName)
 257     throws DebuggerException {
 258         startServer(javaExecutableName, coreFileName, null);
 259     }
 260 
 261     /** This may only be called on the server side after startServer()
 262       has been called */
 263     public synchronized boolean shutdownServer() throws DebuggerException {
 264         if (!isServer) {
 265             throw new DebuggerException("Should not call shutdownServer() for client configuration");
 266         }
 267         return detachInternal();
 268     }
 269 
 270 
 271     //--------------------------------------------------------------------------------
 272     // Internals only below this point
 273     //
 274 
 275     private boolean detachInternal() {
 276         if (debugger == null) {
 277             return false;
 278         }
 279         boolean retval = true;
 280         if (!isServer) {
 281             VM.shutdown();
 282         }
 283         // We must not call detach() if we are a client and are connected
 284         // to a remote debugger
 285         Debugger dbg = null;
 286         DebuggerException ex = null;
 287         if (isServer) {
 288             try {
 289                 RMIHelper.unbind(serverID);
 290             }
 291             catch (DebuggerException de) {
 292                 ex = de;
 293             }
 294             dbg = debugger;
 295         } else {
 296             if (startupMode != REMOTE_MODE) {
 297                 dbg = debugger;
 298             }
 299         }
 300         if (dbg != null) {
 301             retval = dbg.detach();
 302         }
 303 
 304         debugger = null;
 305         machDesc = null;
 306         db = null;
 307         if (ex != null) {
 308             throw(ex);
 309         }
 310         return retval;
 311     }
 312 
 313     private void go() {
 314         setupDebugger();
 315         setupVM();
 316     }
 317 
 318     private void setupDebugger() {
 319         if (startupMode != REMOTE_MODE) {
 320             //
 321             // Local mode (client attaching to local process or setting up
 322             // server, but not client attaching to server)
 323             //
 324 
 325             try {
 326                 os  = PlatformInfo.getOS();
 327                 cpu = PlatformInfo.getCPU();
 328             }
 329             catch (UnsupportedPlatformException e) {
 330                 throw new DebuggerException(e);
 331             }
 332             fileSep = System.getProperty("file.separator");
 333 
 334             if (os.equals("solaris")) {
 335                 setupDebuggerSolaris();
 336             } else if (os.equals("win32")) {
 337                 setupDebuggerWin32();
 338             } else if (os.equals("linux")) {
 339                 setupDebuggerLinux();
 340             } else {
 341                 // Add support for more operating systems here
 342                 throw new DebuggerException("Operating system " + os + " not yet supported");
 343             }
 344 
 345             if (isServer) {
 346                 RemoteDebuggerServer remote = null;
 347                 try {
 348                     remote = new RemoteDebuggerServer(debugger);
 349                 }
 350                 catch (RemoteException rem) {
 351                     throw new DebuggerException(rem);
 352                 }
 353                 RMIHelper.rebind(serverID, remote);
 354             }
 355         } else {
 356             //
 357             // Remote mode (client attaching to server)
 358             //
 359 
 360             // Create and install a security manager
 361 
 362             // FIXME: currently commented out because we were having
 363             // security problems since we're "in the sun.* hierarchy" here.
 364             // Perhaps a permissive policy file would work around this. In
 365             // the long run, will probably have to move into com.sun.*.
 366 
 367             //    if (System.getSecurityManager() == null) {
 368             //      System.setSecurityManager(new RMISecurityManager());
 369             //    }
 370 
 371             connectRemoteDebugger();
 372         }
 373     }
 374 
 375     private void setupVM() {
 376         // We need to instantiate a HotSpotTypeDataBase on both the client
 377         // and server machine. On the server it is only currently used to
 378         // configure the Java primitive type sizes (which we should
 379         // consider making constant). On the client it is used to
 380         // configure the VM.
 381 
 382         try {
 383             if (os.equals("solaris")) {
 384                 db = new HotSpotTypeDataBase(machDesc,
 385                 new HotSpotSolarisVtblAccess(debugger, jvmLibNames),
 386                 debugger, jvmLibNames);
 387             } else if (os.equals("win32")) {
 388                 db = new HotSpotTypeDataBase(machDesc,
 389                 new Win32VtblAccess(debugger, jvmLibNames),
 390                 debugger, jvmLibNames);
 391             } else if (os.equals("linux")) {
 392                 db = new HotSpotTypeDataBase(machDesc,
 393                 new LinuxVtblAccess(debugger, jvmLibNames),
 394                 debugger, jvmLibNames);
 395             } else {
 396                 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)");
 397             }
 398         }
 399         catch (NoSuchSymbolException e) {
 400             throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" +
 401             e.getSymbol() + "\" in remote process)");
 402         }
 403 
 404         if (startupMode != REMOTE_MODE) {
 405             // Configure the debugger with the primitive type sizes just obtained from the VM
 406             debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(),
 407             db.getJByteType().getSize(),
 408             db.getJCharType().getSize(),
 409             db.getJDoubleType().getSize(),
 410             db.getJFloatType().getSize(),
 411             db.getJIntType().getSize(),
 412             db.getJLongType().getSize(),
 413             db.getJShortType().getSize());
 414         }
 415 
 416         if (!isServer) {
 417             // Do not initialize the VM on the server (unnecessary, since it's
 418             // instantiated on the client)
 419             try {
 420                 VM.initialize(db, debugger);
 421             } catch (DebuggerException e) {
 422                 throw (e);
 423             } catch (Exception e) {
 424                 throw new DebuggerException(e);
 425             }
 426         }
 427     }
 428 
 429     //--------------------------------------------------------------------------------
 430     // OS-specific debugger setup/connect routines
 431     //
 432 
 433     //
 434     // Solaris
 435     //
 436 
 437     private void setupDebuggerSolaris() {
 438         setupJVMLibNamesSolaris();
 439         if(System.getProperty("sun.jvm.hotspot.debugger.useProcDebugger") != null) {
 440             ProcDebuggerLocal dbg = new ProcDebuggerLocal(null, true);
 441             debugger = dbg;
 442             attachDebugger();
 443 
 444             // Set up CPU-dependent stuff
 445             if (cpu.equals("x86")) {
 446                 machDesc = new MachineDescriptionIntelX86();
 447             } else if (cpu.equals("sparc")) {
 448                 int addressSize = dbg.getRemoteProcessAddressSize();
 449                 if (addressSize == -1) {
 450                     throw new DebuggerException("Error occurred while trying to determine the remote process's " +
 451                     "address size");
 452                 }
 453 
 454                 if (addressSize == 32) {
 455                     machDesc = new MachineDescriptionSPARC32Bit();
 456                 } else if (addressSize == 64) {
 457                     machDesc = new MachineDescriptionSPARC64Bit();
 458                 } else {
 459                     throw new DebuggerException("Address size " + addressSize + " is not supported on SPARC");
 460                 }
 461             } else if (cpu.equals("amd64")) {
 462                 machDesc = new MachineDescriptionAMD64();
 463             } else {
 464                 throw new DebuggerException("Solaris only supported on sparc/sparcv9/x86/amd64");
 465             }
 466 
 467             dbg.setMachineDescription(machDesc);
 468             return;
 469 
 470         } else {
 471             String dbxPathName;
 472             String dbxPathPrefix;
 473             String dbxSvcAgentDSOPathName;
 474             String dbxSvcAgentDSOPathPrefix;
 475             String[] dbxSvcAgentDSOPathNames = null;
 476 
 477             // use path names/prefixes specified on command
 478             dbxPathName = System.getProperty("dbxPathName");
 479             if (dbxPathName == null) {
 480                 dbxPathPrefix = System.getProperty("dbxPathPrefix");
 481                 if (dbxPathPrefix == null) {
 482                     dbxPathPrefix = defaultDbxPathPrefix;
 483                 }
 484                 dbxPathName = dbxPathPrefix + fileSep + os + fileSep + cpu + fileSep + "bin" + fileSep + "dbx";
 485             }
 486 
 487             dbxSvcAgentDSOPathName = System.getProperty("dbxSvcAgentDSOPathName");
 488             if (dbxSvcAgentDSOPathName != null) {
 489                 dbxSvcAgentDSOPathNames = new String[] { dbxSvcAgentDSOPathName } ;
 490             } else {
 491                 dbxSvcAgentDSOPathPrefix = System.getProperty("dbxSvcAgentDSOPathPrefix");
 492                 if (dbxSvcAgentDSOPathPrefix == null) {
 493                     dbxSvcAgentDSOPathPrefix = defaultDbxSvcAgentDSOPathPrefix;
 494                 }
 495                 if (cpu.equals("sparc")) {
 496                     dbxSvcAgentDSOPathNames = new String[] {
 497                         // FIXME: bad hack for SPARC v9. This is necessary because
 498                         // there are two dbx executables on SPARC, one for v8 and one
 499                         // for v9, and it isn't obvious how to tell the two apart
 500                         // using the dbx command line. See
 501                         // DbxDebuggerLocal.importDbxModule().
 502                         dbxSvcAgentDSOPathPrefix + fileSep + os + fileSep + cpu + "v9" + fileSep + "lib" +
 503                         fileSep + "libsvc_agent_dbx.so",
 504                         dbxSvcAgentDSOPathPrefix + fileSep + os + fileSep + cpu + fileSep + "lib" +
 505                         fileSep + "libsvc_agent_dbx.so",
 506                     };
 507                 } else {
 508                     dbxSvcAgentDSOPathNames = new String[] {
 509                         dbxSvcAgentDSOPathPrefix + fileSep + os + fileSep + cpu + fileSep + "lib" +
 510                         fileSep + "libsvc_agent_dbx.so"
 511                     };
 512                 }
 513             }
 514 
 515             // Note we do not use a cache for the local debugger in server
 516             // mode; it's taken care of on the client side
 517             DbxDebuggerLocal dbg = new DbxDebuggerLocal(null, dbxPathName, dbxSvcAgentDSOPathNames, !isServer);
 518             debugger = dbg;
 519 
 520             attachDebugger();
 521 
 522             // Set up CPU-dependent stuff
 523             if (cpu.equals("x86")) {
 524                 machDesc = new MachineDescriptionIntelX86();
 525             } else if (cpu.equals("sparc")) {
 526                 int addressSize = dbg.getRemoteProcessAddressSize();
 527                 if (addressSize == -1) {
 528                     throw new DebuggerException("Error occurred while trying to determine the remote process's " +
 529                     "address size. It's possible that the Serviceability Agent's dbx module failed to " +
 530                     "initialize. Examine the standard output and standard error streams from the dbx " +
 531                     "process for more information.");
 532                 }
 533 
 534                 if (addressSize == 32) {
 535                     machDesc = new MachineDescriptionSPARC32Bit();
 536                 } else if (addressSize == 64) {
 537                     machDesc = new MachineDescriptionSPARC64Bit();
 538                 } else {
 539                     throw new DebuggerException("Address size " + addressSize + " is not supported on SPARC");
 540                 }
 541             }
 542 
 543             dbg.setMachineDescription(machDesc);
 544 
 545         }
 546     }
 547 
 548     private void connectRemoteDebugger() throws DebuggerException {
 549         RemoteDebugger remote =
 550         (RemoteDebugger) RMIHelper.lookup(debugServerID);
 551         debugger = new RemoteDebuggerClient(remote);
 552         machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription();
 553         os = debugger.getOS();
 554         if (os.equals("solaris")) {
 555             setupJVMLibNamesSolaris();
 556         } else if (os.equals("win32")) {
 557             setupJVMLibNamesWin32();
 558         } else if (os.equals("linux")) {
 559             setupJVMLibNamesLinux();
 560         } else {
 561             throw new RuntimeException("Unknown OS type");
 562         }
 563 
 564         cpu = debugger.getCPU();
 565     }
 566 
 567     private void setupJVMLibNamesSolaris() {
 568         jvmLibNames = new String[] { "libjvm.so", "libjvm_g.so", "gamma_g" };
 569     }
 570 
 571     //
 572     // Win32
 573     //
 574 
 575     private void setupDebuggerWin32() {
 576         setupJVMLibNamesWin32();
 577 
 578         if (cpu.equals("x86")) {
 579             machDesc = new MachineDescriptionIntelX86();
 580         } else if (cpu.equals("amd64")) {
 581             machDesc = new MachineDescriptionAMD64();
 582         } else if (cpu.equals("ia64")) {
 583             machDesc = new MachineDescriptionIA64();
 584         } else {
 585             throw new DebuggerException("Win32 supported under x86, amd64 and ia64 only");
 586         }
 587 
 588         // Note we do not use a cache for the local debugger in server
 589         // mode; it will be taken care of on the client side (once remote
 590         // debugging is implemented).
 591 
 592         if (System.getProperty("sun.jvm.hotspot.debugger.useWindbgDebugger") != null) {
 593             debugger = new WindbgDebuggerLocal(machDesc, !isServer);
 594         } else {
 595             debugger = new Win32DebuggerLocal(machDesc, !isServer);
 596         }
 597 
 598         attachDebugger();
 599 
 600         // FIXME: add support for server mode
 601     }
 602 
 603     private void setupJVMLibNamesWin32() {
 604         jvmLibNames = new String[] { "jvm.dll", "jvm_g.dll" };
 605     }
 606 
 607     //
 608     // Linux
 609     //
 610 
 611     private void setupDebuggerLinux() {
 612         setupJVMLibNamesLinux();
 613 
 614         if (cpu.equals("x86")) {
 615             machDesc = new MachineDescriptionIntelX86();
 616         } else if (cpu.equals("ia64")) {
 617             machDesc = new MachineDescriptionIA64();
 618         } else if (cpu.equals("amd64")) {
 619             machDesc = new MachineDescriptionAMD64();
 620         } else if (cpu.equals("sparc")) {
 621             if (LinuxDebuggerLocal.getAddressSize()==8) {
 622                     machDesc = new MachineDescriptionSPARC64Bit();
 623             } else {
 624                     machDesc = new MachineDescriptionSPARC32Bit();
 625             }
 626         } else {
 627             throw new DebuggerException("Linux only supported on x86/ia64/amd64/sparc/sparc64");
 628         }
 629 
 630         LinuxDebuggerLocal dbg =
 631         new LinuxDebuggerLocal(machDesc, !isServer);
 632         debugger = dbg;
 633 
 634         attachDebugger();
 635     }
 636 
 637     private void setupJVMLibNamesLinux() {
 638         jvmLibNames = new String[] { "libjvm.so", "libjvm_g.so" };
 639     }
 640 
 641     /** Convenience routine which should be called by per-platform
 642       debugger setup. Should not be called when startupMode is
 643       REMOTE_MODE. */
 644     private void attachDebugger() {
 645         if (startupMode == PROCESS_MODE) {
 646             debugger.attach(pid);
 647         } else if (startupMode == CORE_FILE_MODE) {
 648             debugger.attach(javaExecutableName, coreFileName);
 649         } else {
 650             throw new DebuggerException("Should not call attach() for startupMode == " + startupMode);
 651         }
 652     }
 653 }