8262520: Add SA Command Line Debugger support to connect to debug server

Reviewed-by: cjplummer, kevinw
This commit is contained in:
Yasumasa Suenaga 2021-03-09 23:41:52 +00:00
parent e5ce97b12d
commit 70342e8513
5 changed files with 196 additions and 64 deletions

View File

@ -36,7 +36,7 @@ Each CLHSDB command can have zero or more arguments and optionally end with outp
<code>
Available commands:
assert true | false <font color="red">turn on/off asserts in SA code</font>
attach pid | exec core <font color="red">attach SA to a process or core</font>
attach pid | exec core | debugserver <font color="red">attach SA to a process, core, or remote debug server</font>
buildreplayjars [all | boot | app] <font color="red">build jars for replay, boot.jar for bootclasses, app.jar for application classes</font>
class name <font color="red">find a Java class from debuggee and print oop</font>
classes <font color="red">print all loaded Java classes with Klass*</font>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2021, 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
@ -33,6 +33,10 @@ import java.util.*;
public class CLHSDB {
public CLHSDB(JVMDebugger d) {
pid = -1;
execPath = null;
coreFilename = null;
debugServerName = null;
jvmDebugger = d;
}
@ -42,10 +46,12 @@ public class CLHSDB {
public void run() {
// If jvmDebugger is already set, we have been given a JVMDebugger.
// Otherwise, if pidText != null we are supposed to attach to it.
// Finally, if execPath != null, it is the path of a jdk/bin/java
// Otherwise, if pid != -1 we are supposed to attach to it.
// If execPath != null, it is the path of a jdk/bin/java
// and coreFilename is the pathname of a core file we are
// supposed to attach to.
// Finally, if debugServerName != null, we are supposed to
// connect to remote debug server.
agent = new HotSpotAgent();
@ -57,10 +63,12 @@ public class CLHSDB {
if (jvmDebugger != null) {
attachDebugger(jvmDebugger);
} else if (pidText != null) {
attachDebugger(pidText);
} else if (pid != -1) {
attachDebugger(pid);
} else if (execPath != null) {
attachDebugger(execPath, coreFilename);
} else if (debugServerName != null) {
connect(debugServerName);
}
@ -71,12 +79,15 @@ public class CLHSDB {
public boolean isAttached() {
return attached;
}
public void attach(String pid) {
public void attach(int pid) {
attachDebugger(pid);
}
public void attach(String java, String core) {
attachDebugger(java, core);
}
public void attach(String debugServerName) {
connect(debugServerName);
}
public void detach() {
detachDebugger();
}
@ -84,8 +95,10 @@ public class CLHSDB {
if (attached) {
detachDebugger();
}
if (pidText != null) {
attach(pidText);
if (pid != -1) {
attach(pid);
} else if (debugServerName != null) {
connect(debugServerName);
} else {
attach(execPath, coreFilename);
}
@ -107,10 +120,10 @@ public class CLHSDB {
private JVMDebugger jvmDebugger;
private boolean attached;
// These had to be made data members because they are referenced in inner classes.
private String pidText;
private int pid;
private String execPath;
private String coreFilename;
private String debugServerName;
private void doUsage() {
System.out.println("Usage: java CLHSDB [[pid] | [path-to-java-executable [path-to-corefile]] | help ]");
@ -122,6 +135,11 @@ public class CLHSDB {
}
private CLHSDB(String[] args) {
pid = -1;
execPath = null;
coreFilename = null;
debugServerName = null;
switch (args.length) {
case (0):
break;
@ -134,9 +152,7 @@ public class CLHSDB {
// If all numbers, it is a PID to attach to
// Else, it is a pathname to a .../bin/java for a core file.
try {
int unused = Integer.parseInt(args[0]);
// If we get here, we have a PID and not a core file name
pidText = args[0];
pid = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
execPath = args[0];
coreFilename = "core";
@ -163,17 +179,10 @@ public class CLHSDB {
/** NOTE we are in a different thread here than either the main
thread or the Swing/AWT event handler thread, so we must be very
careful when creating or removing widgets */
private void attachDebugger(String pidText) {
private void attachDebugger(int pid) {
this.pid = pid;
try {
this.pidText = pidText;
pid = Integer.parseInt(pidText);
}
catch (NumberFormatException e) {
System.err.print("Unable to parse process ID \"" + pidText + "\".\nPlease enter a number.");
}
try {
System.err.println("Attaching to process " + pid + ", please wait...");
System.out.println("Attaching to process " + pid + ", please wait...");
// FIXME: display exec'd debugger's output messages during this
// lengthy call
@ -214,16 +223,17 @@ public class CLHSDB {
/** NOTE we are in a different thread here than either the main
thread or the Swing/AWT event handler thread, so we must be very
careful when creating or removing widgets */
private void connect(final String remoteMachineName) {
private void connect(final String debugServerName) {
// Try to open this core file
try {
System.err.println("Connecting to debug server, please wait...");
agent.attach(remoteMachineName);
System.out.println("Connecting to debug server, please wait...");
agent.attach(debugServerName);
this.debugServerName = debugServerName;
attached = true;
}
catch (DebuggerException e) {
final String errMsg = formatMessage(e.getMessage(), 80);
System.err.println("Unable to connect to machine \"" + remoteMachineName + "\":\n\n" + errMsg);
System.err.println("Unable to connect to debug server \"" + debugServerName + "\":\n\n" + errMsg);
agent.detach();
e.printStackTrace();
return;

View File

@ -114,8 +114,9 @@ public class CommandProcessor {
public abstract static class DebuggerInterface {
public abstract HotSpotAgent getAgent();
public abstract boolean isAttached();
public abstract void attach(String pid);
public abstract void attach(int pid);
public abstract void attach(String java, String core);
public abstract void attach(String debugServerName);
public abstract void detach();
public abstract void reattach();
}
@ -382,12 +383,19 @@ public class CommandProcessor {
postAttach();
}
},
new Command("attach", "attach pid | exec core", true) {
new Command("attach", "attach pid | exec core | remote_server", true) {
public void doit(Tokens t) {
int tokens = t.countTokens();
if (tokens == 1) {
preAttach();
debugger.attach(t.nextToken());
String arg = t.nextToken();
try {
// Attempt to attach as a PID
debugger.attach(Integer.parseInt(arg));
} catch (NumberFormatException e) {
// Attempt to connect to remote debug server
debugger.attach(arg);
}
postAttach();
} else if (tokens == 2) {
preAttach();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2021, 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
@ -78,10 +78,10 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
private JInternalFrame consoleFrame;
private WorkerThread workerThread;
// These had to be made data members because they are referenced in inner classes.
private String pidText;
private int pid;
private String execPath;
private String coreFilename;
private String debugServerName;
private void doUsage() {
System.out.println("Usage: java HSDB [[pid] | [path-to-java-executable [path-to-corefile]] | help ]");
@ -94,10 +94,19 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
}
public HSDB(JVMDebugger d) {
pid = -1;
execPath = null;
coreFilename = null;
debugServerName = null;
jvmDebugger = d;
}
private HSDB(String[] args) {
pid = -1;
execPath = null;
coreFilename = null;
debugServerName = null;
switch (args.length) {
case (0):
break;
@ -109,9 +118,7 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
// If all numbers, it is a PID to attach to
// Else, it is a pathname to a .../bin/java for a core file.
try {
int unused = Integer.parseInt(args[0]);
// If we get here, we have a PID and not a core file name
pidText = args[0];
pid = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
execPath = args[0];
coreFilename = "core";
@ -422,17 +429,21 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
});
// If jvmDebugger is already set, we have been given a JVMDebugger.
// Otherwise, if pidText != null we are supposed to attach to it.
// Finally, if execPath != null, it is the path of a jdk/bin/java
// Otherwise, if pid != -1 we are supposed to attach to it.
// If execPath != null, it is the path of a jdk/bin/java
// and coreFilename is the pathname of a core file we are
// supposed to attach to.
// Finally, if debugServerName != null, we are supposed to
// connect to remote debug server.
if (jvmDebugger != null) {
attach(jvmDebugger);
} else if (pidText != null) {
attach(pidText);
} else if (pid != -1) {
attach(pid);
} else if (execPath != null) {
attach(execPath, coreFilename);
} else if (debugServerName != null) {
connect(debugServerName);
}
}
@ -456,7 +467,7 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
desktop.remove(attachDialog);
workerThread.invokeLater(new Runnable() {
public void run() {
attach(pidTextField.getText());
attach(Integer.parseInt(pidTextField.getText()));
}
});
}
@ -1172,24 +1183,8 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
/** NOTE we are in a different thread here than either the main
thread or the Swing/AWT event handler thread, so we must be very
careful when creating or removing widgets */
private void attach(String pidText) {
try {
this.pidText = pidText;
pid = Integer.parseInt(pidText);
}
catch (NumberFormatException e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setMenuItemsEnabled(attachMenuItems, true);
JOptionPane.showInternalMessageDialog(desktop,
"Unable to parse process ID \"" + HSDB.this.pidText + "\".\nPlease enter a number.",
"Parse error",
JOptionPane.WARNING_MESSAGE);
}
});
return;
}
private void attach(int pid) {
this.pid = pid;
// Try to attach to this process
Runnable remover = new Runnable() {
public void run() {
@ -1293,7 +1288,7 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
/** NOTE we are in a different thread here than either the main
thread or the Swing/AWT event handler thread, so we must be very
careful when creating or removing widgets */
private void connect(final String remoteMachineName) {
private void connect(final String debugServerName) {
// Try to open this core file
Runnable remover = new Runnable() {
public void run() {
@ -1313,7 +1308,7 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
}
});
agent.attach(remoteMachineName);
agent.attach(debugServerName);
if (agent.getDebugger().hasConsole()) {
showDbgConsoleMenuItem.setEnabled(true);
}
@ -1327,7 +1322,7 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
public void run() {
setMenuItemsEnabled(attachMenuItems, true);
JOptionPane.showInternalMessageDialog(desktop,
"Unable to connect to machine \"" + remoteMachineName + "\":\n\n" + errMsg,
"Unable to connect to machine \"" + debugServerName + "\":\n\n" + errMsg,
"Unable to Connect",
JOptionPane.WARNING_MESSAGE);
}
@ -1499,11 +1494,14 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
public boolean isAttached() {
return attached;
}
public void attach(String pid) {
public void attach(int pid) {
HSDB.this.attach(pid);
}
public void attach(String java, String core) {
}
public void attach(String debugServerName) {
HSDB.this.connect(debugServerName);
}
public void detach() {
detachDebugger();
}
@ -1511,8 +1509,10 @@ public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
if (attached) {
detachDebugger();
}
if (pidText != null) {
attach(pidText);
if (pid != -1) {
attach(pid);
} else if (debugServerName != null) {
connect(debugServerName);
} else {
attach(execPath, coreFilename);
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021 NTT DATA.
* 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.PrintStream;
import jdk.test.lib.JDKToolLauncher;
import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.SA.SATestUtils;
import jtreg.SkippedException;
/**
* @test
* @bug 8262520
* @summary Test clhsdb connect, detach, reattach commands
* @requires vm.hasSA
* @requires os.family != "windows"
* @library /test/lib
* @run main/othervm ClhsdbAttachToDebugServer
*/
public class ClhsdbAttachToDebugServer {
public static void main(String[] args) throws Exception {
SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work.
if (SATestUtils.needsPrivileges()) {
// This tests has issues if you try adding privileges on OSX. The debugd process cannot
// be killed if you do this (because it is a root process and the test is not), so the destroy()
// call fails to do anything, and then waitFor() will time out. If you try to manually kill it with
// a "sudo kill" command, that seems to work, but then leaves the LingeredApp it was
// attached to in a stuck state for some unknown reason, causing the stopApp() call
// to timeout. For that reason we don't run this test when privileges are needed. Note
// it does appear to run fine as root, so we still allow it to run on OSX when privileges
// are not required.
throw new SkippedException("Cannot run this test on OSX if adding privileges is required.");
}
System.out.println("Starting ClhsdbAttachToDebugServer test");
LingeredApp theApp = null;
DebugdUtils debugd = null;
try {
theApp = LingeredApp.startApp();
System.out.println("Started LingeredApp with pid " + theApp.getPid());
debugd = new DebugdUtils(null);
debugd.attach(theApp.getPid());
JDKToolLauncher jhsdbLauncher = JDKToolLauncher.createUsingTestJDK("jhsdb");
jhsdbLauncher.addToolArg("clhsdb");
Process jhsdb = (SATestUtils.createProcessBuilder(jhsdbLauncher)).start();
OutputAnalyzer out = new OutputAnalyzer(jhsdb);
try (PrintStream console = new PrintStream(jhsdb.getOutputStream(), true)) {
console.println("echo true");
console.println("verbose true");
console.println("attach localhost");
console.println("class java.lang.Object");
console.println("detach");
console.println("reattach");
console.println("class java.lang.String");
console.println("quit");
}
jhsdb.waitFor();
System.out.println(out.getStdout());
System.err.println(out.getStderr());
out.stderrShouldBeEmptyIgnoreDeprecatedWarnings();
out.shouldMatch("^java/lang/Object @0x[0-9a-f]+$"); // for "class java.lang.Object"
out.shouldMatch("^java/lang/String @0x[0-9a-f]+$"); // for "class java.lang.String"
out.shouldHaveExitValue(0);
// This will detect most SA failures, including during the attach.
out.shouldNotMatch("^sun.jvm.hotspot.debugger.DebuggerException:.*$");
// This will detect unexpected exceptions, like NPEs and asserts, that are caught
// by sun.jvm.hotspot.CommandProcessor.
out.shouldNotMatch("^Error: .*$");
} catch (SkippedException se) {
throw se;
} catch (Exception ex) {
throw new RuntimeException("Test ERROR " + ex, ex);
} finally {
if (debugd != null) {
debugd.detach();
}
LingeredApp.stopApp(theApp);
}
System.out.println("Test PASSED");
}
}