diff --git a/core/pom.xml b/core/pom.xml index 32bfbee62..cce76380e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -70,6 +70,11 @@ arthas-common ${project.version} + + com.taobao.arthas + arthas-memorycompiler + ${project.version} + org.ow2.asm asm diff --git a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java index 712c97882..f9693c456 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java +++ b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java @@ -17,6 +17,7 @@ import com.taobao.arthas.core.command.klass100.ClassLoaderCommand; import com.taobao.arthas.core.command.klass100.DumpClassCommand; import com.taobao.arthas.core.command.klass100.GetStaticCommand; import com.taobao.arthas.core.command.klass100.JadCommand; +import com.taobao.arthas.core.command.klass100.MemoryCompilerCommand; import com.taobao.arthas.core.command.klass100.OgnlCommand; import com.taobao.arthas.core.command.klass100.RedefineCommand; import com.taobao.arthas.core.command.klass100.SearchClassCommand; @@ -69,6 +70,8 @@ public class BuiltinCommandPack implements CommandResolver { commands.add(Command.create(JvmCommand.class)); // commands.add(Command.create(GroovyScriptCommand.class)); commands.add(Command.create(OgnlCommand.class)); + commands.add(Command.create(MemoryCompilerCommand.class)); + commands.add(Command.create(RedefineCommand.class)); commands.add(Command.create(DashboardCommand.class)); commands.add(Command.create(DumpClassCommand.class)); commands.add(Command.create(JulyCommand.class)); @@ -81,7 +84,6 @@ public class BuiltinCommandPack implements CommandResolver { commands.add(Command.create(SessionCommand.class)); commands.add(Command.create(SystemPropertyCommand.class)); commands.add(Command.create(SystemEnvCommand.class)); - commands.add(Command.create(RedefineCommand.class)); commands.add(Command.create(HistoryCommand.class)); } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java index f93189fc5..ca0db73ef 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java @@ -65,7 +65,7 @@ public class ClassLoaderCommand extends AnnotatedCommand { } @Option(shortName = "c", longName = "classloader") - @Description("Display ClassLoader urls") + @Description("The hash code of the special ClassLoader") public void setHashCode(String hashCode) { this.hashCode = hashCode; } diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/MemoryCompilerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/MemoryCompilerCommand.java new file mode 100644 index 000000000..01aad6a74 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/MemoryCompilerCommand.java @@ -0,0 +1,131 @@ +package com.taobao.arthas.core.command.klass100; + +import java.io.File; +import java.lang.instrument.Instrumentation; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.taobao.arthas.compiler.DynamicCompiler; +import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.shell.command.AnnotatedCommand; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.ClassLoaderUtils; +import com.taobao.arthas.core.util.FileUtils; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.arthas.core.util.affect.RowAffect; +import com.taobao.middleware.cli.annotations.Argument; +import com.taobao.middleware.cli.annotations.Description; +import com.taobao.middleware.cli.annotations.Name; +import com.taobao.middleware.cli.annotations.Option; +import com.taobao.middleware.cli.annotations.Summary; +import com.taobao.middleware.logger.Logger; + +/** + * + * @author hengyunabc 2019-02-05 + * + */ +@Name("mc") +@Summary("Memory compiler, compiles java files into bytecode and class files in memory.") +@Description(Constants.EXAMPLE + " mc /tmp/Test.java\n" + " mc -c 327a647b /tmp/Test.java\n" + + " mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java\n" + Constants.WIKI + Constants.WIKI_HOME + + "mc") +public class MemoryCompilerCommand extends AnnotatedCommand { + + private static final Logger logger = LogUtil.getArthasLogger(); + + private String directory; + private String hashCode; + private String encoding; + + private List sourcefiles; + + @Argument(argName = "sourcefiles", index = 0) + @Description("source files") + public void setClassPattern(List sourcefiles) { + this.sourcefiles = sourcefiles; + } + + @Option(shortName = "c", longName = "classloader") + @Description("The hash code of the special ClassLoader") + public void setHashCode(String hashCode) { + this.hashCode = hashCode; + } + + @Option(longName = "encoding") + @Description("Source file encoding") + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + @Option(shortName = "d", longName = "directory") + @Description("Sets the destination directory for class files") + public void setDirectory(String directory) { + this.directory = directory; + } + + @Override + public void process(CommandProcess process) { + int exitCode = 0; + RowAffect affect = new RowAffect(); + + try { + Instrumentation inst = process.session().getInstrumentation(); + ClassLoader classloader = null; + if (hashCode == null) { + classloader = ClassLoader.getSystemClassLoader(); + } else { + classloader = ClassLoaderUtils.getClassLoader(inst, hashCode); + if (classloader == null) { + process.write("Can not find classloader with hashCode: " + hashCode + ".\n"); + exitCode = -1; + return; + } + } + + DynamicCompiler dynamicCompiler = new DynamicCompiler(classloader); + + Charset charset = Charset.defaultCharset(); + if (encoding != null) { + charset = Charset.forName(encoding); + } + + for (String sourceFile : sourcefiles) { + String sourceCode = FileUtils.readFileToString(new File(sourceFile), charset); + String name = new File(sourceFile).getName(); + if (name.endsWith(".java")) { + name = name.substring(0, name.length() - ".java".length()); + } + dynamicCompiler.addSource(name, sourceCode); + } + + Map byteCodes = dynamicCompiler.buildByteCodes(); + + File outputDir = null; + if (this.directory != null) { + outputDir = new File(this.directory); + } else { + outputDir = new File("").getAbsoluteFile(); + } + + process.write("Memory compiler output:\n"); + for (Entry entry : byteCodes.entrySet()) { + File byteCodeFile = new File(outputDir, entry.getKey().replace('.', '/') + ".class"); + FileUtils.writeByteArrayToFile(byteCodeFile, entry.getValue()); + process.write(byteCodeFile.getAbsolutePath() + '\n'); + affect.rCnt(1); + } + + } catch (Throwable e) { + logger.warn("Memory compiler error", e); + process.write("Memory compiler error, exception message: " + e.getMessage() + + ", please check $HOME/logs/arthas/arthas.log for more details. \n"); + exitCode = -1; + } finally { + process.write(affect + "\n"); + process.end(exitCode); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java b/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java new file mode 100644 index 000000000..7fd27ac37 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java @@ -0,0 +1,41 @@ +package com.taobao.arthas.core.util; + +import java.lang.instrument.Instrumentation; +import java.util.HashSet; +import java.util.Set; + +/** + * + * @author hengyunabc 2019-02-05 + * + */ +public class ClassLoaderUtils { + + public static Set getAllClassLoader(Instrumentation inst) { + Set classLoaderSet = new HashSet(); + + for (Class clazz : inst.getAllLoadedClasses()) { + ClassLoader classLoader = clazz.getClassLoader(); + if (classLoader != null) { + classLoaderSet.add(classLoader); + } + } + return classLoaderSet; + } + + public static ClassLoader getClassLoader(Instrumentation inst, String hashCode) { + if (hashCode == null || hashCode.isEmpty()) { + return null; + } + + for (Class clazz : inst.getAllLoadedClasses()) { + ClassLoader classLoader = clazz.getClassLoader(); + if (classLoader != null) { + if (Integer.toHexString(classLoader.hashCode()).equals(hashCode)) { + return classLoader; + } + } + } + return null; + } +}