99import java .net .URL ;
1010import java .nio .charset .StandardCharsets ;
1111import java .util .Arrays ;
12+ import java .util .Comparator ;
1213import java .util .jar .JarOutputStream ;
1314import java .util .zip .ZipEntry ;
1415
@@ -32,10 +33,7 @@ public static void agentmain(String cmdline, Instrumentation instrumentation) th
3233 report .println ("Agent loaded with options: %s%n" , String .join (" " , args ));
3334
3435 report .println ("Querying classes..." );
35- Class <?>[] classes = Arrays .stream (instrumentation .getAllLoadedClasses ())
36- .filter (instrumentation ::isModifiableClass )
37- .filter (clazz -> options .getFilterPredicate ().test (clazz .getName ()))
38- .toArray (Class <?>[]::new );
36+ Class <?>[] classes = getAllLoadedClasses (instrumentation , options );
3937 report .println ("" );
4038
4139 // The transformer could (as a side effect by the JVM) be called with classes not in the list which is why we pass the filtered classes to it as well
@@ -46,26 +44,9 @@ public static void agentmain(String cmdline, Instrumentation instrumentation) th
4644 instrumentation .addTransformer (dumper , true );
4745
4846 report .println ("Dumping started..." );
49-
5047 // Invoke the transformer and remove it when filtered classes are processed
5148 try {
52- for (int from = 0 ; from < classes .length ; from += DUMP_BATCH_SIZE ) {
53- int to = Math .min (from + DUMP_BATCH_SIZE , classes .length );
54- Class <?>[] batch = Arrays .copyOfRange (classes , from , to );
55- try {
56- // Transform the full batch in one go, and if this throws an exception, no classes have been retransformed.
57- instrumentation .retransformClasses (batch );
58- } catch (Throwable ignored ) {
59- // Transform classes one-by-one if batch transformation failed
60- for (Class <?> clazz : batch ) {
61- try {
62- instrumentation .retransformClasses (clazz );
63- } catch (Throwable th ) {
64- report .println ("Failed to dump %s" , clazz .getName ());
65- }
66- }
67- }
68- }
49+ retransformClasses (instrumentation , classes , report );
6950 } finally {
7051 instrumentation .removeTransformer (dumper );
7152 }
@@ -76,7 +57,7 @@ public static void agentmain(String cmdline, Instrumentation instrumentation) th
7657
7758 // Validate that no exceptions were generated during the dump process
7859 if (dumper .getLastException () != null ) {
79- report .println ("WARNING: One or more exceptions occurred while dumping classes." );
60+ report .println ("WARNING: One or more transformer exceptions occurred while dumping classes." );
8061 report .dump (dumper .getLastException ());
8162 report .println ("" );
8263 }
@@ -85,10 +66,12 @@ public static void agentmain(String cmdline, Instrumentation instrumentation) th
8566
8667 report .println ("Creating jar..." );
8768 try (JarOutputStream jar = new JarOutputStream (new FileOutputStream (destination ))) {
88- dumper .getClassInfos ().stream ().sorted ().forEach (classInfo -> {
69+ dumper .getClassInfos ().entrySet ().stream ().sorted (Comparator .comparing (o -> o .getKey ().getName ())).forEach (entry -> {
70+ Class <?> clazz = entry .getKey ();
71+ byte [] bytecode = entry .getValue ();
8972 try {
90- report .dump (classInfo );
91- writeZipEntry (jar , classInfo . getNativeClassName () + ".class" , classInfo . getBytecode () );
73+ report .dump (clazz , bytecode );
74+ writeZipEntry (jar , Utils . toNativeClassName ( clazz . getName ()) + ".class" , bytecode );
9275 } catch (IOException e ) {
9376 throw new RuntimeException (e );
9477 }
@@ -98,35 +81,69 @@ public static void agentmain(String cmdline, Instrumentation instrumentation) th
9881 report .println ("%nDumped classes, including report.txt, can be found in: %s" , options .getDestination ());
9982 }
10083
84+ private static Class <?>[] getAllLoadedClasses (Instrumentation instrumentation , Options options ) {
85+ return Arrays .stream (instrumentation .getAllLoadedClasses ())
86+ .filter (instrumentation ::isModifiableClass )
87+ .filter (clazz -> options .getFilterPredicate ().test (clazz .getName ()))
88+ .filter (clazz -> !options .isIgnoreSystemClassloader () || clazz .getClassLoader () != null )
89+ .filter (clazz -> !options .isIgnorePlatformClassloader () || clazz .getClassLoader () != ClassLoader .getPlatformClassLoader ())
90+ .sorted (Comparator .comparing (Class ::getName ))
91+ .toArray (Class <?>[]::new );
92+ }
93+
94+ private static void retransformClasses (Instrumentation instrumentation , Class <?>[] classes , Report report ) {
95+ for (int from = 0 ; from < classes .length ; from += DUMP_BATCH_SIZE ) {
96+ int to = Math .min (from + DUMP_BATCH_SIZE , classes .length );
97+ Class <?>[] batch = Arrays .copyOfRange (classes , from , to );
98+ try {
99+ // Transform the full batch in one go, and if this throws an exception some classes may have been dumped but we deduplicate later on.
100+ instrumentation .retransformClasses (batch );
101+ } catch (Throwable ignored ) {
102+ // Transform classes one-by-one if batch transformation failed
103+ for (Class <?> clazz : batch ) {
104+ try {
105+ instrumentation .retransformClasses (clazz );
106+ } catch (Throwable th ) {
107+ report .println ("Failed to dump %s" , clazz .getName ());
108+ }
109+ }
110+ }
111+ }
112+ }
113+
114+
101115 private static void writeZipEntry (JarOutputStream jar , String name , byte [] data ) throws IOException {
102116 jar .putNextEntry (new ZipEntry (name ));
103117 jar .write (data );
104118 }
105119
120+ @ SuppressWarnings ("ConcatenationWithEmptyString" )
106121 private static String getHeader () {
107122 return "" +
108123 "---------------------------------------------------------%n" +
109- "--> Java Forensics Toolkit v1.0.2 by Benjamin Sølberg <--%n" +
124+ "--> Java Forensics Toolkit v1.1.0 by Benjamin Sølberg <--%n" +
110125 "---------------------------------------------------------%n" +
111126 "https://github.com/BenjaminSoelberg/JavaForensicsToolkit%n%n" ;
112127 }
113128
114129 private static void showUsage () {
115- System .out .println ("usage: java -jar JavaForensicsToolkit.jar [-v] [-e] [-d destination.jar] [-f filter]... [-x] <pid>" );
130+ System .out .println ("usage: java -jar JavaForensicsToolkit.jar [-v] [-e] [-d destination.jar] [-s] [-p] [- f filter]... [-x] <pid>" );
116131 System .out .println ();
117132 System .out .println ("options:" );
118133 System .out .println ("-v\t verbose agent logging" );
119134 System .out .println ("-e\t agent will log to stderr instead of stdout" );
120135 System .out .println ("-d\t jar file destination of dumped classes" );
121136 System .out .println ("\t Relative paths will be relative with respect to the target process." );
122137 System .out .println ("\t A jar file in temp will be generated if no destination was provided." );
138+ System .out .println ("-s\t ignore system class loader (like java.lang.String)" );
139+ System .out .println ("-p\t ignore platform class loader (like system extensions)" );
123140 System .out .println ("-f\t regular expression class name filter" );
124141 System .out .println ("\t Can be specified multiple times." );
125142 System .out .println ("-x\t exclude classes matching the filter" );
126143 System .out .println ("pid\t process id of the target java process" );
127144 System .out .println ();
128145 System .out .println ("example:" );
129- System .out .println ("java -jar JavaForensicsToolkit.jar -d dump.jar -f java\\ \\ ..* -f sun\\ \\ ..* -f jdk\\ \\ ..* -f com\\ \\ .sun\\ \\ ..* -x 123456" );
146+ System .out .println ("java -jar JavaForensicsToolkit.jar -d dump.jar -f ' java\\ \\ ..*' -f ' sun\\ \\ ..*' -f ' jdk\\ \\ ..*' -f ' com\\ \\ .sun\\ \\ ..*' -x 123456" );
130147 }
131148
132149 /**
0 commit comments