add retransform command. #1651

pull/1649/merge
hengyunabc 4 years ago
parent 62aed21be8
commit aa396f8f9a

@ -23,6 +23,11 @@ public class TransformerManager {
private Instrumentation instrumentation;
private List<ClassFileTransformer> watchTransformers = new CopyOnWriteArrayList<ClassFileTransformer>();
private List<ClassFileTransformer> traceTransformers = new CopyOnWriteArrayList<ClassFileTransformer>();
/**
* watch/trace Transformer TODO order
*/
private List<ClassFileTransformer> reTransformers = new CopyOnWriteArrayList<ClassFileTransformer>();
private ClassFileTransformer classFileTransformer;
@ -34,6 +39,13 @@ public class TransformerManager {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
for (ClassFileTransformer classFileTransformer : reTransformers) {
byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
protectionDomain, classfileBuffer);
if (transformResult != null) {
classfileBuffer = transformResult;
}
}
for (ClassFileTransformer classFileTransformer : watchTransformers) {
byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
@ -66,12 +78,18 @@ public class TransformerManager {
}
}
public void addRetransformer(ClassFileTransformer transformer) {
reTransformers.add(transformer);
}
public void removeTransformer(ClassFileTransformer transformer) {
reTransformers.remove(transformer);
watchTransformers.remove(transformer);
traceTransformers.remove(transformer);
}
public void destroy() {
reTransformers.clear();
watchTransformers.clear();
traceTransformers.clear();
instrumentation.removeTransformer(classFileTransformer);

@ -28,6 +28,7 @@ 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.RetransformCommand;
import com.taobao.arthas.core.command.klass100.SearchClassCommand;
import com.taobao.arthas.core.command.klass100.SearchMethodCommand;
import com.taobao.arthas.core.command.logger.LoggerCommand;
@ -86,6 +87,7 @@ public class BuiltinCommandPack implements CommandResolver {
commands.add(Command.create(OgnlCommand.class));
commands.add(Command.create(MemoryCompilerCommand.class));
commands.add(Command.create(RedefineCommand.class));
commands.add(Command.create(RetransformCommand.class));
commands.add(Command.create(DashboardCommand.class));
commands.add(Command.create(DumpClassCommand.class));
commands.add(Command.create(HeapDumpCommand.class));

@ -0,0 +1,498 @@
package com.taobao.arthas.core.command.klass100;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.alibaba.deps.org.objectweb.asm.ClassReader;
import com.taobao.arthas.core.advisor.TransformerManager;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.command.model.RetransformModel;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.cli.CliToken;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
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.ClassUtils;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.DefaultValue;
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;
/**
*
* Retransform Classes.
*
* @author hengyunabc 2021-01-05
* @see java.lang.instrument.Instrumentation#retransformClasses(Class...)
*/
@Name("retransform")
@Summary("Retransform classes. @see Instrumentation#retransformClasses(Class...)")
@Description(Constants.EXAMPLE + " retransform /tmp/Test.class\n"
+ " retransform -l \n"
+ " retransform -d 1 # delete retransform entry\n"
+ " retransform --deleteAll # delete all retransform entries\n"
+ " retransform --classPattern demo.* # triger retransform classes\n"
+ " retransform -c 327a647b /tmp/Test.class /tmp/Test\\$Inner.class \n"
+ " retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class\n"
+ Constants.WIKI + Constants.WIKI_HOME
+ "retransform")
public class RetransformCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(RetransformCommand.class);
private static final int MAX_FILE_SIZE = 10 * 1024 * 1024;
private static volatile List<RetransformEntry> retransformEntries = new ArrayList<RetransformEntry>();
private static volatile ClassFileTransformer transformer = null;
private String hashCode;
private String classLoaderClass;
private List<String> paths;
private boolean list;
private int delete = -1;
private boolean deleteAll;
private String classPattern;
private int limit;
@Option(shortName = "l", longName = "list", flag = true)
@Description("list all retransform entry.")
public void setList(boolean list) {
this.list = list;
}
@Option(shortName = "d", longName = "delete")
@Description("delete retransform entry by id.")
public void setDelete(int delete) {
this.delete = delete;
}
@Option(longName = "deleteAll", flag = true)
@Description("delete all retransform entries.")
public void setDeleteAll(boolean deleteAll) {
this.deleteAll = deleteAll;
}
@Option(longName = "classPattern")
@Description("trigger retransform matched classes by class pattern.")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Option(shortName = "c", longName = "classloader")
@Description("classLoader hashcode")
public void setHashCode(String hashCode) {
this.hashCode = hashCode;
}
@Option(longName = "classLoaderClass")
@Description("The class name of the special class's classLoader.")
public void setClassLoaderClass(String classLoaderClass) {
this.classLoaderClass = classLoaderClass;
}
@Argument(argName = "classfilePaths", index = 0, required = false)
@Description(".class file paths")
public void setPaths(List<String> paths) {
this.paths = paths;
}
@Option(longName = "limit")
@Description("The limit of dump classes size, default value is 5")
@DefaultValue("50")
public void setLimit(int limit) {
this.limit = limit;
}
private static void initTransformer() {
if (transformer != null) {
return;
} else {
synchronized (RetransformCommand.class) {
if (transformer == null) {
transformer = new RetransformClassFileTransformer();
TransformerManager transformerManager = ArthasBootstrap.getInstance().getTransformerManager();
transformerManager.addRetransformer(transformer);
}
}
}
}
@Override
public void process(CommandProcess process) {
initTransformer();
RetransformModel retransformModel = new RetransformModel();
Instrumentation inst = process.session().getInstrumentation();
if (this.list) {
List<RetransformEntry> retransformEntryList = allRetransformEntries();
retransformModel.setRetransformEntries(retransformEntryList);
process.appendResult(retransformModel);
process.end();
return;
} else if (this.deleteAll) {
deleteAllRetransformEntry();
process.appendResult(retransformModel);
process.end();
return;
} else if (this.delete > 0) {
deleteRetransformEntry(this.delete);
process.end();
return;
} else if (this.classPattern != null) {
Set<Class<?>> searchClass = SearchUtils.searchClass(inst, classPattern, false, this.hashCode);
if (searchClass.isEmpty()) {
process.end(-1, "These classes are not found in the JVM and may not be loaded: " + classPattern);
return;
}
if (searchClass.size() > limit) {
process.end(-1, "match classes size: " + searchClass.size() + ", more than limit: " + limit
+ ", It is recommended to use a more precise class pattern.");
}
try {
inst.retransformClasses(searchClass.toArray(new Class[0]));
for (Class<?> clazz : searchClass) {
retransformModel.addRetransformClass(clazz.getName());
}
process.appendResult(retransformModel);
process.end();
return;
} catch (Throwable e) {
String message = "retransform error! " + e.toString();
logger.error(message, e);
process.end(-1, message);
return;
}
}
for (String path : paths) {
File file = new File(path);
if (!file.exists()) {
process.end(-1, "file does not exist, path:" + path);
return;
}
if (!file.isFile()) {
process.end(-1, "not a normal file, path:" + path);
return;
}
if (file.length() >= MAX_FILE_SIZE) {
process.end(-1, "file size: " + file.length() + " >= " + MAX_FILE_SIZE + ", path: " + path);
return;
}
}
Map<String, byte[]> bytesMap = new HashMap<String, byte[]>();
for (String path : paths) {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(path, "r");
final byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
final String clazzName = readClassName(bytes);
bytesMap.put(clazzName, bytes);
} catch (Exception e) {
logger.warn("load class file failed: " + path, e);
process.end(-1, "load class file failed: " + path + ", error: " + e);
return;
} finally {
if (f != null) {
try {
f.close();
} catch (IOException e) {
// ignore
}
}
}
}
if (bytesMap.size() != paths.size()) {
process.end(-1, "paths may contains same class name!");
return;
}
List<RetransformEntry> retransformEntryList = new ArrayList<RetransformEntry>();
List<Class<?>> classList = new ArrayList<Class<?>>();
for (Class<?> clazz : inst.getAllLoadedClasses()) {
if (bytesMap.containsKey(clazz.getName())) {
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst,
classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils
.createClassLoaderVOList(matchedClassLoaders);
retransformModel.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(retransformModel);
process.end(-1,
"Found more than one classloader by class name, please specify classloader with '-c <classloader hash>'");
return;
} else {
process.end(-1, "Can not find classloader by class name: " + classLoaderClass + ".");
return;
}
}
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader != null && hashCode != null
&& !Integer.toHexString(classLoader.hashCode()).equals(hashCode)) {
continue;
}
RetransformEntry retransformEntry = new RetransformEntry(clazz.getName(), bytesMap.get(clazz.getName()),
hashCode, classLoaderClass);
retransformEntryList.add(retransformEntry);
classList.add(clazz);
retransformModel.addRetransformClass(clazz.getName());
addRetransformEntry(retransformEntry);
logger.info("Try retransform class name: {}, ClassLoader: {}", clazz.getName(), clazz.getClassLoader());
}
}
try {
if (retransformEntryList.isEmpty()) {
process.end(-1, "These classes are not found in the JVM and may not be loaded: " + bytesMap.keySet());
return;
}
inst.retransformClasses(classList.toArray(new Class[0]));
process.appendResult(retransformModel);
process.end();
} catch (Throwable e) {
String message = "retransform error! " + e.toString();
logger.error(message, e);
process.end(-1, message);
}
}
private static String readClassName(final byte[] bytes) {
return new ClassReader(bytes).getClassName().replace('/', '.');
}
@Override
public void complete(Completion completion) {
List<CliToken> tokens = completion.lineTokens();
if (CompletionUtils.shouldCompleteOption(completion, "--classPattern")) {
CompletionUtils.completeClassName(completion);
return;
}
for (CliToken token : tokens) {
String tokenStr = token.value();
if (tokenStr != null && tokenStr.startsWith("-")) {
super.complete(completion);
return;
}
}
// 最后,没有有 - 开头的,才尝试补全 file path
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
public static class RetransformEntry {
private static final AtomicInteger counter = new AtomicInteger(0);
private int id;
private String className;
private byte[] bytes;
private String hashCode;
private String classLoaderClass;
/**
* transform
*/
private int transformCount = 0;
public RetransformEntry(String className, byte[] bytes, String hashCode, String classLoaderClass) {
id = counter.incrementAndGet();
this.className = className;
this.bytes = bytes;
this.hashCode = hashCode;
this.classLoaderClass = classLoaderClass;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getTransformCount() {
return transformCount;
}
public void setTransformCount(int transformCount) {
this.transformCount = transformCount;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public String getHashCode() {
return hashCode;
}
public void setHashCode(String hashCode) {
this.hashCode = hashCode;
}
public String getClassLoaderClass() {
return classLoaderClass;
}
public void setClassLoaderClass(String classLoaderClass) {
this.classLoaderClass = classLoaderClass;
}
}
public static synchronized void addRetransformEntry(RetransformEntry retransformEntry) {
List<RetransformEntry> tmp = new ArrayList<RetransformEntry>();
tmp.addAll(retransformEntries);
tmp.add(retransformEntry);
Collections.sort(tmp, new Comparator<RetransformEntry>() {
@Override
public int compare(RetransformEntry entry1, RetransformEntry entry2) {
return entry1.getId() - entry2.getId();
}
});
retransformEntries = tmp;
}
public static synchronized RetransformEntry deleteRetransformEntry(int id) {
RetransformEntry result = null;
List<RetransformEntry> tmp = new ArrayList<RetransformEntry>();
for (RetransformEntry entry : retransformEntries) {
if (entry.getId() != id) {
tmp.add(entry);
} else {
result = entry;
}
}
retransformEntries = tmp;
return result;
}
public static List<RetransformEntry> allRetransformEntries() {
return retransformEntries;
}
public static synchronized void deleteAllRetransformEntry() {
retransformEntries = new ArrayList<RetransformEntry>();
}
static class RetransformClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className == null) {
return null;
}
className = className.replace('/', '.');
List<RetransformEntry> allRetransformEntries = allRetransformEntries();
// 倒序,因为要执行的配置生效
ListIterator<RetransformEntry> listIterator = allRetransformEntries
.listIterator(allRetransformEntries.size());
while (listIterator.hasPrevious()) {
RetransformEntry retransformEntry = listIterator.previous();
int id = retransformEntry.getId();
// 判断类名是否一致
boolean updateFlag = false;
// 类名一致,则看是否要比较 loader如果不需要比较 loader则认为成功
if (className.equals(retransformEntry.getClassName())) {
if (retransformEntry.getClassLoaderClass() != null || retransformEntry.getHashCode() != null) {
updateFlag = isLoaderMatch(retransformEntry, loader);
} else {
updateFlag = true;
}
}
if (updateFlag) {
logger.info("RetransformCommand match class: {}, id: {}, classLoaderClass: {}, hashCode: {}",
className, id, retransformEntry.getClassLoaderClass(), retransformEntry.getHashCode());
return retransformEntry.getBytes();
}
}
return null;
}
private boolean isLoaderMatch(RetransformEntry retransformEntry, ClassLoader loader) {
if (loader == null) {
return false;
}
if (retransformEntry.getClassLoaderClass() != null) {
if (loader.getClass().getName().equals(retransformEntry.getClassLoaderClass())) {
return true;
}
}
if (retransformEntry.getHashCode() != null) {
String hashCode = Integer.toHexString(loader.hashCode());
if (hashCode.equals(retransformEntry.getHashCode())) {
return true;
}
}
return false;
}
}
}

