ASM

本文介绍了ASM3.0,一个用于动态生成和修改类的Java字节码框架。ASM与APT、AspectJ、Javassist的关系分别进行了阐述,并通过示例解释了如何使用ASM进行类文件的修改,包括字节码的概念和访问者模式的应用。最后,文章提到了Javassist在修改字节码方面的便利性。

今天介绍下ASM3.0,开始之前先思考几个问题:
1.ASM是什么?
2.ASM 跟传说中的AOP三剑客APT、aspectJ、Javassit有什么关系?
3.ASM是怎样修改class文件的?
带着问题开始今天的分享:

  1. ASM是什么?
    ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。说白了asm是直接通过字节码来修改class文件。
  2. ASM 跟传说中的AOP三剑客APT、aspectJ、Javassit有什么关系?
    分别解释下这几个名词
    APT:APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件
    aspectJ:AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的[编译器]用来生成遵守Java字节编码规范的Class文件。适合在某一个方法前后插入部分代码,处理某些逻辑:比如方法运行时间、插入动态权限检查等。问题会造成很多的冗余代码,产生很多代理类。简单来说就是在生成class时动态织入代码
    Javassit:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba(千叶滋)所创建的。简单来说就是源码级别的api去修改字节码
    各种方式作用时机
  3. ASM是怎样修改class文件的?
    开始这个问题之前我们先学习几个东西。
    字节码:这里的字节码主要说的是Java字节码
    访问者模式:一个称为元素(Element),另一个称为访问者(Visitor)。元素有一个accept方法,该方法接收访问者作为参数;accept()方法调用访问者的visit()方法,并且将元素自身作为参数传递给访问者。由元素本身决定是否访问
    在ASM中元素(被访问者)ClassReader、MethodNode等等,访问者接口包含ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor
    下面我们先简单实现一个插桩操作
    有如下代码:
public class Main2Activity extends AppCompatActivity {
    private static int MESSAGE_KEY = 0x2019;
    @SuppressLint("HandlerLeak")
    private static Handler sHandler =new Handler() {
        @Override        
        public void handleMessage(Message msg) {    
            if (msg.what ==MESSAGE_KEY) {            
                if (msg.obj!=null) {                 
                   Log.i("xmq", String.valueOf(msg.obj));                
                   }           
                 }      
          }   
 };       

@Override    
protected void onCreate(Bundle savedInstanceState) {        
       super.onCreate(savedInstanceState);        
       setContentView(R.layout.activity_main);                      
       sendMessage(getClass().getSimpleName());
       }     

private void sendMessage(String string) {
        Message message = new Message();       
        message.what =MESSAGE_KEY;        
        sHandler.sendMessage(message);
}
}

我们要在sendMessage方法中添加一行代码变为下列

private void sendMessage(String string) {
    Message message = new Message();        
    message.what =MESSAGE_KEY;        
    message.obj = string;        
    sHandler.sendMessage(message);
}

1)我们这里使用一个Android studio的plugin (ASM ByteCode Outline)查看Main2Activity的ASM代码,看主要的sendMessage部分

{ 
           mv = cw.visitMethod(ACC_PRIVATE, "sendMessage",
 "(Ljava/lang/String;)V", null, null); 
           mv.visitCode();            
           Label l0 = new Label();            
           mv.visitLabel(l0);            
           mv.visitLineNumber(33, l0);            
           mv.visitTypeInsn(NEW, "android/os/Message");            
           mv.visitInsn(DUP);            
           mv.visitMethodInsn(INVOKESPECIAL, "android/os/Message", "<init>", "()V", false);                
           mv.visitVarInsn(ASTORE, 2);            
           Label l1 = new Label();            
           mv.visitLabel(l1);            
           mv.visitLineNumber(34, l1);            
           mv.visitVarInsn(ALOAD, 2);            
           mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "MESSAGE_KEY", "I");            
           mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");            
           Label l2 = new Label();            
           mv.visitLabel(l2);            
           mv.visitLineNumber(36, l2);            
           mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "sHandler", "Landroid/os/Handler;");            
           mv.visitVarInsn(ALOAD, 2);            
           mv.visitMethodInsn(INVOKEVIRTUAL, "android/os/Handler", "sendMessage", "(Landroid/os/Message;)Z", false);            
           mv.visitInsn(POP);            
           Label l3 = new Label();            
           mv.visitLabel(l3);            
           mv.visitLineNumber(37, l3);            
           mv.visitInsn(RETURN);           
           Label l4 = new Label();            
           mv.visitLabel(l4);            
           mv.visitLocalVariable("this", "Lcom/lucky/lib/studyapp/Main2Activity;", null, l0, l4, 0);            
           mv.visitLocalVariable("string", "Ljava/lang/String;", null, l0, l4, 1);            
           mv.visitLocalVariable("message", "Landroid/os/Message;", null, l1, l4, 2);            
           mv.visitMaxs(2, 3);            
           mv.visitEnd();        
       }
      

