ASM 中的栈模型

Label 介绍

在 ASM 中,每一个 Label 必须对应一个 Frame,两个 Label 可以共享一个 Frame,可以理解为将两个 Label 合并了,而一个 Frame 只对应一个 Label,就是创建它的 Label。每一次定义一个方法,即执行 ClassWriter#visitMethod 方法时,调用 MethodWriter 构造方法,都会在构造方法中创建一个 Label,作为 firstBasicBlock 使用,接着访问切换到这个 Label。

在 Frame 中,会存在

inputLocals : 方法参数存放于此,对应 LOAD 指令

inputStack :类似于一个中转

outputLocals : 各种 STORE 指令会操作这个栈

outputStack :方法的运行操作主要在这个栈中执行,各种指令,其实最后都是转化成了针对这个outputStack 的 pop 和 push 操作

栈的初始化

此处 MethodWriter.compute 为 COMPUTE_ALL_FRAMES

inputLocals: 

inputStack:

  final void setInputFrameFromDescriptor(final SymbolTable symbolTable,final int access,final String descriptor,final int maxLocals) {inputLocals = new int[maxLocals];inputStack = new int[0];int inputLocalIndex = 0;if ((access & Opcodes.ACC_STATIC) == 0) {if ((access & Constants.ACC_CONSTRUCTOR) == 0) {inputLocals[inputLocalIndex++] =REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());} else {inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS;}}for (Type argumentType : Type.getArgumentTypes(descriptor)) {int abstractType =getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0);inputLocals[inputLocalIndex++] = abstractType;if (abstractType == LONG || abstractType == DOUBLE) {inputLocals[inputLocalIndex++] = TOP;}}while (inputLocalIndex < maxLocals) {inputLocals[inputLocalIndex++] = TOP;}}

可以看到,非 static 方法,非构造方法,第一个参数对应抽REFERENCE_KIND,表示这个类,即我们常用的 this,接着遍历参数,放入每个参数对应的抽象类型,这点与Java虚拟机栈保持一致。上面描述的方法,会在自定义字节码操作完成,执行 MethodWriter#visitMax 时进行调用。