@ -0,0 +1,96 @@
package com.taobao.arthas.core.command.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.taobao.arthas.core.command.klass100.RetransformCommand.RetransformEntry;
import com.taobao.arthas.core.util.ClassUtils;
/**
*
* @author hengyunabc 2021-01-06
*
*/
public class RetransformModel extends ResultModel {
private int retransformCount;
private List<String> retransformClasses;
private Collection<ClassLoaderVO> matchedClassLoaders;
private String classLoaderClass;
private List<RetransformEntry> retransformEntries;
private RetransformEntry deletedRetransformEntry;
// private List<ClassVO> trigger
// List<ClassVO> classVOs = ClassUtils.createClassVOList(matchedClasses);
public RetransformModel() {
}
public void addRetransformClass(String className) {
if (retransformClasses == null) {
retransformClasses = new ArrayList<String>();
}
retransformClasses.add(className);
retransformCount++;
}
public int getRetransformCount() {
return retransformCount;
}
public void setRetransformCount(int retransformCount) {
this.retransformCount = retransformCount;
}
public List<String> getRetransformClasses() {
return retransformClasses;
}
public void setRetransformClasses(List<String> retransformClasses) {
this.retransformClasses = retransformClasses;
}
public String getClassLoaderClass() {
return classLoaderClass;
}
public RetransformModel setClassLoaderClass(String classLoaderClass) {
this.classLoaderClass = classLoaderClass;
return this;
}
public Collection<ClassLoaderVO> getMatchedClassLoaders() {
return matchedClassLoaders;
}
public RetransformModel setMatchedClassLoaders(Collection<ClassLoaderVO> matchedClassLoaders) {
this.matchedClassLoaders = matchedClassLoaders;
return this;
}
public List<RetransformEntry> getRetransformEntries() {
return retransformEntries;
}
public void setRetransformEntries(List<RetransformEntry> retransformEntries) {
this.retransformEntries = retransformEntries;
}
public RetransformEntry getDeletedRetransformEntry() {
return deletedRetransformEntry;
}
public void setDeletedRetransformEntry(RetransformEntry deletedRetransformEntry) {
this.deletedRetransformEntry = deletedRetransformEntry;
}
@Override
public String getType() {
return "retransform";
}
}

