ASM框架学习(一)-基本概念

简介

ASM是java字节码操作框架,利用该框架可以达到动态修改java运行对象代码的目的,也可以实现动态代理等功能;

线程和栈帧

要了解ASM字节码操作,先要熟悉jvm线程与栈帧结构,jvm开辟一个线程,便会开辟属于这个线程虚拟机栈,本地方法栈,程序计数器,其主要作用如下:

  • 虚拟机栈:以栈帧为基本单位,一个栈帧的开始地址代表一个方法的入口,栈帧里面有操作数栈,局部变量表,动态链接,方法出口,其他信息;其说明如下:
    • 操作数栈::用来存放需要cpu进行计算的基本类型和对象引用,以4个字节的slot为基本单位;
    • 局部变量表:存放当前栈帧涉及到的变量表,以4个字节的slot为基本单位;
    • 动态链接: 存放执行常量池的引用,可以实现当前方法的动态链接;
    • 方法出口:用于方法的返回,可以是异常的发生,return操作指令等等;
  • 本地方法栈:类似于虚拟机栈,但是保存的是java本地代码库的一些信息;
  • 程序计数器:保存该线程指令执行位置,待下次调度时可以接着往下执行;
    具体如下图所示:


    线程虚拟机栈结构.png

基本类型

java字节码中的类型表达和java代码是不一样的,在java字节码中利用如下符号来表达基本类型:

java类型 type 含义
boolean Z 布尔
char C 字符
byte B 字节
short S 短整型
int I 整型
long J 长整型
float F 浮点数
reference L 类的引用
void V
double D 双精度浮点型
Object Ljava/lang/Object; 对象
int[] [I 整型数组
Object[][] [[Ljava/lang/Object; 对象数组

注: L+className;代表某类的引用(";"不能省略)

字节码实例:

Java代码 字节码表示 注释
double[][] [[D
Object run(int i,double d,Thread t) (IDLjava/lang/Thread)Ljava/lang/Object; (方法参数字节码类型)方法返回参数类型

字节码指令操作

字节码指令操作其实主要操作局部变量表和操作数栈,具体流程是:load局部变量到操作数栈,然后给cpu下达执行指令,然后将操作数栈栈顶元素弹出,从而实现一个操作;
字节码指令都有一定的格式:[type+]op["_"+value];

其中type根据基本类型可以为:i(int 整数),s(short 短整数),b(byte 字节),c(char字符),l(long长整数),d(double双精度浮点数),f(float 浮点数),a(reference 引用);
value指的是操作数,如果操作数为负数时需要添加'm"前缀,例如iconstant_m1表示将-1压栈;如果操作数值超过一定大小,则会将该操作数存放在常量池,用#indexbyte表示其位置;
op指的是操作码,通常用一个字节表示;

字节码指令操作主要分为九大指令:

  • 加载和存储指令:用于将数据在操作数栈和局部变量表来回传输;
  • 运算指令:用于将操作数栈栈顶的两个数值进行运算,然后重新放入操作数栈顶;
  • 类型转换指令:用于将两个不同数值类型进行相互转换;
  • 对象创建和访问指令: 用于创建对象和访问对象
  • 操作数栈管理指令:用于管理操作数栈,类似普通栈管理
  • 控制转移指令:用于让jvm从指定位置的指令开始执行而不是控制下一条指令位置开始执行;
  • 方法调用和返回指令:用于方法调用和方法返回;
  • 异常处理指令:对jvm抛出的异常进行处理指令;
  • 方法同步指令:用来控制不同线程对方法的同步控制;

加载和存储指令

加载指令主要是将局部变量和常量压入到操作数栈,具体指令有:

  • 常量压栈指令,常量压栈指令时根据常量所占字节大小划分,指令如下:
    • constant:该常量字节大小为-1到5的数值,例如iconstant_0将整数0压栈,lconstant_5将长整数5压栈;
    • bipush:将字节值为byte类型的数值转换为整型,然后压栈(byte值大小为-128-127);
    • sipush:将字节值为short类型的数值转换为整型,然后压栈(short值为-32768-32767);
    • ldc: 根据指定索引值(需要一个字节存储的indexbyte)从常量池取出大小在-2147483648~2147483647的常量值,如int,float,Reference型常量值;
    • ldc_w:根据指定宽索引值(需要两个字节存储的索引值)从常量池取出如int,float,Reference等常量值进行压栈;
    • ldc2_w:根据指定宽索引值从常量池中取出如long,double等常量值进行压栈;
      如下图:


      常量压栈.png

      如上图所示,其中32767表示操作数值,在[-32768,32767]之间是不会保存到常量池的,而超过这个值则需要利用indexbyte(#30,#35)代表的索引,去常量池中查找

  • load:将局部变量指定位置(具体值或者索引)处的对象压栈;aload_0将局部变量表0处的引用类型入栈,
    iload indexbyte将局部变量表中indexbyte表示的int类型入栈;caload从char类型数组中装载指定项的值(先转换为int类型值,后压栈)
  • store :将操作数栈栈顶值弹出并保存到局部变量表中;例如:istore_3将short,byte,char,int类型保存到局部变量表3处根据类型转换,lstore [opNum] (opNum需大于3)则将long类型保存到局部变量opNum处;dstore用来保存栈顶的double类型,fstore用来保存栈顶的float类型;

如下图所示:


加载存储指令.png

运算指令

运算指令有以下几种:

  • (T)add:将栈顶T类型的两个数值相加后入栈,T:float,int,short,long,double
  • (T)sub:将栈顶T类型的两个数值相减后入栈,T:float,int,short,long,double
  • (T)mul:将栈顶T类型的两个数值相乘后入栈,T:float,int,short,long,double
  • (T)div:将栈顶T类型的两个数值相除后入栈,T:float,int,short,long,double
  • (T)rem:将栈顶T类型的两个数值取模后入栈,T:float,int,short,long,double
  • (T)neg:将栈顶T类型的取负后入栈,T:float,int,short,long,double
  • (T)iinc [indexbyte,constantbyte]:将整数值constbyte加到indexbyte指定的int类型的局部变量中;


    运算指令.png
  • (T)shl:算数左移后入栈,T为非浮点类型的基本类型;
  • (T)shr:算数左移后入栈,T为非浮点类型的基本类型;
  • (T)ushl:逻辑左移后入栈,T为非浮点类型的基本类型;
  • (T)ushr:逻辑右移后入栈,T为非浮点类型的基本类型;
  • (T)and:与操作,T为非浮点类型的基本类型;
  • (T)or:或操作,T为非浮点类型的基本类型;
  • (T)xor:异或操作,T为非浮点类型的基本类型;

类型转换指令

类型转换指令有以下几种:

  • (T)2(V):将T基本类型转换成V基本类型,如果是长字节类型转换短字节类型,则需要把高位字节截断;如l2i:将long转换成int则会把高4个字节截断后剩下的四个字节转换成int;

对象创建和访问指令

对象创建和访问指令通常需要两个操作数indexbyte1和indexbyte2

  • new :创建新的对象实例;
  • checkcast:强制类型转换;
  • instanceof:判断是否类实例;
  • getField:获取类实例字段值;
  • putField:给类实例字段赋值;
  • getStatic:获取类静态变量值;
  • putStatic:给类静态变量赋值;
  • newarray:创建基本类型数组;
  • anewarray:创建引用类型数组;
  • arraylength:获取一维数组长度;

操作数栈管理指令

字等于两个字节,半个slot,16位

  • nop: 空操作;
  • pop :弹出栈顶一个字长数据;
  • pop2:弹出栈顶两个字长的数据;
  • dup:复制栈顶一个字长的数据,同时将该数据入栈;
  • dup_x1:复制栈顶一个字长的数据,同时弹出栈顶两个字长的数据,然后再将复制的数据入栈,再将弹出的两个字入栈;
  • dup_x2:复制栈顶一个字长的数据,同时弹出栈顶三个字长的数据,然后再将复制的数据入栈,再将弹出的三个字入栈;
  • dup2:复制栈顶两个字长的数据,同时将该数据入栈;
  • dup2_x1:复制栈顶两个字长的数据,同时弹出栈顶三个字长的数据,然后再将复制的数据入栈,再将弹出的三个字入栈;
  • dup2_x2:复制栈顶两个字长的数据,同时弹出栈顶四个字长的数据,然后再将复制的数据入栈,再将弹出的四个字入栈;
  • swap:交换栈顶两个字长的数据,Java指令中没有提供交换两个字长为单位的交换指令;

控制转移指令:

控制转移指令分为跳转指令和比较指令,无条件跳转指令,表跳转指令,异常跳转指令;
跳转指令:

  • ifeq:若栈顶int类型为0则跳转;
  • ifne:若栈顶int类型不为0则跳转;
  • iflt:若栈顶int类型小于0则跳转;
  • ifle: 若栈顶int类型小于等于0则跳转;
  • ifgt:若栈顶int类型大于0则跳转;
  • ifge:若栈顶int类型大于等于0则跳转;
  • if_icmpeq:若栈顶两int类型相等则跳转;
  • if_icmpne: 若栈顶两int类型相等则跳转;
  • if_icmplt:若栈顶int前小于后则跳转;
  • if_icpmle:若栈顶int前小于等于后则跳转;
  • if_icpmgt: 若栈顶int前大于后则跳转;
  • if_icpmge: 若栈顶int前大于等于后则跳转;
  • ifnull: 如栈顶引用为空则跳转;
  • ifnonnull:若栈顶引用不为空则跳转;
  • if_acmpeq:若栈顶两引用相等则跳转;
  • if_acmpne: 若栈顶两引用不相等则跳转;

比较指令:

  • (T)cmp:比较栈顶两个T类型大小,前者大,则1入栈;相等则0入栈;后者大则-1入栈;
  • (T)cmpl:比较栈顶两个T类型大小,前者大,则1入栈;相等则0入栈;后者大则-1入栈;有NAN存在,则-1入栈;
  • (T)cmpg:比较栈顶两个T类型大小,前者大,则1入栈;相等则0入栈;后者大则-1入栈;有NAN存在,则-1入栈;

无条件转移指令:

  • goto :无条件转移到指定位置;
  • goto_w:无条件转移到指定位置(宽索引);

表跳转指令:

  • tableswitch:通过索引访问跳转表,并跳转;
  • lookupswitch: 通过健值访问跳转表,并跳转;

异常跳转指令:

  • athrow:抛出异常;
  • jsr:跳转到指定程序;
  • jsr_w:跳转到指定程序(宽索引);
  • ret:返回到指定程序;

方法调用和返回指令:

  • invokerspecial:指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法,编译时绑定;
  • invokevirtual:指令用于调用对象的实例方法,根据对象的实际类型进行分派,运行时绑定;
  • invokestatic:调用静态方法;
  • invokeinterface:用以调用接口方法,在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用;
  • invokedynamic:用于处理新的方法分派:它允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持,lambda方法实现就是依赖于该指令;
  • (T)return:方法退出指令,T表示返回类型;
    关于invokespecial和invokevirtual如下图:


    invokespecial和invokevirtual.png

同步方法指令:

  • monitorenter: 进入并获得对象监视器;
  • monitorexit:退出并释放对象监视器;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容