terminaljvm

How to dissasemble .class files to jvm bytecode from the command prompt?


Are there any utilities that allow me to get a .class file and convert it into jvm bytecode?

There is a question for class decompilation, I do not want that, I want dissasembly. From the terminal.

I only find gui projects. https://github.com/java-decompiler/jd-gui I only want a simple utility


Solution

  • Given a .class object, there are some tools that help you dissasemble it. One of those is javap that ships with the JDK using the -c flag, let's see it in action.

    $ javap -c Hello.class
    Compiled from "Hello.java"
    public class Hello {
      public Hello();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #3                  // String Hello, world!
           5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    

    Now this is all fine, except this is read only, which makes it hard to do edits, however since this is the official dissasembler it is a nice reference. There is another dissasembler for jasmin, which is a java assembler, a bit dated, written in C++ for windows. Assuming you have a c++ compiler (for example one distributed from msys2) This one does not require a JRE.

    git clone https://github.com/masakioba/jhoja.git
    cd jhoja
    g++ jhoja.cpp -fpermissive -o jhoja.exe
    

    We can then try

    $ ./jhoja.exe /z/jvmstuff/Hello.class
    ;/* Class file Z:/jvmstuff/Hello.class version 50.0 */
    .source Hello.java
    
    .class  public synchronized Hello
    .super  java/lang/Object
    
    .method public <init>()V
    
            .limit stack 1
            .limit locals 1
    
            aload_0
            invokespecial   java/lang/Object/<init>()V
            return
    
    .end method
    
    .method public static main([Ljava/lang/String;)V
    
            .limit stack 2
            .limit locals 1
    
            getstatic       java/lang/System/out Ljava/io/PrintStream;
            ldc     "Hello, world!"
            invokevirtual   java/io/PrintStream/println(Ljava/lang/String;)V
            return
    
    .end method
    
    

    This in turn let you assemble it back using jasmin.

    There is yet another one, actually there are many jasm program out there, this however requires JRE 17 to run, you could package it locally like this

    # Check if ~/.local/bin and ~/.local/share/java exist, and create them if not
    mkdir -p ~/.local/bin ~/.local/share/java
    
    # Check if ~/.local/bin is in PATH
    if ! echo "$PATH" | grep -q ":.*/\.local/bin"; then
        # Append ~/.local/bin to .bashrc
        echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc
        # Append ~/.local/bin to the current PATH
        export PATH="$PATH:$HOME/.local/bin"
    fi
    
    # Download jasm-cli-2.4.0-all.jar
    wget -O ~/.local/share/java/jasm-cli-2.4.0-all.jar https://github.com/jumanji144/Jasm/releases/download/2.4.0/jasm-cli-2.4.0-all.jar
    
    # Create the jasm script
    cat > ~/.local/bin/jasm << 'EOF'
    #!/bin/bash
    java -jar ~/.local/share/java/jasm-cli-2.4.0-all.jar "$@"
    EOF
    chmod +x ~/.local/bin/jasm
    
    # Test the jasm script
    jasm --version
    

    And test the output

    $ jasm decompile /z/jvmstuff/Hello.class
    .super java/lang/Object
    .class public super Hello {
    
    
        .method public <init> ()V {
            parameters: { this },
            code: {
            A:
                aload this
                invokespecial java/lang/Object.<init> ()V
                return
            }
        }
    
        .method public static main ([Ljava/lang/String;)V {
            parameters: { p0 },
            code: {
            A:
                getstatic java/lang/System.out Ljava/io/PrintStream;
                ldc "Hello, world!"
                invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
            B:
                return
            }
        }
    
    }
    

    There is also a python dissasembler

    git clone --depth=1 --single-branch --branch master https://github.com/Storyyeller/Krakatau.git
    

    It works however with python 2.7 and 3 but not the modern one on windows. These will generate .j files just use python Krakatau/dissasembler.py Hello.class

    This is how they look

    .version 50 0
    .class public super Hello
    .super java/lang/Object
    
    .method public <init> : ()V
        .code stack 1 locals 1
    L0:     aload_0
    L1:     invokespecial Method java/lang/Object <init> ()V
    L4:     return
    L5:
            .linenumbertable
                L0 1
            .end linenumbertable
        .end code
    .end method
    
    .method public static main : ([Ljava/lang/String;)V
        .code stack 2 locals 1
    L0:     getstatic Field java/lang/System out Ljava/io/PrintStream;
    L3:     ldc 'Hello, world!'
    L5:     invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V
    L8:     return
    L9:
            .linenumbertable
                L0 3
                L8 4
            .end linenumbertable
        .end code
    .end method
    .sourcefile 'Hello.java'
    .end class
    

    There are possibly other ways, but these are just some I have found, both 2nd and 3rd let you convert it to bytecode, the 3rd one includes both the assembler and dissasembler, and the 2nd one you need to get jasmin separately. The reason why there are so many assemblers is that Sun/Oracle never many any official ones. In case you want to inspect the class struct itself, https://ide.kaitai.io/ is a good start.

    Edit: I wrote a small java dissasembler in JavaScript. You can run it as:

    $ npx -p jvm_parser disassembler Hello.class
    

    Or run it web.

    async function processFile() {
                const fileInput = document.getElementById('fileInput');
                const outputDiv = document.getElementById('output');
    
                if (fileInput.files.length === 0) {
                    outputDiv.textContent = 'Please select a file.';
                    return;
                }
    
                const file = fileInput.files[0];
                const arrayBuffer = await file.arrayBuffer();
                const uint8Array = new Uint8Array(arrayBuffer);
    
                try {
                    const parser = await import("https://esm.sh/jvm_parser");
                    const disassembled = await parser.getDisassembled(uint8Array);
                    outputDiv.textContent = disassembled;
                } catch (error) {
                    outputDiv.textContent = `Error: ${error.message}`;
                }
            }
       body {
                font-family: Arial, sans-serif;
                max-width: 600px;
                margin: 0 auto;
                padding: 20px;
            }
            #output {
                white-space: pre-wrap;
                background-color: #f4f4f4;
                padding: 10px;
                border-radius: 5px;
                margin-top: 20px;
            }
     <h1>JVM Parser File Input</h1>
        <input type="file" id="fileInput" accept=".class">
        <button onclick="processFile()">Process File</button>
        <div id="output"></div>