8196751: Add jhsdb option to specify debug server RMI connector port

Reviewed-by: sspitsyn, ysuenaga
This commit is contained in:
Daniil Titov 2020-03-26 09:03:52 -07:00
parent e11ab6a46c
commit 54e2c6fe3e
5 changed files with 248 additions and 42 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
* 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
@ -88,6 +88,7 @@ public class HotSpotAgent {
private String javaExecutableName;
private String coreFileName;
private String debugServerID;
private int rmiPort;
// All needed information for server side
private String serverID;
@ -200,8 +201,12 @@ public class HotSpotAgent {
/** 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) {
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");
}
@ -209,9 +214,18 @@ public class HotSpotAgent {
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 */
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. */
@ -223,10 +237,12 @@ public class HotSpotAgent {
/** 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 */
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) {
String coreFileName,
String uniqueID,
int rmiPort) {
if (debugger != null) {
throw new DebuggerException("Already attached");
}
@ -238,9 +254,20 @@ public class HotSpotAgent {
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 */
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. */
@ -349,7 +376,7 @@ public class HotSpotAgent {
if (isServer) {
RemoteDebuggerServer remote = null;
try {
remote = new RemoteDebuggerServer(debugger);
remote = new RemoteDebuggerServer(debugger, rmiPort);
}
catch (RemoteException rem) {
throw new DebuggerException(rem);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import sun.jvm.hotspot.debugger.DebuggerException;
import sun.jvm.hotspot.tools.JStack;
import sun.jvm.hotspot.tools.JMap;
import sun.jvm.hotspot.tools.JInfo;
@ -94,6 +95,15 @@ public class SALauncher {
// [options] <pid> [server-id]
// [options] <executable> <core> [server-id]
System.out.println(" --serverid <id> A unique identifier for this debug server.");
System.out.println(" --rmiport <port> Sets the port number to which the RMI connector is bound." +
" If not specified a random available port is used.");
System.out.println(" --registryport <port> Sets the RMI registry port." +
" This option overrides the system property 'sun.jvm.hotspot.rmi.port'. If not specified," +
" the system property is used. If the system property is not set, the default port 1099 is used.");
System.out.println(" --hostname <hostname> Sets the hostname the RMI connector is bound. The value could" +
" be a hostname or an IPv4/IPv6 address. This option overrides the system property" +
" 'java.rmi.server.hostname'. If not specified, the system property is used. If the system" +
" property is not set, a system hostname is used.");
return commonHelp("debugd");
}
@ -342,7 +352,7 @@ public class SALauncher {
JSnap.main(buildAttachArgs(newArgMap, false));
}
private static void runDEBUGD(String[] oldArgs) {
private static void runDEBUGD(String[] args) {
// By default SA agent classes prefer Windows process debugger
// to windbg debugger. SA expects special properties to be set
// to choose other debuggers. We will set those here before
@ -350,21 +360,88 @@ public class SALauncher {
System.setProperty("sun.jvm.hotspot.debugger.useWindbgDebugger", "true");
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid",
"serverid=", "serverid");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
var serverid = newArgMap.remove("serverid");
List<String> newArgArray = new ArrayList<>();
newArgArray.addAll(Arrays.asList(buildAttachArgs(newArgMap, false)));
"core=", "core",
"pid=", "pid",
"serverid=", "serverid",
"rmiport=", "rmiport",
"registryport=", "registryport",
"hostname=", "hostname");
// `serverid` must be located at the tail.
if (serverid != null) {
newArgArray.add(serverid);
Map<String, String> argMap = parseOptions(args, longOptsMap);
// Run the basic check for the options. If the check fails
// SAGetoptException will be thrown
buildAttachArgs(new HashMap<>(argMap), false);
String serverID = argMap.get("serverid");
String rmiPortString = argMap.get("rmiport");
String registryPort = argMap.get("registryport");
String host = argMap.get("hostname");
String javaExecutableName = argMap.get("exe");
String coreFileName = argMap.get("core");
String pidString = argMap.get("pid");
// Set RMI registry port, if specified
if (registryPort != null) {
try {
Integer.parseInt(registryPort);
} catch (NumberFormatException ex) {
throw new SAGetoptException("Invalid registry port: " + registryPort);
}
System.setProperty("sun.jvm.hotspot.rmi.port", registryPort);
}
// delegate to the actual SA debug server.
DebugServer.main(newArgArray.toArray(new String[0]));
// Set RMI connector hostname, if specified
if (host != null && !host.trim().isEmpty()) {
System.setProperty("java.rmi.server.hostname", host);
}
// Set RMI connector port, if specified
int rmiPort = 0;
if (rmiPortString != null) {
try {
rmiPort = Integer.parseInt(rmiPortString);
} catch (NumberFormatException ex) {
throw new SAGetoptException("Invalid RMI connector port: " + rmiPortString);
}
}
final HotSpotAgent agent = new HotSpotAgent();
if (pidString != null) {
int pid = 0;
try {
pid = Integer.parseInt(pidString);
} catch (NumberFormatException ex) {
throw new SAGetoptException("Invalid pid: " + pidString);
}
System.err.println("Attaching to process ID " + pid + " and starting RMI services," +
" please wait...");
try {
agent.startServer(pid, serverID, rmiPort);
} catch (DebuggerException e) {
System.err.print("Error attaching to process or starting server: ");
e.printStackTrace();
System.exit(1);
} catch (NumberFormatException ex) {
throw new SAGetoptException("Invalid pid: " + pid);
}
} else if (javaExecutableName != null) {
System.err.println("Attaching to core " + coreFileName +
" from executable " + javaExecutableName + " and starting RMI services, please wait...");
try {
agent.startServer(javaExecutableName, coreFileName, serverID, rmiPort);
} catch (DebuggerException e) {
System.err.print("Error attaching to core file or starting server: ");
e.printStackTrace();
System.exit(1);
}
}
// shutdown hook to clean-up the server in case of forced exit.
Runtime.getRuntime().addShutdownHook(new java.lang.Thread(agent::shutdownServer));
System.err.println("Debugger attached and RMI services started." + ((rmiPortString != null) ?
(" RMI connector is bound to port " + rmiPort + ".") : ""));
}
// Key: tool name, Value: launcher method

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 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
@ -43,12 +43,18 @@ public class RemoteDebuggerServer extends UnicastRemoteObject
}
/** This is the constructor used on the machine where the debuggee
process lies */
public RemoteDebuggerServer(Debugger debugger) throws RemoteException {
super();
process lies that accepts an RMI connector port */
public RemoteDebuggerServer(Debugger debugger, int port) throws RemoteException {
super(port);
this.debugger = debugger;
}
/** This is the constructor used on the machine where the debuggee
process lies */
public RemoteDebuggerServer(Debugger debugger) throws RemoteException {
this(debugger, 0);
}
public String getOS() throws RemoteException {
return debugger.getOS();
}

View File

@ -23,8 +23,8 @@
/**
* @test
* @bug 8163805 8224252
* @summary Checks that the jshdb debugd utility sucessfully starts
* @bug 8163805 8224252 8196751
* @summary Checks that the jshdb debugd utility successfully starts
* and tries to attach to a running process
* @requires vm.hasSA
* @requires os.family != "windows"
@ -39,13 +39,20 @@ import java.util.concurrent.TimeUnit;
import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.JDKToolLauncher;
import jdk.test.lib.SA.SATestUtils;
import static jdk.test.lib.process.ProcessTools.startProcess;
import jdk.test.lib.Utils;
import static jdk.test.lib.process.ProcessTools.startProcess;
import jtreg.SkippedException;
public class SADebugDTest {
private static final String GOLDEN = "Debugger attached";
private static final String RMI_CONNECTOR_IS_BOUND = "RMI connector is bound to port ";
private static final String ADDRESS_ALREADY_IN_USE = "Address already in use";
private static final int REGISTRY_DEFAULT_PORT = 1099;
private static volatile boolean testResult = false;
private static volatile boolean portInUse = false;
public static void main(String[] args) throws Exception {
SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work.
@ -61,31 +68,84 @@ public class SADebugDTest {
// are not required.
throw new SkippedException("Cannot run this test on OSX if adding privileges is required.");
}
runTests();
}
private static void runTests() throws Exception {
boolean[] boolArray = {true, false};
for (boolean useRmiPort : boolArray) {
for (boolean useRegistryPort : boolArray) {
for (boolean useHostname : boolArray) {
testWithPid(useRmiPort, useRegistryPort, useHostname);
}
}
}
}
private static void testWithPid(final boolean useRmiPort, final boolean useRegistryPort, final boolean useHostName) throws Exception {
LingeredApp app = null;
try {
app = LingeredApp.startApp();
System.out.println("Started LingeredApp with pid " + app.getPid());
JDKToolLauncher jhsdbLauncher = JDKToolLauncher.createUsingTestJDK("jhsdb");
jhsdbLauncher.addToolArg("debugd");
jhsdbLauncher.addToolArg("--pid");
jhsdbLauncher.addToolArg(Long.toString(app.getPid()));
ProcessBuilder pb = SATestUtils.createProcessBuilder(jhsdbLauncher);
do {
testResult = false;
portInUse = false;
JDKToolLauncher jhsdbLauncher = JDKToolLauncher.createUsingTestJDK("jhsdb");
jhsdbLauncher.addToolArg("debugd");
jhsdbLauncher.addToolArg("--pid");
jhsdbLauncher.addToolArg(Long.toString(app.getPid()));
// The startProcess will block untl the 'golden' string appears in either process' stdout or stderr
// In case of timeout startProcess kills the debugd process
Process debugd = startProcess("debugd", pb, null, l -> l.contains(GOLDEN), 20, TimeUnit.SECONDS);
int registryPort = REGISTRY_DEFAULT_PORT;
if (useRegistryPort) {
registryPort = Utils.findUnreservedFreePort(REGISTRY_DEFAULT_PORT);
jhsdbLauncher.addToolArg("--registryport");
jhsdbLauncher.addToolArg(Integer.toString(registryPort));
}
// If we are here, this means we have received the golden line and the test has passed
// The debugd remains running, we have to kill it
debugd.destroy();
debugd.waitFor();
int rmiPort = -1;
if (useRmiPort) {
rmiPort = Utils.findUnreservedFreePort(REGISTRY_DEFAULT_PORT, registryPort);
jhsdbLauncher.addToolArg("--rmiport");
jhsdbLauncher.addToolArg(Integer.toString(rmiPort));
}
if (useHostName) {
jhsdbLauncher.addToolArg("--hostname");
jhsdbLauncher.addToolArg("testhost");
}
ProcessBuilder pb = SATestUtils.createProcessBuilder(jhsdbLauncher);
final int finalRmiPort = rmiPort;
// The startProcess will block until the 'golden' string appears in either process' stdout or stderr
// In case of timeout startProcess kills the debugd process
Process debugd = startProcess("debugd", pb, null,
l -> {
if (!useRmiPort && l.contains(GOLDEN)) {
testResult = true;
} else if (useRmiPort && l.contains(RMI_CONNECTOR_IS_BOUND + finalRmiPort)) {
testResult = true;
} else if (l.contains(ADDRESS_ALREADY_IN_USE)) {
portInUse = true;
}
return (l.contains(GOLDEN) || portInUse);
}, 20, TimeUnit.SECONDS);
// If we are here, this means we have received the golden line and the test has passed
// The debugd remains running, we have to kill it
debugd.destroy();
debugd.waitFor();
if (!testResult) {
throw new RuntimeException("Expected message \"" +
RMI_CONNECTOR_IS_BOUND + rmiPort + "\" is not found in the output.");
}
} while (portInUse); // Repeat the test if the port is already in use
} finally {
LingeredApp.stopApp(app);
}
}
}

View File

@ -120,6 +120,11 @@ public final class Utils {
*/
private static volatile Random RANDOM_GENERATOR;
/**
* Maximum number of attempts to get free socket
*/
private static final int MAX_SOCKET_TRIES = 10;
/**
* Contains the seed value used for {@link java.util.Random} creation.
*/
@ -315,6 +320,37 @@ public final class Utils {
}
}
/**
* Returns the free unreserved port on the local host.
*
* @param reservedPorts reserved ports
* @return The port number or -1 if failed to find a free port
*/
public static int findUnreservedFreePort(int... reservedPorts) {
int numTries = 0;
while (numTries++ < MAX_SOCKET_TRIES) {
int port = -1;
try {
port = getFreePort();
} catch (IOException e) {
e.printStackTrace();
}
if (port > 0 && !isReserved(port, reservedPorts)) {
return port;
}
}
return -1;
}
private static boolean isReserved(int port, int[] reservedPorts) {
for (int p : reservedPorts) {
if (p == port) {
return true;
}
}
return false;
}
/**
* Returns the name of the local host.
*