Path: blob/master/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java
41159 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*22*/2324package sun.jvm.hotspot;2526import java.util.ArrayList;27import java.util.Arrays;28import java.util.ArrayList;29import java.util.HashMap;30import java.util.List;31import java.util.Map;32import java.util.function.Consumer;3334import sun.jvm.hotspot.debugger.DebuggerException;35import sun.jvm.hotspot.tools.JStack;36import sun.jvm.hotspot.tools.JMap;37import sun.jvm.hotspot.tools.JInfo;38import sun.jvm.hotspot.tools.JSnap;3940public class SALauncher {4142private static boolean launcherHelp() {43System.out.println(" clhsdb \tcommand line debugger");44System.out.println(" hsdb \tui debugger");45System.out.println(" debugd --help\tto get more information");46System.out.println(" jstack --help\tto get more information");47System.out.println(" jmap --help\tto get more information");48System.out.println(" jinfo --help\tto get more information");49System.out.println(" jsnap --help\tto get more information");50return false;51}5253private static boolean commonHelp(String mode) {54return commonHelp(mode, false);55}5657private static boolean commonHelpWithConnect(String mode) {58return commonHelp(mode, true);59}6061private static boolean commonHelp(String mode, boolean canConnectToRemote) {62// --pid <pid>63// --exe <exe>64// --core <core>65// --connect [<id>@]<host>[:registryport]66System.out.println(" --pid <pid> To attach to and operate on the given live process.");67System.out.println(" --core <corefile> To operate on the given core file.");68System.out.println(" --exe <executable for corefile>");69if (canConnectToRemote) {70System.out.println(" --connect [<serverid>@]<host>[:registryport][/servername] To connect to a remote debug server (debugd).");71}72System.out.println();73System.out.println(" The --core and --exe options must be set together to give the core");74System.out.println(" file, and associated executable, to operate on. They can use");75System.out.println(" absolute or relative paths.");76System.out.println(" The --pid option can be set to operate on a live process.");77if (canConnectToRemote) {78System.out.println(" The --connect option can be set to connect to a debug server (debugd).");79System.out.println(" --core, --pid, and --connect are mutually exclusive.");80} else {81System.out.println(" --core and --pid are mutually exclusive.");82}83System.out.println();84System.out.println(" Examples: jhsdb " + mode + " --pid 1234");85System.out.println(" or jhsdb " + mode + " --core ./core.1234 --exe ./myexe");86if (canConnectToRemote) {87System.out.println(" or jhsdb " + mode + " --connect serverid@debugserver:1234/servername");88}89return false;90}9192private static boolean debugdHelp() {93System.out.println(" --serverid <id> A unique identifier for this debugd server.");94System.out.println(" --servername <name> Instance name of debugd server.");95System.out.println(" --rmiport <port> Sets the port number to which the RMI connector is bound." +96" If not specified a random available port is used.");97System.out.println(" --registryport <port> Sets the RMI registry port." +98" This option overrides the system property 'sun.jvm.hotspot.rmi.port'. If not specified," +99" the system property is used. If the system property is not set, the default port 1099 is used.");100System.out.println(" --disable-registry Do not start RMI registry (use already started RMI registry)");101System.out.println(" --hostname <hostname> Sets the hostname the RMI connector is bound. The value could" +102" be a hostname or an IPv4/IPv6 address. This option overrides the system property" +103" 'java.rmi.server.hostname'. If not specified, the system property is used. If the system" +104" property is not set, a system hostname is used.");105return commonHelp("debugd");106}107108private static boolean jinfoHelp() {109// --flags -> -flags110// --sysprops -> -sysprops111System.out.println(" --flags To print VM flags.");112System.out.println(" --sysprops To print Java System properties.");113System.out.println(" <no option> To print both of the above.");114return commonHelpWithConnect("jinfo");115}116117private static boolean jmapHelp() {118// --heap -> -heap119// --binaryheap -> -heap:format=b120// --histo -> -histo121// --clstats -> -clstats122// --finalizerinfo -> -finalizerinfo123124System.out.println(" <no option> To print same info as Solaris pmap.");125System.out.println(" --heap To print java heap summary.");126System.out.println(" --binaryheap To dump java heap in hprof binary format.");127System.out.println(" --dumpfile <name> The name of the dump file. Only valid with --binaryheap.");128System.out.println(" --gz <1-9> The compression level for gzipped dump file. Only valid with --binaryheap.");129System.out.println(" --histo To print histogram of java object heap.");130System.out.println(" --clstats To print class loader statistics.");131System.out.println(" --finalizerinfo To print information on objects awaiting finalization.");132return commonHelpWithConnect("jmap");133}134135private static boolean jstackHelp() {136// --locks -> -l137// --mixed -> -m138System.out.println(" --locks To print java.util.concurrent locks.");139System.out.println(" --mixed To print both Java and native frames (mixed mode).");140return commonHelpWithConnect("jstack");141}142143private static boolean jsnapHelp() {144System.out.println(" --all To print all performance counters.");145return commonHelpWithConnect("jsnap");146}147148private static boolean toolHelp(String toolName) {149switch (toolName) {150case "jstack":151return jstackHelp();152case "jinfo":153return jinfoHelp();154case "jmap":155return jmapHelp();156case "jsnap":157return jsnapHelp();158case "debugd":159return debugdHelp();160case "hsdb":161case "clhsdb":162return commonHelpWithConnect(toolName);163default:164return launcherHelp();165}166}167168private static final String NO_REMOTE = null;169170private static String[] buildAttachArgs(Map<String, String> newArgMap,171boolean allowEmpty) {172String pid = newArgMap.remove("pid");173String exe = newArgMap.remove("exe");174String core = newArgMap.remove("core");175String connect = newArgMap.remove("connect");176if (!allowEmpty && (pid == null) && (exe == null) && (connect == NO_REMOTE)) {177throw new SAGetoptException("You have to set --pid or --exe or --connect.");178}179180List<String> newArgs = new ArrayList<>();181for (var entry : newArgMap.entrySet()) {182newArgs.add(entry.getKey());183if (entry.getValue() != null) {184newArgs.add(entry.getValue());185}186}187188if (pid != null) { // Attach to live process189if (exe != null) {190throw new SAGetoptException("Unnecessary argument: --exe");191} else if (core != null) {192throw new SAGetoptException("Unnecessary argument: --core");193} else if (connect != NO_REMOTE) {194throw new SAGetoptException("Unnecessary argument: --connect");195} else if (!pid.matches("^\\d+$")) {196throw new SAGetoptException("Invalid pid: " + pid);197}198199newArgs.add(pid);200} else if (exe != null) {201if (connect != NO_REMOTE) {202throw new SAGetoptException("Unnecessary argument: --connect");203} else if (exe.length() == 0) {204throw new SAGetoptException("You have to set --exe.");205}206207newArgs.add(exe);208209if ((core == null) || (core.length() == 0)) {210throw new SAGetoptException("You have to set --core.");211}212213newArgs.add(core);214} else if (connect != NO_REMOTE) {215newArgs.add(connect);216}217218return newArgs.toArray(new String[0]);219}220221/**222* This method converts jhsdb-style options (oldArgs) to old fashioned223* style. SALauncher delegates the work to the entry point of each tool.224* Thus we need to convert the arguments.225* For example, `jhsdb jstack --mixed` needs to be converted to `jstack -m`.226*227* longOptsMap holds the rule how this method should convert the args.228* The key is the name of jhsdb style, the value is the name of229* old fashioned style. If you want to convert mixed option in jstack,230* you need to set "mixed" to the key, and to set "-m" to the value231* in longOptsMap. If the option have the value, you need to add "=" to232* the key like "exe=".233*234* You also can set the options which cannot be mapped to old fashioned235* arguments. For example, `jhsdb jmap --binaryheap` cannot be mapped to236* `jmap` option directly. But you set it to longOptsMap, then you can know237* the user sets "binaryheap" option, and SALauncher should set238* "-heap:format:b" to jmap option.239*240* This method returns the map of the old fashioned key/val pairs.241* It can be used to build args in string array at buildAttachArgs().242*/243private static Map<String, String> parseOptions(String[] oldArgs,244Map<String, String> longOptsMap) {245SAGetopt sg = new SAGetopt(oldArgs);246String[] longOpts = longOptsMap.keySet().toArray(new String[0]);247Map<String, String> newArgMap = new HashMap<>();248249/*250* Parse each jhsdb-style option via SAGetopt.251* SAGetopt parses and validates the argument. If the user passes invalid252* option, SAGetoptException will be occurred at SAGetopt::next.253* Thus there is no need to validate it here.254*255* We can get option value via SAGetopt::get. If jhsdb-style option has256* '=' at the tail, we put old fashioned option with it to newArgMap.257*/258String s;259while ((s = sg.next(null, longOpts)) != null) {260var val = longOptsMap.get(s);261if (val != null) {262newArgMap.put(val, null);263} else {264val = longOptsMap.get(s + "=");265if (val != null) {266newArgMap.put(val, sg.getOptarg());267}268}269}270271return newArgMap;272}273274private static void runCLHSDB(String[] oldArgs) {275Map<String, String> longOptsMap = Map.of("exe=", "exe",276"core=", "core",277"pid=", "pid",278"connect=", "connect");279Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);280CLHSDB.main(buildAttachArgs(newArgMap, true));281}282283private static void runHSDB(String[] oldArgs) {284Map<String, String> longOptsMap = Map.of("exe=", "exe",285"core=", "core",286"pid=", "pid",287"connect=", "connect");288Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);289HSDB.main(buildAttachArgs(newArgMap, true));290}291292private static void runJSTACK(String[] oldArgs) {293Map<String, String> longOptsMap = Map.of("exe=", "exe",294"core=", "core",295"pid=", "pid",296"connect=", "connect",297"mixed", "-m",298"locks", "-l");299Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);300JStack jstack = new JStack(false, false);301jstack.runWithArgs(buildAttachArgs(newArgMap, false));302}303304private static void runJMAP(String[] oldArgs) {305Map<String, String> longOptsMap = Map.ofEntries(306Map.entry("exe=", "exe"),307Map.entry("core=", "core"),308Map.entry("pid=", "pid"),309Map.entry("connect=", "connect"),310Map.entry("heap", "-heap"),311Map.entry("binaryheap", "binaryheap"),312Map.entry("dumpfile=", "dumpfile"),313Map.entry("gz=", "gz"),314Map.entry("histo", "-histo"),315Map.entry("clstats", "-clstats"),316Map.entry("finalizerinfo", "-finalizerinfo"));317Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);318319boolean requestHeapdump = newArgMap.containsKey("binaryheap");320String dumpfile = newArgMap.get("dumpfile");321String gzLevel = newArgMap.get("gz");322String command = "-heap:format=b";323if (!requestHeapdump && (dumpfile != null)) {324throw new IllegalArgumentException("Unexpected argument: dumpfile");325}326if (requestHeapdump) {327if (gzLevel != null) {328command += ",gz=" + gzLevel;329}330if (dumpfile != null) {331command += ",file=" + dumpfile;332}333newArgMap.put(command, null);334}335336newArgMap.remove("binaryheap");337newArgMap.remove("dumpfile");338newArgMap.remove("gz");339JMap.main(buildAttachArgs(newArgMap, false));340}341342private static void runJINFO(String[] oldArgs) {343Map<String, String> longOptsMap = Map.of("exe=", "exe",344"core=", "core",345"pid=", "pid",346"connect=", "connect",347"flags", "-flags",348"sysprops", "-sysprops");349Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);350JInfo.main(buildAttachArgs(newArgMap, false));351}352353private static void runJSNAP(String[] oldArgs) {354Map<String, String> longOptsMap = Map.of("exe=", "exe",355"core=", "core",356"pid=", "pid",357"connect=", "connect",358"all", "-a");359Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);360JSnap.main(buildAttachArgs(newArgMap, false));361}362363private static void runDEBUGD(String[] args) {364// By default SA agent classes prefer Windows process debugger365// to windbg debugger. SA expects special properties to be set366// to choose other debuggers. We will set those here before367// attaching to SA agent.368System.setProperty("sun.jvm.hotspot.debugger.useWindbgDebugger", "true");369370Map<String, String> longOptsMap = Map.of("exe=", "exe",371"core=", "core",372"pid=", "pid",373"serverid=", "serverid",374"rmiport=", "rmiport",375"registryport=", "registryport",376"disable-registry", "disable-registry",377"hostname=", "hostname",378"servername=", "servername");379380Map<String, String> argMap = parseOptions(args, longOptsMap);381382// Run the basic check for the options. If the check fails383// SAGetoptException will be thrown384buildAttachArgs(new HashMap<>(argMap), false);385386String serverID = argMap.get("serverid");387String rmiPortString = argMap.get("rmiport");388String registryPort = argMap.get("registryport");389String host = argMap.get("hostname");390String javaExecutableName = argMap.get("exe");391String coreFileName = argMap.get("core");392String pidString = argMap.get("pid");393String serverName = argMap.get("servername");394395// Set RMI registry port, if specified396if (registryPort != null) {397try {398Integer.parseInt(registryPort);399} catch (NumberFormatException ex) {400throw new SAGetoptException("Invalid registry port: " + registryPort);401}402System.setProperty("sun.jvm.hotspot.rmi.port", registryPort);403}404405// Disable RMI registry if specified406if (argMap.containsKey("disable-registry")) {407System.setProperty("sun.jvm.hotspot.rmi.startRegistry", "false");408}409410// Set RMI connector hostname, if specified411if (host != null && !host.trim().isEmpty()) {412System.setProperty("java.rmi.server.hostname", host);413}414415// Set RMI connector port, if specified416int rmiPort = 0;417if (rmiPortString != null) {418try {419rmiPort = Integer.parseInt(rmiPortString);420} catch (NumberFormatException ex) {421throw new SAGetoptException("Invalid RMI connector port: " + rmiPortString);422}423}424425final HotSpotAgent agent = new HotSpotAgent();426427if (pidString != null) {428int pid = 0;429try {430pid = Integer.parseInt(pidString);431} catch (NumberFormatException ex) {432throw new SAGetoptException("Invalid pid: " + pidString);433}434System.err.println("Attaching to process ID " + pid + " and starting RMI services," +435" please wait...");436try {437agent.startServer(pid, serverID, serverName, rmiPort);438} catch (DebuggerException e) {439System.err.print("Error attaching to process or starting server: ");440e.printStackTrace();441System.exit(1);442} catch (NumberFormatException ex) {443throw new SAGetoptException("Invalid pid: " + pid);444}445} else if (javaExecutableName != null) {446System.err.println("Attaching to core " + coreFileName +447" from executable " + javaExecutableName + " and starting RMI services, please wait...");448try {449agent.startServer(javaExecutableName, coreFileName, serverID, serverName, rmiPort);450} catch (DebuggerException e) {451System.err.print("Error attaching to core file or starting server: ");452e.printStackTrace();453System.exit(1);454}455}456// shutdown hook to clean-up the server in case of forced exit.457Runtime.getRuntime().addShutdownHook(new java.lang.Thread(agent::shutdownServer));458System.err.println("Debugger attached and RMI services started." + ((rmiPortString != null) ?459(" RMI connector is bound to port " + rmiPort + ".") : ""));460461}462463// Key: tool name, Value: launcher method464private static Map<String, Consumer<String[]>> toolMap =465Map.of("clhsdb", SALauncher::runCLHSDB,466"hsdb", SALauncher::runHSDB,467"jstack", SALauncher::runJSTACK,468"jmap", SALauncher::runJMAP,469"jinfo", SALauncher::runJINFO,470"jsnap", SALauncher::runJSNAP,471"debugd", SALauncher::runDEBUGD);472473public static void main(String[] args) {474// Provide a help475if (args.length == 0) {476launcherHelp();477return;478}479// No arguments imply help for debugd, jstack, jmap, jinfo but launch clhsdb and hsdb480if (args.length == 1 && !args[0].equals("clhsdb") && !args[0].equals("hsdb")) {481toolHelp(args[0]);482return;483}484485for (String arg : args) {486if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help")) {487toolHelp(args[0]);488return;489}490}491492String[] oldArgs = Arrays.copyOfRange(args, 1, args.length);493494try {495var func = toolMap.get(args[0]);496if (func == null) {497throw new SAGetoptException("Unknown tool: " + args[0]);498} else {499func.accept(oldArgs);500}501} catch (SAGetoptException e) {502System.err.println(e.getMessage());503toolHelp(args[0]);504// Exit with error status505System.exit(1);506}507}508}509510511