看起来很懵逼,其实这里只不过是ASM帮助我们调用java bytecode罢了。
2)修改你想要的代码,同样使用ASM ByteCode Outlineplugin对比差异代码

***
 mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "MESSAGE_KEY", "I");
 mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");
 Label l2 = new Label();
 mv.visitLabel(l2);
 mv.visitLineNumber(35, l2);
 mv.visitVarInsn(ALOAD, 2);
 mv.visitVarInsn(ALOAD, 1);
 mv.visitFieldInsn(PUTFIELD, "android/os/Message", "obj", "Ljava/lang/Object;");
 Label l3 = new Label();
 mv.visitLabel(l3);
 mv.visitLineNumber(36, l3);
 ***

看的出我们在mv.visitFieldInsn(PUTFIELD, “android/os/Message”, “what”, “I”);后边插入了三行代码(其中的Label以及行数设置可以不用理),那么这三行代码什么意思呢?这里就用到了上边提到的java bytecode知识,意思是:将变量2,1分别入栈,并将2变量赋值给message的obj。
好了那么我们开始写ASM,这里我们使用android transform api作为前置条件扫描文件。开始之前先解释几个类:
1)Opcodes接口定义了一些常量,尤其是版本号,访问标示符,字节码等信息;
2)ClassReader用于读取Class文件,主要用于Class文件的分析,可接受一个ClassVisitor;ClassReader会将解析过程中产生的类的部分信息,比如访问标识符,字段,方法逐个送入ClassVisitor,后者在接收到对应的信息后,进行各自的处理;
3)ClassVisitor的子类ClassWriter: 负责进行Class文件的输出和生成。ClassVisitor在进行字段和方法处理的时候,会委托给FieldVistor和MethodVisitor进行处理;在类的处理过程中,会创建对应的FieldVisitor和MethodVisitor对象;FieldVisitor和MethodVisitor类也各自有1个重要的子类,FieldWriter和MethodWriter;当ClassWriter进行字段和方法的处理时,也是依赖这两个类进行的;
4)ClassVisitor,FieldVisitor,MethodVisitor都可以使用委托的方式,将实际的处理工作交给内部的委托类进行;它们内部有一些列的visitXXX方法,这些方法就是ASM 的实际方法code。
创建元素跟访问者

private static byte [] scanClass(InputStream inputStream) {
        //被访问者(元素)        
        ClassReader cr = new ClassReader(inputStream)        
        //访问者        
        ClassWriter cw = new ClassWriter(cr, 0)        
        ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5,cw) //ClassWriter 的代理类        
        cr.accept(cv, ClassReader.EXPAND_FRAMES)        
        return cw.toByteArray()    
 }

这里的访问者ScanClassVisitor继承了ClassVisitor,这里我们要修改某一个方法,所以实现了visitMethod方法,并筛选其中的sendMessage方法
static class ScanClassVisitor extends ClassVisitor{
ScanClassVisitor(int api, ClassVisitor cv) { //这里很奇怪我无法使用继承Opcodes,内部直接调用ASM5,只能传参数

        super(api, cv)        
       }        
       @Override        
       MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {            
       MethodVisitor mv =  cv.visitMethod(access, name, desc, signature, exceptions)            
       if (name == "sendMessage") {             
          mv = new ScanMethodVisitor(Opcodes.ASM5,mv)            
       }            
       return mv        
     }    
   }

上边的ScanMethodVisitor是实现了MethodVisitor访问者,看ASM代码,我们需要在mv.visitFieldInsn(PUTFIELD, “android/os/Message”, “what”, “I”);行后插入代码,所以需要实现visitFieldInsn方法

opcode: PUTFIELD
owner: "android/os/Message"
name:"what"
desc:"I"
static class ScanMethodVisitor extends MethodVisitor {
        ScanMethodVisitor(int api, MethodVisitor mv) {      
              super(api, mv)        
        }        
        @Override        
        void visitFieldInsn(int opcode, String owner, String name, String desc) {       
             if (opcode == Opcodes.PUTFIELD && owner.equals("android/os/Message")) {                
             mv.visitFieldInsn(opcode, owner, name, desc)                
             mv.visitVarInsn(Opcodes.ALOAD, 2)                
             mv.visitVarInsn(Opcodes.ALOAD, 1)                
             mv.visitFieldInsn(Opcodes.PUTFIELD, "android/os/Message", "obj", "Ljava/lang/Object;")            
        } else {         
            mv.visitFieldInsn(opcode, owner, name, desc)            
        }        
      }   
   }

