/*
* Copyright (c) 2008, 2016, 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. Oracle designates this
* particular file as subject to the Classpath exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.btrace.agent;
import com.sun.btrace.runtime.BTraceTransformer;
import com.sun.btrace.DebugSupport;
import com.sun.btrace.SharedSettings;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.net.Socket;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import com.sun.btrace.BTraceRuntime;
import com.sun.btrace.runtime.Constants;
import com.sun.btrace.comm.ErrorCommand;
import com.sun.btrace.util.Messages;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.regex.Pattern;
/**
* This is the main class for BTrace java.lang.instrument agent.
*
* @author A. Sundararajan
* @author Joachim Skeie (rolling output)
*/
public final class Main {
private static volatile Map<String, String> argMap;
private static volatile Instrumentation inst;
private static volatile Long fileRollMilliseconds;
private static final Pattern KV_PATTERN = Pattern.compile(",");
private static final SharedSettings settings = SharedSettings.GLOBAL;
private static final DebugSupport debug = new DebugSupport(settings);
private static final BTraceTransformer transformer = new BTraceTransformer(debug);
// #BTRACE-42: Non-daemon thread prevents traced application from exiting
private static final ThreadFactory daemonizedThreadFactory = new ThreadFactory() {
ThreadFactory delegate = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
Thread result = delegate.newThread(r);
result.setDaemon(true);
return result;
}
};
private static final ExecutorService serializedExecutor = Executors.newSingleThreadExecutor(daemonizedThreadFactory);
public static void premain(String args, Instrumentation inst) {
main(args, inst);
}
public static void agentmain(String args, Instrumentation inst) {
main(args, inst);
}
private static synchronized void main(final String args, final Instrumentation inst) {
if (Main.inst != null) {
return;
} else {
Main.inst = inst;
}
loadArgs(args);
if (argMap.containsKey("debug")) {
debugPrint("parsed command line arguments");
}
parseArgs();
inst.addTransformer(transformer, true);
startScripts();
String tmp = argMap.get("noServer");
boolean noServer = tmp != null && !"false".equals(tmp);
if (noServer) {
if (isDebug()) {
debugPrint("noServer is true, server not started");
}
return;
}
Thread agentThread = new Thread(new Runnable() {
@Override
public void run() {
BTraceRuntime.enter();
try {
startServer();
} finally {
BTraceRuntime.leave();
}
}
});
BTraceRuntime.initUnsafe();
BTraceRuntime.enter();
try {
agentThread.setDaemon(true);
if (isDebug()) {
debugPrint("starting agent thread");
}
agentThread.start();
} finally {
BTraceRuntime.leave();
}
}
private static void loadDefaultArguments(String config) {
try {
String propTarget = Constants.EMBEDDED_BTRACE_SECTION_HEADER + "agent.properties";
InputStream is = ClassLoader.getSystemResourceAsStream(propTarget);
if (is != null) {
Properties ps = new Properties();
ps.load(is);
StringBuilder log = new StringBuilder();
for (Map.Entry<Object, Object> entry : ps.entrySet()) {
String keyConfig = "";
String argKey = (String) entry.getKey();
int configPos = argKey.lastIndexOf('#');
if (configPos > -1) {
keyConfig = argKey.substring(0, configPos);
argKey = argKey.substring(configPos + 1);
}
if (config == null || config.equals(keyConfig)) {
String argVal = (String) entry.getValue();
switch (argKey) {
case "script": {
// special treatment for the 'script' parameter
boolean replace = false;
String scriptVal = argVal;
if (scriptVal.startsWith("!")) {
scriptVal = scriptVal.substring(1);
replace = true;
} else {
String oldVal = argMap.get(argKey);
if (oldVal != null && !oldVal.isEmpty()) {
scriptVal = oldVal + ":" + scriptVal;
} else {
replace = true;
}
log.append("augmenting default agent argument '").append(argKey)
.append("':'").append(argMap.get(argKey)).append("' with '")
.append(argVal).append("'\n");
}
if (replace) {
log.append("setting default agent argument '").append(argKey)
.append("' with '").append(scriptVal).append("'\n");
} else {
log.append("augmenting default agent argument '").append(argKey)
.append("':'").append(argMap.get(argKey)).append("' with '").append(argVal)
.append("'\n");
}
argMap.put(argKey, scriptVal);
break;
}
case "systemClassPath": // fall through
case "bootClassPath": // fall through
case "config": {
log.append("argument '").append(argKey).append("' is not overridable\n");
break;
}
default: {
if (!argMap.containsKey(argKey)) {
log.append("applying default agent argument '").append(argKey)
.append("'='").append(argVal).append("'\n");
argMap.put(argKey, argVal);
}
}
}
}
}
if (argMap.containsKey("debug")) {
DebugSupport.info(log.toString());
}
}
} catch (IOException e) {
debug.debug(e);
}
}
private static void startScripts() {
String p = argMap.get("stdout");
boolean traceToStdOut = p != null && !"false".equals(p);
if (isDebug()) {
debugPrint("stdout is " + traceToStdOut);
}
String script = argMap.get("script");
String scriptDir = argMap.get("scriptdir");
if (script != null) {
StringTokenizer tokenizer = new StringTokenizer(script, ":");
if (isDebug()) {
debugPrint(((tokenizer.countTokens() == 1) ? "initial script is " : "initial scripts are ") + script);
}
while (tokenizer.hasMoreTokens()) {
loadBTraceScript(tokenizer.nextToken(), traceToStdOut);
}
}
if (scriptDir != null) {
File dir = new File(scriptDir);
if (dir.isDirectory()) {
if (isDebug()) {
debugPrint("found scriptdir: " + dir.getAbsolutePath());
}
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
loadBTraceScript(file.getAbsolutePath(), traceToStdOut);
}
}
}
}
}
private static void usage() {
System.out.println(Messages.get("btrace.agent.usage"));
System.exit(0);
}
private static void loadArgs(String args) {
if (args == null) {
args = "";
}
String[] pairs = KV_PATTERN.split(args);
argMap = new HashMap<>();
for (String s : pairs) {
int i = s.indexOf('=');
String key, value = "";
if (i != -1) {
key = s.substring(0, i).trim();
if (i + 1 < s.length()) {
value = s.substring(i + 1).trim();
}
} else {
key = s;
}
argMap.put(key, value);
}
}
private static void parseArgs() {
String p = argMap.get("help");
if (p != null) {
usage();
}
processClasspaths();
loadDefaultArguments(argMap.get("config"));
p = argMap.get("debug");
settings.setDebug(p != null && !"false".equals(p));
if (isDebug()) {
debugPrint("debugMode is " + settings.isDebug());
}
p = argMap.get("startupRetransform");
settings.setRetransformStartup(p == null || !"false".equals(p));
if (isDebug()) {
debugPrint("startupRetransform is " + settings.isRetransformStartup());
}
p = argMap.get("dumpClasses");
boolean dumpClasses = p != null && !"false".equals(p);
if (isDebug()) {
debugPrint("dumpClasses is " + dumpClasses);
}
if (dumpClasses) {
String dumpDir = argMap.get("dumpDir");
settings.setDumpDir(dumpDir != null ? dumpDir : ".");
if (isDebug()) {
debugPrint("dumpDir is " + dumpDir);
}
}
p = argMap.get("cmdQueueLimit");
if (p != null) {
debugPrint("cmdQueueLimit provided: " + p);
System.setProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, p);
}
p = argMap.get("trackRetransforms");
settings.setTrackRetransforms(p != null && !"false".equals(p));
if (settings.isTrackRetransforms()) {
debugPrint("trackRetransforms is " + settings.isTrackRetransforms());
}
p = argMap.get("scriptOutputFile");
if (p != null && p.length() > 0) {
settings.setOutputFile(p);
if (isDebug()) {
debugPrint("scriptOutputFile is " + p);
}
}
p = argMap.get("scriptOutputDir");
if (p != null && p.length() > 0) {
settings.setOutputDir(p);
if (isDebug()) {
debugPrint("scriptOutputDir is " + p);
}
}
p = argMap.get("fileRollMilliseconds");
if (p != null && p.length() > 0) {
Long msParsed = null;
try {
msParsed = Long.parseLong(p);
fileRollMilliseconds = msParsed;
} catch (NumberFormatException nfe) {
fileRollMilliseconds = null;
}
if (fileRollMilliseconds != null) {
settings.setFileRollMilliseconds(fileRollMilliseconds.intValue());
if (isDebug()) {
debugPrint("fileRollMilliseconds is " + fileRollMilliseconds);
}
}
}
p = argMap.get("fileRollMaxRolls");
if (p != null && p.length() > 0) {
Integer rolls = null;
try {
rolls = Integer.parseInt(p);
} catch (NumberFormatException nfe) {
rolls = null;
}
if (rolls != null) {
settings.setFileRollMaxRolls(rolls);
}
}
boolean trusted = false;
p = argMap.get("unsafe");
trusted |= (p != null && "true".equals(p));
p = argMap.get("trusted");
trusted |= (p != null && "true".equals(p));
settings.setTrusted(trusted);
if (isDebug()) {
debugPrint("trustedMode is " + settings.isTrusted());
}
String statsdDef = argMap.get("statsd");
if (statsdDef != null) {
String[] parts = statsdDef.split(":");
if (parts.length == 2) {
settings.setStatsdHost(parts[0].trim());
try {
settings.setStatsdPort(Integer.parseInt(parts[1].trim()));
} catch (NumberFormatException e) {
DebugSupport.warning("Invalid statsd port number: " + parts[1]);
// leave the port unconfigured
}
} else if (parts.length == 1) {
settings.setStatsdHost(parts[0].trim());
}
}
String probeDescPath = argMap.get("probeDescPath");
settings.setProbeDescPath(probeDescPath != null ? probeDescPath : ".");
if (isDebug()) {
debugPrint("probe descriptor path is " + settings.getProbeDescPath());
}
}
private static void processClasspaths() {
String bootClassPath = argMap.get("bootClassPath");
if (bootClassPath != null) {
if (isDebug()) {
debugPrint("Bootstrap ClassPath: " + bootClassPath);
}
StringTokenizer tokenizer = new StringTokenizer(bootClassPath, File.pathSeparator);
try {
while (tokenizer.hasMoreTokens()) {
String path = tokenizer.nextToken();
File f = new File(path);
if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) {
JarFile jf = new JarFile(f);
inst.appendToBootstrapClassLoaderSearch(jf);
} else {
debugPrint("ignoring boot classpath element '" + path
+ "' - only jar files allowed");
}
}
} catch (IOException ex) {
debugPrint("adding to boot classpath failed!");
debugPrint(ex);
return;
}
}
String systemClassPath = argMap.get("systemClassPath");
if (systemClassPath != null) {
if (isDebug()) {
debugPrint("System ClassPath: " + systemClassPath);
}
StringTokenizer tokenizer = new StringTokenizer(systemClassPath, File.pathSeparator);
try {
while (tokenizer.hasMoreTokens()) {
String path = tokenizer.nextToken();
File f = new File(path);
if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) {
JarFile jf = new JarFile(f);
inst.appendToSystemClassLoaderSearch(jf);
} else {
debugPrint("ignoring system classpath element '" + path
+ "' - only jar files allowed");
}
}
} catch (IOException ex) {
debugPrint("adding to boot classpath failed!");
debugPrint(ex);
return;
}
}
addPreconfLibs();
}
private static void addPreconfLibs() {
URL u = Main.class.getClassLoader().getResource(Main.class.getName().replace('.', '/') + ".class");
if (u != null) {
String path = u.toString();
int delimiterPos = path.lastIndexOf('!');
if (delimiterPos > -1) {
String jar = path.substring(9, delimiterPos);
File jarFile = new File(jar);
String libPath = new File(jarFile.getParent() + File.separator + "btrace-libs").getAbsolutePath();
File bootLibs = new File(libPath + File.separator + "boot");
File sysLibs = new File(libPath + File.separator + "system");
if (bootLibs.exists()) {
for (File f : bootLibs.listFiles()) {
if (f.getName().toLowerCase().endsWith(".jar")) {
try {
inst.appendToBootstrapClassLoaderSearch(new JarFile(f));
} catch (IOException e) {
}
}
}
}
if (sysLibs.exists()) {
for (File f : sysLibs.listFiles()) {
if (f.getName().toLowerCase().endsWith(".jar")) {
try {
inst.appendToSystemClassLoaderSearch(new JarFile(f));
} catch (IOException e) {
}
}
}
}
}
}
}
private static void loadBTraceScript(String filePath, boolean traceToStdOut) {
try {
String scriptName = "";
String scriptParent = "";
File traceScript = new File(filePath);
scriptName = traceScript.getName();
scriptParent = traceScript.getParent();
if (!traceScript.exists()) {
traceScript = new File(Constants.EMBEDDED_BTRACE_SECTION_HEADER + filePath);
}
if (!scriptName.endsWith(".class")) {
if (isDebug()) {
debugPrint("refusing " + filePath + " - script should be a pre-compiled .class file");
}
return;
}
SharedSettings clientSettings = new SharedSettings();
clientSettings.from(settings);
clientSettings.setClientName(scriptName);
if (traceToStdOut) {
clientSettings.setOutputFile("::stdout");
} else {
String traceOutput = clientSettings.getOutputFile();
String outDir = clientSettings.getOutputDir();
if (traceOutput == null || traceOutput.length() == 0) {
clientSettings.setOutputFile("${client}-${agent}.${ts}.btrace[default]");
if (outDir == null || outDir.length() == 0) {
clientSettings.setOutputDir(scriptParent);
}
}
}
ClientContext ctx = new ClientContext(inst, transformer, clientSettings);
Client client = new FileClient(ctx, traceScript);
handleNewClient(client).get();
} catch (NullPointerException e) {
if (isDebug()) {
debugPrint("script " + filePath + " does not exist!");
}
} catch (RuntimeException | IOException | ExecutionException re) {
if (isDebug()) {
debugPrint(re);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static final int BTRACE_DEFAULT_PORT = 2020;
//-- Internals only below this point
private static void startServer() {
int port = BTRACE_DEFAULT_PORT;
String p = argMap.get("port");
if (p != null) {
try {
port = Integer.parseInt(p);
} catch (NumberFormatException exp) {
error("invalid port assuming default..");
}
}
ServerSocket ss;
try {
if (isDebug()) {
debugPrint("starting server at " + port);
}
System.setProperty("btrace.port", String.valueOf(port));
String scriptOutputFile = settings.getOutputFile();
if (scriptOutputFile != null && scriptOutputFile.length() > 0) {
System.setProperty("btrace.output", scriptOutputFile);
}
ss = new ServerSocket(port);
} catch (IOException ioexp) {
ioexp.printStackTrace();
return;
}
while (true) {
try {
if (isDebug()) {
debugPrint("waiting for clients");
}
Socket sock = ss.accept();
if (isDebug()) {
debugPrint("client accepted " + sock);
}
ClientContext ctx = new ClientContext(inst, transformer, settings);
Client client = new RemoteClient(ctx, sock);
handleNewClient(client).get();
} catch (RuntimeException | IOException | ExecutionException re) {
if (isDebug()) {
debugPrint(re);
}
} catch (InterruptedException e) {
return;
}
}
}
private static Future<?> handleNewClient(final Client client) {
return serializedExecutor.submit(new Runnable() {
@Override
public void run() {
boolean entered = BTraceRuntime.enter();
try {
client.debugPrint("new Client created " + client);
client.retransformLoaded();
} catch (UnmodifiableClassException uce) {
if (isDebug()) {
debugPrint(uce);
}
client.getRuntime().send(new ErrorCommand(uce));
} finally {
if (entered) {
BTraceRuntime.leave();
}
}
}
});
}
private static void error(String msg) {
System.err.println("btrace ERROR: " + msg);
}
private static boolean isDebug() {
return settings.isDebug();
}
private static void debugPrint(String msg) {
debug.debug(msg);
}
private static void debugPrint(Throwable th) {
debug.debug(th);
}
}