outputLocals:

  private void setLocal(final int localIndex, final int abstractType) {// Create and/or resize the output local variables array if necessary.if (outputLocals == null) {outputLocals = new int[10];}int outputLocalsLength = outputLocals.length;if (localIndex >= outputLocalsLength) {int[] newOutputLocals = new int[Math.max(localIndex + 1, 2 * outputLocalsLength)];System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength);outputLocals = newOutputLocals;}// Set the local variable.outputLocals[localIndex] = abstractType;}

当执行各种 STORE 指令时,会进行 outputLocals 的初始化。

outputStack:

  private void push(final int abstractType) {// Create and/or resize the output stack array if necessary.if (outputStack == null) {outputStack = new int[10];}int outputStackLength = outputStack.length;if (outputStackTop >= outputStackLength) {int[] newOutputStack = new int[Math.max(outputStackTop + 1, 2 * outputStackLength)];System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength);outputStack = newOutputStack;}// Pushes the abstract type on the output stack.outputStack[outputStackTop++] = abstractType;// Updates the maximum size reached by the output stack, if needed (note that this size is// relative to the input stack size, which is not known yet).short outputStackSize = (short) (outputStackStart + outputStackTop);if (outputStackSize > owner.outputStackMax) {owner.outputStackMax = outputStackSize;}}

当执行涉及到入栈的指令时,例如获取属性 GETFIELD、加载 ALOAD、方法调用 INVOKEVIRTUAL 等指令时,会进行 outputStack 的初始化。

并且从上面可以看到,各种栈都是 int 型,所以当遇到转化为抽象类型为 LONG 和 DOUBLE 的变量类型时,在各种栈中占两位,即 2 个 int,8 个 字节。

模型简介

在 ASM 的栈,分为 inputStack 和 outputStack,outputStack 紧接着 inputStack。还有一个参数outputStackStart,通过这个参数控制 inputStack 的大小,这个参数只能为 0 或 负数,为负数表明用到了上一任 Label 中的变量。

参照源码中对 outputStackStart 的注释:

outputStackStart:输出栈相对于输入栈的起始位置。这个偏移量总是负的或为空。空偏移量意味着输出栈必须追加到输入栈上。-n偏移量意味着前n个输出栈元素必须替换前n个输入栈栈顶元素,而其他元素必须追加到输入栈上。

所以当前 Label 输入栈大小:

numInputStack = inputStack.length + outputStackStart

不同的 Label 之间,通过设置 successor 这种关系,可以使用前任 Label 的输入栈和输出栈。其中,前任的输入栈和输出栈会作为 successor 的输入栈,所以:

前任的输入栈大小:numInputStack

前任的输出栈大小:outputStackTop

successor 的输入栈大小:numInputStack + outputStackTop

应用

利用设置的 successor 关系,操作变量

public class Generate49 implements Opcodes {public static void main(String[] args) {String generateClassName = "ASM$Generate49";ClassLoaderUtils.outputClass(generate(generateClassName), generateClassName);}private static byte[] generate(String generateClassName) {ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// declare_classcw.visit(V1_8, ACC_PUBLIC, generateClassName, null, "java/lang/Object", null);// declare_fieldcw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);// declare_methodMethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "toUppercaseName", "()Ljava/lang/String;", null, null);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, generateClassName, "name", "Ljava/lang/String;");mv.visitInsn(DUP);Label l1 = new Label();mv.visitJumpInsn(IFNULL, l1);mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(String.class), "toUpperCase", "()Ljava/lang/String;", false);mv.visitInsn(ARETURN);mv.visitLabel(l1);mv.visitInsn(ACONST_NULL);mv.visitInsn(ARETURN);mv.visitMaxs(0, 0);return cw.toByteArray();}
}

上面代码中的 DUP 指令,就是为了保证在执行 INVOKEVIRTUAL 指令时,可以利用到前任 Label的输出(在执行 visitJumpInsn 时,已经发生了 Label 的切换),即通过 GETFIELD 指令获取到的name 属性,接着执行方法调用。

利用 STORE 操作变量

public class Generate491 implements Opcodes {public static void main(String[] args) {String generateClassName = "ASM$Generate491";ClassLoaderUtils.outputClass(generate(generateClassName), generateClassName);}private static byte[] generate(String generateClassName) {ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// declare_classcw.visit(V1_8, ACC_PUBLIC, generateClassName, null, "java/lang/Object", null);// declare_fieldcw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);// declare_methodMethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "toUppercaseName", "()Ljava/lang/String;", null, null);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, generateClassName, "name", "Ljava/lang/String;");mv.visitVarInsn(ASTORE, 1);Label l1 = new Label();mv.visitVarInsn(ALOAD, 1);mv.visitJumpInsn(IFNULL, l1);mv.visitVarInsn(ALOAD, 1);mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(String.class), "toUpperCase", "()Ljava/lang/String;", false);mv.visitInsn(ARETURN);mv.visitLabel(l1);mv.visitInsn(ACONST_NULL);mv.visitInsn(ARETURN);mv.visitMaxs(0, 0);return cw.toByteArray();}
}

可以看到,使用 STORE 命令后,每一次操作使用到变量,都需先进行 LOAD,增加了代码量,但又直观的反映了栈操作的过程,即:先入栈,再出栈。

生成的代码如下:

public class ASM$Generate49 {private String name;public String toUppercaseName() {String var10000 = this.name;return var10000 != null ? var10000.toUpperCase() : null;}
}

可以看到,即使是很简单的一个类和方法,通过ASM操作起来,代码量都是生成类中代码的好几倍。而为了便于使用,衍生出了 CGLIB 这样的开源项目,其实就是ASM的一个具体应用。

附上上述代码中的工具类ClassLoaderUtils:

public class ClassLoaderUtils extends ClassLoader {public static final String CLASS_SUFFIX = ".class";public Class<?> defineClass(String name, byte[] bytes) {return super.defineClass(name, bytes,0, bytes.length);}public static void outputClass(byte[] bytes, String name) {FileOutputStream fos = null;try {String pathName = ClassLoaderUtils.class.getResource("/").getPath() + name + CLASS_SUFFIX;fos = new FileOutputStream(new File(pathName));fos.write(bytes);} catch (IOException e) {e.printStackTrace();} finally {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/814153.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字体体积压缩

环境:python3 关键步骤: pip install fontTools目录详情: 执行 pyftsubset.exe SourceHanSansCN-Medium.ttf --text-file3500.txt然后打开:TTF To Woff2,选择文件上传,等待处理,下载,使用 附常用汉字,字体文件请善用百度 3500.txt 工才下寸丈大与万上小口山巾千乞川亿个…

【C++算法竞赛 · 图论】图的存储

前言 图的存储 邻接矩阵 方法 复杂度 应用 例题 题解 邻接表 方法 复杂度 应用 前言 上一篇文章中&#xff08;【C算法竞赛 图论】图论基础&#xff09;&#xff0c;介绍了图论相关的概念和一种图的存储的方法&#xff0c;这篇文章将会介绍剩下的两种方法&#xff…

大模型之一:大语言模型预训练的过程

介绍 大语言模型的一般训练过程&#xff08;3步&#xff09;&#xff1a;1、预训练学知识&#xff0c;2、指令微调学格式&#xff0c;3、强化学习对齐人类偏好 预训练 所以要想大模型有领域知识&#xff0c;得增量预训练&#xff08;靠指令微调记知识不靠谱&#xff0c;不是…

【大语言模型】应用:10分钟实现搜索引擎

本文利用20Newsgroup这个数据集作为Corpus(语料库)&#xff0c;用户可以通过搜索关键字来进行查询关联度最高的News&#xff0c;实现对文本的搜索引擎&#xff1a; 1. 导入数据集 from sklearn.datasets import fetch_20newsgroupsnewsgroups fetch_20newsgroups()print(fNu…

zookeeper和kafka消息队列

zookeeper zookeeper介绍 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目 zookeeper特点 zookeeper是由一个领导者(leader)&#xff0c;多个跟随者(follower)组成的集群 Zookeepe集群中只要有半数以上节点存活&#xff0c;Zookeeper集群…

使用Postman发送跨域请求实验

使用Postman发送跨域请求 1 跨域是什么&#xff1f;2 何为同源呢?3 跨域请求是如何被检测到的&#xff1f;4 Postman跨域请求测试4.1 后端准备4.2 测试用例4.2.1 后端未配置跨域请求(1) 前端不跨域&#xff08;2&#xff09;前端跨域 4.2.2 后端配置跨域信息&#xff08;1&…

数据结构-链表刷题集(长期更新)

文章目录 1. leetcode 2 两数之和1.1 解法一 1. leetcode 2 两数之和 1.1 解法一 题目及其相关实例如下 要做这个题,首先我们要学会模拟竖式的加法,我们知道即使是java基本数据中最大的long类型范围也是有限的,那如果超出范围了我们该怎么办呢,我们就需要用字符串来模拟这个…

Mac 下 Python+Selenium 自动上传西瓜视频

背景 研究下 PythonSelenium 自动化测试框架&#xff0c;简单实现 Mac 下自动化批量上传视频西瓜视频并发布&#xff0c;分享给需要的同学&#xff08;未做过多的异常处理&#xff09;。 脚本实现 首先通过手工手机号登录&#xff0c;保存西瓜视频网站的 cookie 文件 之后加载…

HTML的文档说明

1.告诉浏览器当前网页的版本 2.写法&#xff1a; &#xff01;以前的写法&#xff1a;要依据网页的HTML的版本去确定&#xff0c;紫萼发油很多很多。 具体的写法可以参考&#xff1a;W3C官网的文档说明 &#xff01;新写法&#xff1a;W3C都推荐用h5的写法 <DOCTYPE ht…

C语言CRC通用模块代码

我这几天看了下CRC具体校验原理&#xff0c;我看网上都没有一个通用的CRC库&#xff0c;都是一个函数写一种校验方式的那种&#xff0c;以下代码是随手写的一个通用的CRC软件模块&#xff0c; 支持最小单位字节的输入&#xff0c;有问题大家一起讨论。移植时需要修改的点可能是…

【蓝桥杯】第十五届蓝桥杯大赛软件赛省赛(Java研究生组)个人解题思路及代码分享

文章目录 试题A&#xff1a;劲舞团试题B&#xff1a;召唤数字精灵试题C&#xff1a;封闭图形的个数试题D&#xff1a;商品库存管理试题E&#xff1a;砍柴试题F&#xff1a;回文字符串试题G&#xff1a;最大异或节点试题H&#xff1a;植物生命力 试题A&#xff1a;劲舞团 【问题…

napi系列学习高阶篇——通过IDE集成C/C++三方库并开发napi接口

简介 应用在调用系统固件集成的C/C三方库时&#xff0c;可能会由于系统固件集成端与IDE的NDK中libc版本不一致导致调用失败&#xff0c;而且系统固件集成的C/C三方库对于应用的调式也很不友好&#xff0c;需要多方编译调试&#xff0c;很不方便。因此本文将通过在IDE上适配ope…

mp3怎样才能转换成wav格式?音频互相转换的方法

一&#xff0c;什么是WAV WAV&#xff0c;全称为波形音频文件&#xff08;Waveform Audio File Format&#xff09;&#xff0c;是一种由微软公司和IBM公司联合开发的音频文件格式。自1991年问世以来&#xff0c;WAV格式因其无损的音频质量和广泛的兼容性&#xff0c;成为了多…

【opencv】示例-morphology2.cpp 形态学操作:膨胀、腐蚀、开运算、闭运算

element_shape MORPH_ELLIPSE; element_shape MORPH_RECT element_shape MORPH_CROSS; // 包含必要的OpenCV头文件 #include "opencv2/imgproc.hpp" // 图像处理 #include "opencv2/imgcodecs.hpp" // 图像编码解码 #include "opencv2/highgui.hpp…

Zynq学习笔记--AXI 总线概述

目录 1. AXI总线概述 1.1 主要特点 1.2 通道功能 1.3 信号概览 2. AXI Interconnect 2.1 信号说明 2.2 内部结构 3. PS-PL AXI Interface 3.1 AXI FPD/LFP/ACP 3.2 Address Editor 3.3 地址空间 3.4 AXI-DDR 4. 通过ILA观察AXI信号 4.1 AXI 读通道 1. AXI总线概述…

Linux高级IO——多路转接之poll

本章代码Gitee地址&#xff1a;PollServer 文章目录 1. poll2. poll_server 1. poll poll的作用和select一模一样&#xff0c;只负责等待 poll在select的基础之上解决了select的两个硬伤&#xff1a; select等待的fd有上限select输入输出参数较多 #include <poll.h> …

【软件设计师知识点】八、数据库技术基础

文章目录 数据库基本术语关系型数据库基本术语数据库模型三级模式二级映射数据的独立性数据模型常用数据模型E-R 图(概念设计)数据库操作完整性规则关系代数运算集合运算符关系运算符数据库语言 SQL数据定义语言(DDL)

uniapp开发小程序手写板、签名、签字

可以使用这个插件进行操作 手写板-签名签字-lime-signature - DCloud 插件市场 但是目前这个插件没有vue3 setup Composition API的写法。所以对于此文档提供的可以直接使用,需要使用Composition API方式实现的,可以继续看。 因为Composition API方式,更加的简单、灵活,…

Java编程题目 | 四个数的三三组合

大家可以关注一下专栏&#xff0c;方便大家需要的时候直接查找&#xff0c;专栏将持续更新~ 题目描述 使用数字1、2、3、4&#xff0c;编写一个Java程序&#xff0c;找出所有互不相同且每个三位数中不含有重复数字的组合&#xff0c;并输出这些组合。 解题思路 使…

记录一个腾讯云上kafka不能正常启动问题

问题描述&#xff1a;刚在新的腾讯云三台节点上安了Zookeeper和kafka&#xff0c;改好对应配置文件后&#xff0c;启动zk和kafka。 启动zk后&#xff0c;启动kafka jpsall以后 发现两个进程都启了&#xff08;这里有猫腻&#xff0c;kafka其实没起来&#xff0c;过几秒就自动掉…