We will use jrunscript and internal hotspot API to access and export the classfile data. To do that we will use 2 jrunscript processes, a "debuggee" process and a "debugger" jrunscript process. The debugger process will attach to and inspect the debuggee process (internal) via hotspot API. For this example we use the "latest" Java 9 build b41 on OSX.
Motivation for this example was given by the fact that the latest JDK 9 release b41 doesn't ship an rt.jar anymore.
The content from rt.jar was now moved to a new platform specific format with the extension .jimage
in the JDK_HOME/lib/modules
.
You can find more information about the new packaging in Java 9 in Mark Reinholds blogpost: http://mreinhold.org/blog/jigsaw-modular-images
Sometimes it is handy to be able to look at class definitions with a decompiler or bytecode disassembler. Since there is no rt.jar anymore we have to come up with other techniques for accessing the bytecode of the runtime classes and this example shows one way to do it :)
Launch the "debuggee" jrunscript in a separate console and keep it running
Start jrunscript
Print the PID of this JVM process
Java.type("java.lang.management.ManagementFactory").getRuntimeMXBean().getName().split("@")[0]
> 3357
Make sure you run a Java 9 JDK. Note: In some cases you need to start the debugger with sudo.
Launch the "debugger" jrunscript via sudo jrunscript
//Connect to the other JVM process
var HotSpotAgent = Java.type("sun.jvm.hotspot.HotSpotAgent")
var agent = new HotSpotAgent()
agent.attach(3357) //use the PID from the debugee
//helper function to add support for multi-line input.
function ml() { return eval(read('>', true)) }
//Setup the necessary java types
var SystemDictionaryHelper = Java.type("sun.jvm.hotspot.utilities.SystemDictionaryHelper")
var File = Java.type("java.io.File")
var ClassWriter = Java.type("sun.jvm.hotspot.tools.jcore.ClassWriter")
var outputDir = "."
var allInstanceKlasses = SystemDictionaryHelper.getAllInstanceKlasses() //collect all instance classes
//enter multi-line mode
ml()
//Export the classes of all instances to the local folder
for each (var kls in allInstanceKlasses) {
var klassName = null
try{
klassName = kls.getName().asString().replace('/', File.separatorChar)
var index = klassName.lastIndexOf(File.separatorChar)
var dir = index != -1 ? new File(outputDir, klassName.substring(0, index)) : new File(outputDir)
dir.mkdirs()
var outputFile = new File(dir, klassName.substring(index + 1) + ".class")
outputFile.createNewFile()
var os = new BufferedOutputStream(new FileOutputStream(outputFile))
var cw = new ClassWriter(kls, os)
cw.write()
os.close()
}catch(e){
println("Got error: " + e + " for class: " + klassName + ": " + e.getMessage())
}
}
Run the debuggee
tom@gauss ~/dev/repos/thomasdarimont/playground/tmp $ java9
tom@gauss ~/dev/repos/thomasdarimont/playground/tmp $ java -version
java version "1.9.0-ea"
Java(TM) SE Runtime Environment (build 1.9.0-ea-b41)
Java HotSpot(TM) 64-Bit Server VM (build 1.9.0-ea-b41, mixed mode)
tom@gauss ~/dev/repos/thomasdarimont/playground/tmp $ jrunscript
nashorn> Java.type("java.lang.management.ManagementFactory").getRuntimeMXBean().getName().split("@")[0]
3357
nashorn>
Run the debugger
tom@gauss ~/dev/repos/thomasdarimont/playground/tmp $ java9
tom@gauss ~/dev/repos/thomasdarimont/playground/tmp $ java -version
java version "1.9.0-ea"
Java(TM) SE Runtime Environment (build 1.9.0-ea-b41)
Java HotSpot(TM) 64-Bit Server VM (build 1.9.0-ea-b41, mixed mode)
tom@gauss ~/dev/repos/thomasdarimont/playground/tmp $ sudo jrunscript
nashorn> var HotSpotAgent = Java.type("sun.jvm.hotspot.HotSpotAgent")
nashorn> var agent = new HotSpotAgent()
nashorn> agent.attach(3357) //use the PID from the debugee
nashorn>
nashorn> function ml() { return eval(read('>', true)) }
function ml() { return eval(read('>', true)) }
nashorn>
nashorn> var SystemDictionaryHelper = Java.type("sun.jvm.hotspot.utilities.SystemDictionaryHelper")
nashorn> var File = Java.type("java.io.File")
nashorn> var ClassWriter = Java.type("sun.jvm.hotspot.tools.jcore.ClassWriter")
nashorn>
nashorn> var outputDir = "."
nashorn> var allInstanceKlasses = SystemDictionaryHelper.getAllInstanceKlasses() //collect all instance classes
nashorn>
nashorn> ml()
>for each (var kls in allInstanceKlasses) {
var klassName = null
try{
klassName = kls.getName().asString().replace('/', File.separatorChar)
var index = klassName.lastIndexOf(File.separatorChar)
var dir = index != -1 ? new File(outputDir, klassName.substring(0, index)) : new File(outputDir)
dir.mkdirs()
var outputFile = new File(dir, klassName.substring(index + 1) + ".class")
outputFile.createNewFile()
var os = new BufferedOutputStream(new FileOutputStream(outputFile))
var cw = new ClassWriter(kls, os)
cw.write()
os.close()
}catch(e){
println("Got error: " + e + " for class: " + klassName + ": "+ e.getMessage())
}
}>>>>>>>>>>>>>>>>
>
nashorn>
Got error: java.lang.InternalError for class: java/lang/reflect/GenericDeclaration: null
Got error: java.lang.InternalError for class: java/util/Deque: null
Got error: java.lang.InternalError for class: java/util/NavigableMap: null
Got error: java.lang.InternalError for class: java/util/NavigableSet: null
Got error: java.lang.InternalError for class: java/util/Queue: null
Got error: java.lang.InternalError for class: java/util/SortedMap: nul
The current directory should now contain the classfiles that could be exported.
Note: This example is based on the functionality that can be found in the (former) sa-jdi.jar (Hotspot Serviceability Agent) - for which I wrote a small post (some years ago in german) here: https://www.tutorials.de/threads/mit-debugger-interne-hotspot-jvm-informationen-auslesen-in-java-7.387020/