Android 进阶之路:ASM 修改字节码,这样学就对了!

看了很多 ASM 入门的文章,都感觉文章写的很轻松,站立的高度都太高了,我个人觉得想要能够编写 ASM 相关代码,能看懂字节码是必不可少的,所以本文会以字节码为切入点,带大家简单的入门一下 ASM。

Java Class 文件结构

大家都知道*.java 文件经过 javac 编译之后会生成 *.class 文件,*.class 文件会被 Java 虚拟机进行加载。

Java 虚拟机之所有能够加载class 文件,前提肯定是能够按照某种规则读取 class 文件的内容。

那么这个规则就是*.class 文件的内部结构。

比如我们编写一个非常简单的 Java类 Hello.java:

public class Hello{
	
	public static void main(String[] args){
		System.out.println(Hello.class.getSuperclass().getName());
	}
	
}

然后我们编译生成 Hello.class 文件。

这个时候我们来看下 Hello.class 文件结构,我们把 Hello.class 文件拖进一个支持 16 进制查看的编辑器(本文为010 Editor)中。

1613575770587.jpg

可以看到整个文件都是二进制的代码,图中是以 16 进制的方式展示给大家,当然了这一对 16 进制的代码我们当然是看不懂的,不过如果你对 class 文件有一点熟悉,你可能知道开头这几个 16 进制字符CAFEBABE的含义。

这几个字符为 class 文件的魔数,大家都知道 Java 和咖啡有着一些秘密,通过图标也能看出来,所以魔术为 CAFEBABE 并不奇怪,好奇的可以去搜索下这个魔数的由来。

恩...剩下的我们就很难看懂了...

当然你可以选择搜索「Java 文件格式」,这样你会找到非常详细的博文,来告诉你里面每段二进制代码的含义。

这样你就可以通过一篇详细的 class 文件格式的规范,来整理出其完整的面貌。

这是一件很枯燥的事情,不过我们是程序员。

如果 class文件可以按照某种固定的格式来解析,那么我们不就可以写出一个程序来解析所有的class文件了吗?

没错,是这样的。

解析了之后,我们还可以将各个区域存放在我们设定的数据格式中,我们对外暴露接口去修改这些数据结构,最后按照 class 文件格式,再反向输出到文件。

这样,我们这套程序不但能够解析 class 文件,还能够修改 class 文件。

没错,当然了,这套程序我们都能想到,市面上肯定已经有成熟的方案了:

所以,我们今天文章的主角出来了:

ASM,就是其中一个非常成熟的开源库,它可以充当「这套程序」的角色,帮助我们解析 class 文件、修改 class 文件。

先来一个开胃菜,修改类的继承关系

你可能会疑惑,当我们熟悉了 class文件夹的结构,做一下修改,就能够完成 class 文件的编辑吗?

没错。

下面我们演示一下class 文件的修改,当然我不准备用 ASM,我们准备手工修改一下。

还是刚才的例子:

public class Hello{
	
	public static void main(String[] args){
		System.out.println(Hello.class.getSuperclass().getName());
	}
	
}

我们 main()方法中输出了其父类的类名。

所以...我准备修改掉 Hello 的父类,目前 Hello继承自Object。

按照我们前面的描述,我们只要能够找到这个 Hello.class 文件中表示其父类的字段区域,对它就行修改就行了。

恩,为了大家能够看明白我们怎么找到 class文件的对应区域,我们可以借助 010 Editor,它内部有 class 文件模板,可以帮助我们较为清晰的看到每一部分的结构:

1613576815454.jpg

可以看到我们class 文件在一系列的常量池之后,会包含访问修饰符,当前类名,以及父类名。

父类名对应的值为 7,7 代表了常量池中的第 7 个元素,我们找到第 7 个常量:

1613577020688.jpg

u1 tag =7代表这个是Class 类型常量,常量值的索引为25。

我们再往下看第 25 个常量:

WX20210217-235331.png

长度为 16,字符数组为:106,97...这一串数字。

这一串数字其实就是 ASCII 码,你可以随便找到个码表:

WX20210217-235601.png

对应查出来,即为java/lang/Object

历经这么多流程我们终于找到了对应父类的 16 进制代码的代码和编码了。

我们现在把 Hello 的继承类换成java/lang/Number

那么只需要把 Object对应的 16 进制代码换成 Number 就可以了。

换之前:

1613577583864.jpg

换之后:

WX20210218-000015.png

详细的你可以看到 4F 换成了 4E,换算为 10 进制为:79,79 对应的 ASCII 码为 N,你可以按照如果规律,发现我们把 Object 换成了 Number。

然后我们保存后,执行一下:

1613577757088.jpg

在更换前后,我分别执行了 java,可以看到我们输出的父类名已经完成了更换。

我们甚至可以 javap 一下:

1613577828009.jpg

没错吧,确实继承关系发生了改变。

可以看到,只要我们能够找到指定区域,去修改这个区域的二进制代码,这样我们就能对 class 文件为所欲为。

是不是很简单。

不过事实上并不是那么简单。

因为我们修改的这个文件极其简单,如果内容非常多,发生字符串常量池复用的时候,我们就不能这么随意的修改某个常量池的内容了。

二来刚好java/lang/Numberjava/lang/Object长度完全一致,否则我们还要做非常多的对齐工作。

所以,修改 class 文件也不是那么容易的事情。

别担心,我们有 ASM。

不过,即使有 ASM 这样的类库,也不代表我们不需要了解 class 文件就可以完成 class 文件的修改了。

最起码,从我来看 ASM 类库的本意并不像 hibernate 那样,让不了解 sql 的开发者也能写出操作数据库的代码。

我们还是要了解 class 文件内部的组成,并且在修改代码时,我们还要了解代码执行时,局部变量表是如何工作的,指令对栈帧的影响等等。

我们往下看,你就明白了。

开始引入 ASM

引入 ASM

好了,下面我们开始正式学习 ASM。

首先我们找到ASM 的官网:

asm.ow2.io/

在官网你可以看到目前最新的版本,还有一份详细的 User guide,基本包含了所有 API 的介绍。

看官网上版本迭代目前已经跟新到9.1了,那就试用最新版本吧:

// https://mvnrepository.com/artifact/org.ow2.asm/asm-commons
implementation group: 'org.ow2.asm', name: 'asm-commons', version: '9.1'

尝试分析 Class 文件

从学习的角度来说,在修改 class文件之前,我们可以先学习下怎么读取 class 文件内部的各个部分。

比如我想在编译期间通过编译的*.class的文件,获取其内部的所有方法名称,字段名称。

Tree Api

对于分析class 文件,我们最希望的方式是什么?

肯定是:我给你个 class 文件,然后你给我返回个 ClassNode对象,这个对象最好

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值