zoukankan      html  css  js  c++  java
  • 第19篇-加载与存储指令(1)

    TemplateInterpreterGenerator::generate_all()函数会生成许多例程(也就是机器指令片段,英文叫Stub),包括调用set_entry_points_for_all_bytes()函数生成各个字节码对应的例程。

    最终会调用到TemplateInterpreterGenerator::generate_and_dispatch()函数,调用堆栈如下:

    TemplateTable::geneate()                                templateTable_x86_64.cpp
    TemplateInterpreterGenerator::generate_and_dispatch()   templateInterpreter.cpp	
    TemplateInterpreterGenerator::set_vtos_entry_points()   templateInterpreter_x86_64.cpp	
    TemplateInterpreterGenerator::set_short_entry_points()  templateInterpreter.cpp
    TemplateInterpreterGenerator::set_entry_points()        templateInterpreter.cpp
    TemplateInterpreterGenerator::set_entry_points_for_all_bytes()   templateInterpreter.cpp	
    TemplateInterpreterGenerator::generate_all()            templateInterpreter.cpp
    InterpreterGenerator::InterpreterGenerator()            templateInterpreter_x86_64.cpp	
    TemplateInterpreter::initialize()                       templateInterpreter.cpp
    interpreter_init()                                      interpreter.cpp
    init_globals()                                          init.cpp
    

    调用堆栈上的许多函数在之前介绍过,每个字节码都会指定一个generator函数,通过Template的_gen属性保存。在TemplateTable::generate()函数中调用。_gen会生成每个字节码对应的机器指令片段,所以非常重要。

    首先看一个非常简单的nop字节码指令。这个指令的模板属性如下:

    // Java spec bytecodes  ubcp|disp|clvm|iswd  in    out   generator   argument
    def(Bytecodes::_nop   , ____|____|____|____, vtos, vtos, nop        ,  _      );

    nop字节码指令的生成函数generator不会生成任何机器指令,所以nop字节码指令对应的汇编代码中只有栈顶缓存的逻辑。调用set_vtos_entry_points()函数生成的汇编代码如下:

    // aep
    0x00007fffe1027c00: push   %rax
    0x00007fffe1027c01: jmpq   0x00007fffe1027c30
    
    // fep
    0x00007fffe1027c06: sub    $0x8,%rsp
    0x00007fffe1027c0a: vmovss %xmm0,(%rsp)
    0x00007fffe1027c0f: jmpq   0x00007fffe1027c30
    
    // dep
    0x00007fffe1027c14: sub    $0x10,%rsp
    0x00007fffe1027c18: vmovsd %xmm0,(%rsp)
    0x00007fffe1027c1d: jmpq   0x00007fffe1027c30
    
    // lep
    0x00007fffe1027c22: sub    $0x10,%rsp
    0x00007fffe1027c26: mov    %rax,(%rsp)
    0x00007fffe1027c2a: jmpq   0x00007fffe1027c30
    
    // bep cep sep iep
    0x00007fffe1027c2f: push   %rax
    
    // vep
    
    // 接下来为取指逻辑,开始的地址为0x00007fffe1027c30
    

    可以看到,由于tos_in为vtos,所以如果是aep、bep、cep、sep与iep时,直接使用push指令将%rax中存储的栈顶缓存值压入表达式栈中。对于fep、dep与lep来说,在栈上开辟对应内存的大小,然后将寄存器中的值存储到表达式的栈顶上,与push指令的效果相同。

    在set_vtos_entry_points()函数中会调用generate_and_dispatch()函数生成nop指令的机器指令片段及取下一条字节码指令的机器指令片段。nop不会生成任何机器指令,而取指的片段如下:

    // movzbl 将做了零扩展的字节传送到双字,地址为0x00007fffe1027c30
    0x00007fffe1027c30: movzbl  0x1(%r13),%ebx       
    
    0x00007fffe1027c35: inc %r13 
    
    0x00007fffe1027c38: movabs $0x7ffff73ba4a0,%r10 
    
    // movabs的源操作数只能是立即数或标号(本质还是立即数),目的操作数是寄存器 
    0x00007fffe1027c42: jmpq *(%r10,%rbx,8)
    

    r13指向当前要取的字节码指令的地址。那么%r13+1就是跳过了当前的nop指令而指向了下一个字节码指令的地址,然后执行movzbl指令将所指向的Opcode加载到%ebx中。

    通过jmpq的跳转地址为%r10+%rbx*8,关于这个跳转地址在前面详细介绍过,这里不再介绍。 

    我们讲解了nop指令,把栈顶缓存的逻辑和取指逻辑又回顾了一遍,对于每个字节码指令来说都会有有栈顶缓存和取指逻辑,后面在介绍字节码指令时就不会再介绍这2个逻辑。

    加载与存储相关操作的字节码指令如下表所示。

    字节码

    助词符

    指令含义

    0x00

    nop

    什么都不做

    0x01

    aconst_null    

    null推送至栈顶

    0x02

    iconst_m1

    int型-1推送至栈顶

    0x03

    iconst_0

    int型0推送至栈顶

    0x04

    iconst_1

    int型1推送至栈顶

    0x05

    iconst_2

    int型2推送至栈顶

    0x06

    iconst_3

    int型3推送至栈顶

    0x07

    iconst_4

    int型4推送至栈顶

    0x08

    iconst_5

    int型5推送至栈顶

    0x09

    lconst_0

    long型0推送至栈顶

    0x0a

    lconst_1

    long型1推送至栈顶

    0x0b

    fconst_0

    float型0推送至栈顶

    0x0c

    fconst_1

    float型1推送至栈顶

    0x0d

    fconst_2

    float型2推送至栈顶

    0x0e

    dconst_0

    double0推送至栈顶

    0x0f

    dconst_1

    double1推送至栈顶

    0x10

    bipush

    将单字节的常量值-128~127推送至栈顶

    0x11

    sipush

    将一个短整型常量值-32768~32767推送至栈顶

    0x12

    ldc

    intfloatString型常量值从常量池中推送至栈顶

    0x13

    ldc_w

    int,floatString型常量值从常量池中推送至栈顶(宽索引

    0x14

    ldc2_w

    longdouble型常量值从常量池中推送至栈顶宽索引

    0x15

    iload

    将指定的int型本地变量推送至栈顶

    0x16

    lload

    将指定的long型本地变量推送至栈顶

    0x17

    fload

    将指定的float型本地变量推送至栈顶

    0x18

    dload

    将指定的double型本地变量推送至栈顶

    0x19

    aload

    将指定的引用类型本地变量推送至栈顶

    0x1a

    iload_0

    将第一个int型本地变量推送至栈顶

    0x1b

    iload_1

    将第二个int型本地变量推送至栈顶

    0x1c

    iload_2

    将第三个int型本地变量推送至栈顶

    0x1d

    iload_3

    将第四个int型本地变量推送至栈顶

    0x1e

    lload_0

    将第一个long型本地变量推送至栈顶

    0x1f

    lload_1

    将第二个long型本地变量推送至栈顶

    0x20

    lload_2

    将第三个long型本地变量推送至栈顶

    0x21

    lload_3

    将第四个long型本地变量推送至栈顶

    0x22

    fload_0

    将第一个float型本地变量推送至栈顶

    0x23

    fload_1

    将第二个float型本地变量推送至栈顶

    0x24

    fload_2

    将第三个float型本地变量推送至栈顶

    0x25

    fload_3

    将第四个float型本地变量推送至栈顶

    0x26

    dload_0

    将第一个double型本地变量推送至栈顶

    0x27

    dload_1

    将第二个double型本地变量推送至栈顶

    0x28

    dload_2

    将第三个double型本地变量推送至栈顶

    0x29

    dload_3

    将第四个double型本地变量推送至栈顶

    0x2a

    aload_0

    将第一个引用类型本地变量推送至栈顶

    0x2b

    aload_1

    将第二个引用类型本地变量推送至栈顶

    0x2c

    aload_2

    将第三个引用类型本地变量推送至栈顶

    0x2d

    aload_3

    将第四个引用类型本地变量推送至栈顶

    0x2e

    iaload

    int型数组指定索引的值推送至栈顶

    0x2f

    laload

    long型数组指定索引的值推送至栈顶

    0x30

    faload

    float型数组指定索引的值推送至栈顶

    0x31

    daload

    double型数组指定索引的值推送至栈顶

    0x32

    aaload

    将引用型数组指定索引的值推送至栈顶

    0x33

    baload

    boolean或byte型数组指定索引的值推送至栈顶

    0x34

    caload

    char型数组指定索引的值推送至栈顶

    0x35

    saload

    short型数组指定索引的值推送至栈顶

    0x36

    istore

    将栈顶int型数值存入指定本地变量

    0x37

    lstore

    将栈顶long型数值存入指定本地变量

    0x38

    fstore

    将栈顶float型数值存入指定本地变量

    0x39

    dstore

    将栈顶double型数值存入指定本地变量

    0x3a

    astore

    将栈顶引用型数值存入指定本地变量

    0x3b

    istore_0

    将栈顶int型数值存入第一个本地变量

    0x3c

    istore_1

    将栈顶int型数值存入第二个本地变量

    0x3d

    istore_2

    将栈顶int型数值存入第三个本地变量

    0x3e

    istore_3

    将栈顶int型数值存入第四个本地变量

    0x3f

    lstore_0

    将栈顶long型数值存入第一个本地变量

    0x40

    lstore_1

    将栈顶long型数值存入第二个本地变量

    0x41

    lstore_2

    将栈顶long型数值存入第三个本地变量

    0x42

    lstore_3

    将栈顶long型数值存入第四个本地变量

    0x43

    fstore_0

    将栈顶float型数值存入第一个本地变量

    0x44

    fstore_1

    将栈顶float型数值存入第二个本地变量

    0x45

    fstore_2

    将栈顶float型数值存入第三个本地变量

    0x46

    fstore_3

    将栈顶float型数值存入第四个本地变量

    0x47

    dstore_0

    将栈顶double型数值存入第一个本地变量

    0x48

    dstore_1

    将栈顶double型数值存入第二个本地变量

    0x49

    dstore_2

    将栈顶double型数值存入第三个本地变量

    0x4a

    dstore_3

    将栈顶double型数值存入第四个本地变量

    0x4b

    astore_0

    将栈顶引用型数值存入第一个本地变量

    0x4c

    astore_1

    将栈顶引用型数值存入第二个本地变量

    0x4d

    astore_2

    将栈顶引用型数值存入第三个本地变量

    0x4e

    astore_3

    将栈顶引用型数值存入第四个本地变量

    0x4f

    iastore

    将栈顶int型数值存入指定数组的指定索引位置

    0x50

    lastore

    将栈顶long型数值存入指定数组的指定索引位置

    0x51

    fastore

    将栈顶float型数值存入指定数组的指定索引位置

    0x52

    dastore

    将栈顶double型数值存入指定数组的指定索引位置

    0x53

    aastore

    将栈顶引用型数值存入指定数组的指定索引位置

    0x54

    bastore

    将栈顶boolean或byte型数值存入指定数组的指定索引位置

    0x55

    castore

    将栈顶char型数值存入指定数组的指定索引位置

    0x56

    sastore

    将栈顶short型数值存入指定数组的指定索引位置

    0xc4

    wide

    扩充局部变量表的访问索引的指令

    我们不会对每个字节码指令都查看对应的机器指令片段的逻辑(其实是反编译机器指令片段为汇编后,通过查看汇编理解执行逻辑),有些指令的逻辑是类似的,这里只选择几个典型的介绍。

    1、压栈类型的指令

    (1)aconst_null指令

    aconst_null表示将null送到栈顶,模板定义如下:

    def(Bytecodes::_aconst_null , ____|____|____|____, vtos, atos, aconst_null  ,  _ );
    

    指令的汇编代码如下:

    // xor 指令在两个操作数的对应位之间进行逻辑异或操作,并将结果存放在目标操作数中
    // 第1个操作数和第2个操作数相同时,执行异或操作就相当于执行清零操作
    xor    %eax,%eax 
    

    由于tos_out为atos,所以栈顶的结果是缓存在%eax寄存器中的,只对%eax寄存器执行xor操作即可。 

    (2)iconst_m1指令

    iconst_m1表示将-1压入栈内,模板定义如下:

    def(Bytecodes::_iconst_m1 , ____|____|____|____, vtos, itos, iconst , -1 );
    

    生成的机器指令经过反汇编后,得到的汇编代码如下:  

    mov    $0xffffffff,%eax 

    其它的与iconst_m1字节码指令类似的字节码指令,如iconst_0、iconst_1等,模板定义如下:

    def(Bytecodes::_iconst_m1           , ____|____|____|____, vtos, itos, iconst              , -1           );
    def(Bytecodes::_iconst_0            , ____|____|____|____, vtos, itos, iconst              ,  0           );
    def(Bytecodes::_iconst_1            , ____|____|____|____, vtos, itos, iconst              ,  1           );
    def(Bytecodes::_iconst_2            , ____|____|____|____, vtos, itos, iconst              ,  2           );
    def(Bytecodes::_iconst_3            , ____|____|____|____, vtos, itos, iconst              ,  3           );
    def(Bytecodes::_iconst_4            , ____|____|____|____, vtos, itos, iconst              ,  4           );
    def(Bytecodes::_iconst_5            , ____|____|____|____, vtos, itos, iconst              ,  5           );

    可以看到,生成函数都是同一个TemplateTable::iconst()函数。

    iconst_0的汇编代码如下:

    xor    %eax,%eax

    iconst_@(@为1、2、3、4、5)的字节码指令对应的汇编代码如下:

    // aep  
    0x00007fffe10150a0: push   %rax
    0x00007fffe10150a1: jmpq   0x00007fffe10150d0
    
    // fep
    0x00007fffe10150a6: sub    $0x8,%rsp
    0x00007fffe10150aa: vmovss %xmm0,(%rsp)
    0x00007fffe10150af: jmpq   0x00007fffe10150d0
    
    // dep
    0x00007fffe10150b4: sub    $0x10,%rsp
    0x00007fffe10150b8: vmovsd %xmm0,(%rsp)
    0x00007fffe10150bd: jmpq   0x00007fffe10150d0
    
    // lep
    0x00007fffe10150c2: sub    $0x10,%rsp
    0x00007fffe10150c6: mov    %rax,(%rsp)
    0x00007fffe10150ca: jmpq   0x00007fffe10150d0
    
    // bep/cep/sep/iep
    0x00007fffe10150cf: push   %rax
    
    // vep
    0x00007fffe10150d0 mov $0x@,%eax // @代表1、2、3、4、5
    

    如果看过我之前写的文章,那么如上的汇编代码应该能看懂,我在这里就不再做过多介绍了。  

    (3)bipush

    bipush 将单字节的常量值推送至栈顶。模板定义如下:

    def(Bytecodes::_bipush , ubcp|____|____|____, vtos, itos, bipush ,  _ );

    指令的汇编代码如下:

    // %r13指向字节码指令的地址,偏移1位
    // 后取出1个字节的内容存储到%eax中
    movsbl 0x1(%r13),%eax 
    

    由于tos_out为itos,所以将单字节的常量值存储到%eax中,这个寄存器是专门用来进行栈顶缓存的。 

    (4)sipush

    sipush将一个短整型常量值推送到栈顶,模板定义如下:

    def(Bytecodes::_bipush , ubcp|____|____|____, vtos, itos, bipush ,  _  );

    生成的汇编代码如下:

    // movzwl传送做了符号扩展字到双字
    movzwl 0x1(%r13),%eax 
    // bswap 以字节为单位,把32/64位寄存器的值按照低和高的字节交换
    bswap  %eax     
    // (算术右移)指令将目的操作数进行算术右移      
    sar    $0x10,%eax    
    

    Java中的短整型占用2个字节,所以需要对32位寄存器%eax进行一些操作。由于字节码采用大端存储,所以在处理时统一变换为小端存储。

    2、存储类型指令

    istore指令会将int类型数值存入指定索引的本地变量表,模板定义如下:

    def(Bytecodes::_istore , ubcp|____|clvm|____, itos, vtos, istore ,  _ );

    生成函数为TemplateTable::istore(),生成的汇编代码如下:

    movzbl 0x1(%r13),%ebx
    neg    %rbx
    mov    %eax,(%r14,%rbx,8)

    由于栈顶缓存tos_in为itos,所以直接将%eax中的值存储到指定索引的本地变量表中。

    模板中指定ubcp,因为生成的汇编代码中会使用%r13,也就是字节码指令指针。

    其它的istore、dstore等字节码指令的汇编代码逻辑也类似,这里不过多介绍。

    推荐阅读:

    第1篇-关于JVM运行时,开篇说的简单些

    第2篇-JVM虚拟机这样来调用Java主类的main()方法

    第3篇-CallStub新栈帧的创建

    第4篇-JVM终于开始调用Java主类的main()方法啦

    第5篇-调用Java方法后弹出栈帧及处理返回结果

    第6篇-Java方法新栈帧的创建

    第7篇-为Java方法创建栈帧

    第8篇-dispatch_next()函数分派字节码

    第9篇-字节码指令的定义

    第10篇-初始化模板表

    第11篇-认识Stub与StubQueue

    第12篇-认识CodeletMark

    第13篇-通过InterpreterCodelet存储机器指令片段

    第14篇-生成重要的例程

    第15章-解释器及解释器生成器

    第16章-虚拟机中的汇编器

    第17章-x86-64寄存器

    第18章-x86指令集之常用指令

    如果有问题可直接评论留言或加作者微信mazhimazh

    关注公众号,有HotSpot VM源码剖析系列文章!

     

      

  • 相关阅读:
    正则表达式匹配负数和数字
    下拉框select chosen被遮盖
    获取JavaScript对象的方法
    管理机--Jumpserver由docker搭建
    腾讯云--腾讯云sdk-实现脚本修改腾讯云负载均衡权重
    Linux系统中使用confluence构建企业wiki
    腾讯云--对象存储cos绑定自定义域名
    python(一)python的操作符
    pytest(五)用例传fixture参数
    pytest(四)firture自定义用例预置条件
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/15245738.html
Copyright © 2011-2022 走看看