吧里有谁会java和java程序反汇编编

Android逆向(1)
Dalvik虚拟机的特点
体积小,占用内存空间小
专有的DEX可执行文件格式,体积更小,执行速度更快
常量池采用32位索引值,寻址类方法名、字段名、常量更快
基于寄存器架构,并拥有一套完整的指令系统
提供了声明周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等功能
所有的Android程序都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例
Java虚拟机与Dalvik虚拟机的区别
Java虚拟机运行字节码,Dalvik虚拟机运行Dalvik字节码
Dalvik可执行文件体积更小
Java虚拟机基于栈架构,Dalvik虚拟机基于寄存器架构
实例说明Dalvik字节码比Java字节码体积更小
public class Hello{
public int foo(int a, int b){
return (a + b) * (a - b);
public static void main(String[] args){
Hello hello = new Hello();
System.out.println(hello.foo(5, 3));
执行如下命令进行编译
javac Hello.java
然后执行如下命令反编译查看Java字节码
javap -c -p -classpath . Hello
javap程序位于jdk的bin目录下,说明一下各个参数
-c 对代码进行反汇编
-p 显示所有类和成员
-classpath 查找用户类文件的路径
反编译后的字节码
Compiled from "Hello.java"
public class Hello {
public Hello();
0: aload_0
1: invokespecial #1
// Method java/lang/Object."&init&":()V
public int foo(int, int);
0: iload_1
1: iload_2
3: iload_1
4: iload_2
7: ireturn
public static void main(java.lang.String[]);
// class Hello
4: invokespecial #3
// Method "&init&":()V
7: astore_1
8: getstatic
// Field java/lang/System.out:Ljava/io/PrintS
11: aload_1
12: iconst_5
13: iconst_3
14: invokevirtual #5
// Method foo:(II)I
17: invokevirtual #6
// Method java/io/PrintStream.println:(I)V
20: return
执行如下命令生成class文件对应的dex文件
. -- --. .
注意:dx程序位于sdk目录/build-tools/版本号/,必须将class文件拷贝到该目录再执行上面的命令,不然会出现各种莫名奇妙的问题。
接着执行如下命令看到dex文件对应的Dalvik字节码
dexdump -d Hello.dex
dexdump程序位于sdk目录/build-tools/版本号/,结果如下
Processing 'Hello.dex'...
Opened 'Hello.dex', DEX version '035'
Class descriptor
Access flags
: 0x0001 (PUBLIC)
Superclass
: 'Ljava/lang/O'
Interfaces
Static fields
Instance fields
Direct methods
: '&init&'
: 0x10001 (PUBLIC CONSTRUCTOR)
insns size
: 4 16-bit code units
|[00014c] Hello.&init&:()V
00015c: 7010 0400 0000
|0000: invoke-direct , Ljava/lang/O.&init&:()V
000162: 0e00
|0003: return-void
0x0000 line=1
0x0000 - 0x0004 reg=0 this LH
: '([Ljava/lang/S)V'
: 0x0009 (PUBLIC STATIC)
insns size
: 17 16-bit code units
|[000164] Hello.main:([Ljava/lang/S)V
000174: 2200 0100
|0000: new-instance v0, LH
000178: 7010 0000 0000
|0002: invoke-direct , LH.&init&:()V
00017e: 6201 0000
|0005: sget-object v1, Ljava/lang/S.out:Ljava/io/PrintS
000182: 1252
|0007: const/4 v2, #int 5
000184: 1233
|0008: const/4 v3, #int 3
000186: 6e30 0100 2003
|0009: invoke-virtual , LH.foo:(II)I
00018c: 0a00
|000c: move-result v0
00018e: 6e20 0300 0100
|000d: invoke-virtual , Ljava/io/PrintS.println:(I)V
000194: 0e00
|0010: return-void
0x0000 line=7
0x0005 line=8
0x0010 line=9
Virtual methods
: 0x0001 (PUBLIC)
insns size
: 6 16-bit code units
|[000198] Hello.foo:(II)I
0001a8: 9000 0304
|0000: add-int v0, v3, v4
0001ac: 9101 0304
|0002: sub-int v1, v3, v4
0001b0: b210
|0004: mul-int/2addr v0, v1
0001b2: 0f00
|0005: return v0
0x0000 line=3
0x0000 - 0x0006 reg=2 this LH
source_file_idx
: 1 (Hello.java)
看得眼花缭乱不知所措了,没关系我们挑一部分看即可,我们来单独看看foo()这个函数在Java字节码和Dalvik字节码中的样子。
Java字节码:
public int foo(int, int);
0: iload_1
1: iload_2
3: iload_1
4: iload_2
7: ireturn
Dalvik字节码
[000198] Hello.foo:(II)I
0000: add-int v0, v3, v4
0002: sub-int v1, v3, v4
0004: mul-int/2addr v0, v1
0005: return v0
先从直观来看貌似也看不出同样的函数Dalvik指令占用体积小,那就来分析一波。
先看Java字节码
Java虚拟机是基于栈结构的,这里的函数用于求值,也是用到了栈结构,被称为求值栈。
解释一下iload_n指令,i表示加载一个int类型的参数,load表示加载一个局部变量进栈,后面的n表示第几个局部变量,索引值从0开始计数。有人会有疑问,load_0去哪里了,看看最前面我们反编译出的完整的字节码,有0: aload_0,这里是加载了一个引用,将我们的索引值0用掉了,所以此处从1开始。
上面的代码add是加指令,sub是减指令,mul是乘指令。来画个图解释一下。
先把两个参数入栈,遇到add指令从栈顶弹出两个参数想加后将结果入栈,然后再将两个参数入栈,遇到sub指令从栈顶弹出两个参数相减,将结果入栈,然后遇到mul指令再从栈顶弹出两个参数相乘,就是最终的计算结果。
联想一下使用栈配合后缀表达式来做四则混合运算的求值,是不是就可以完整理解上面的代码了。
上面的指令,每条指令占一个字节,所以整个foo()函数占用8个字节。
再来看看Dalvik指令
由于Dalvik虚拟机是基于寄存器的,所以这里的vn都是表示寄存器。
稍微解释一下上面的代码,将v3和v4寄存器的值相加放入v0寄存器,将v3和v4寄存器的值相减放入v1寄存器,将v0和v1寄存器的值相乘放入v0寄存器,最终将v0寄存器的值返回。
其实Dalvik虚拟机在这个过程中也用到了栈,是程序的调用栈,这个栈维护了一个寄存器列表,用于具体的操作求值。
可以看到Dalvik字节码只用了4条指令。
结论:通过对比,发现同样的函数,Dalvik字节码生成指令更少,代码体积更小。
文章:29篇
阅读:46012
阅读:2276
阅读:5001
对博客中的内容有疑问,或者有更好的建议,可以发私信给我。javap是JDK自带的反汇编器,可以查看java编译器为我们生成的字节码。通过它,我们可以对照源代码和字节码,从而了解很多编译器内部的工作。
  javap [ 命令选项 ] class. . .
  javap 命令用于解析类文件。其输出取决于所用的选项。若没有使用选项,javap 将输出传递给它的类的 public 域及方法。javap 将其输出到标准输出设备上。
  -help&输出 javap 的帮助信息。
  -l&输出行及局部变量表。
  -b&确保与 JDK 1.1 javap 的向后兼容性。
  -public&只显示 public 类及成员。
  -protected&只显示 protected 和 public 类及成员。
  -package&只显示包、protected 和 public 类及成员。这是缺省设置。
  -private&显示所有类和成员。
  -J[flag]&直接将 flag 传给运行时系统。
  -s&输出内部类型签名。
  -c&输出类中各方法的未解析的代码,即构成 Java 字节码的指令。
  -verbose&输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本
  -classpath[路径] 指定 javap 用来查找类的路径。如果设置了该选项,则它将覆盖缺省值或 CLASSPATH 环境变量。目录用冒号分隔。
&  - bootclasspath[路径] 指定加载自举类所用的路径。缺省情况下,自举类是实现核心 Java 平台的类,位于 jrelib
  t.jar 和 jrelibi18n.jar 中。
  -extdirs[dirs]&覆盖搜索安装方式扩展的位置。扩展的缺省位置是 jrelibext。
Hello.java文件
public class&Hello
static void main(String args[])
int j=100;
int m=i+j;
System.out.println("m:"+m);
int n=a+b+c+d+e+f;
static int get2()
int d=a+b;
然后再执行以下命令:
javac Hello.java
javap -c Hello
Compiled from "Hello.java"
public class Hello extends java.lang.Object{
public Hello();
& &0: aload_0
& &1: invokespecial #1; //Method java/lang/Object."&init&":()V
& &4: return
static void main(java.lang.String[]);
& &0: bipush 10
& &2: istore_1
& &3: bipush 100
& &5: istore_2
& &6: iload_1
& &7: iload_2
& &8: iadd
& &9: istore_3
& &10: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintS
& &13: new #3; //class java/lang/StringBuilder
& &16: dup
& &17: invokespecial #4; //Method java/lang/StringBuilder."&init&":()V
& &20: ldc #5; //String m:
& &22: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/S)Ljava/lang/StringB
& &25: iload_3
& &26: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringB
& &29: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/S
& &32: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/S)V
& &35: return
int get();
& &0: iconst_1
& &1: istore_1
& &2: iconst_2
& &3: istore_2
& &4: iconst_3
& &5: istore_3
& &6: iconst_4
& &7: istore 4
& &9: iconst_5
& &10: istore 5
& &12: bipush 6
& &14: istore 6
& &16: iload_1
& &17: iload_2
& &18: iadd
& &19: iload_3
& &20: iadd
& &21: iload 4
& &23: iadd
& &24: iload 5
& &26: iadd
& &27: iload 6
& &29: iadd
& &30: istore 7
& &32: iload 7
& &34: ireturn
static int get2();
& &0: iconst_1
& &1: istore_0
& &2: bipush 12
& &4: istore_1
& &5: bipush 39
& &7: istore_2
& &8: iload_0
& &9: iload_1
& &10: iadd
& &11: istore_3
& &12: iload_3
& &13: ireturn
另外关于如何使用javap命令查看class文件的编译版本请参考《》
阅读(...) 评论()java反汇编工具怎么用?_百度知道
java反汇编工具怎么用?
java反汇编工具(即DJ Java Decompiler 2.9)怎么用?
我有更好的答案
  java class反编译工具JODE使用方式如下:
1.JODE是一个可运行的JAR文件,在windows环境下双击即可运行。
2.JODE不是通过常规的Open-&File的方式来加载JAVA编译后的类文件(*.class)或是类包(*.jar)的,而是通过在Options菜单中的Set Classpath来实现的,单独的类文件可以将它的上一级目录作为Classpath输入,然后再选择Reload Classpath即可。
JODE是全球最大的开源项目网站Sourceforge.net的成员,不要以为源码开放就小瞧它,在所有的JAVA反编译器中,JODE的反编译效果是最好的,尤其是对付一些常见的加密手段,例如混淆技术等,更是出类拔粹。
中国IT职业教育领先品牌
主营:教育培训-JAVA培训-大数据培训-Html5培训-UI培训-python培训等
java反汇编工具用来反汇编java程序,而反汇编目的是修改检查等
先去陈氏集团的共享库下载个class文件寻找器再进行反汇编
你把要反编的class文件用这个工具打打开就可以看到了
其他1条回答
为您推荐:
其他类似问题
您可能关注的内容
反汇编的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。登录以解锁更多InfoQ新功能
获取更新并接收通知
给您喜爱的内容点赞
关注您喜爱的编辑与同行
966,690 二月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
Java字节码忍者禁术
Java字节码忍者禁术
16&他的粉丝
3&他的粉丝
日. 估计阅读时间:
:开启与Netflix、微软、ThoughtWorks等公司的技术创新之路!
亲爱的读者:我们最近添加了一些个人消息定制功能,您只需选择感兴趣的技术主题,即可获取重要资讯的。
在阅读本文前,我假设读者对于字节码已经有了基本的了解。如果你需要了解一些基本的背景知识,请参考《Java程序员修炼之道》(Well-Grounded Java Developer)一书(作者为Evans与Verburg,由Manning于 2012年出版),或是来自于RebelLabs的这篇(下载PDF需要注册)。
让我们来看一下这个示例,它对于还不熟悉的JVM字节码的新手来说很可能会感到困惑。该示例使用了javap工具,它本质上是一个Java字节码的反汇编工具,在下载的JDK或JRE中可以找到它。在这个示例中,我们将讨论一个简单的类,它实现了Callable接口:
public class ExampleCallable implements Callable {
public Double call() {
return 3.1415;
相关厂商内容
相关赞助商
我们可以通过对javap工具进行最简单形式的使用,对这个类进行反汇编后得到以下结果:
$ javap kathik/java/bytecode_examples/ExampleCallable.class
Compiled from &ExampleCallable.java&
public class kathik.java.bytecode_examples.ExampleCallable
implements java.util.concurrent.Callable {
public kathik.java.bytecode_examples.ExampleCallable();
public java.lang.Double call();
public java.lang.Object call() throws java.lang.E
这个反汇编后的结果看上去似乎是错误的,毕竟我们只写一个call方法,而不是两个。而且即使我们尝试手工创建这两个方法,javac也会提示,代码中有两个具有相同名称和参数的方法,它们仅有返回类型的不同,因此这段代码是无法编译的。然而,这个类确确实实是由上面那个真实的、有效的Java源文件所生成的。
这个示例能够清晰地表明在使用Java中广为人知的一种限制:不可对返回类型进行重载,其实这只是Java语言的一种限制,而不是JVM字符码本身的强制要求。javac确实会在代码中插入一些不存在于原始的类文件中的内容,如果你为此感到担忧,那大可放心,因为这种事每时每刻都在发生!每一位Java程序员最先学到的一个知识点就是:“如果你不提供一个构造函数,那么编译器会为你自动添加一个简单的构造函数”。在javap的输出中,你也能看到其中有一个构造函数存在,而它并不存在于我们的代码中。
这些额外的方法从某种程度上表明,语言规格说明的需求比VM规格说明中的细节更为严格。如果我们能够直接编写字节码,就可以实现许多“不可能”实现的功能,而这种字节码虽然是合法的,却没有任何一个Java编译器能够生成它们。
举例来说,我们可以创建出完全不含构造函数的类。Java语言规格说明中要求每个类至少要包含一个构造函数,而如果我们在代码中没有加入构造函数,javac会自动加入一个简单的void构造函数。但是,如果我们能够直接编写字节码,我们完全可以忽略构造函数。这种类是无法实例化的,即使通过反射也不行。
我们的最后一个例子已经接近成功了,但还是差一口气。在字节码中,我们可以编写一个方法,它将试图调用一个其它类中定义的私有方法。这段字节码是有效的,但如果任何程序打算加载它,它将无法正确地进行链接。这是因为在类型加载器中(classloader)的校验器会检测出这个方法调用的访问控制限制,并且拒绝这个非法访问。
如果我们打算在创建的代码中实现这些超越Java语言的行为,那就需要完全手动创建这样的一个类文件。由于这个类文件的格式是两进制的,因此可以选择使用某种类库,它能够让我们对某个抽象的数据结构进行操作,随后将其转换为字节码,并通过流方式将其写入磁盘。
具备这种功能的类库有多个选择,但在本文中我们将关注于ASM。这是一个非常常见的类库,在Java 8分发包中有一个以内部API的形式提供的版本(其内容稍有不同)。对于用户代码来说,我们选择使用通用的开源类库,而不是JDK中提供的版本,毕竟我们不应当依赖于内部API来实现所需的功能。
ASM的核心功能在于,它提供了一种API,虽然它看上去有些神秘莫测(有时也会显得有些粗糙),但能够以一种直接的方式反映出字节码的数据结构。
我们看到的Java运行时是由多年之前的各种设计决策所产生的结果,而在后续各个版本的类文件格式中,我们能够清晰地看到各种新增的内容。
ASM致力于尽量使构建的类文件接近于真实形态,因此它的基础API会分解为一系列相对简单的方法片段(而这些片段正是用于建模的二进制所关注的)。
如果程序员打算完全手动编写类文件,就必需理解类文件的整体结构,而这种结构是会随时改变的。幸运的是,ASM能够处理多个不同Java版本中的类文件格式之间的细微差别,而Java平台本身对于可兼容性的高要求也侧面帮助了我们。
一个类文件依次包含以下内容:
某个特殊的数字(在传统的Unix平台上,Java中的特殊数字是这个历史悠久的、人见人爱的0xCAFEBABE)
正在使用中的类文件格式版本号
访问控制标记(例如类的访问范围是public、protected还是package等等)
该类的类型名称
该类的超类
该类所实现的接口
该类拥有的字段(处于超类中的字段上方)
该类拥有的方法(处于超类中的方法上方)
属性(类级别的注解)
可以用下面这个方法帮助你记忆JVM类文件中的主要部分:
ASM中提供了两个API,其中最简单的那个依赖于访问者模式。在常见的形式中,ASM只包含最简单的字段以及ClassWrite类(当已经熟悉了ASM的使用和直接操作字节码的方式之后,许多开发者会发现CheckClassAdapter是一个很实用的起点,作为一个ClassVisitor,它对代码进行检查的方式,与Java的类加载子系统中的校验器的工作方式非常想像。)
让我们看几个简单的类生成的例子,它们都是按照常规的模式创建的:
启动一个ClassVisitor(在我们的示例中就是一个ClassWriter)
写入头信息
生成必要的方法和构造函数
将ClassVisitor转换为字节数组,并写入输出
public class Simple implements ClassGenerator {
// Helpful constants
private static final String GEN_CLASS_NAME = &GetterSetter&;
private static final String GEN_CLASS_STR = PKG_STR + GEN_CLASS_NAME;
public byte[] generateClass() {
ClassWriter cw = new ClassWriter(0);
CheckClassAdapter cv = new CheckClassAdapter(cw);
// Visit the class header
cv.visit(V1_7, ACC_PUBLIC, GEN_CLASS_STR, null, J_L_O, new String[0]);
generateGetterSetter(cv);
generateCtor(cv);
cv.visitEnd();
return cw.toByteArray();
private void generateGetterSetter(ClassVisitor cv) {
// Create the private field myInt of type int. Effectively:
// private int myI
cv.visitField(ACC_PRIVATE, &myInt&, &I&, null, 1).visitEnd();
// Create a public getter method
// public int getMyInt();
MethodVisitor getterVisitor =
cv.visitMethod(ACC_PUBLIC, &getMyInt&, &()I&, null, null);
// Get ready to start writing out the bytecode for the method
getterVisitor.visitCode();
// Write ALOAD_0 bytecode (push the this reference onto stack)
getterVisitor.visitVarInsn(ALOAD, 0);
// Write the GETFIELD instruction, which uses the instance on
// the stack (& consumes it) and puts the current value of the
// field onto the top of the stack
getterVisitor.visitFieldInsn(GETFIELD, GEN_CLASS_STR, &myInt&, &I&);
// Write IRETURN instruction - this returns an int to caller.
// To be valid bytecode, stack must have only one thing on it
// (which must be an int) when the method returns
getterVisitor.visitInsn(IRETURN);
// Indicate the maximum stack depth and local variables this
// method requires
getterVisitor.visitMaxs(1, 1);
// Mark that we've reached the end of writing out the method
getterVisitor.visitEnd();
// Create a setter
// public void setMyInt(int i);
MethodVisitor setterVisitor =
cv.visitMethod(ACC_PUBLIC, &setMyInt&, &(I)V&, null, null);
setterVisitor.visitCode();
// Load this onto the stack
setterVisitor.visitVarInsn(ALOAD, 0);
// Load the method parameter (which is an int) onto the stack
setterVisitor.visitVarInsn(ILOAD, 1);
// Write the PUTFIELD instruction, which takes the top two
// entries on the execution stack (the object instance and
// the int that was passed as a parameter) and set the field
// myInt to be the value of the int on top of the stack.
// Consumes the top two entries from the stack
setterVisitor.visitFieldInsn(PUTFIELD, GEN_CLASS_STR, &myInt&, &I&);
setterVisitor.visitInsn(RETURN);
setterVisitor.visitMaxs(2, 2);
setterVisitor.visitEnd();
private void generateCtor(ClassVisitor cv) {
// Constructor bodies are methods with special name
MethodVisitor mv =
cv.visitMethod(ACC_PUBLIC, INST_CTOR, VOID_SIG, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
// Invoke the superclass constructor (we are basically
// mimicing the behaviour of the default constructor
// inserted by javac)
// Invoking the superclass constructor consumes the entry on the top
// of the stack.
mv.visitMethodInsn(INVOKESPECIAL, J_L_O, INST_CTOR, VOID_SIG);
// The void return instruction
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
public String getGenClassName() {
return GEN_CLASS_NAME;
这段代码使用了一个简单的接口,用一个单一的方法生成类的字节,一个辅助方法以返回生成的类名,以及一些实用的常量:
interface ClassGenerator {
public byte[] generateClass();
public String getGenClassName();
// Helpful constants
public static final String PKG_STR = &kathik/java/bytecode_examples/&;
public static final String INST_CTOR = &&;
public static final String CL_INST_CTOR = &&;
public static final String J_L_O = &java/lang/Object&;
public static final String VOID_SIG = &()V&;
为了驾驭生成的类,我们需要使用一个harness类,它叫做Main。Main类提供了一个简单的类加载器,并且提供了一种反射式的方式对生成类中的方法进行回调。为了简便起见,我们将生成的类定入Maven的目标文件夹的正确位置,让IDE中的classpath能够顺利地找到它:
public class Main {
public static void main(String[] args) {
Main m = new Main();
ClassGenerator cg = new Simple();
byte[] b = cg.generateClass();
Files.write(Paths.get("target/classes/" + PKG_STR +
cg.getGenClassName() + ".class"), b, StandardOpenOption.CREATE);
} catch (IOException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
m.callReflexive(cg.getGenClassName(), "getMyInt");
下面的类提供了一种方法,能够对受保护的defineClass()进行访问,这样一来我们就能够将一个字节数组转换为某个类对象,以便在反射中使用。
private static class SimpleClassLoader extends ClassLoader {
public Class simpleDefineClass(byte[] clazzBytes) {
return defineClass(null, clazzBytes, 0, clazzBytes.length);
private void callReflexive(String typeName, String methodName) {
byte[] buffy =
buffy = Files.readAllBytes(Paths.get(&target/classes/& + PKG_STR +
typeName + &.class&));
if (buffy != null) {
SimpleClassLoader myCl = new SimpleClassLoader();
Class newClz = myCl.simpleDefineClass(buffy);
Object o = newClz.newInstance();
Method m = newClz.getMethod(methodName, new Class[0]);
if (o != null && m != null) {
Object res = m.invoke(o, new Object[0]);
System.out.println(&Result: & + res);
} catch (IOException | InstantiationException | IllegalAccessException |
NoSuchMethodException | SecurityException |
IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
有了这个类以后,我们只要通过细微的改动,就可以方便地测试各种不同的类生成器,以此对字节码生成器的各个方面进行探索。
实现无构造函数的类的方式也很相似。举例来说,以下这种方式可以在生成的类中仅包含一个静态字段,以及它的getter和setter(生成器不会调用generateCtor()方法):
private void generateStaticGetterSetter(ClassVisitor cv) {
// Generate the static field
cv.visitField(ACC_PRIVATE | ACC_STATIC, "myStaticInt", "I", null,
1).visitEnd();
MethodVisitor getterVisitor = cv.visitMethod(ACC_PUBLIC | ACC_STATIC,
"getMyInt", "()I", null, null);
getterVisitor.visitCode();
getterVisitor.visitFieldInsn(GETSTATIC, GEN_CLASS_STR, "myStaticInt", "I");
getterVisitor.visitInsn(IRETURN);
getterVisitor.visitMaxs(1, 1);
getterVisitor.visitEnd();
MethodVisitor setterVisitor = cv.visitMethod(ACC_PUBLIC | ACC_STATIC, "setMyInt",
"(I)V", null, null);
setterVisitor.visitCode();
setterVisitor.visitVarInsn(ILOAD, 0);
setterVisitor.visitFieldInsn(PUTSTATIC, GEN_CLASS_STR, "myStaticInt", "I");
setterVisitor.visitInsn(RETURN);setterVisitor.visitMaxs(2,2);setterVisitor.visitEnd();
请留意一下该方法在生成时使用了ACC_STATIC标记,此外还请注意方法的参数是位于本地变量列表中的最前面的(这里使用的ILOAD 0 模式暗示了这一点 —— 而在生成实例方法时,此处应该改为ILOAD 1,这是因为实例方法中的“this”引用存储在本地变量表中的偏移量为0)。
通过使用javap,我们就能够确认在生成的类中确实不包括任何构造函数:
$ javap -c kathik/java/bytecode_examples/StaticOnly.class
public class kathik.StaticOnly {
public static int getMyInt(); Code:
0: getstatic
// Field myStaticInt:I
3: ireturn
public static void setMyInt(int); Code:
0: iload_0
1: putstatic
// Field myStaticInt:I
使用生成的类
目前为止,我们是使用反射的方式调用我们通过ASM所生成的类的。这有助于保持这个示例的自包含性,但在很多情况下,我们希望能够将这些代码生成在常规的Java文件中。要实现这一点非常简单。以下示例将生成的类保存在Maven的目标目录下,写法很简单:
$ cd target/classes
$ jar cvf gen-asm.jar kathik/java/bytecode_examples/GetterSetter.class kathik/java/bytecode_examples/StaticOnly.class
$ mv gen-asm.jar ../../lib/gen-asm.jar
这样一来我们就得到了一个JAR文件,可以作为依赖项在其它代码中使用。比方说,我们可以这样使用这个GetterSetter类:
import kathik.java.bytecode_examples.GetterS
public class UseGenCodeExamples {
public static void main(String[] args) {
UseGenCodeExamples ugcx = new UseGenCodeExamples();
ugcx.run();
private void run() {
GetterSetter gs = new GetterSetter();
gs.setMyInt(42);
System.out.println(gs.getMyInt());
这段代码在IDE中是无法通过编译的(因为GetterSetter类没有配置在classpath中)。但如果我们直接使用命令行,并且在classpath中指向正确的依赖,就可以正确地运行了:
$ cd ../../src/main/java/
$ javac -cp ../../../lib/gen-asm.jar kathik/java/bytecode_examples/withgen/UseGenCodeExamples.java
$ java -cp .:../../../lib/gen-asm.jar kathik.java.bytecode_examples.withgen.UseGenCodeExamples
在本文中,我们通过使用ASM类库中所提供的简单API,学习了完全手动生成类文件的基础知识。我们也为读者展示了Java语言和字节码有哪些不同的要求,并且了解到Java中的某些规则其实只是语言本身的规范,而不是运行时所强制的要求。我们还看到,一个正确编写的手工类文件可以直接在语言中使用,与通过javac生成的文件没有区别。这一点也是Java与其它非Java语言,例如Groovy或Scala进行互操作的基础。
这方面的应用还有许多高级技巧,通过本文的学习,读者应该已经掌握了基本的知识,并且能够进一步深入研究JVM的运行时,以及如何对它进行各种操作的技术。
Ben Evans是Java/JVM性能分析初创公司jClarity的CEO。在业余时间他是伦敦Java社区的领导者之一并且是Java社区进程执行委员会的一员。之前的项目经验包括谷歌IPO的性能测试,金融交易系统,为90年代一些最大的电影编写备受好评的网站,以及其他。
查看英文原文:
Author Contacted
语言 & 开发
280 他的粉丝
1 他的粉丝
4 他的粉丝
2882 他的粉丝
1 他的粉丝
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
InfoQ.com及所有内容,版权所有 ©
C4Media Inc. InfoQ.com 服务器由 提供, 我们最信赖的ISP伙伴。
极客邦控股(北京)有限公司
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。}

我要回帖

更多关于 反汇编 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信