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