@ -57,6 +57,7 @@ public class ResultViewResolver {
registerView(MemoryCompilerView.class);
registerView(OgnlView.class);
registerView(RedefineView.class);
registerView(RetransformView.class);
registerView(SearchClassView.class);
registerView(SearchMethodView.class);

@ -0,0 +1,63 @@
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.klass100.RetransformCommand.RetransformEntry;
import com.taobao.arthas.core.command.model.RetransformModel;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.text.Decoration;
import com.taobao.text.ui.RowElement;
import com.taobao.text.ui.TableElement;
import com.taobao.text.util.RenderUtil;
/**
*
* @author hengyunabc 2021-01-06
*
*/
public class RetransformView extends ResultView<RetransformModel> {
@Override
public void draw(CommandProcess process, RetransformModel result) {
// 匹配到多个 classloader
if (result.getMatchedClassLoaders() != null) {
process.write("Matched classloaders: \n");
ClassLoaderView.drawClassLoaders(process, result.getMatchedClassLoaders(), false);
process.write("\n");
return;
}
// retransform -d
if (result.getDeletedRetransformEntry() != null) {
process.write("Delete RetransformEntry by id success. id: " + result.getDeletedRetransformEntry().getId());
process.write("\n");
return;
}
// retransform -l
if (result.getRetransformEntries() != null) {
// header
TableElement table = new TableElement(1, 1, 1, 1).rightCellPadding(1);
table.add(new RowElement().style(Decoration.bold.bold()).add("Id", "ClassName", "LoaderHash",
"LoaderClassName"));
for (RetransformEntry entry : result.getRetransformEntries()) {
table.row("" + entry.getId(), "" + entry.getClassName(), "" + entry.getHashCode(),
"" + entry.getClassLoaderClass());
}
process.write(RenderUtil.render(table));
return;
}
// retransform /tmp/Demo.class
if (result.getRetransformClasses() != null) {
StringBuilder sb = new StringBuilder();
for (String aClass : result.getRetransformClasses()) {
sb.append(aClass).append("\n");
}
process.write("retransform success, size: " + result.getRetransformCount()).write(", classes:\n")
.write(sb.toString());
}
}
}

@ -320,4 +320,39 @@ public class CompletionUtils {
}
}
}
/**
* <pre>
* option
* option --classPattern tokens
* 2 '--classPattern' ' '
* 3 '--classPattern' ' ' 'demo.'
* </pre>
*
* @param option
* @return
*/
public static boolean shouldCompleteOption(Completion completion, String option) {
List<CliToken> tokens = completion.lineTokens();
// 有两个 tocken, 然后 倒数第一个不是 - 开头的
if (tokens.size() >= 2) {
CliToken cliToken_1 = tokens.get(tokens.size() - 1);
CliToken cliToken_2 = tokens.get(tokens.size() - 2);
String token_2 = cliToken_2.value();
if (!cliToken_1.value().startsWith("-") && token_2.equals(option)) {
return CompletionUtils.completeClassName(completion);
}
}
// 有三个 token然后 倒数第一个不是 - 开头的倒数第2是空的倒数第3是 --classPattern
if (tokens.size() >= 3) {
CliToken cliToken_1 = tokens.get(tokens.size() - 1);
CliToken cliToken_2 = tokens.get(tokens.size() - 2);
CliToken cliToken_3 = tokens.get(tokens.size() - 3);
if (!cliToken_1.value().startsWith("-") && cliToken_2.isBlank()
&& cliToken_3.value().equals(option)) {
return CompletionUtils.completeClassName(completion);
}
}
return false;
}
}

