package sun.jvm.hotspot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import sun.jvm.hotspot.tools.JStack;
import sun.jvm.hotspot.tools.JMap;
import sun.jvm.hotspot.tools.JInfo;
import sun.jvm.hotspot.tools.JSnap;
public class SALauncher {
private static boolean launcherHelp() {
System.out.println(" clhsdb \tcommand line debugger");
System.out.println(" hsdb \tui debugger");
System.out.println(" debugd --help\tto get more information");
System.out.println(" jstack --help\tto get more information");
System.out.println(" jmap --help\tto get more information");
System.out.println(" jinfo --help\tto get more information");
System.out.println(" jsnap --help\tto get more information");
return false;
}
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) {
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. 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() {
System.out.println(" --serverid <id> A unique identifier for this debug server.");
return commonHelp("debugd");
}
private static boolean jinfoHelp() {
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() {
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() {
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 To print all performance counters.");
return commonHelpWithConnect("jsnap");
}
private static boolean toolHelp(String toolName) {
switch (toolName) {
case "jstack":
return jstackHelp();
case "jinfo":
return jinfoHelp();
case "jmap":
return jmapHelp();
case "jsnap":
return jsnapHelp();
case "debugd":
return debugdHelp();
case "hsdb":
case "clhsdb":
return commonHelp(toolName);
default:
return launcherHelp();
}
}
private static final String NO_REMOTE = null;
private static String[] buildAttachArgs(Map<String, String> newArgMap,
boolean allowEmpty) {
String pid = newArgMap.remove("pid");
String exe = newArgMap.remove("exe");
String core = newArgMap.remove("core");
String connect = newArgMap.remove("connect");
if (!allowEmpty && (pid == null) && (exe == null) && (connect == NO_REMOTE)) {
throw new SAGetoptException("You have to set --pid or --exe or --connect.");
}
List<String> newArgs = new ArrayList<>();
for (var entry : newArgMap.entrySet()) {
newArgs.add(entry.getKey());
if (entry.getValue() != null) {
newArgs.add(entry.getValue());
}
}
if (pid != null) {
if (exe != null) {
throw new SAGetoptException("Unnecessary argument: --exe");
} else if (core != null) {
throw new SAGetoptException("Unnecessary argument: --core");
} else if (connect != 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 (connect != NO_REMOTE) {
throw new SAGetoptException("Unnecessary argument: --connect");
} else if (exe.length() == 0) {
throw new SAGetoptException("You have to set --exe.");
}
newArgs.add(exe);
if ((core == null) || (core.length() == 0)) {
throw new SAGetoptException("You have to set --core.");
}
newArgs.add(core);
} else if (connect != NO_REMOTE) {
newArgs.add(connect);
}
return newArgs.toArray(new String[0]);
}
private static Map<String, String> parseOptions(String[] oldArgs,
Map<String, String> longOptsMap) {
SAGetopt sg = new SAGetopt(oldArgs);
String[] longOpts = longOptsMap.keySet().toArray(new String[0]);
Map<String, String> newArgMap = new HashMap<>();
String s;
while ((s = sg.next(null, longOpts)) != null) {
var val = longOptsMap.get(s);
if (val != null) {
newArgMap.put(val, null);
} else {
val = longOptsMap.get(s + "=");
if (val != null) {
newArgMap.put(val, sg.getOptarg());
}
}
}
return newArgMap;
}
private static void runCLHSDB(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
CLHSDB.main(buildAttachArgs(newArgMap, true));
}
private static void runHSDB(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
HSDB.main(buildAttachArgs(newArgMap, true));
}
private static void runJSTACK(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid",
"connect=", "connect",
"mixed", "-m",
"locks", "-l");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
JStack jstack = new JStack(false, false);
jstack.runWithArgs(buildAttachArgs(newArgMap, false));
}
private static void runJMAP(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid",
"connect=", "connect",
"heap", "-heap",
"binaryheap", "binaryheap",
"dumpfile=", "dumpfile",
"histo", "-histo",
"clstats", "-clstats",
"finalizerinfo", "-finalizerinfo");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
boolean requestHeapdump = newArgMap.containsKey("binaryheap");
String dumpfile = newArgMap.get("dumpfile");
if (!requestHeapdump && (dumpfile != null)) {
throw new IllegalArgumentException("Unexpected argument: dumpfile");
}
if (requestHeapdump) {
if (dumpfile == null) {
newArgMap.put("-heap:format=b", null);
} else {
newArgMap.put("-heap:format=b,file=" + dumpfile, null);
}
}
newArgMap.remove("binaryheap");
newArgMap.remove("dumpfile");
JMap.main(buildAttachArgs(newArgMap, false));
}
private static void runJINFO(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid",
"connect=", "connect",
"flags", "-flags",
"sysprops", "-sysprops");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
JInfo.main(buildAttachArgs(newArgMap, false));
}
private static void runJSNAP(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid",
"connect=", "connect",
"all", "-a");
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
JSnap.main(buildAttachArgs(newArgMap, false));
}
private static void runDEBUGD(String[] oldArgs) {
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)));
if (serverid != null) {
newArgArray.add(serverid);
}
DebugServer.main(newArgArray.toArray(new String[0]));
}
private static Map<String, Consumer<String[]>> toolMap =
Map.of("clhsdb", SALauncher::runCLHSDB,
"hsdb", SALauncher::runHSDB,
"jstack", SALauncher::runJSTACK,
"jmap", SALauncher::runJMAP,
"jinfo", SALauncher::runJINFO,
"jsnap", SALauncher::runJSNAP,
"debugd", SALauncher::runDEBUGD);
public static void main(String[] args) {
if (args.length == 0) {
launcherHelp();
return;
}
if (args.length == 1 && !args[0].equals("clhsdb") && !args[0].equals("hsdb")) {
toolHelp(args[0]);
return;
}
for (String arg : args) {
if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help")) {
toolHelp(args[0]);
return;
}
}
String[] oldArgs = Arrays.copyOfRange(args, 1, args.length);
try {
var func = toolMap.get(args[0]);
if (func == null) {
throw new SAGetoptException("Unknown tool: " + args[0]);
} else {
func.accept(oldArgs);
}
} catch (SAGetoptException e) {
System.err.println(e.getMessage());
toolHelp(args[0]);
}
}
}