博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 代码注入的记录
阅读量:3556 次
发布时间:2019-05-20

本文共 5115 字,大约阅读时间需要 17 分钟。

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

前言

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

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

文章目录

前置知识

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

Java

Class文件的结构

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

  • 摘录自:
    • Core API / Classes / Structure / Overview

在这里插入图片描述

Java基本类型与Class类型

  • 摘录自:
    • 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声明

  • 摘录自:

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

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

Class修饰符

修饰符 ID 说明
ACC_PUBLIC 0X0001 public类型
ACC_FINAL 0X0010 声明为final,只有类可以设置
ACC_SUPER 0X0020 使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类都为真
ACC_INTERFACE 0X0200 接口
ACC_ABSTRACT 0X0400 abstract类型,对于接口或者抽象类来说,此标志值为真,其他类为假
ACC_SYNTHETIC 0X1000 这个类并非由用户代码产生
ACC_ANNOTATION 0X2000 注解
ACC_ENUM 0X4000 枚举

Class常量池类型

常量池的类型 说明
CONSTANT_Utf8_info tag标志位为1, UTF-8编码的字符串
CONSTANT_Integer_info tag标志位为3, 整形字面量
CONSTANT_Float_info tag标志位为4, 浮点型字面量
CONSTANT_Long_info tag标志位为5, 长整形字面量
CONSTANT_Double_info tag标志位为6, 双精度字面量
CONSTANT_Class_info tag标志位为7, 类或接口的符号引用
CONSTANT_String_info tag标志位为8,字符串类型的字面量
CONSTANT_Fieldref_info tag标志位为9, 字段的符号引用
CONSTANT_Methodref_info tag标志位为10,类中方法的符号引用
CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用
CONSTANT_NameAndType_info tag 标志位为12,字段和方法的名称以及类型的符号引用
CONSTANT_Method-Handle_info tag标志位为15,方法句柄
CONSTANT_Method-Type_info tag标志位为16,方法类型
CONSTANT_Invoke-Dynamic_info tag标志位为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的文档

  • ASM官网:,源码:
  • 官方文档:
  • 官方文档:
  • 官方FAQ:

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

    • 英文原版:

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

ASM的整体架构

  • 整体架构图:
  • 原图源自:。

ASM Core API调用流程图

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

在这里插入图片描述

实践

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

实践 地址
添加变量
添加方法
删除变量
删除方法
添加耗时统计 ,还有这部分也是:
添加注解
删除注解
使用Transformer添加变量
使用Transformer添加方法
使用Transformer删除变量
使用Transformer删除方法
  • 更多内容请见:。

文档

MethodVisitor

org.objectweb.asm.MethodVisitor

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

Sample & QA

增加注解

  • 官方添加注解例子:

注入静态变量

  • 关于静态变量的注入只有基本类型可以注入。否则会抛出Unexpected static-value typeException,比如Unexpected static-value type java.lang.Class
  • 代码摘录自:
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在类构造方法中完成初始化,可见:

附录

字节码工具

  • Android Studio
  • , is a bytecode editor suitable for viewing and modifying java class files.
  • 可以方便的查看Java字节码,便于比对注入的字节码是否符合要求。
  • 下载地址:

ASM

  • ASM官网:,源码:
  • 官方文档:
  • 官方文档:

  • 有详细注释:

Gradle

  • https://developer.android.com/studio/build/gradle-tips
  • 在项目中使用Gradle和ASM做一些优化:

其他

  • 推荐阅读:

转载地址:http://whzrj.baihongyu.com/

你可能感兴趣的文章
C#-快速排序算法
查看>>
docker 部署SpringBoot项目
查看>>
mybatis基础知识(四)&输入映射与输出映射
查看>>
gitflow工作流
查看>>
【MongoDB】update修改器($set、$unset、$inc、$push、$pull、$pop)
查看>>
JAVA 继承
查看>>
电脑键盘突然不能打字,很多键变成快捷键了
查看>>
Hbase表映射Hive表三种方法
查看>>
Java中获取List长度
查看>>
this关键字有什么用处?怎么用? 1.访问成员变量,区分成员变量和局部变量。 2.访问成员方法。 3.访问构造方法。 4.返回对当前对象的引用 5.将对当前对象的引用作为参数传递给其他方法。
查看>>
自学sql
查看>>
基于Springboot的社区开发项目
查看>>
nowcoder 左神算法1
查看>>
code刷题
查看>>
左神进阶2窗口
查看>>
dubbo入门
查看>>
http 错误类型
查看>>
一篇文章解决HTTP 请求头!
查看>>
学习日记02
查看>>
学习日记03
查看>>