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