@ -43,6 +43,7 @@
* [sm](sm.md)——查看已加载类的方法信息
* [jad](jad.md)——反编译指定已加载类的源码
* [mc](mc.md)——内存编译器,内存编译`.java`文件为`.class`文件
* [retransform](retransform.md)——加载外部的`.class`文件retransform到JVM里
* [redefine](redefine.md)——加载外部的`.class`文件redefine到JVM里
* [dump](dump.md)——dump 已加载类的 byte code 到特定目录
* [classloader](classloader.md)——查看classloader的继承树urls类加载信息使用classloader去getResource

@ -22,6 +22,7 @@
* [jad](jad.md)
* [classloader](classloader.md)
* [mc](mc.md)
* [retransform](retransform.md)
* [redefine](redefine.md)
* [monitor](monitor.md)

@ -40,6 +40,7 @@ Advanced Usage
* [sm](sm.md) - check methods info for the loaded classes
* [jad](jad.md) - decompile the specified loaded classes
* [mc](mc.md) - Memory compiler, compiles `.java` files into `.class` files in memory
* [retransform](retransform.md) - load external `*.class` files and retransform it into JVM
* [redefine](redefine.md) - load external `*.class` files and re-define it into JVM
* [dump](dump.md) - dump the loaded classes in byte code to the specified location
* [classloader](classloader.md) - check the inheritance structure, urls, class loading info for the specified class; using classloader to get the url of the resource e.g. `java/lang/String.class`

