Android 代码注入的记录

运动健身、早睡早起、三餐规律、多读好书并保持输入输出,如果你真的想不明白自己要什么,做这些永远不会错。坚持一年,就算你还是没有目标,也能有一个好的身体、博学的脑袋,这些足以让你超越80%的同龄人。

前言

最近因为工作需要,需要使用代码注入的功能,这里简单介绍下代码注入的流程和心得。

  • 这篇文章主要是包含了我收集的一些有用的东西,没有其他的一些具体分享。

前置知识

  • Android的代码注入需要对Class的一些指令比较熟悉,才可以完成基本的操作。

Java

Class文件的结构

Class文件的结构大概如下所示,其中*表示0个或者多个。

  • 摘录自:ASM4-Guide
    • Core API / Classes / Structure / Overview

在这里插入图片描述

Java基本类型与Class类型

  • 摘录自:ASM4-Guide
    • Core API / Classes / Structure / Overview
Class字段含义
B基本类型byte
C基本类型char
D基本类型double
F基本类型float
I基本类型int
J基本类型long
S基本类型short
Z基本类型boolean
V特殊类型void
L对象类型,以分号结尾,如Ljava/lang/Object;
[Ljava/lang/String;数组类型,每一位使用一个前置的[字符来表示

Java方法声明与Class声明

  • 摘录自:ASM4-Guide

    • Core API / Classes / Structure / Overview
  • 括号中的是参数声明,括号后紧跟着的是返回类型。

Class字段Java方法声明
(IF)Vvoid m(int i, float f)
(Ljava/lang/Object;)Iint m(Object o)
(ILjava/lang/String;)[Iint[] m(int i, String s);
([I)Ljava/lang/Object;Object m(int[] i);

Class修饰符

修饰符ID说明
ACC_PUBLIC0X0001public类型
ACC_FINAL0X0010声明为final,只有类可以设置
ACC_SUPER0X0020使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类都为真
ACC_INTERFACE0X0200接口
ACC_ABSTRACT0X0400abstract类型,对于接口或者抽象类来说,此标志值为真,其他类为假
ACC_SYNTHETIC0X1000这个类并非由用户代码产生
ACC_ANNOTATION0X2000注解
ACC_ENUM0X4000枚举

Class常量池类型

常量池的类型说明
CONSTANT_Utf8_infotag标志位为1, UTF-8编码的字符串
CONSTANT_Integer_infotag标志位为3, 整形字面量
CONSTANT_Float_infotag标志位为4, 浮点型字面量
CONSTANT_Long_infotag标志位为5, 长整形字面量
CONSTANT_Double_infotag标志位为6, 双精度字面量
CONSTANT_Class_infotag标志位为7, 类或接口的符号引用
CONSTANT_String_infotag标志位为8,字符串类型的字面量
CONSTANT_Fieldref_infotag标志位为9, 字段的符号引用
CONSTANT_Methodref_infotag标志位为10,类中方法的符号引用
CONSTANT_InterfaceMethodref_infotag标志位为11, 接口中方法的符号引用
CONSTANT_NameAndType_infotag 标志位为12,字段和方法的名称以及类型的符号引用
CONSTANT_Method-Handle_infotag标志位为15,方法句柄
CONSTANT_Method-Type_infotag标志位为16,方法类型
CONSTANT_Invoke-Dynamic_infotag标志位为18,动态方法调用点

方法调用指令

关于方法的调用,Java 共提供了 5 个指令,来调用不同类型的函数:

调用指令说明绑定类型
invokestatic用来调用静态方法。静态绑定
invokevirtual用于调用非私有实例方法,比如 public 和 protected,大多数方法调用属于这一种。动态绑定
invokeinterface和上面这条指令类似,不过作用于接口类。动态绑定
invokespecial用于调用私有实例方法、构造器及 super 关键字等。静态绑定
invokedynamic用于调用动态方法。动态绑定

PS:

  • 静态绑定,指的是能够直接识别目标方法的情况。
  • 动态绑定指的是需要在运行过程中根据调用者的类型来确定目标方法的情况。

相比于静态绑定的方法调用,动态绑定的调用会更加耗时一些。由于方法的调用非常的频繁,JVM 对动态调用的代码进行了比较多的优化,比如使用方法表来加快对具体方法的寻址,以及使用更快的缓冲区来直接寻址( 内联缓存)。


大多数普通方法调用,使用的是invokevirtual指令,它其实和invokeinterface是一类的,都属于虚方法调用。很多时候,JVM 需要根据调用者的动态类型,来确定调用的目标方法,这就是动态绑定的过程。

invokevirtual指令有多态查找的机制,该指令运行时,解析过程如下:

  1. 找到操作数栈顶的第一个元素所指向的对象实际类型,记做 c;
  2. 如果在类型 c 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验。如果通过则返回这个方法直接引用,查找过程结束,不通过则返回java.lang.IllegalAccessError
  3. 否则,按照继承关系从下往上依次对 c 的各个父类进行第二步的搜索和验证过程;
  4. 如果始终没找到合适的方法,则抛出java.lang.AbstractMethodError异常,这就是 Java 语言中方法重写的本质。

ASM的文档

如果啃不动英文的文档,可以看这个博主的译文

IBM Developer社区的文章也是非常的不错的,可以作为入门用。

ASM的整体架构

ASM Core API调用流程图

  • ASM的架构分析和原理介绍详见:Whyn的文章ASM 简介,下图源于该文章。

在这里插入图片描述

实践

直接使用Sample的例子对照着改就好了,O(∩_∩)O

实践地址
添加变量AddFieldAdapter.java
添加方法AddMethodAdapter.java
删除变量RemoveFieldAdapter.java
删除方法RemoveMethodAdapter.java
添加耗时统计AddTimerAdapter,还有这部分也是:Other AddTimerAdapter
添加注解AddAnnotationAdapter.java
删除注解RemoveAnnotationAdapter.java
使用Transformer添加变量AddFieldTransformer.java
使用Transformer添加方法AddMethodTransformer.java
使用Transformer删除变量RemoveFieldTransformer.java
使用Transformer删除方法RemoveMethodTransformer.java

文档

MethodVisitor

org.objectweb.asm.MethodVisitor

  • ASM提供的接口和Java class的指令一致,可以对照着Class指令找对应的方法。
参数说明解释
invokevirtualInvoke instance method; dispatch based on class执行一般实例方法,创建完实例对象后,obj.method()调用的
invokespecialInvoke instance method; special handling for superclass, private, and instance initialization method invocations实例初始化方法(构造函数)、父类的方法(super.method()方式调用)、私有方法
invokeinterfaceInvoke interface method执行接口方法
invokestaticInvoke a class (static) method执行静态方法
invokedynamicInvoke dynamic methodjdk1.7新增,执行动态方法,不需要在编译时确定

Sample & QA

增加注解

注入静态变量

  • 关于静态变量的注入只有基本类型可以注入。否则会抛出Unexpected static-value typeException,比如Unexpected static-value type java.lang.Class
  • 代码摘录自:JarClassFileReader.java
private DexValue getStaticValue(Object value, DexType type) {
  if (value == null) {
    return null;
  }
  DexItemFactory factory = parent.application.getFactory();
  if (type == factory.booleanType) {
    int i = (Integer) value;
    assert 0 <= i && i <= 1;
    return DexValueBoolean.create(i == 1);
  }
  if (type == factory.byteType) {
    return DexValueByte.create(((Integer) value).byteValue());
  }
  if (type == factory.shortType) {
    return DexValueShort.create(((Integer) value).shortValue());
  }
  if (type == factory.charType) {
    return DexValueChar.create((char) ((Integer) value).intValue());
  }
  if (type == factory.intType) {
    return DexValueInt.create((Integer) value);
  }
  if (type == factory.floatType) {
    return DexValueFloat.create((Float) value);
  }
  if (type == factory.longType) {
    return DexValueLong.create((Long) value);
  }
  if (type == factory.doubleType) {
    return DexValueDouble.create((Double) value);
  }
  if (type == factory.stringType) {
    return new DexValueString(factory.createString((String) value));
  }
  throw new Unreachable("Unexpected static-value type " + type);
}

附录

字节码工具

ASM


Gradle

其他

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值