├── src └── main │ ├── resources │ ├── dump.png │ ├── refresh.png │ ├── java-keywords.css │ └── scene.fxml │ └── java │ └── ru │ └── hyndo │ └── javadumper │ ├── VMsProviderImpl.java │ ├── outmode │ ├── OutMode.java │ ├── TextAreaPrintWriter.java │ ├── RawByteCodeOutMode.java │ ├── AsmifierOutMode.java │ └── FernFlowerOutMode.java │ ├── VMsProvider.java │ ├── ProxiedPrintStream.java │ ├── JavaDumperApplication.java │ ├── VmDataProvider.java │ └── MainFormController.java ├── README.md ├── pom.xml └── .gitignore /src/main/resources/dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyndor/JvmDumper/HEAD/src/main/resources/dump.png -------------------------------------------------------------------------------- /src/main/resources/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyndor/JvmDumper/HEAD/src/main/resources/refresh.png -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/VMsProviderImpl.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper; 2 | 3 | import com.sun.tools.attach.VirtualMachineDescriptor; 4 | 5 | import java.util.List; 6 | 7 | public class VMsProviderImpl implements VMsProvider { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/outmode/OutMode.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper.outmode; 2 | 3 | import java.nio.file.Path; 4 | import java.util.function.Consumer; 5 | 6 | public interface OutMode { 7 | 8 | String name(); 9 | 10 | void print(Path classFile, String className); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/java-keywords.css: -------------------------------------------------------------------------------- 1 | .keyword { 2 | -fx-fill: purple; 3 | -fx-font-weight: bold; 4 | } 5 | .semicolon { 6 | -fx-font-weight: bold; 7 | } 8 | .paren { 9 | -fx-fill: firebrick; 10 | -fx-font-weight: bold; 11 | } 12 | .bracket { 13 | -fx-fill: darkgreen; 14 | -fx-font-weight: bold; 15 | } 16 | .brace { 17 | -fx-fill: teal; 18 | -fx-font-weight: bold; 19 | } 20 | .string { 21 | -fx-fill: blue; 22 | } 23 | 24 | .comment { 25 | -fx-fill: cadetblue; 26 | } 27 | 28 | /*noinspection ALL*/ 29 | .paragraph-box:has-caret { 30 | -fx-background-color: #f2f9fc; 31 | } -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/VMsProvider.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper; 2 | 3 | import com.sun.tools.attach.VirtualMachine; 4 | import com.sun.tools.attach.VirtualMachineDescriptor; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | import static ru.hyndo.javadumper.JavaDumperApplication.APPLICATION_NAME; 10 | 11 | public interface VMsProvider { 12 | 13 | default List getVMs() { 14 | return VirtualMachine 15 | .list() 16 | .stream() 17 | .filter(vm -> !vm.displayName().contains(APPLICATION_NAME)) 18 | .collect(Collectors.toList()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JvmDumper 2 | Allows you to inspect/decompile/dump byte code loaded in JVM at runtime 3 | 4 | Attach to any java proccess and inspect any loaded class with three built-in modes: FernFlower decompiler, Asmifier (ByteCode), RawByteCode Mode 5 | 6 | **To build it you have to install sa-jdi.jar and tools.jar to your local maven repo. 7 | You can do it with commands:** 8 | ``` 9 | mvn install:install-file -DartifactId=sa-jdi -DgroupId=com.sun -Dversion=1.8 -Dfile="C:\Program Files\Java\jdk1.8.0_144\lib\sa-jdi.jar" -Dpackaging=jar 10 | mvn install:install-file -DartifactId=tools -DgroupId=com.sun -Dversion=1.8 -Dfile="C:\Program Files\Java\jdk1.8.0_144\lib\tools.jar" -Dpackaging=jar 11 | ``` 12 | Replace *C:\Program Files\Java\jdk1.8.0_144* with your path to jdk. 13 | 14 | **You can also download already built jar in release section - https://github.com/hyndor/JvmDumper/releases** 15 | 16 | **It requires Java 8 and java JDK(NOT JRE!!!) to work correctly** 17 | 18 | ![Asmifier Image](https://i.imgur.com/BjoJWTc.png) 19 | 20 | ![FernFlower Image](https://i.imgur.com/ZoGYu11.png) 21 | 22 | ![RawByteCode Image](https://i.imgur.com/EAZ8rQu.png) 23 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/ProxiedPrintStream.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper; 2 | 3 | import java.io.OutputStream; 4 | import java.io.PrintStream; 5 | import java.util.function.BiConsumer; 6 | 7 | public class ProxiedPrintStream extends OutputStream { 8 | 9 | private PrintStream source; 10 | private StringBuffer currentLine = new StringBuffer(); 11 | private BiConsumer newLineHandler = (a, b) -> {}; 12 | 13 | ProxiedPrintStream(PrintStream source) { 14 | this.source = source; 15 | } 16 | 17 | public void addNewLineHandler(BiConsumer newLineHandler) { 18 | this.newLineHandler = this.newLineHandler.andThen(newLineHandler); 19 | } 20 | 21 | @Override 22 | public void write(int b) { 23 | source.write(b); 24 | if(b == '\n') { 25 | newLineHandler.accept(currentLine.toString(), this); 26 | currentLine = new StringBuffer(); 27 | } else { 28 | currentLine.append((char) b); 29 | } 30 | } 31 | 32 | public PrintStream getSource() { 33 | return source; 34 | } 35 | 36 | public StringBuffer getCurrentLine() { 37 | return currentLine; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/outmode/TextAreaPrintWriter.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper.outmode; 2 | 3 | import javafx.scene.control.TextArea; 4 | 5 | import java.io.*; 6 | 7 | public class TextAreaPrintWriter extends PrintWriter { 8 | 9 | public TextAreaPrintWriter(TextArea textArea) { 10 | super(new TextAreaWriter(textArea)); 11 | } 12 | 13 | private static class TextAreaWriter extends Writer { 14 | 15 | private final TextArea textArea; 16 | 17 | TextAreaWriter(TextArea textArea) { 18 | this.textArea = textArea; 19 | } 20 | 21 | @Override 22 | public void write(String str) { 23 | // textArea.setText(textArea.getText() + str); 24 | synchronized (textArea) { 25 | textArea.appendText(str); 26 | } 27 | // System.out.println(str); 28 | } 29 | 30 | @Override 31 | public void write(String str, int off, int len) throws IOException { 32 | write(str); 33 | } 34 | 35 | @Override 36 | public void write(char[] cbuf, int off, int len) throws IOException { 37 | throw new UnsupportedEncodingException(); 38 | } 39 | 40 | @Override 41 | public void flush() throws IOException { 42 | 43 | } 44 | 45 | @Override 46 | public void close() throws IOException { 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/outmode/RawByteCodeOutMode.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper.outmode; 2 | 3 | import javafx.application.Platform; 4 | import org.fxmisc.richtext.CodeArea; 5 | import org.objectweb.asm.ClassReader; 6 | import org.objectweb.asm.util.TraceClassVisitor; 7 | 8 | import java.io.FileInputStream; 9 | import java.io.PrintWriter; 10 | import java.io.StringWriter; 11 | import java.nio.file.Path; 12 | 13 | public class RawByteCodeOutMode implements OutMode { 14 | 15 | private CodeArea codeArea; 16 | 17 | public RawByteCodeOutMode(CodeArea codeArea) { 18 | this.codeArea = codeArea; 19 | } 20 | 21 | @Override 22 | public String name() { 23 | return "Raw byte code"; 24 | } 25 | 26 | @Override 27 | public void print(Path classFile, String className) { 28 | try { 29 | Platform.runLater(() -> codeArea.replaceText("")); 30 | print(classFile.toString()); 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | private void print(String classPath) throws Exception { 37 | int i = 0; 38 | int flags = ClassReader.SKIP_DEBUG; 39 | ClassReader cr = new ClassReader(new FileInputStream(classPath)); 40 | StringWriter strWriter = new StringWriter(); 41 | cr.accept(new TraceClassVisitor(new PrintWriter(strWriter)), flags); 42 | Platform.runLater(() -> codeArea.replaceText(strWriter.getBuffer().toString())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/outmode/AsmifierOutMode.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper.outmode; 2 | 3 | import javafx.application.Platform; 4 | import org.fxmisc.richtext.CodeArea; 5 | import org.objectweb.asm.ClassReader; 6 | import org.objectweb.asm.util.ASMifier; 7 | import org.objectweb.asm.util.TraceClassVisitor; 8 | 9 | import java.io.FileInputStream; 10 | import java.io.PrintWriter; 11 | import java.io.StringWriter; 12 | import java.nio.file.Path; 13 | 14 | public class AsmifierOutMode implements OutMode { 15 | 16 | private CodeArea codeArea; 17 | 18 | public AsmifierOutMode(CodeArea codeArea) { 19 | this.codeArea = codeArea; 20 | } 21 | 22 | @Override 23 | public String name() { 24 | return "Asmifier (ByteCode)"; 25 | } 26 | 27 | @Override 28 | public void print(Path classFile, String className) { 29 | try { 30 | Platform.runLater(() -> codeArea.replaceText("")); 31 | print(classFile.toString()); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | 37 | private void print(String classPath) throws Exception { 38 | int i = 0; 39 | int flags = ClassReader.SKIP_DEBUG; 40 | ClassReader cr = new ClassReader(new FileInputStream(classPath)); 41 | StringWriter strWriter = new StringWriter(); 42 | PrintWriter writer = new PrintWriter(strWriter); 43 | cr.accept(new TraceClassVisitor(null, new ASMifier(), writer), flags); 44 | Platform.runLater(() -> codeArea.replaceText(strWriter.getBuffer().toString())); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/outmode/FernFlowerOutMode.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper.outmode; 2 | 3 | import javafx.application.Platform; 4 | import org.fxmisc.richtext.CodeArea; 5 | import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler; 6 | 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | 13 | public class FernFlowerOutMode implements OutMode { 14 | 15 | private Path outputPath; 16 | private CodeArea codeArea; 17 | 18 | public FernFlowerOutMode(Path outputPath, CodeArea codeArea) { 19 | this.outputPath = outputPath; 20 | this.codeArea = codeArea; 21 | } 22 | 23 | @Override 24 | public String name() { 25 | return "Fern Flower (Java code)"; 26 | } 27 | 28 | @Override 29 | public void print(Path classFile, String className) { 30 | Platform.runLater(() -> { 31 | ConsoleDecompiler.main(new String[]{classFile.toString(), outputPath.toString()}); 32 | try { 33 | Optional opt = 34 | Files.walk(outputPath) 35 | .filter(path -> path.toString().endsWith(className.substring(className.lastIndexOf(".") + 1) + ".java")) 36 | .findFirst(); 37 | if (opt.isPresent()) { 38 | codeArea.replaceText(Files.lines(opt.get()).collect(Collectors.joining("\n"))); 39 | } else { 40 | codeArea.replaceText("Can not find decompiled java file in " + outputPath.toString()); 41 | } 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ru.hyndo.jvm-dumper 8 | jvm-dumper 9 | 1.3-SNAPSHOT 10 | 11 | 12 | 13 | com.sun 14 | tools 15 | 1.8 16 | 17 | 18 | com.sun 19 | sa-jdi 20 | 1.8 21 | 22 | 23 | org.ow2.asm 24 | asm-all 25 | 5.2 26 | 27 | 28 | 29 | org.fxmisc.richtext 30 | richtextfx 31 | 0.9.1 32 | 33 | 34 | 35 | org.jboss.windup.decompiler 36 | decompiler-fernflower 37 | 2.3.0.Final 38 | 39 | 40 | 41 | com.google.guava 42 | guava 43 | 24.1-jre 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-shade-plugin 52 | 3.1.1 53 | 54 | 55 | package 56 | 57 | shade 58 | 59 | 60 | 61 | 63 | ru.hyndo.javadumper.JavaDumperApplication 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-compiler-plugin 73 | 3.6.1 74 | 75 | 1.8 76 | 1.8 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/ 2 | 3 | ### Intellij ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 5 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 6 | 7 | # User-specific stuff: 8 | .idea/ 9 | *.iws 10 | /out/ 11 | *.iml 12 | .idea_modules/ 13 | 14 | # JIRA plugin 15 | atlassian-ide-plugin.xml 16 | 17 | # Crashlytics plugin (for Android Studio and IntelliJ) 18 | com_crashlytics_export_strings.xml 19 | crashlytics.properties 20 | crashlytics-build.properties 21 | fabric.properties 22 | 23 | 24 | ### Maven ### 25 | target/ 26 | pom.xml.tag 27 | pom.xml.releaseBackup 28 | pom.xml.versionsBackup 29 | pom.xml.next 30 | release.properties 31 | dependency-reduced-pom.xml 32 | buildNumber.properties 33 | .mvn/timing.properties 34 | 35 | 36 | ### Eclipse ### 37 | 38 | .metadata 39 | bin/ 40 | tmp/ 41 | *.tmp 42 | *.bak 43 | *.swp 44 | *~.nib 45 | local.properties 46 | .settings/ 47 | .loadpath 48 | .recommenders 49 | 50 | # Eclipse Core 51 | .project 52 | 53 | # External tool builders 54 | .externalToolBuilders/ 55 | 56 | # Locally stored "Eclipse launch configurations" 57 | *.launch 58 | 59 | # PyDev specific (Python IDE for Eclipse) 60 | *.pydevproject 61 | 62 | # CDT-specific (C/C++ Development Tooling) 63 | .cproject 64 | 65 | # JDT-specific (Eclipse Java Development Tools) 66 | .classpath 67 | 68 | # Java annotation processor (APT) 69 | .factorypath 70 | 71 | # PDT-specific (PHP Development Tools) 72 | .buildpath 73 | 74 | # sbteclipse plugin 75 | .target 76 | 77 | # Tern plugin 78 | .tern-project 79 | 80 | # TeXlipse plugin 81 | .texlipse 82 | 83 | # STS (Spring Tool Suite) 84 | .springBeans 85 | 86 | # Code Recommenders 87 | .recommenders/ 88 | 89 | 90 | ### Linux ### 91 | *~ 92 | 93 | # temporary files which can be created if a process still has a handle open of a deleted file 94 | .fuse_hidden* 95 | 96 | # KDE directory preferences 97 | .directory 98 | 99 | # Linux trash folder which might appear on any partition or disk 100 | .Trash-* 101 | 102 | # .nfs files are created when an open file is removed but is still being accessed 103 | .nfs* 104 | 105 | 106 | ### macOS ### 107 | *.DS_Store 108 | .AppleDouble 109 | .LSOverride 110 | 111 | # Icon must end with two \r 112 | Icon 113 | # Thumbnails 114 | ._* 115 | # Files that might appear in the root of a volume 116 | .DocumentRevisions-V100 117 | .fseventsd 118 | .Spotlight-V100 119 | .TemporaryItems 120 | .Trashes 121 | .VolumeIcon.icns 122 | .com.apple.timemachine.donotpresent 123 | # Directories potentially created on remote AFP share 124 | .AppleDB 125 | .AppleDesktop 126 | Network Trash Folder 127 | Temporary Items 128 | .apdisk 129 | 130 | 131 | ### Windows ### 132 | # Windows image file caches 133 | Thumbs.db 134 | ehthumbs.db 135 | 136 | # Folder config file 137 | Desktop.ini 138 | 139 | # Recycle Bin used on file shares 140 | $RECYCLE.BIN/ 141 | 142 | # Windows Installer files 143 | *.cab 144 | *.msi 145 | *.msm 146 | *.msp 147 | 148 | # Windows shortcuts 149 | *.lnk 150 | 151 | 152 | ### Java ### 153 | *.class 154 | 155 | # Mobile Tools for Java (J2ME) 156 | .mtj.tmp/ 157 | 158 | # Package Files # 159 | *.jar 160 | *.war 161 | *.ear 162 | 163 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 164 | hs_err_pid* 165 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/JavaDumperApplication.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper; 2 | 3 | import javafx.application.Application; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.Parent; 6 | import javafx.scene.Scene; 7 | import javafx.stage.Stage; 8 | 9 | import javax.swing.*; 10 | import javax.tools.JavaCompiler; 11 | import javax.tools.ToolProvider; 12 | import java.io.PrintStream; 13 | import java.util.Vector; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | 17 | public class JavaDumperApplication extends Application { 18 | 19 | private ExecutorService executorService = Executors.newCachedThreadPool(); 20 | 21 | private static final java.lang.reflect.Field LIBRARIES; 22 | 23 | static { 24 | try { 25 | LIBRARIES = ClassLoader.class.getDeclaredField("loadedLibraryNames"); 26 | LIBRARIES.setAccessible(true); 27 | } catch (NoSuchFieldException e) { 28 | throw new RuntimeException(e); 29 | } 30 | 31 | } 32 | 33 | public static final String APPLICATION_NAME = "JVM DUMPER"; 34 | 35 | public static void main(String[] args) { 36 | launch(args); 37 | } 38 | 39 | @Override 40 | public void start(Stage primaryStage) throws Exception { 41 | if (Float.parseFloat(System.getProperty("java.class.version")) != 52.0) { 42 | JOptionPane.showMessageDialog(null, "Java Dumper requires Java 8 to work correctly"); 43 | System.exit(0); 44 | return; 45 | } 46 | try { 47 | JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 48 | if (c == null) { 49 | JOptionPane.showMessageDialog(null, "Probably you are running on JRE. Java Dumper requires JDK to work correctly"); 50 | System.exit(1); 51 | return; 52 | } 53 | } catch (Throwable t) { 54 | JOptionPane.showMessageDialog(null, "Probably you are running on JRE. Java Dumper requires JDK to work correctly"); 55 | System.exit(1); 56 | return; 57 | } 58 | System.setProperty("sun.jvm.hotspot.runtime.VM.disableVersionCheck", "true"); 59 | FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("scene.fxml")); 60 | Parent root = loader.load(); 61 | MainFormController mainFormController = loader.getController(); 62 | ProxiedPrintStream proxy = new ProxiedPrintStream(System.out); 63 | System.setErr(new PrintStream(proxy, true)); 64 | proxy.addNewLineHandler((str, a) -> { 65 | if(str.contains("Error attaching to process")) { 66 | executorService.submit(() -> { 67 | JOptionPane.showMessageDialog(null, "Error attaching to process: " + "\"" + str + "\""); 68 | }); 69 | } 70 | }); 71 | mainFormController.setVMsProvider(new VMsProviderImpl()); 72 | mainFormController.init(); 73 | Scene scene = new Scene(root); 74 | //noinspection ConstantConditions 75 | scene.getStylesheets().add(JavaDumperApplication.class.getClassLoader().getResource("java-keywords.css").toExternalForm()); 76 | primaryStage.setScene(scene); 77 | primaryStage.setResizable(false); 78 | primaryStage.setTitle(APPLICATION_NAME); 79 | primaryStage.show(); 80 | } 81 | 82 | public static String[] getLoadedLibraries(final ClassLoader loader) { 83 | final Vector libraries; 84 | try { 85 | //noinspection unchecked 86 | libraries = (Vector) LIBRARIES.get(loader); 87 | return libraries.toArray(new String[]{}); 88 | } catch (IllegalAccessException e) { 89 | throw new RuntimeException(e); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/VmDataProvider.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper; 2 | 3 | import com.sun.tools.attach.VirtualMachineDescriptor; 4 | import ru.hyndo.javadumper.outmode.OutMode; 5 | import sun.jvm.hotspot.debugger.DebuggerException; 6 | import sun.jvm.hotspot.runtime.VM; 7 | import sun.jvm.hotspot.runtime.VMVersionMismatchException; 8 | import sun.jvm.hotspot.tools.Tool; 9 | import sun.jvm.hotspot.tools.jcore.ClassDump; 10 | 11 | import javax.swing.*; 12 | import java.io.IOException; 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.lang.reflect.Method; 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | import java.util.function.Predicate; 20 | 21 | public class VmDataProvider extends Tool { 22 | 23 | private static Method startMethod; 24 | 25 | static { 26 | try { 27 | startMethod = Tool.class.getDeclaredMethod("start", String[].class); 28 | startMethod.setAccessible(true); 29 | } catch (NoSuchMethodException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | private VirtualMachineDescriptor vm; 35 | private DumpMode dumpMode; 36 | private Set classNames = new HashSet<>(); 37 | private String classNameToDump; 38 | private Path tempDataOutput; 39 | private boolean parsed = false; 40 | 41 | VmDataProvider(VirtualMachineDescriptor vm, DumpMode dumpMode) { 42 | this.vm = vm; 43 | this.dumpMode = dumpMode; 44 | } 45 | 46 | VmDataProvider(VirtualMachineDescriptor vm, DumpMode dumpMode, String classNameToDump, Path tempDataOutput) { 47 | this.vm = vm; 48 | this.dumpMode = dumpMode; 49 | this.classNameToDump = classNameToDump; 50 | this.tempDataOutput = tempDataOutput; 51 | } 52 | 53 | public static void main(String[] args) { 54 | VmDataProvider vmDataProvider = new VmDataProvider(new VMsProviderImpl().getVMs().stream() 55 | .filter(virtualMachineDescriptor -> virtualMachineDescriptor.id().equalsIgnoreCase("7864")).findAny().get(), DumpMode.CLASS_LIST); 56 | System.setProperty("sun.jvm.hotspot.runtime.VM.disableVersionCheck", "true"); 57 | System.out.println("stat"); 58 | try { 59 | vmDataProvider.startParsing("7864"); 60 | } catch (Throwable e) { 61 | System.out.println("exception"); 62 | } 63 | System.out.println("end"); 64 | } 65 | 66 | private void startParsing(String PID) { 67 | try { 68 | //noinspection JavaReflectionInvocation 69 | startMethod.invoke(this, ((Object) (new String[]{PID}))); 70 | } catch (VMVersionMismatchException | DebuggerException | IllegalAccessException | InvocationTargetException e) { 71 | JOptionPane.showMessageDialog(null, e.getMessage()); 72 | stop(); 73 | return; 74 | } 75 | stop(); 76 | } 77 | 78 | Set getClassNames() { 79 | if (!parsed) { 80 | startParsing(vm.id()); 81 | parsed = true; 82 | } 83 | return classNames; 84 | } 85 | 86 | void dumpClass(OutMode outMode) { 87 | startParsing(vm.id()); 88 | try { 89 | Files.walk(tempDataOutput) 90 | .filter(file -> file.toString().endsWith(".class")) 91 | .filter(file -> file.toFile().getName().equalsIgnoreCase(classNameToDump.substring(classNameToDump.lastIndexOf(".") + 1) + ".class")) 92 | .forEach(classFile -> { 93 | new Thread(() -> outMode.print(classFile, classNameToDump)).start(); 94 | }); 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | @Override 101 | public void run() { 102 | VM.getVM().getSystemDictionary().classesDo(klass -> { 103 | String className = klass.getName().asString().replace('/', '.'); 104 | if (dumpMode == DumpMode.CLASS_LIST) { 105 | classNames.add(className); 106 | } else { 107 | if (className.equals(classNameToDump)) { 108 | String filter = className.substring(0, className.lastIndexOf(".")); 109 | ClassDump dumper = new ClassDump(null, filter); 110 | dumper.setOutputDirectory(tempDataOutput.toAbsolutePath().toString()); 111 | dumper.run(); 112 | } 113 | } 114 | }); 115 | } 116 | 117 | public enum DumpMode { 118 | CLASS_LIST, DUMP_CLASS 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/resources/scene.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 69 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/main/java/ru/hyndo/javadumper/MainFormController.java: -------------------------------------------------------------------------------- 1 | package ru.hyndo.javadumper; 2 | 3 | import com.google.common.collect.HashMultimap; 4 | import com.google.common.collect.Multimap; 5 | import com.sun.tools.attach.VirtualMachineDescriptor; 6 | import javafx.event.EventHandler; 7 | import javafx.fxml.FXML; 8 | import javafx.fxml.Initializable; 9 | import javafx.scene.control.*; 10 | import javafx.scene.input.KeyEvent; 11 | import javafx.scene.input.MouseEvent; 12 | import javafx.scene.layout.GridPane; 13 | import javafx.util.StringConverter; 14 | import org.fxmisc.richtext.CodeArea; 15 | import org.fxmisc.richtext.LineNumberFactory; 16 | import org.fxmisc.richtext.StyleClassedTextArea; 17 | import org.fxmisc.richtext.model.StyleSpans; 18 | import org.fxmisc.richtext.model.StyleSpansBuilder; 19 | import org.reactfx.Subscription; 20 | import ru.hyndo.javadumper.outmode.AsmifierOutMode; 21 | import ru.hyndo.javadumper.outmode.FernFlowerOutMode; 22 | import ru.hyndo.javadumper.outmode.OutMode; 23 | import ru.hyndo.javadumper.outmode.RawByteCodeOutMode; 24 | import sun.jvm.hotspot.memory.SystemDictionary; 25 | import sun.jvm.hotspot.oops.Klass; 26 | import sun.jvm.hotspot.runtime.VM; 27 | 28 | import java.io.IOException; 29 | import java.net.URL; 30 | import java.nio.file.Files; 31 | import java.nio.file.Path; 32 | import java.time.Duration; 33 | import java.util.*; 34 | import java.util.regex.Matcher; 35 | import java.util.regex.Pattern; 36 | 37 | @SuppressWarnings({"RegExpSingleCharAlternation", "RegExpRedundantEscape"}) 38 | public class MainFormController implements Initializable { 39 | 40 | private static final String[] KEYWORDS = new String[] { 41 | "abstract", "assert", "boolean", "break", "byte", 42 | "case", "catch", "char", "class", "const", 43 | "continue", "default", "do", "double", "else", 44 | "enum", "extends", "final", "finally", "float", 45 | "for", "goto", "if", "implements", "import", 46 | "instanceof", "int", "interface", "long", "native", 47 | "new", "package", "private", "protected", "public", 48 | "return", "short", "static", "strictfp", "super", 49 | "switch", "synchronized", "this", "throw", "throws", 50 | "transient", "try", "void", "volatile", "while" 51 | }; 52 | 53 | private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b"; 54 | private static final String PAREN_PATTERN = "\\(|\\)"; 55 | private static final String BRACE_PATTERN = "\\{|\\}"; 56 | private static final String BRACKET_PATTERN = "\\[|\\]"; 57 | private static final String SEMICOLON_PATTERN = "\\;"; 58 | private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""; 59 | private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"; 60 | 61 | private static final Pattern PATTERN = Pattern.compile( 62 | "(?" + KEYWORD_PATTERN + ")" 63 | + "|(?" + PAREN_PATTERN + ")" 64 | + "|(?" + BRACE_PATTERN + ")" 65 | + "|(?" + BRACKET_PATTERN + ")" 66 | + "|(?" + SEMICOLON_PATTERN + ")" 67 | + "|(?" + STRING_PATTERN + ")" 68 | + "|(?" + COMMENT_PATTERN + ")" 69 | ); 70 | 71 | private VMsProvider vMsProvider; 72 | 73 | @FXML 74 | private TreeView treeClassView; 75 | private CodeArea codeArea; 76 | @FXML 77 | private ChoiceBox outputMode; 78 | @FXML 79 | private ChoiceBox applicationPid; 80 | @FXML 81 | private Button dumpVmButton; 82 | @FXML 83 | private Button refreshVMsButton; 84 | @FXML 85 | private GridPane secondInnerGridPane; 86 | private Path tempOutPutDirectory; 87 | 88 | private void initClassOutputInfo() { 89 | } 90 | 91 | private void initApplicationPids() { 92 | applicationPid.setConverter(new StringConverter() { 93 | @Override 94 | public String toString(VirtualMachineDescriptor vm) { 95 | return vmToString(vm); 96 | } 97 | 98 | @Override 99 | public VirtualMachineDescriptor fromString(String string) { 100 | throw new UnsupportedOperationException("Cannot convert"); 101 | } 102 | }); 103 | vMsProvider 104 | .getVMs() 105 | .stream() 106 | .filter(Objects::nonNull) 107 | .forEach(vm -> 108 | applicationPid 109 | .getItems() 110 | .add(vm)); 111 | Iterator vmiter = applicationPid.getItems().iterator(); 112 | if(vmiter.hasNext()) { 113 | applicationPid.setValue(vmiter.next()); 114 | } 115 | } 116 | 117 | private String vmToString(VirtualMachineDescriptor vm) { 118 | if(vm.displayName().length() > 90) { 119 | return vm.id() + " " + vm.displayName().substring(0, 90); 120 | } 121 | return vm.id() + " " + vm.displayName(); 122 | } 123 | 124 | private void initChoiceBox() { 125 | OutMode asmifier = new AsmifierOutMode(codeArea); 126 | outputMode.getItems().add(asmifier); 127 | outputMode.getItems().add(new FernFlowerOutMode(tempOutPutDirectory, codeArea)); 128 | outputMode.getItems().add(new RawByteCodeOutMode(codeArea)); 129 | outputMode.setValue(asmifier); 130 | outputMode.setConverter(new StringConverter() { 131 | @Override 132 | public String toString(OutMode object) { 133 | return object.name(); 134 | } 135 | 136 | @Override 137 | public OutMode fromString(String string) { 138 | throw new UnsupportedOperationException(); 139 | } 140 | }); 141 | } 142 | 143 | private void initTreeClassview() { 144 | treeClassView.setShowRoot(false); 145 | treeClassView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { 146 | if(newValue == null) return; 147 | if(newValue.getChildren().isEmpty()) { 148 | String fullClassName = newValue.getParent().getValue() + "." + newValue.getValue(); 149 | if(applicationPid.getValue() != null) { 150 | VmDataProvider vmDataProvider = new VmDataProvider(applicationPid.getValue(), VmDataProvider.DumpMode.DUMP_CLASS, fullClassName, tempOutPutDirectory); 151 | vmDataProvider.dumpClass(outputMode.getValue()); 152 | } 153 | } 154 | }); 155 | } 156 | 157 | public MainFormController setVMsProvider(VMsProvider vMsProvider) { 158 | this.vMsProvider = vMsProvider; 159 | return this; 160 | } 161 | 162 | private void initClassOutputInfoElement() { 163 | codeArea = new CodeArea(); 164 | codeArea.setEditable(false); 165 | codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea)); 166 | Subscription cleanupWhenNoLongerNeedIt = codeArea 167 | .multiPlainChanges() 168 | .successionEnds(Duration.ofMillis(500)) 169 | .subscribe(ignore -> codeArea.setStyleSpans(0, computeHighlighting(codeArea.getText()))); 170 | secondInnerGridPane.add(codeArea, 0, 1, 1, 1); 171 | } 172 | 173 | public void init() { 174 | try { 175 | tempOutPutDirectory = Files.createTempDirectory("jvmdumper"); 176 | } catch (IOException e) { 177 | e.printStackTrace(); 178 | } 179 | initClassOutputInfoElement(); 180 | initTreeClassview(); 181 | initChoiceBox(); 182 | initClassOutputInfo(); 183 | initApplicationPids(); 184 | StyleClassedTextArea textArea = new StyleClassedTextArea(); 185 | 186 | dumpVmButton.setOnMouseClicked(event -> { 187 | if(event.getClickCount() == 1) { 188 | updateCurrentVM(); 189 | } 190 | }); 191 | refreshVMsButton.setOnMouseClicked(event -> { 192 | if(event.getClickCount() == 1) { 193 | applicationPid.getItems().clear(); 194 | initApplicationPids(); 195 | } 196 | }); 197 | } 198 | 199 | private static StyleSpans> computeHighlighting(String text) { 200 | Matcher matcher = PATTERN.matcher(text); 201 | int lastKwEnd = 0; 202 | StyleSpansBuilder> spansBuilder 203 | = new StyleSpansBuilder<>(); 204 | while(matcher.find()) { 205 | String styleClass = 206 | matcher.group("KEYWORD") != null ? "keyword" : 207 | matcher.group("PAREN") != null ? "paren" : 208 | matcher.group("BRACE") != null ? "brace" : 209 | matcher.group("BRACKET") != null ? "bracket" : 210 | matcher.group("SEMICOLON") != null ? "semicolon" : 211 | matcher.group("STRING") != null ? "string" : 212 | matcher.group("COMMENT") != null ? "comment" : 213 | null; /* never happens */ assert styleClass != null; 214 | spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); 215 | spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start()); 216 | lastKwEnd = matcher.end(); 217 | } 218 | spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); 219 | return spansBuilder.create(); 220 | } 221 | 222 | private void updateCurrentVM() { 223 | Set classNames = new VmDataProvider(applicationPid.getValue(), VmDataProvider.DumpMode.CLASS_LIST).getClassNames(); 224 | // package, classname 225 | Multimap packages = HashMultimap.create(); 226 | TreeItem root = new TreeItem<>("Root"); 227 | for (String className : classNames) { 228 | int index = className.lastIndexOf("."); 229 | if(index != -1) { 230 | String packageName = className.substring(0, index); 231 | String klass = className.substring(index + 1); 232 | packages.put(packageName, klass); 233 | } 234 | } 235 | Map> treeItemsOfPackages = new TreeMap<>(); 236 | packages.forEach((packageName, klass) -> { 237 | TreeItem treeItem = treeItemsOfPackages.computeIfAbsent(packageName, (__) -> new TreeItem<>(packageName)); 238 | treeItem.getChildren().add(new TreeItem<>(klass)); 239 | }); 240 | root.getChildren().addAll(treeItemsOfPackages.values()); 241 | treeClassView.setRoot(root); 242 | } 243 | 244 | @Override 245 | public void initialize(URL location, ResourceBundle resources) { 246 | 247 | } 248 | } 249 | --------------------------------------------------------------------------------