/*
* Copyright (c) 2000, 2020, 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.MachineDescriptionIntelX86;
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.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.
/** <P> 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. </P>
*
* <P> FIXME: especially with the addition of remote debugging, this
* has turned into a mess; needs rethinking. </P>
*/
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;
private int rmiPort;
// 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. /** 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 /** 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 /** 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. /** 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 /** 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. Allows to specify the port number
to which the RMI connector is bound. If not specified a random
available port is used. /** 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. Allows to specify the port number
to which the RMI connector is bound. If not specified a random
available port is used. */
public synchronized void startServer(int processID,
String uniqueID,
int rmiPort) {
if (debugger != null) {
throw new DebuggerException("Already attached");
}
pid = processID;
startupMode = PROCESS_MODE;
isServer = true;
serverID = uniqueID;
this.rmiPort = rmiPort;
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. Uses specified name to uniquely identify a
specific debuggee on the server /** 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) {
startServer(processID, uniqueID, 0);
}
This attaches to a process running on the local machine and
starts a debug server, allowing remote machines to connect and
examine this process. /** 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
debuggee. Allows to specify the port number to which the RMI connector
is bound. If not specified a random available port is used. /** 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
debuggee. Allows to specify the port number to which the RMI connector
is bound. If not specified a random available port is used. */
public synchronized void startServer(String javaExecutableName,
String coreFileName,
String uniqueID,
int rmiPort) {
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;
this.rmiPort = rmiPort;
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. Uses supplied uniqueID to uniquely identify a specific
debugee /** 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) {
startServer(javaExecutableName, coreFileName, uniqueID, 0);
}
This opens a core file on the local machine and starts a debug
server, allowing remote machines to connect and examine this
core file. /** 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 /** 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("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, rmiPort);
}
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("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);
}
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("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");
}
}
//
// Win32
//
private void setupDebuggerWin32() {
setupJVMLibNamesWin32();
if (cpu.equals("x86")) {
machDesc = new MachineDescriptionIntelX86();
} else if (cpu.equals("amd64")) {
machDesc = new MachineDescriptionAMD64();
} else if (cpu.equals("aarch64")) {
machDesc = new MachineDescriptionAArch64();
} else {
throw new DebuggerException("Win32 supported under x86, amd64 and aarch64 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("amd64")) {
machDesc = new MachineDescriptionAMD64();
} else if (cpu.equals("ppc64")) {
machDesc = new MachineDescriptionPPC64();
} else if (cpu.equals("aarch64")) {
machDesc = new MachineDescriptionAArch64();
} else {
try {
machDesc = (MachineDescription)
Class.forName("sun.jvm.hotspot.debugger.MachineDescription" +
cpu.toUpperCase()).getDeclaredConstructor().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. /** 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);
}
}
}