是否有工具能够直接使用JVM字节码编写程序?

软件工程师、主攻高级编程语言虚拟机的设计与实现

74 👍 / 16 💬

问题描述

最近想了解一下JVM,有没有工具能够用类似于“汇编”的方式在字节码的层面上编写程序呢?

有哇当然有的。不过没有像.NET的MSIL那样比较标准化的语法的文本形式,都是大家自己想怎么搞就怎么搞。



===============================================

Jasm / Jdis

这是OpenJDK的最新成果:

asmtools - Code Tools

我之前都没留意到(应该说曾经留意到过然后没用上就又忘了…)有这么好用的新工具出来了。

Jasm + Jdis就可以达到.NET的ilasm + ildasm的roundtrip效果,这敢情好啊!再也不用人肉用十六进制编辑器改Class文件了…

引用官方文档:

AsmTools consist of a set of (Java class file) assembler/disassemblers:

- an assembler language that provides a Java-like declaration of member signatures, while providing Java VM specification compliant mnemonics for byte-code instructions. Jasm also provides high-level syntax for constructs often found within classfile attributes. Jasm encoded tests are useful for sequencing byte codes in a way that Javac compiled code might not normally sequence byte-codes.

- an assembler language that provides byte-code containers of class-file constructs. JCod encoded tests are useful for testing the well-formedness of class-files, as well as creating collections within a class-file construct that might be size-bounded by a normal Java compiler. JCod can also be used to 'fuzz' class files in a methodical way that respects class-file constructs.

AsmTools are completely reflexive - Java binary (.class) files may be disassembled into textual representations, which in turn can be assembled back to the same binary file.

然后引用OpenJDK里的一个例子:

jdk9/jdk9/hotspot: 3b1c4562953d test/runtime/verifier/primArray.jasm
// Method castToByteArray() tries to return an array of ints when an array
// of bytes is expected.
super class primArray
version 52:0
{

    public Method "<init>":"()V"
    stack 1 locals 1
    {
        aload_0;
        invokespecial Method java/lang/Object."<init>":"()V";
        return;
    }

    public static Method castToByteArray:"([I)[B"
        stack 1 locals 1
    {
        aload_0;
        areturn;
    }

} // end Class primArray

===============================================

以下是之前的回答:

相比.NET MSIL的ilasm / ildasm工具,下面列举的解决方案共通的缺点是:只负责写出(汇编),不负责读入(反汇编),无法做到roundtrip——把已有的Class文件给反汇编到文本形式的字节码,编辑后再把文本形式的字节码给汇编回到Class文件。ilasm + ildasm的这种roundtrip功能就非常非常赞。

Jasmin

官网:

jasmin.sourceforge.net/

直接用文本形式编写Java字节码,最常见的工具/语言是Jasmin。

(工具名称叫做Jasmin,其所实现的语言也叫做Jasmin)

引用Wikipedia的代码例子:

Jasmin (software)
.class public HelloWorld.j
.super java/lang/Object

.method public <init>()V
   aload_0
   invokespecial java/lang/Object/<init>()V
   return
.end method

.method public static main([Ljava/lang/String;)V
   .limit stack 2
   .limit locals 2
   getstatic      java/lang/System/out Ljava/io/PrintStream;
   ldc            "Hello World."
   invokevirtual  java/io/PrintStream/println(Ljava/lang/String;)V
   return
.end method

其它例子可以参考Jasmin自带的:

SourceForge.net Repository

Jasmin是 Jon Meyer 和 Troy Downing 在编写他们的《Java Virtual Machine》一书时写的一套工具,可以把Jasmin语法的Java字节码汇编成实际的Class文件。这玩儿1996年就写出来了…

还有其它书也使用了Jasmin作为实验工具,例如

《计算机组成及汇编语言原理》

而后来的

smali

是参照Jasmin而实现的对应Android dex字节码的汇编器。

从现在的角度看,Jasmin最大的问题就是很久没更新了。所以写一些老的Class文件还行,写带有新功能(Java 6的StackMapTable、Java 7的invokedynamic / MethodHandle、Java 8的default methods)的话就力不从心了。

===============================================

BiteScript

官网:

GitHub - headius/bitescript: The BiteScript API and language

这是我用得比较多的一个工具。它是Charles Nutter在JRuby上写的一套internal DSL,把ASM库包装成一个非常好用的语言。

直接用ASM写字节码,写起来颇冗长,谁用谁知道。纯粹为了手写些字节码例子的话ASM用起来太痛苦。

有了BiteScript之后写字节码就几乎跟Jasmin那样的专用语言一样顺手了。

我的博客上的几个例子:

(好孩子不要学我在hotspot_evil里写的东西…)

引用上面第二个链接里我写的例子:

require 'rubygems'
require 'bitescript'
include BiteScript

fb = FileBuilder.build(__FILE__) do
  public_class 'TestMethodSameName' do
    public_static_method 'foo', void, int do
      ldc 'TestMethodSameName.foo:(I)V'
      aprintln
      returnvoid
    end
    
    public_static_method 'foo', int, int do
      ldc 'TestMethodSameName.foo:(I)I'
      aprintln
      iload 0
      ireturn
    end
    
    public_static_method 'main', void, string[] do
      push_int 123
      invokestatic this, 'foo', [void, int]
      push_int 456
      invokestatic this, 'foo', [int, int]
      pop
      returnvoid
    end
  end
end

fb.generate do |filename, class_builder|
  File.open(filename, 'w') do |file|
    file.write(class_builder.generate)
  end
end

对我来说这看起来已经颇顺眼了,而且灵活读还颇高,用着爽啊。

唯一的“缺点”就是用BiteScript要装JRuby。但我的系统上总是有装JRuby的,所以这不是问题。

===============================================

JiteScript

官网:

GitHub - qmx/jitescript: Jitescript

像BiteScript那么好玩的internal DSL,怎么能让JRuby独美呢?所以就有大大把这个概念移植到了Java,而这个项目就是JiteScript。

引用一个例子:

jitescript/JiteClassTest.java at master · qmx/jitescript · GitHub
    @Test
    public void testDSL() throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        final String className = "helloTest";
        JiteClass jiteClass = new JiteClass(className) {
            {
                // you can use the pre-constructor style
                defineMethod("main", ACC_PUBLIC | ACC_STATIC, sig(void.class, String[].class), new CodeBlock() {
                    {
                        ldc("helloWorld");
                        getstatic(p(System.class), "out", ci(PrintStream.class));
                        swap();
                        invokevirtual(p(PrintStream.class), "println", sig(void.class, Object.class));
                        voidreturn();
                    }
                });
                // or use chained api
                defineMethod("hello", ACC_PUBLIC | ACC_STATIC, sig(String.class), newCodeBlock().ldc("helloWorld").areturn());

            }
        };

        Class<?> clazz = new DynamicClassLoader().define(jiteClass);
        Method helloMethod = clazz.getMethod("hello");
        Object result = helloMethod.invoke(null);
        Assert.assertEquals("helloWorld", result);

        Method mainMethod = clazz.getMethod("main", String[].class);
        mainMethod.invoke(null, (Object) new String[]{});

    }

嗯…我还是更喜欢BiteScript一些。