问题描述
最近想了解一下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
官网:
http://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 RepositoryJasmin是 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那样的专用语言一样顺手了。
我的博客上的几个例子:
- 一个通不过Java字节码校验的例子 <- 这个是用ASM的例子
- 答复: 通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比(二) <- 这个和下面都是用BiteScript的例子
- JVM在校验阶段不检查接口的实现状况
- hotspot_evil/ReinterpretCast.java at master · rednaxelafx/hotspot_evil · GitHub
(好孩子不要学我在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一些。