基本概念
这一末节 内容会比较单调,也比较难。 个人建议的便是 有空读一下 揭秘java虚拟机 这本书的相关章节,然后再来看这一末节,理解起来会更简略一些。
另外这一末节最佳的学习办法还是自己下个asm bytecode viewer 的插件,然后自己多写代码 然后比对着翻译后的字节码指令 多领会。
常见的字节码指令
字节码指令由一个标识该指令的操作码和固定数目的参数组成:
- 操作码是一个无符号字节值——即字节代码名,由注记符号标识
- 参数是静态值,确定了精确的指令行为,紧跟在操作码之后 字节码指令大致能够分为两类,一类指令用于局部变量和操作数栈之间传递值。另一类用于对操作数栈的值 进行弹出和计算,并压入栈中。
常见的局部变量操作指令有:
- ILOAD:用于加载 boolean、int、byte、short 和 char 类型的局部变量到操作数栈
- FLOAD:用于加载 float 类型局部变量到操作数栈
- LLOAD:用于加载 lang 类型局部变量到操作数栈,需求加载两个槽 slot
- DLOAD:用于加载 double 类型局部变量到操作数栈,需求加载两个槽 slot
- ALOAD:用于加载非基础类型的局部变量到操作数栈,比方目标之类的
常见的操作数栈指令有:
- ISTORE:从操作数栈弹出 boolean、int、byte、short 和 char 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
- FSTORE:从操作数栈弹出 float 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
- LSTORE:从操作数栈弹出 long 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
- DSTORE:从操作数栈弹出 double 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
- ASTORE:用于弹出非基础类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
经过上面能够看到,每种对应的数据类型都对应不同的 XLOAD
或 XSTORE
。这是为了确保不会履行
不合法的转化。
- 将一个值存储在局部变量表中,在以不同的类型加载它是不合法操作,比方存入 ISTORE 类型,运用 FLOAD 加载
- 假如向一个局部变量表中的方位存储一个值,而这个值不同于原来的存储类型,这种操作是合法的
局部变量表
局部变量表是一组变量值的存储空间,用于寄存办法参数和办法内部界说的局部变量。在 .java 编译成 .class 文件时,已经确定了所需求分配的局部变量表和操作数栈的巨细。
变量槽
局部变量表的容量单位是变量槽(Variable Slot)。每个变量槽最多能够存储 32 为长度的空间,所以关于 byte、char、boolean、short、int、float、reference 是占用一个变量槽, 关于 64 位长度的 long 和 double 占用连个变量槽。 运用局部变量表时,经过索引定位对应数据的方位,索引值的规模是从 0 开端至局部变量表最大的变量槽数量。 假如拜访的是 32 位数据类型的变量,索引 N 就代表了运用第 N 个变量槽,假如拜访的是 64 位数据类型的变量,则说明会一起运用第 N 和 N+1 两个变量槽。 关于两个相邻的共同寄存一个 64 位数据的两个变量槽,虚拟机不允许选用任何办法独自拜访其中的某一个,假如遇到进行这种操作的字节码,Java 虚拟机就会在类加载的校验阶段中抛出反常。
当一个办法被调用时,会运用局部变量表来完结参数值到参数变量列表的传递进程。假如履行的是目标实例的成员办法(不是 static 润饰的办法),那么局部变量表中第 0 位索引的变量槽默许便是该目标实例的引证(this),在办法中能够经过关键字 this 来拜访到这个隐含的参数。 其他参数则依照参数表次序排列,参数表分配完毕后,再依据办法体内部界说的局部变量次序和作用域分配其他的变量槽。 为了尽可能节省栈帧所耗的内存空间,局部变量表中的变量槽是能够重用的,当办法体中界说的局部变量超出其作用域时,该局部变量对应的变量槽就能够交给其他变量来重用。
虚拟机重要概念
JVM 中的解说器会逐条解说履行字节码指令。履行进程中,JVM 会保护一个栈,用于存储局部变量、操作数和回来地址。在履行每个指令时,JVM 从栈中取出操作数,履行相应的操作,然后将成果压回栈中。例如,加法指令会从栈中弹出两个数值,相加后将成果压回栈中。
咱们能够看一个简略的比如
能够看下 字节码:
很简略的,取第一个局部变量,取第二个局部变量, 然后 调用add指令,最后return 回来值
MAXSTACK 和 MAXLOCALS 是 Java 字节码文件中的两个属性,用于界说办法的最大操作数栈和最大局部变量表巨细。
MAXSTACK 表示该办法运行时所需的最大操作数栈的深度,即在办法履行进程中所需求运用的最大的栈空间。 这个值是在编译期间计算出来的,由于 Java 字节码是一种根据栈的言语,因此在办法运行时,JVM 会依照该值来为办法分配栈空间。
MAXLOCALS 则表示该办法运行时所需的最大局部变量表的巨细,即该办法所运用的局部变量的数量。这个值也是在编译期间计算出来的,由于在办法履行进程中,需求为局部变量分配内存空间。
这儿有的人会觉得奇怪,为什么局部变量表的巨细是3, 我这儿分明只有2个局部变量 分别是参数x 和参数y啊
由于这个办法不是一个静态办法,他是一个类的目标办法,关于这种办法来说,会有一个躲藏的参数 也便是 他自己 便是 this, 所以这儿是局部变量表的巨细是3
这儿要谨记 所有类的成员办法 都有一个躲藏的入参 参数 this
databean的 字节码分析
看一下 get 办法
看下字节码:
第一条指令 ALOAD 0 ,这个其实便是 将this 压入操作数栈。 上一个末节咱们提到过 类的成员办法 都有一个躲藏的入参 是this ,方位是在第一个方位 也便是0方位上
第二条指令 便是 从栈中弹出这个值, 并将这个目标的name字段 压入栈中,
最后一条指令 便是从栈中弹出这个值了
再看下set 办法
第一条指令 前面讲过了 不说了
第二条指令便是 把函数的参数 这个局部变量 压入操作数栈
第三条指令 弹出这2个值,并将int值 存储在name 字段中
最后一条指令 是return,关于void办法来说,这儿都是躲藏的一条retuan指令
再看下这个 databean的结构办法:
第一条指令 不说了 第二条指令 其实便是 调用Object的结构器
INVOKESPECIAL指令首要用于两种状况:
- 调用超类的结构办法:在一个子类的结构办法中,假如需求调用超类的结构办法完结目标初始化,能够运用INVOKESPECIAL指令来调用超类的结构办法。
- 调用私有办法:在Java中,私有办法不能被子类重写或继承,可是它们能够在同一类中被其他办法调用。这种状况下,能够运用INVOKESPECIAL指令来调用私有办法。
总归,INVOKESPECIAL指令是Java字节码中的一种重要指令,它首要用于调用超类结构器或私有办法。
一个稍微复杂点的set办法
在Java字节码中,IFLE是一种条件分支指令,它用于在栈顶值小于或等于0时跳转到指定的目标指令。
这个就很好理解了吧, 这儿是先取的 局部变量表中 index为1的 局部变量 也便是函数参数的age
对他进行压栈操作,然后 IFLE 指令 对他进行判断,小于等于0的时分
这儿要注意的便是 在java中 抛出一个反常的固定指令标准 是 先new 再dup 再invokespecial
反常处理器
TRYCATCHBLOCK指令需求四个参数,分别是:
- 第一个参数是一个指向try块的开端方位的偏移量(从字节码办法的最初开端计算)。
- 第二个参数是一个指向try块的完毕方位的偏移量。
- 第三个参数是一个指向catch块的开端方位的偏移量。
- 第四个参数是一个指向catch块反常处理程序代码的反常类型。
接口与组件
上一篇文章中 能够使用visitor来遍历一个类的办法和字段,同样的,关于办法来说,咱们也能够用visitor来遍历这个办法
上一末节咱们有了acccode的扩展函数,这会咱们增加一个**opcode 的扩展函数 **
fun Int.opCode2String(): String {
mapOpcodes.forEach {
if (it.value == this) {
return it.key
}
}
return ""
}
还是要走ClassReader 只不过这次咱们在ClassVisitor的 method回调办法中,传入了 咱们自界说的MethodVisitor
val classReader = ClassReader("TestMethod")
val classVisitor = object : ClassVisitor(Opcodes.ASM7) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
// 最关键便是这儿要回来一个自界说的 MethodVisitor
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
return MethodPrintVisitor(Opcodes.ASM7, mv)
}
}
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG)
由于每个办法的字节码都不同,所以这些回调函数的次序也是不同的,但基本上都是遵循visitCode开端 和visitMaxs 完毕
这2个回调之间的 那些回调函数的次序以及呈现次数 就彻底取决于 你函数内部的完成了
package com.andoter.asm_example.part3.vivo
import com.andoter.asm_example.part2.vivo.Utils.opCode2String
import org.objectweb.asm.*
/**
* MethodVisit 首先会拜访注释和属性信息,然后才是办法的字节码,这些拜访次序在 visitCode 和 visitMaxs 调用之间。所以
* 这两个办法能够用于检测字节码在拜访序列中的开端和完毕。ASM 中供给了三个根据 MethodVisitor API 的组件,用于生成和转化办法。
*
* - ClassReader 类分析已编译办法的内容,其在 accept 办法的参数中传递了 ClassVisitor,ClassReader 类将针对这一 ClassVisitor 回来的
* MethodVisitor 目标调用呼应的办法。
* - ClassWriter 的 visitMethod 办法回来 MethodVisitor 接口的一个完成,它直接以二进制形式生成已编译办法
* - MethodVisitor类将它接收到的所有办法调用委托给另一个MethodVisitor办法。能够将它看作一个事情挑选器。
*
* MethodVisitor 回调办法有:
* - visitParameter:拜访办法一个参数
* - visitAnnotationDefualt:拜访注解接口办法的默许值
* - visitAnnotaion:拜访办法的一个注解
* - visitTypeAnnotation:拜访办法签名上的一个类型的注解
* - visitAnnotableParameterCount:拜访注解参数数量,便是拜访办法参数有注解参数个数
* - visitParameterAnnotation:拜访参数的注解,回来一个 AnnotationVisitor 能够拜访该注解值
* - visitAttribute:拜访办法的属性
* - 重要----visitCode:开端拜访办法代码,此处能够增加办法运行前拦截器 ,这个回调能够认为是办法拜访的开端
* - visitFrame:拜访办法局部变量的当时状况以及操作栈成员信息
* - visitIntInsn:拜访数值类型指令,当 int 取值-1~5选用 ICONST 指令,取值 -128~127 选用 BIPUSH 指令,取值 -32768~32767 选用 SIPUSH 指令,取值 -2147483648~2147483647 选用 ldc 指令。
* - 重要---- visitVarInsn:拜访本地变量类型指令
* - visitTypeInsn:拜访类型指令,类型指令会把类的内部称号当成参数 Type
* - 重要-----visitFieldInsn:域操作指令,用来加载或许存储目标的 Field
* - 重要---- visitMethodInsn:拜访办法操作指令
* - visitDynamicInsn:拜访动态类型指令
* - 重要----visitJumpInsn:拜访比较跳转指令
* - visitLabelInsn:拜访 label,当会在调用该办法后拜访该label标记一个指令
* - visitLdcInsn:拜访 LDC 指令,也便是拜访常量池索引
* - visitLineNumber:拜访行号描绘
* - 重要-----visitMaxs:拜访操作数栈最大值和本地变量表最大值 能够认为是办法拜访的完毕
* - visitLocalVariable:拜访本地变量描绘
*/
class MethodPrintVisitor(api: Int, methodVisitor: MethodVisitor?) : MethodVisitor(api, methodVisitor) {
override fun visitMultiANewArrayInsn(descriptor: String?, numDimensions: Int) {
super.visitMultiANewArrayInsn(descriptor, numDimensions)
println("visitMultiANewArrayInsn, descriptor = $descriptor, numDimensions = $numDimensions")
}
override fun visitFrame(
type: Int,
numLocal: Int,
local: Array<out Any>?,
numStack: Int,
stack: Array<out Any>?
) {
super.visitFrame(type, numLocal, local, numStack, stack)
println("visitFrame, type = $type, numLocal = $numLocal, local.size = $(local.size), numStack = $numStack")
}
/**
* visitVarInsn:拜访本地变量类型指令
*/
override fun visitVarInsn(opcode: Int, `var`: Int) {
super.visitVarInsn(opcode, `var`)
println("visitVarInsn, opcode = ${opcode.opCode2String()}, var = $`var`")
}
override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) {
super.visitTryCatchBlock(start, end, handler, type)
println("visitTryCatchBlock")
}
override fun visitLookupSwitchInsn(dflt: Label?, keys: IntArray?, labels: Array<out Label>?) {
super.visitLookupSwitchInsn(dflt, keys, labels)
println("visitLookupSwitchInsn")
}
/**
* visitJumpInsn:拜访比较跳转指令
*/
override fun visitJumpInsn(opcode: Int, label: Label?) {
super.visitJumpInsn(opcode, label)
println("visitJumpInsn, opcode = ${opcode.opCode2String()}")
}
override fun visitLdcInsn(value: Any?) {
super.visitLdcInsn(value)
println("visitLdcInsn, value = $value")
}
override fun visitAnnotableParameterCount(parameterCount: Int, visible: Boolean) {
super.visitAnnotableParameterCount(parameterCount, visible)
}
override fun visitIntInsn(opcode: Int, operand: Int) {
super.visitIntInsn(opcode, operand)
println("visitIntInsn, opcode = ${opcode.opCode2String()}, operand = $operand")
}
override fun visitTypeInsn(opcode: Int, type: String?) {
super.visitTypeInsn(opcode, type)
println("visitTypeInsn, opcode = ${opcode.opCode2String()}, type = $type")
}
override fun visitAnnotationDefault(): AnnotationVisitor? {
println("visitAnnotationDefault")
return super.visitAnnotationDefault()
}
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
println("visitAnnotation")
return super.visitAnnotation(descriptor, visible)
}
override fun visitTypeAnnotation(
typeRef: Int,
typePath: TypePath?,
descriptor: String?,
visible: Boolean
): AnnotationVisitor? {
println("visitTypeAnnotation")
return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
}
/**
* 办法拜访的完毕
*/
override fun visitMaxs(maxStack: Int, maxLocals: Int) {
super.visitMaxs(maxStack, maxLocals)
println("visitMaxs")
}
override fun visitInvokeDynamicInsn(
name: String?,
descriptor: String?,
bootstrapMethodHandle: Handle?,
vararg bootstrapMethodArguments: Any?
) {
println("visitInvokeDynamicInsn, name = $name, descriptor = $descriptor, bootstrapMethodHandle = ${bootstrapMethodHandle?.name}")
super.visitInvokeDynamicInsn(
name,
descriptor,
bootstrapMethodHandle,
*bootstrapMethodArguments
)
}
/**
* 当ASM解析字节码时遇到一个标签时,它将调用MethodVisitor的visitLabel办法,并传递标签目标作为参数
*/
override fun visitLabel(label: Label?) {
super.visitLabel(label)
println("visitLabel ")
}
override fun visitTryCatchAnnotation(
typeRef: Int,
typePath: TypePath?,
descriptor: String?,
visible: Boolean
): AnnotationVisitor? {
return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible)
}
override fun visitMethodInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) {
super.visitMethodInsn(opcode, owner, name, descriptor)
println("visitMethodInsn, opcode = ${opcode.opCode2String()}, owner = $owner, name = $name, descriptor = $descriptor")
}
/**
* visitMethodInsn:拜访办法操作指令
*/
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
println("visitMethodInsn, opcode = ${opcode.opCode2String()}, owner = $owner, name = $name, descriptor = $descriptor")
}
/**
* 当ASM在解析字节码时遇到一个不需求操作数的指令时(例如RETURN或NOP指令),、
* 它将调用MethodVisitor的visitInsn办法,并传递该指令的操作码(opcode)作为参数。
*/
override fun visitInsn(opcode: Int) {
super.visitInsn(opcode)
println("visitInsn, opcode = ${opcode.opCode2String()}")
}
override fun visitInsnAnnotation(
typeRef: Int,
typePath: TypePath?,
descriptor: String?,
visible: Boolean
): AnnotationVisitor? {
println("visitInsnAnnotation")
return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible)
}
override fun visitParameterAnnotation(
parameter: Int,
descriptor: String?,
visible: Boolean
): AnnotationVisitor? {
println("visitParameterAnnotation")
return super.visitParameterAnnotation(parameter, descriptor, visible)
}
override fun visitIincInsn(`var`: Int, increment: Int) {
super.visitIincInsn(`var`, increment)
println("visitIincInsn")
}
override fun visitLineNumber(line: Int, start: Label?) {
super.visitLineNumber(line, start)
println("visitLineNumber")
}
override fun visitLocalVariableAnnotation(
typeRef: Int,
typePath: TypePath?,
start: Array<out Label>?,
end: Array<out Label>?,
index: IntArray?,
descriptor: String?,
visible: Boolean
): AnnotationVisitor? {
println("visitLocalVariableAnnotation")
return super.visitLocalVariableAnnotation(
typeRef,
typePath,
start,
end,
index,
descriptor,
visible
)
}
override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label?, vararg labels: Label?) {
super.visitTableSwitchInsn(min, max, dflt, *labels)
println("visitTableSwitchInsn")
}
override fun visitEnd() {
super.visitEnd()
println("visitEnd")
}
override fun visitLocalVariable(
name: String?,
descriptor: String?,
signature: String?,
start: Label?,
end: Label?,
index: Int
) {
super.visitLocalVariable(name, descriptor, signature, start, end, index)
println(
"visitLocalVariable, name = $name, descriptor = $descriptor, " +
"signature = $signature, start = $start + end = $end, index = $index"
)
}
override fun visitParameter(name: String?, access: Int) {
super.visitParameter(name, access)
println("visitParameter, name = $name, access = ${access.opCode2String()}")
}
override fun visitAttribute(attribute: Attribute?) {
super.visitAttribute(attribute)
println("visitAttribute")
}
/**
* visitFieldInsn:域操作指令,用来加载或许存储目标的 Field
*/
override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) {
super.visitFieldInsn(opcode, owner, name, descriptor)
println("visitFieldInsn, opcode = ${opcode.opCode2String()}, owner = $owner, name = $name, descriptor = $descriptor")
}
/**
* 开端拜访办法代码,此处能够增加办法运行前拦截器 ,
* 这个回调能够认为是办法拜访的开端
*/
override fun visitCode() {
super.visitCode()
println("visitCode")
}
}
这儿给一个小比如 稍微领会一下methodvisitor
比方说这个结构函数:
在visit的回调里面 就变成了:
修正办法的指令—无状况转化
这一末节,咱们来尝试修正一下办法的指令 领会一下如何在字节码中修正一个办法
看一个简略的办法:
public class AddTimerTest {
public void addTimer() throws InterruptedException {
Thread.sleep(1000);
}
}
看一下他的字节码:
LDC指令是Java虚拟机(JVM)中的一种指令,用于将常量(Constant)推送到栈顶
这个办法的指令特别简略,不解说了
假定此刻咱们想经过字节码的办法 把这个办法修正成:
public class AddTimerTest2 {
private static long time = 0L;
public void addTimer() throws InterruptedException {
time -= System.currentTimeMillis();
Thread.sleep(1000);
time += System.currentTimeMillis();
}
}
看下他的字节码
LSUB和LADD指令是Java虚拟机(JVM)中的两种指令,分别用于履行长整型(Long)的减法和加法操作。
能够调查一下 这两个办法的字节码指令 差异就在于 最初和结束 增加了4条指令,咱们要做的便是 经过字节码修正的办法 来修正这个办法
fun main() {
val classReader = ClassReader("com.andoter.asm_example.part3.vivo.AddTimerTest")
// 第二个参数很重要,能够省略咱们计算max的操作,绝大多数场景咱们用这个参数就能够了
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
classReader.accept(object : ClassVisitor(Opcodes.ASM7, classWriter) {
var owner: String? = ""
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
owner = name
}
// 不要忘掉 新增一个static的变量
override fun visitEnd() {
val fieldVisitor =
cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "time", "J", null, null)
fieldVisitor?.visitEnd()
super.visitEnd()
}
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
var methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions)
// 不是结构办法才需求增加办法修正逻辑
if (methodVisitor != null && name != "<init>") {
methodVisitor = object :MethodVisitor(Opcodes.ASM7, methodVisitor){
override fun visitCode() {
mv.visitCode()
// 在办法的开端处 增加字节码
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "time", "J")
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J"
)
mv.visitInsn(Opcodes.LSUB)
mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "time", "J")
}
override fun visitInsn(opcode: Int) {
// 咱们要在return句子之前 增加这段代码
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "time", "J")
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J")
mv.visitInsn(Opcodes.LADD)
mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "time", "J")
}
mv.visitInsn(opcode)
}
}
}
return methodVisitor
}
}, ClassReader.SKIP_DEBUG)
val bytes = classWriter.toByteArray()
// 实践写文件
File("asm_example/files2/AddTimerTest.class").sink().buffer().apply {
write(bytes)
flush()
close()
}
}
咱们当然也能够 实践运行一下这个类 看一下作用:
val myClassloader = MyClassloader()
val loadedClass = myClassloader.loadClassFromFile("/asm_example/files2/AddTimerTest.class", "com.andoter.asm_example.part3.xxx.AddTimerTest")
val method=loadedClass.getMethod("addTimer")
// 获取 time 字段
val field : Field? = loadedClass.getDeclaredField("time")
// 触发办法
method.invoke(loadedClass.newInstance())
// 打印 time 耗时
println("time = ${field?.getLong(loadedClass.newInstance())}")
看一下履行成果 生效的