8209790: SA tools not providing option to connect to debug server
authorysuenaga
Thu, 11 Jul 2019 08:19:56 +0900
changeset 55650 56e8c0a3fe9a
parent 55649 ad8e3b295615
child 55651 45fc36beb0aa
8209790: SA tools not providing option to connect to debug server Reviewed-by: sspitsyn, cjplummer
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java
test/hotspot/jtreg/serviceability/sa/sadebugd/DebugdConnectTest.java
test/hotspot/jtreg/serviceability/sa/sadebugd/DebugdUtils.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java	Wed Jul 10 22:33:23 2019 +0200
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java	Thu Jul 11 08:19:56 2019 +0900
@@ -46,37 +46,59 @@
     }
 
     private static boolean commonHelp(String mode) {
+        return commonHelp(mode, false);
+    }
+
+    private static boolean commonHelpWithConnect(String mode) {
+        return commonHelp(mode, true);
+    }
+
+    private static boolean commonHelp(String mode, boolean canConnectToRemote) {
         // --pid <pid>
         // --exe <exe>
         // --core <core>
-        System.out.println("    --pid <pid>      \tTo attach to and operate on the given live process.");
-        System.out.println("    --core <corefile>\tTo operate on the given core file.");
+        // --connect [<id>@]<host>
+        System.out.println("    --pid <pid>             To attach to and operate on the given live process.");
+        System.out.println("    --core <corefile>       To operate on the given core file.");
         System.out.println("    --exe <executable for corefile>");
+        if (canConnectToRemote) {
+            System.out.println("    --connect [<id>@]<host> To connect to a remote debug server (debugd).");
+        }
         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 connect to a debug server (debugd).");
+            System.out.println("    --core, --pid, and --connect are mutually exclusive.");
+        } else {
+            System.out.println("    --core and --pid are mutually 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] <pid> [server-id]
         // [options] <executable> <core> [server-id]
-        System.out.println("    --serverid <id>  \tA unique identifier for this debug server.");
+        System.out.println("    --serverid <id>         A 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("    <no option>      \tTo print both of the above.");
-        return commonHelp("jinfo");
+        System.out.println("    --flags                 To print VM flags.");
+        System.out.println("    --sysprops              To print Java System properties.");
+        System.out.println("    <no option>             To print both of the above.");
+        return commonHelpWithConnect("jinfo");
     }
 
     private static boolean jmapHelp() {
@@ -86,27 +108,27 @@
         // --clstats -> -clstats
         // --finalizerinfo -> -finalizerinfo
 
-        System.out.println("    <no option>      \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 <name>\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("    <no option>             To print same info as Solaris pmap.");
+        System.out.println("    --heap                  To print java heap summary.");
+        System.out.println("    --binaryheap            To dump java heap in hprof binary format.");
+        System.out.println("    --dumpfile <name>       The name of the dump file.");
+        System.out.println("    --histo                 To print histogram of java object heap.");
+        System.out.println("    --clstats               To print class loader statistics.");
+        System.out.println("    --finalizerinfo         To print information on objects awaiting finalization.");
+        return commonHelpWithConnect("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                 To print java.util.concurrent locks.");
+        System.out.println("    --mixed                 To print both Java and native frames (mixed mode).");
+        return commonHelpWithConnect("jstack");
     }
 
     private static boolean jsnapHelp() {
-        System.out.println("    --all            \tTo print all performance counters.");
-        return commonHelp("jsnap");
+        System.out.println("    --all                   To print all performance counters.");
+        return commonHelpWithConnect("jsnap");
     }
 
     private static boolean toolHelp(String toolName) {
@@ -134,10 +156,12 @@
         return launcherHelp();
     }
 
+    private static final String NO_REMOTE = null;
+
     private static void buildAttachArgs(ArrayList<String> 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 == NO_REMOTE)) {
+            throw new SAGetoptException("You have to set --pid or --exe or --connect.");
         }
 
         if (pid != null) { // Attach to live process
@@ -145,13 +169,17 @@
                 throw new SAGetoptException("Unnecessary argument: --exe");
             } else if (core != null) {
                 throw new SAGetoptException("Unnecessary argument: --core");
+            } else if (remote != NO_REMOTE) {
+                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 != NO_REMOTE) {
+                throw new SAGetoptException("Unnecessary argument: --connect");
+            } else if (exe.length() == 0) {
                 throw new SAGetoptException("You have to set --exe.");
             }
 
@@ -162,6 +190,8 @@
             }
 
             newArgs.add(core);
+        } else if (remote != NO_REMOTE) {
+            newArgs.add(remote);
         }
     }
 
@@ -190,7 +220,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 +249,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<String> 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 +278,10 @@
                 pid = sg.getOptarg();
                 continue;
             }
+            if (s.equals("connect")) {
+                remote = sg.getOptarg();
+                continue;
+            }
             if (s.equals("mixed")) {
                 newArgs.add("-m");
                 continue;
@@ -257,20 +292,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<String> 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 +324,10 @@
                 pid = sg.getOptarg();
                 continue;
             }
+            if (s.equals("connect")) {
+                remote = sg.getOptarg();
+                continue;
+            }
             if (s.equals("heap")) {
                 newArgs.add("-heap");
                 continue;
@@ -325,19 +365,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<String> 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 +394,10 @@
                 pid = sg.getOptarg();
                 continue;
             }
+            if (s.equals("connect")) {
+                remote = sg.getOptarg();
+                continue;
+            }
             if (s.equals("flags")) {
                 newArgs.add("-flags");
                 continue;
@@ -363,18 +408,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<String> 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 +436,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 +486,7 @@
           }
         }
 
-        buildAttachArgs(newArgs, pid, exe, core, false);
+        buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, false);
         if (serverid != null) {
             newArgs.add(serverid);
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/sa/sadebugd/DebugdConnectTest.java	Thu Jul 11 08:19:56 2019 +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	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/sa/sadebugd/DebugdUtils.java	Thu Jul 11 08:19:56 2019 +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();
+        }
+    }
+}