mirror of https://github.com/alibaba/arthas.git
add retransform command. #1651
parent
62aed21be8
commit
aa396f8f9a
@ -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";
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue