/* * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ package sun.jvm.hotspot; import java.rmi.RemoteException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import sun.jvm.hotspot.debugger.Debugger; import sun.jvm.hotspot.debugger.DebuggerException; import sun.jvm.hotspot.debugger.JVMDebugger; import sun.jvm.hotspot.debugger.MachineDescription; import sun.jvm.hotspot.debugger.MachineDescriptionAMD64; import sun.jvm.hotspot.debugger.MachineDescriptionPPC64; import sun.jvm.hotspot.debugger.MachineDescriptionAArch64; import sun.jvm.hotspot.debugger.MachineDescriptionIA64; import sun.jvm.hotspot.debugger.MachineDescriptionIntelX86; import sun.jvm.hotspot.debugger.MachineDescriptionSPARC32Bit; import sun.jvm.hotspot.debugger.MachineDescriptionSPARC64Bit; import sun.jvm.hotspot.debugger.NoSuchSymbolException; import sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal; import sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal; import sun.jvm.hotspot.debugger.proc.ProcDebuggerLocal; import sun.jvm.hotspot.debugger.remote.RemoteDebugger; import sun.jvm.hotspot.debugger.remote.RemoteDebuggerClient; import sun.jvm.hotspot.debugger.remote.RemoteDebuggerServer; import sun.jvm.hotspot.debugger.windbg.WindbgDebuggerLocal; import sun.jvm.hotspot.runtime.VM; import sun.jvm.hotspot.types.TypeDataBase; import sun.jvm.hotspot.utilities.PlatformInfo; import sun.jvm.hotspot.utilities.UnsupportedPlatformException; /**

This class wraps much of the basic functionality and is the * highest-level factory for VM data structures. It makes it simple * to start up the debugging system.

* *

FIXME: especially with the addition of remote debugging, this * has turned into a mess; needs rethinking.

*/ public class HotSpotAgent { private JVMDebugger debugger; private MachineDescription machDesc; private TypeDataBase db; private String os; private String cpu; // The system can work in several ways: // - Attaching to local process // - Attaching to local core file // - Connecting to remote debug server // - Starting debug server for process // - Starting debug server for core file // These are options for the "client" side of things private static final int PROCESS_MODE = 0; private static final int CORE_FILE_MODE = 1; private static final int REMOTE_MODE = 2; private int startupMode; // This indicates whether we are really starting a server or not private boolean isServer; // All possible required information for connecting private int pid; private String javaExecutableName; private String coreFileName; private String debugServerID; // All needed information for server side private String serverID; private String[] jvmLibNames; static void showUsage() { } public HotSpotAgent() { // for non-server add shutdown hook to clean-up debugger in case // of forced exit. For remote server, shutdown hook is added by // DebugServer. Runtime.getRuntime().addShutdownHook(new java.lang.Thread( new Runnable() { public void run() { synchronized (HotSpotAgent.this) { if (!isServer) { detach(); } } } })); } //-------------------------------------------------------------------------------- // Accessors (once the system is set up) // public synchronized Debugger getDebugger() { return debugger; } public synchronized TypeDataBase getTypeDataBase() { return db; } //-------------------------------------------------------------------------------- // Client-side operations // /** This attaches to a process running on the local machine. */ public synchronized void attach(int processID) throws DebuggerException { if (debugger != null) { throw new DebuggerException("Already attached"); } pid = processID; startupMode = PROCESS_MODE; isServer = false; go(); } /** This opens a core file on the local machine */ public synchronized void attach(String javaExecutableName, String coreFileName) throws DebuggerException { if (debugger != null) { throw new DebuggerException("Already attached"); } if ((javaExecutableName == null) || (coreFileName == null)) { throw new DebuggerException("Both the core file name and Java executable name must be specified"); } this.javaExecutableName = javaExecutableName; this.coreFileName = coreFileName; startupMode = CORE_FILE_MODE; isServer = false; go(); } /** This uses a JVMDebugger that is already attached to the core or process */ public synchronized void attach(JVMDebugger d) throws DebuggerException { debugger = d; isServer = false; go(); } /** This attaches to a "debug server" on a remote machine; this remote server has already attached to a process or opened a core file and is waiting for RMI calls on the Debugger object to come in. */ public synchronized void attach(String remoteServerID) throws DebuggerException { if (debugger != null) { throw new DebuggerException("Already attached to a process"); } if (remoteServerID == null) { throw new DebuggerException("Debug server id must be specified"); } debugServerID = remoteServerID; startupMode = REMOTE_MODE; isServer = false; go(); } /** This should only be called by the user on the client machine, not the server machine */ public synchronized boolean detach() throws DebuggerException { if (isServer) { throw new DebuggerException("Should not call detach() for server configuration"); } return detachInternal(); } //-------------------------------------------------------------------------------- // Server-side operations // /** This attaches to a process running on the local machine and starts a debug server, allowing remote machines to connect and examine this process. Uses specified name to uniquely identify a specific debuggee on the server */ public synchronized void startServer(int processID, String uniqueID) { if (debugger != null) { throw new DebuggerException("Already attached"); } pid = processID; startupMode = PROCESS_MODE; isServer = true; serverID = uniqueID; go(); } /** This attaches to a process running on the local machine and starts a debug server, allowing remote machines to connect and examine this process. */ public synchronized void startServer(int processID) throws DebuggerException { startServer(processID, null); } /** This opens a core file on the local machine and starts a debug server, allowing remote machines to connect and examine this core file. Uses supplied uniqueID to uniquely identify a specific debugee */ public synchronized void startServer(String javaExecutableName, String coreFileName, String uniqueID) { if (debugger != null) { throw new DebuggerException("Already attached"); } if ((javaExecutableName == null) || (coreFileName == null)) { throw new DebuggerException("Both the core file name and Java executable name must be specified"); } this.javaExecutableName = javaExecutableName; this.coreFileName = coreFileName; startupMode = CORE_FILE_MODE; isServer = true; serverID = uniqueID; go(); } /** This opens a core file on the local machine and starts a debug server, allowing remote machines to connect and examine this core file. */ public synchronized void startServer(String javaExecutableName, String coreFileName) throws DebuggerException { startServer(javaExecutableName, coreFileName, null); } /** This may only be called on the server side after startServer() has been called */ public synchronized boolean shutdownServer() throws DebuggerException { if (!isServer) { throw new DebuggerException("Should not call shutdownServer() for client configuration"); } return detachInternal(); } //-------------------------------------------------------------------------------- // Internals only below this point // private boolean detachInternal() { if (debugger == null) { return false; } boolean retval = true; if (!isServer) { VM.shutdown(); } // We must not call detach() if we are a client and are connected // to a remote debugger Debugger dbg = null; DebuggerException ex = null; if (isServer) { try { RMIHelper.unbind(serverID); } catch (DebuggerException de) { ex = de; } dbg = debugger; } else { if (startupMode != REMOTE_MODE) { dbg = debugger; } } if (dbg != null) { retval = dbg.detach(); } debugger = null; machDesc = null; db = null; if (ex != null) { throw(ex); } return retval; } private void go() { setupDebugger(); setupVM(); } private void setupDebugger() { if (startupMode != REMOTE_MODE) { // // Local mode (client attaching to local process or setting up // server, but not client attaching to server) // // Handle existing or alternate JVMDebugger: // these will set os, cpu independently of our PlatformInfo implementation. String alternateDebugger = System.getProperty("sa.altDebugger"); if (debugger != null) { setupDebuggerExisting(); } else if (alternateDebugger != null) { setupDebuggerAlternate(alternateDebugger); } else { // Otherwise, os, cpu are those of our current platform: try { os = PlatformInfo.getOS(); cpu = PlatformInfo.getCPU(); } catch (UnsupportedPlatformException e) { throw new DebuggerException(e); } if (os.equals("solaris")) { setupDebuggerSolaris(); } else if (os.equals("win32")) { setupDebuggerWin32(); } else if (os.equals("linux")) { setupDebuggerLinux(); } else if (os.equals("bsd")) { setupDebuggerBsd(); } else if (os.equals("darwin")) { setupDebuggerDarwin(); } else { // Add support for more operating systems here throw new DebuggerException("Operating system " + os + " not yet supported"); } } if (isServer) { RemoteDebuggerServer remote = null; try { remote = new RemoteDebuggerServer(debugger); } catch (RemoteException rem) { throw new DebuggerException(rem); } RMIHelper.rebind(serverID, remote); } } else { // // Remote mode (client attaching to server) // // Create and install a security manager // FIXME: currently commented out because we were having // security problems since we're "in the sun.* hierarchy" here. // Perhaps a permissive policy file would work around this. In // the long run, will probably have to move into com.sun.*. // if (System.getSecurityManager() == null) { // System.setSecurityManager(new RMISecurityManager()); // } connectRemoteDebugger(); } } private void setupVM() { // We need to instantiate a HotSpotTypeDataBase on both the client // and server machine. On the server it is only currently used to // configure the Java primitive type sizes (which we should // consider making constant). On the client it is used to // configure the VM. try { if (os.equals("solaris")) { db = new HotSpotTypeDataBase(machDesc, new HotSpotSolarisVtblAccess(debugger, jvmLibNames), debugger, jvmLibNames); } else if (os.equals("win32")) { db = new HotSpotTypeDataBase(machDesc, new Win32VtblAccess(debugger, jvmLibNames), debugger, jvmLibNames); } else if (os.equals("linux")) { db = new HotSpotTypeDataBase(machDesc, new LinuxVtblAccess(debugger, jvmLibNames), debugger, jvmLibNames); } else if (os.equals("bsd")) { db = new HotSpotTypeDataBase(machDesc, new BsdVtblAccess(debugger, jvmLibNames), debugger, jvmLibNames); } else if (os.equals("darwin")) { db = new HotSpotTypeDataBase(machDesc, new BsdVtblAccess(debugger, jvmLibNames), debugger, jvmLibNames); } else { throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); } } catch (NoSuchSymbolException e) { throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + e.getSymbol() + "\" in remote process)"); } if (startupMode != REMOTE_MODE) { // Configure the debugger with the primitive type sizes just obtained from the VM debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), db.getJByteType().getSize(), db.getJCharType().getSize(), db.getJDoubleType().getSize(), db.getJFloatType().getSize(), db.getJIntType().getSize(), db.getJLongType().getSize(), db.getJShortType().getSize()); } if (!isServer) { // Do not initialize the VM on the server (unnecessary, since it's // instantiated on the client) try { VM.initialize(db, debugger); } catch (DebuggerException e) { throw (e); } catch (Exception e) { throw new DebuggerException(e); } } } //-------------------------------------------------------------------------------- // OS-specific debugger setup/connect routines // // Use the existing JVMDebugger, as passed to our constructor. // Retrieve os and cpu from that debugger, not the current platform. private void setupDebuggerExisting() { os = debugger.getOS(); cpu = debugger.getCPU(); setupJVMLibNames(os); machDesc = debugger.getMachineDescription(); } // Given a classname, load an alternate implementation of JVMDebugger. private void setupDebuggerAlternate(String alternateName) { try { Class c = Class.forName(alternateName); Constructor cons = c.getConstructor(); debugger = (JVMDebugger) cons.newInstance(); attachDebugger(); setupDebuggerExisting(); } catch (ClassNotFoundException cnfe) { throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'"); } catch (NoSuchMethodException nsme) { throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor."); } catch (InstantiationException ie) { throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie); } catch (IllegalAccessException iae) { throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); } catch (InvocationTargetException iae) { throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); } System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName); } // // Solaris // private void setupDebuggerSolaris() { setupJVMLibNamesSolaris(); ProcDebuggerLocal dbg = new ProcDebuggerLocal(null, true); debugger = dbg; attachDebugger(); // Set up CPU-dependent stuff if (cpu.equals("x86")) { machDesc = new MachineDescriptionIntelX86(); } else if (cpu.equals("sparc")) { int addressSize = dbg.getRemoteProcessAddressSize(); if (addressSize == -1) { throw new DebuggerException("Error occurred while trying to determine the remote process's " + "address size"); } if (addressSize == 32) { machDesc = new MachineDescriptionSPARC32Bit(); } else if (addressSize == 64) { machDesc = new MachineDescriptionSPARC64Bit(); } else { throw new DebuggerException("Address size " + addressSize + " is not supported on SPARC"); } } else if (cpu.equals("amd64")) { machDesc = new MachineDescriptionAMD64(); } else { throw new DebuggerException("Solaris only supported on sparc/sparcv9/x86/amd64"); } dbg.setMachineDescription(machDesc); return; } private void connectRemoteDebugger() throws DebuggerException { RemoteDebugger remote = (RemoteDebugger) RMIHelper.lookup(debugServerID); debugger = new RemoteDebuggerClient(remote); machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); os = debugger.getOS(); setupJVMLibNames(os); cpu = debugger.getCPU(); } private void setupJVMLibNames(String os) { if (os.equals("solaris")) { setupJVMLibNamesSolaris(); } else if (os.equals("win32")) { setupJVMLibNamesWin32(); } else if (os.equals("linux")) { setupJVMLibNamesLinux(); } else if (os.equals("bsd")) { setupJVMLibNamesBsd(); } else if (os.equals("darwin")) { setupJVMLibNamesDarwin(); } else { throw new RuntimeException("Unknown OS type"); } } private void setupJVMLibNamesSolaris() { jvmLibNames = new String[] { "libjvm.so" }; } // // Win32 // private void setupDebuggerWin32() { setupJVMLibNamesWin32(); if (cpu.equals("x86")) { machDesc = new MachineDescriptionIntelX86(); } else if (cpu.equals("amd64")) { machDesc = new MachineDescriptionAMD64(); } else if (cpu.equals("ia64")) { machDesc = new MachineDescriptionIA64(); } else { throw new DebuggerException("Win32 supported under x86, amd64 and ia64 only"); } // Note we do not use a cache for the local debugger in server // mode; it will be taken care of on the client side (once remote // debugging is implemented). debugger = new WindbgDebuggerLocal(machDesc, !isServer); attachDebugger(); // FIXME: add support for server mode } private void setupJVMLibNamesWin32() { jvmLibNames = new String[] { "jvm.dll" }; } // // Linux // private void setupDebuggerLinux() { setupJVMLibNamesLinux(); if (cpu.equals("x86")) { machDesc = new MachineDescriptionIntelX86(); } else if (cpu.equals("ia64")) { machDesc = new MachineDescriptionIA64(); } else if (cpu.equals("amd64")) { machDesc = new MachineDescriptionAMD64(); } else if (cpu.equals("ppc64")) { machDesc = new MachineDescriptionPPC64(); } else if (cpu.equals("aarch64")) { machDesc = new MachineDescriptionAArch64(); } else if (cpu.equals("sparc")) { if (LinuxDebuggerLocal.getAddressSize()==8) { machDesc = new MachineDescriptionSPARC64Bit(); } else { machDesc = new MachineDescriptionSPARC32Bit(); } } else { try { machDesc = (MachineDescription) Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + cpu.toUpperCase()).newInstance(); } catch (Exception e) { throw new DebuggerException("Linux not supported on machine type " + cpu); } } LinuxDebuggerLocal dbg = new LinuxDebuggerLocal(machDesc, !isServer); debugger = dbg; attachDebugger(); } private void setupJVMLibNamesLinux() { jvmLibNames = new String[] { "libjvm.so" }; } // // BSD // private void setupDebuggerBsd() { setupJVMLibNamesBsd(); if (cpu.equals("x86")) { machDesc = new MachineDescriptionIntelX86(); } else if (cpu.equals("amd64") || cpu.equals("x86_64")) { machDesc = new MachineDescriptionAMD64(); } else { throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu); } BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); debugger = dbg; attachDebugger(); } private void setupJVMLibNamesBsd() { jvmLibNames = new String[] { "libjvm.so" }; } // // Darwin // private void setupDebuggerDarwin() { setupJVMLibNamesDarwin(); if (cpu.equals("amd64") || cpu.equals("x86_64")) { machDesc = new MachineDescriptionAMD64(); } else { throw new DebuggerException("Darwin only supported on x86_64. Current arch: " + cpu); } BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); debugger = dbg; attachDebugger(); } private void setupJVMLibNamesDarwin() { jvmLibNames = new String[] { "libjvm.dylib" }; } /** Convenience routine which should be called by per-platform debugger setup. Should not be called when startupMode is REMOTE_MODE. */ private void attachDebugger() { if (startupMode == PROCESS_MODE) { debugger.attach(pid); } else if (startupMode == CORE_FILE_MODE) { debugger.attach(javaExecutableName, coreFileName); } else { throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); } } }