好了接下来运行代码transform看效果

public class Main2Activity extends AppCompatActivity {
    private static int MESSAGE_KEY = 8217;    
    @SuppressLint({"HandlerLeak"})    
    private static Handler sHandler = new Handler() {        
    public void handleMessage(Message msg) {       
         if (msg.what == Main2Activity.MESSAGE_KEY && msg.obj != null) {                
                 Log.i("xmq", String.valueOf(msg.obj));           
         }    

     }   
 };     
 public Main2Activity() {    
 }   

protected void onCreate(Bundle savedInstanceState) {  
      super.onCreate(savedInstanceState);        
      this.setContentView(2131296284);        
      this.sendMessage(this.getClass().getSimpleName());    
}    

private void sendMessage(String string) {   
     Message message = new Message();        
     message.what = MESSAGE_KEY;        
     message.obj = string;        
     sHandler.sendMessage(message);
   }
}

总结:
ASM直接修改class文件确实效率很高,但因直接操作字节码,需要有字节码知识,不适合直接上手,相比较来Javassit源码级修改class文件更方便些。
对标阿里P6级架构师
获取更多资料,可以扫码进群或关注微信。带你向Java架构师迈进。
在这里插入图片描述

ASM在不同场景下有不同含义,常见的有自动存储管理(Automatic Storage Management)和汇编语言(Assembly Language)。 ### 自动存储管理(Automatic Storage Management) 自动存储管理是Oracle数据库提供的一项功能,用于简化数据库存储管理。 - **别名机制**:在ASM中,别名是一种方便管理数据文件的方式。例如,创建的数据文件`kel.dbf`实际上相当于一个链接,指向了真正的数据文件`KEL.299.851556787`,使用`ASMCMD > ls -l`命令可以查看相关信息,如下所示: ```plaintext ASMCMD> ls -l Type Redund Striped Time Sys Name DATAFILE UNPROT COARSE JUN 29 23:00:00 Y KEL.299.851556787 DATAFILE UNPROT COARSE JUN 29 19:00:00 Y SYSAUX.258.850693003 DATAFILE UNPROT COARSE JUN 29 19:00:00 Y SYSTEM.259.850692939 DATAFILE UNPROT COARSE JUN 29 19:00:00 Y UNDOTBS1.257.850693039 DATAFILE UNPROT COARSE JUN 29 19:00:00 Y USERS.256.850693045 N kel.dbf => +KEL/IPAP/DATAFILE/KEL.299.851556787 ``` 这样可以通过别名来引用数据文件,而不必记住复杂的实际文件名[^1]。 - **查询ASM与存储信息**:可以使用SQL语句查询ASM磁盘组和磁盘的相关信息。例如,查询磁盘组信息的语句为: ```sql SET line 120 col NAME FOR a20 col PATH FOR a30 col mode_status FOR a11 col voting_files FOR a12 SELECT group_number,name,total_mb,free_mb,voting_files FROM v$asm_diskgroup ORDER BY group_number; ``` 查询磁盘信息的语句为: ```sql SELECT NAME,PATH,mode_status FROM v$asm_disk ORDER BY NAME; ``` 这些语句可以帮助管理员了解ASM磁盘组和磁盘的状态、容量等信息[^2]。 ### 汇编语言(Assembly Language) 汇编语言是一种低级编程语言,它使用助记符来表示机器指令,与特定的计算机体系结构密切相关。汇编语言通常用于需要对计算机硬件进行底层控制的场景,如操作系统开发、嵌入式系统编程等。例如,以下是一个简单的x86汇编语言程序示例,用于将两个寄存器的值相加: ```asm section .data ; 数据段,可定义变量等 section .text global _start _start: ; 将立即数5放入eax寄存器 mov eax, 5 ; 将立即数3放入ebx寄存器 mov ebx, 3 ; 将eax和ebx的值相加,结果存于eax add eax, ebx ; 退出程序 mov eax, 1 ; 系统调用号1表示退出程序 xor ebx, ebx ; 返回值为0 int 0x80 ; 执行系统调用 ``` 这个程序首先将两个值分别放入`eax`和`ebx`寄存器,然后将它们相加,最后退出程序。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值