--- old/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java 2019-06-25 16:43:42.525570877 +0900 +++ new/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java 2019-06-25 16:43:42.417571788 +0900 @@ -46,37 +46,60 @@ } private static boolean commonHelp(String mode) { + return commonHelp(mode, false); + } + + private static boolean commonHelpWithRemote(String mode) { + return commonHelp(mode, true); + } + + private static boolean commonHelp(String mode, boolean canConnectToRemote) { // --pid // --exe // --core - System.out.println(" --pid \tTo attach to and operate on the given live process."); - System.out.println(" --core \tTo operate on the given core file."); + // --connect <[id@]server> + System.out.println(" --pid \tTo attach to and operate on the given"); + System.out.println(" \tlive process."); + System.out.println(" --core \tTo operate on the given core file."); System.out.println(" --exe "); + if (canConnectToRemote) { + System.out.println(" --connect <[id@]server>\tTo operate on the remote debug server."); + } System.out.println(); System.out.println(" The --core and --exe options must be set together to give the core"); - System.out.println(" file, and associated executable, to operate on. Otherwise the --pid"); - System.out.println(" option can be set to operate on a live process."); - System.out.println(" The arguments for --exe and --core can use absolute or relative paths."); + System.out.println(" file, and associated executable, to operate on. They can use"); + System.out.println(" absolute or relative paths."); + System.out.println(" The --pid option can be set to operate on a live process."); + if (canConnectToRemote) { + System.out.println(" The --connect option can be set to operate on a debug server."); + System.out.println(" <--core --exe> and <--pid> and <--connect> are exclusive."); + } else { + System.out.println(" <--core --exe> and <--pid> are exclusive."); + } System.out.println(); System.out.println(" Examples: jhsdb " + mode + " --pid 1234"); System.out.println(" or jhsdb " + mode + " --core ./core.1234 --exe ./myexe"); + if (canConnectToRemote) { + System.out.println(" or jhsdb " + mode + " --connect debugserver"); + System.out.println(" or jhsdb " + mode + " --connect id@debugserver"); + } return false; } private static boolean debugdHelp() { // [options] [server-id] // [options] [server-id] - System.out.println(" --serverid \tA unique identifier for this debug server."); + System.out.println(" --serverid \tA unique identifier for this debug server."); return commonHelp("debugd"); } private static boolean jinfoHelp() { // --flags -> -flags // --sysprops -> -sysprops - System.out.println(" --flags \tTo print VM flags."); - System.out.println(" --sysprops \tTo print Java System properties."); - System.out.println(" \tTo print both of the above."); - return commonHelp("jinfo"); + System.out.println(" --flags \tTo print VM flags."); + System.out.println(" --sysprops \tTo print Java System properties."); + System.out.println(" \tTo print both of the above."); + return commonHelpWithRemote("jinfo"); } private static boolean jmapHelp() { @@ -86,27 +109,27 @@ // --clstats -> -clstats // --finalizerinfo -> -finalizerinfo - System.out.println(" \tTo print same info as Solaris pmap."); - System.out.println(" --heap \tTo print java heap summary."); - System.out.println(" --binaryheap \tTo dump java heap in hprof binary format."); - System.out.println(" --dumpfile \tThe name of the dump file."); - System.out.println(" --histo \tTo print histogram of java object heap."); - System.out.println(" --clstats \tTo print class loader statistics."); - System.out.println(" --finalizerinfo \tTo print information on objects awaiting finalization."); - return commonHelp("jmap"); + System.out.println(" \tTo print same info as Solaris pmap."); + System.out.println(" --heap \tTo print java heap summary."); + System.out.println(" --binaryheap \tTo dump java heap in hprof binary format."); + System.out.println(" --dumpfile \tThe name of the dump file."); + System.out.println(" --histo \tTo print histogram of java object heap."); + System.out.println(" --clstats \tTo print class loader statistics."); + System.out.println(" --finalizerinfo \tTo print information on objects awaiting finalization."); + return commonHelpWithRemote("jmap"); } private static boolean jstackHelp() { // --locks -> -l // --mixed -> -m - System.out.println(" --locks \tTo print java.util.concurrent locks."); - System.out.println(" --mixed \tTo print both Java and native frames (mixed mode)."); - return commonHelp("jstack"); + System.out.println(" --locks \tTo print java.util.concurrent locks."); + System.out.println(" --mixed \tTo print both Java and native frames (mixed mode)."); + return commonHelpWithRemote("jstack"); } private static boolean jsnapHelp() { - System.out.println(" --all \tTo print all performance counters."); - return commonHelp("jsnap"); + System.out.println(" --all \tTo print all performance counters."); + return commonHelpWithRemote("jsnap"); } private static boolean toolHelp(String toolName) { @@ -134,10 +157,12 @@ return launcherHelp(); } + private static final String NO_REMOTE = null; + private static void buildAttachArgs(ArrayList newArgs, String pid, - String exe, String core, boolean allowEmpty) { - if (!allowEmpty && (pid == null) && (exe == null)) { - throw new SAGetoptException("You have to set --pid or --exe."); + String exe, String core, String remote, boolean allowEmpty) { + if (!allowEmpty && (pid == null) && (exe == null) && (remote == null)) { + throw new SAGetoptException("You have to set --pid or --exe or --connect."); } if (pid != null) { // Attach to live process @@ -145,13 +170,17 @@ throw new SAGetoptException("Unnecessary argument: --exe"); } else if (core != null) { throw new SAGetoptException("Unnecessary argument: --core"); + } else if (remote != null) { + throw new SAGetoptException("Unnecessary argument: --connect"); } else if (!pid.matches("^\\d+$")) { throw new SAGetoptException("Invalid pid: " + pid); } newArgs.add(pid); } else if (exe != null) { - if (exe.length() == 0) { + if (remote != null) { + throw new SAGetoptException("Unnecessary argument: --connect"); + } else if (exe.length() == 0) { throw new SAGetoptException("You have to set --exe."); } @@ -162,6 +191,8 @@ } newArgs.add(core); + } else if (remote != null) { + newArgs.add(remote); } } @@ -190,7 +221,7 @@ } } - buildAttachArgs(newArgs, pid, exe, core, true); + buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, true); CLHSDB.main(newArgs.toArray(new String[newArgs.size()])); } @@ -219,19 +250,20 @@ } } - buildAttachArgs(newArgs, pid, exe, core, true); + buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, true); HSDB.main(newArgs.toArray(new String[newArgs.size()])); } private static void runJSTACK(String[] oldArgs) { SAGetopt sg = new SAGetopt(oldArgs); - String[] longOpts = {"exe=", "core=", "pid=", + String[] longOpts = {"exe=", "core=", "pid=", "connect=", "mixed", "locks"}; ArrayList newArgs = new ArrayList(); String pid = null; String exe = null; String core = null; + String remote = NO_REMOTE; String s = null; while((s = sg.next(null, longOpts)) != null) { @@ -247,6 +279,10 @@ pid = sg.getOptarg(); continue; } + if (s.equals("connect")) { + remote = sg.getOptarg(); + continue; + } if (s.equals("mixed")) { newArgs.add("-m"); continue; @@ -257,20 +293,21 @@ } } - buildAttachArgs(newArgs, pid, exe, core, false); + buildAttachArgs(newArgs, pid, exe, core, remote, false); JStack jstack = new JStack(false, false); jstack.runWithArgs(newArgs.toArray(new String[newArgs.size()])); } private static void runJMAP(String[] oldArgs) { SAGetopt sg = new SAGetopt(oldArgs); - String[] longOpts = {"exe=", "core=", "pid=", + String[] longOpts = {"exe=", "core=", "pid=", "connect=", "heap", "binaryheap", "dumpfile=", "histo", "clstats", "finalizerinfo"}; ArrayList newArgs = new ArrayList(); String pid = null; String exe = null; String core = null; + String remote = NO_REMOTE; String s = null; String dumpfile = null; boolean requestHeapdump = false; @@ -288,6 +325,10 @@ pid = sg.getOptarg(); continue; } + if (s.equals("connect")) { + remote = sg.getOptarg(); + continue; + } if (s.equals("heap")) { newArgs.add("-heap"); continue; @@ -325,19 +366,20 @@ } } - buildAttachArgs(newArgs, pid, exe, core, false); + buildAttachArgs(newArgs, pid, exe, core, remote, false); JMap.main(newArgs.toArray(new String[newArgs.size()])); } private static void runJINFO(String[] oldArgs) { SAGetopt sg = new SAGetopt(oldArgs); - String[] longOpts = {"exe=", "core=", "pid=", + String[] longOpts = {"exe=", "core=", "pid=", "connect=", "flags", "sysprops"}; ArrayList newArgs = new ArrayList(); String exe = null; String pid = null; String core = null; + String remote = NO_REMOTE; String s = null; while((s = sg.next(null, longOpts)) != null) { @@ -353,6 +395,10 @@ pid = sg.getOptarg(); continue; } + if (s.equals("connect")) { + remote = sg.getOptarg(); + continue; + } if (s.equals("flags")) { newArgs.add("-flags"); continue; @@ -363,18 +409,19 @@ } } - buildAttachArgs(newArgs, pid, exe, core, false); + buildAttachArgs(newArgs, pid, exe, core, remote, false); JInfo.main(newArgs.toArray(new String[newArgs.size()])); } private static void runJSNAP(String[] oldArgs) { SAGetopt sg = new SAGetopt(oldArgs); - String[] longOpts = {"exe=", "core=", "pid=", "all"}; + String[] longOpts = {"exe=", "core=", "pid=", "connect=", "all"}; ArrayList newArgs = new ArrayList(); String exe = null; String pid = null; String core = null; + String remote = NO_REMOTE; String s = null; while((s = sg.next(null, longOpts)) != null) { @@ -390,13 +437,17 @@ pid = sg.getOptarg(); continue; } + if (s.equals("connect")) { + remote = sg.getOptarg(); + continue; + } if (s.equals("all")) { newArgs.add("-a"); continue; } } - buildAttachArgs(newArgs, pid, exe, core, false); + buildAttachArgs(newArgs, pid, exe, core, remote, false); JSnap.main(newArgs.toArray(new String[newArgs.size()])); } @@ -436,7 +487,7 @@ } } - buildAttachArgs(newArgs, pid, exe, core, false); + buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, false); if (serverid != null) { newArgs.add(serverid); } --- /dev/null 2019-06-25 12:30:59.515000214 +0900 +++ new/test/hotspot/jtreg/serviceability/sa/sadebugd/DebugdConnectTest.java 2019-06-25 16:43:42.845568177 +0900 @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019, 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. + */ + +/** + * @test + * @bug 8209790 + * @summary Checks ability for connecting to debug server (jstack, jmap, jinfo, jsnap) + * @requires vm.hasSAandCanAttach + * @requires os.family != "windows" + * @modules java.base/jdk.internal.misc + * @library /test/lib + * + * @run main/othervm DebugdConnectTest + */ + +import java.io.IOException; + +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.process.OutputAnalyzer; + + +public class DebugdConnectTest { + + private static OutputAnalyzer runJHSDB(String command, String id) throws IOException, InterruptedException { + JDKToolLauncher jhsdbLauncher = JDKToolLauncher.createUsingTestJDK("jhsdb"); + jhsdbLauncher.addToolArg(command); + jhsdbLauncher.addToolArg("--connect"); + if (id != null) { + jhsdbLauncher.addToolArg(id + "@localhost"); + } else { + jhsdbLauncher.addToolArg("localhost"); + } + + Process jhsdb = (new ProcessBuilder(jhsdbLauncher.getCommand())).start(); + OutputAnalyzer out = new OutputAnalyzer(jhsdb); + + jhsdb.waitFor(); + + System.out.println(out.getStdout()); + System.err.println(out.getStderr()); + + return out; + } + + private static void runJSTACK(String id) throws IOException, InterruptedException { + OutputAnalyzer out = runJHSDB("jstack", id); + + out.shouldContain("LingeredApp"); + out.stderrShouldBeEmpty(); + out.shouldHaveExitValue(0); + } + + private static void runJMAP(String id) throws IOException, InterruptedException { + OutputAnalyzer out = runJHSDB("jmap", id); + + out.shouldContain("JVM version is"); + out.stderrShouldBeEmpty(); + out.shouldHaveExitValue(0); + } + + private static void runJINFO(String id) throws IOException, InterruptedException { + OutputAnalyzer out = runJHSDB("jinfo", id); + + out.shouldContain("Java System Properties:"); + out.stderrShouldBeEmpty(); + out.shouldHaveExitValue(0); + } + + private static void runJSNAP(String id) throws IOException, InterruptedException { + OutputAnalyzer out = runJHSDB("jsnap", id); + + out.shouldContain("java.vm.name="); + out.stderrShouldBeEmpty(); + out.shouldHaveExitValue(0); + } + + private static void runTests(String id, long debuggeePid) throws IOException, InterruptedException { + DebugdUtils debugd = new DebugdUtils(id); + debugd.attach(debuggeePid); + + try { + runJSTACK(id); + runJMAP(id); + runJINFO(id); + runJSNAP(id); + } finally { + debugd.detach(); + } + } + + public static void main(String[] args) throws Exception { + LingeredApp app = null; + + try { + app = LingeredApp.startApp(); + System.out.println("Started LingeredApp with pid " + app.getPid()); + + System.out.println("debugd connection test with server id:"); + runTests("test", app.getPid()); + + System.out.println("debugd connection test without server id:"); + runTests(null, app.getPid()); + } finally { + LingeredApp.stopApp(app); + } + + } + +} --- /dev/null 2019-06-25 12:30:59.515000214 +0900 +++ new/test/hotspot/jtreg/serviceability/sa/sadebugd/DebugdUtils.java 2019-06-25 16:43:43.275564548 +0900 @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019, 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. + */ + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +import jdk.test.lib.JDKToolLauncher; + + +public class DebugdUtils { + + private static final String GOLDEN = "Debugger attached"; + + private final String id; + + private Process debugdProcess; + + public DebugdUtils(String id) { + this.id = id; + debugdProcess = null; + } + + public void attach(long pid) throws IOException { + JDKToolLauncher jhsdbLauncher = JDKToolLauncher.createUsingTestJDK("jhsdb"); + jhsdbLauncher.addToolArg("debugd"); + jhsdbLauncher.addToolArg("--pid"); + jhsdbLauncher.addToolArg(Long.toString(pid)); + if (id != null) { + jhsdbLauncher.addToolArg("--serverid"); + jhsdbLauncher.addToolArg(id); + } + debugdProcess = (new ProcessBuilder(jhsdbLauncher.getCommand())).start(); + + // Wait until debug server attached + try (BufferedReader reader = new BufferedReader(new InputStreamReader(debugdProcess.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(GOLDEN)) { + break; + } + } + } + } + + public void detach() throws InterruptedException { + if (debugdProcess != null) { + debugdProcess.destroy(); + debugdProcess.waitFor(); + } + } +}