@ -22,6 +22,7 @@ All Commands
* [jad](jad.md)
* [classloader](classloader.md)
* [mc](mc.md)
* [retransform](retransform.md)
* [redefine](redefine.md)
* [monitor](monitor.md)

@ -57,7 +57,7 @@ CharSequence {
#### Print source only
By default, the decompile result will have the `ClassLoader` information. With the `--source-only` option, you can print only the source code. Conveniently used with the [mc](mc.md)/[redefine](redefine.md) commands.
By default, the decompile result will have the `ClassLoader` information. With the `--source-only` option, you can print only the source code. Conveniently used with the [mc](mc.md)/[retransform](retransform.md) commands.
```
$ jad --source-only demo.MathGame

@ -30,6 +30,6 @@ The output directory can be specified with the `-d` option:
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java
```
After compiling the `.class` file, you can use the [redefine](redefine.md) command to re-define the loaded classes in JVM.
After compiling the `.class` file, you can use the [retransform](retransform.md) command to re-define the loaded classes in JVM.
> Note that the mc command may fail. If the compilation fails, the `.class` file can be compiled locally and uploaded to the server. Refer to the [redefine](redefine.md) command description for details.
> Note that the mc command may fail. If the compilation fails, the `.class` file can be compiled locally and uploaded to the server. Refer to the [retransform](retransform.md) command description for details.

@ -1,6 +1,8 @@
redefine
========
> Recommend to use the [retransform](retransform.md) command.
[`mc-redefine` online tutorial](https://arthas.aliyun.com/doc/arthas-tutorials?language=en&id=command-mc-redefine)
> Load the external `*.class` files to re-define the loaded classes in JVM.
@ -9,6 +11,8 @@ Reference: [Instrumentation#redefineClasses](https://docs.oracle.com/javase/8/do
### Frequently asked questions
> Recommend to use the [retransform](retransform.md) command.
* The class of `redefine` cannot modify, add or delete the field and method of the class, including method parameters, method names and return values.
* If `mc` fails, you can compile the class file in the local development environment, upload it to the target system, and use `redefine` to hot load the class.
@ -29,7 +33,6 @@ Reference: [Instrumentation#redefineClasses](https://docs.oracle.com/javase/8/do
|---:|:---|
|`[c:]`|hashcode of the class loader|
|`[classLoaderClass:]`| The class name of the ClassLoader that executes the expression. |
|`[p:]`|absolute path of the external `*.class`, multiple paths are separated with 'space'|
### Usage

@ -0,0 +1,137 @@
retransform
========
> Load the external `*.class` files to retransform the loaded classes in JVM.
Reference: [Instrumentation#retransformClasses](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#retransformClasses-java.lang.Class...-)
### Usage
```bash
retransform /tmp/Test.class
retransform -l
retransform -d 1 # delete retransform entry
retransform --deleteAll # delete all retransform entries
retransform --classPattern demo.* # triger retransform classes
retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class
```
### retransform the specified .class file
```bash
$ retransform /tmp/MathGame.class
retransform success, size: 1, classes:
demo.MathGame
```
Load the specified .class file, then parse out the class name, and then retransform the corresponding class loaded in the jvm. Every time a `.class` file is loaded, a retransform entry is recorded.
> If retransform is executed multiple times to load the same class file, there will be multiple retransform entries.
### View retransform entry
```bash
$ retransform -l
Id ClassName LoaderHash LoaderClassName
1 demo.MathGame null null
```
### Delete the specified retransform entry
Need to specify id:
```bash
retransform -d 1
```
### Delete all retransform entries
```bash
retransform --deleteAll
```
### Explicitly trigger retransform
```bash
$ retransform --classPattern demo.MathGame
retransform success, size: 1, classes:
demo.MathGame
```
> Note: For the same class, when there are multiple retransform entries, if retransform is explicitly triggered, the entry added last will take effect (the one with the largest id).
### Eliminate the influence of retransform
If you want to eliminate the impact after performing retransform on a class, you need to:
* Delete the retransform entry corresponding to this class
* Re-trigger retransform
> If you do not clear all retransform entries and trigger retransform again, the retransformed classes will still take effect when arthas stop.
### Use with the jad/mc command
```bash
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
mc /tmp/UserController.java -d /tmp
retransform /tmp/com/example/demo/arthas/user/UserController.class
```
* Use `jad` command to decompile bytecode, and then you can use other editors, such as vim to modify the source code.
* `mc` command to compile the modified code
* Load new bytecode with `retransform` command
### Tips for uploading .class files to the server
The `mc` command may fail. You can modify the code locally, compile it, and upload it to the server. Some servers do not allow direct uploading files, you can use the `base64` command to bypass.
1. Convert the `.class` file to base64 first, then save it as result.txt
```bash
Base64 < Test.class > result.txt
```
2. Login the server, create and edit `result.txt`, copy the local content, paste and save
3. Restore `result.txt` on the server to `.class`
```
Base64 -d < result.txt > Test.class
```
4. Use the md5 command to verify that the `.class` files are consistent.
### Restrictions of the retransform command
* New field/method is not allowed
* The function that is running, no exit can not take effect, such as the new `System.out.println` added below, only the `run()` function will take effect.
```java
public class MathGame {
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
while (true) {
game.run();
TimeUnit.SECONDS.sleep(1);
// This doesn't work because the code keeps running in while
System.out.println("in loop");
}
}
public void run() throws InterruptedException {
// This works because the run() function ends completely every time
System.out.println("call run()");
try {
int number = random.nextInt();
List<Integer> primeFactors = primeFactors(number);
print(number, primeFactors);
} catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
}
}
```

@ -57,7 +57,7 @@ CharSequence {
#### 反编译时只显示源代码
默认情况下,反编译结果里会带有`ClassLoader`信息,通过`--source-only`选项,可以只打印源代码。方便和[mc](mc.md)/[redefine](redefine.md)命令结合使用。
默认情况下,反编译结果里会带有`ClassLoader`信息,通过`--source-only`选项,可以只打印源代码。方便和[mc](mc.md)/[retransform](retransform.md)命令结合使用。
```
$ jad --source-only demo.MathGame

@ -30,6 +30,6 @@ Affect(row-cnt:1) cost in 346 ms
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java
```
编译生成`.class`文件之后,可以结合[redefine](redefine.md)命令实现热更新代码。
编译生成`.class`文件之后,可以结合[retransform](retransform.md)命令实现热更新代码。
> 注意mc命令有可能失败。如果编译失败可以在本地编译好`.class`文件,再上传到服务器。具体参考[redefine](redefine.md)命令说明。
> 注意mc命令有可能失败。如果编译失败可以在本地编译好`.class`文件,再上传到服务器。具体参考[retransform](retransform.md)命令说明。

@ -1,6 +1,8 @@
redefine
===
> 推荐使用 [retransform](retransform.md) 命令
[`mc-redefine`在线教程](https://arthas.aliyun.com/doc/arthas-tutorials?language=cn&id=command-mc-redefine)
> 加载外部的`.class`文件redefine jvm已加载的类。
@ -9,6 +11,8 @@ redefine
### 常见问题
> 推荐使用 [retransform](retransform.md) 命令
* redefine的class不能修改、添加、删除类的field和method包括方法参数、方法名称及返回值
* 如果mc失败可以在本地开发环境编译好class文件上传到目标系统使用redefine热加载class
@ -28,9 +32,6 @@ redefine
|---:|:---|
|[c:]|ClassLoader的hashcode|
|`[classLoaderClass:]`|指定执行表达式的 ClassLoader 的 class name|
|[p:]|外部的`.class`文件的完整路径,支持多个|
### 使用参考

@ -0,0 +1,138 @@
retransform
===
> 加载外部的`.class`文件retransform jvm已加载的类。
参考:[Instrumentation#retransformClasses](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#retransformClasses-java.lang.Class...-)
### 使用参考
```bash
retransform /tmp/Test.class
retransform -l
retransform -d 1 # delete retransform entry
retransform --deleteAll # delete all retransform entries
retransform --classPattern demo.* # triger retransform classes
retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class
```
### retransform 指定的 .class 文件
```bash
$ retransform /tmp/MathGame.class
retransform success, size: 1, classes:
demo.MathGame
```
加载指定的 .class 文件然后解析出class name再retransform jvm中已加载的对应的类。每加载一个 `.class` 文件,则会记录一个 retransform entry.
> 如果多次执行 retransform 加载同一个 class 文件,则会有多条 retransform entry.
### 查看 retransform entry
```bash
$ retransform -l
Id ClassName LoaderHash LoaderClassName
1 demo.MathGame null null
```
### 删除指定 retransform entry
需要指定 id
```bash
retransform -d 1
```
### 删除所有 retransform entry
```bash
retransform --deleteAll
```
### 显式触发 retransform
```bash
$ retransform --classPattern demo.MathGame
retransform success, size: 1, classes:
demo.MathGame
```
> 注意:对于同一个类,当存在多个 retransform entry时如果显式触发 retransform 则最后添加的entry生效(id最大的)。
### 消除 retransform 的影响
如果对某个类执行 retransform 之后,想消除影响,则需要:
* 删除这个类对应的 retransform entry
* 重新触发 retransform
> 如果不清除掉所有的 retransform entry并重新触发 retransform 则arthas stop时retransform过的类仍然生效。
### 结合 jad/mc 命令使用
```bash
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
mc /tmp/UserController.java -d /tmp
retransform /tmp/com/example/demo/arthas/user/UserController.class
```
* jad命令反编译然后可以用其它编译器比如vim来修改源码
* mc命令来内存编译修改过的代码
* 用retransform命令加载新的字节码
### 上传 .class 文件到服务器的技巧
使用`mc`命令来编译`jad`的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用`base64`命令来绕过。
1. 在本地先转换`.class`文件为base64再保存为result.txt
```bash
base64 < Test.class > result.txt
```
2. 到服务器上,新建并编辑`result.txt`,复制本地的内容,粘贴再保存
3. 把服务器上的 `result.txt`还原为`.class`
```
base64 -d < result.txt > Test.class
```
4. 用md5命令计算哈希值校验是否一致
### retransform的限制
* 不允许新增加field/method
* 正在跑的函数,没有退出不能生效,比如下面新增加的`System.out.println`,只有`run()`函数里的会生效
```java
public class MathGame {
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
while (true) {
game.run();
TimeUnit.SECONDS.sleep(1);
// 这个不生效,因为代码一直跑在 while里
System.out.println("in loop");
}
}
public void run() throws InterruptedException {
// 这个生效因为run()函数每次都可以完整结束
System.out.println("call run()");
try {
int number = random.nextInt();
List<Integer> primeFactors = primeFactors(number);
print(number, primeFactors);
} catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
}
}
```
Loading…
Cancel
Save