Skip to content

Instantly share code, notes, and snippets.

@thomasdarimont
Last active October 9, 2022 06:37
Show Gist options
  • Save thomasdarimont/dc6ac8e48de17c124480 to your computer and use it in GitHub Desktop.
Save thomasdarimont/dc6ac8e48de17c124480 to your computer and use it in GitHub Desktop.
This small example demonstrates how to dump the class-files of classes currently loaded in the JVM via jrunscript and internal hotspot API.

Dump loaded classes from another JVM process with Java 9

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 :)

Start Debuggee

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

Start Debugger

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())
    }
}

Demo

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.

@thomasdarimont
Copy link
Author

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/

@WhatATopic
Copy link

Hello I am trying to dump classes of a java program but it has become quite difficult for myself. For starters, I do not have access to the java program which is why I want to dump the classes. This java program runs in a specific environment openjdk version "1.8.0_181-0-ojdkbuild." All of the files in this environment are signature checked before the environment starts. The environment is also started with the argument -XX:DisableAttachMechanism. The program that runs the java environment is also signature checked so I don't think its possible to remove the argument unless I went through the trouble of removing the checks which I assume would be difficult because the checking occurs in a windows service. I have no clue what else I can try. Any ideas would be greatly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment