资讯详情

【JVM源码解析】模板解释器解释执行Java字节码指令(上)

本文由HeapDump九摩(马智)性能社区首席讲师授权整理发布

第17章-x86-64寄存器

不同的CPU可以解释的机器语言系统称为指令集架构(ISA,Instruction Set Architecture),也可称为指令集(instruction set)。Intel将x86系列CPU之中的32位CPU指令集架构称为IA-32,IA是“Intel Architecture也可以称为简称i386、x86-32。AMD等于Intell提出了x由于86系列的64位扩展,AMD设计的x86系列的64位指令集架构称为AMD64。后来Intel在自己的CPU中加入和AMD64几乎相同的指令集被称为Intel 64的指令集。AMD64和Intel 64可以统称为x86-64。

x86-64的所有寄存器都与机器字长(数据总线位宽)相同,即64位,x86-64将x8632位通用寄存器扩展到64位(eax、ebx、ecx、edx、eci、edi、ebp、esp),并增加了8个新的64个寄存器(r8-r15),在命名方式上,也从exx”变为”rxx”,但仍保留”exx下表描述了每个寄存器的命名和功能。

描述 32位 64位
通用寄存器组 eax rax
ecx rcx
edx rdx
ebx rbx
esp rsp
ebp rbp
esi rsi
edi rdi
- r8~r15
浮点寄存器组 st0~st7 st0~st7
XMM寄存器组 XMM0~XMM7 XMM0~XMM15

其中的%esp与?p用于保存指向程序栈中特定位置的指针。

另外还有eflags如下图所示,寄存器通过位置表示具体含义。

在HotSpot VM其中,寄存器的类别继承自AbstractRegisterImpl该类别的定义如下:

源代码位置:hotspot/src/share/vm/asm/register.hpp  class AbstractRegisterImpl; typedef AbstractRegisterImpl* AbstractRegister;  class AbstractRegisterImpl {  protected:   int value() const  { return (int)(intx)this; } };  

AbstractRegisterImpl如下图所示。

另外还有个ConcreteRegisterImpl类也继承了AbstractRegisterImpl,这个灰与C编译器的实现有关,这里就不多解释了。

1、RegisterImpl类

RegisterImpl类用来表示通用寄存器,类的定义如下:

源代码位置:cpu/x86/vm/register_x86.hpp  // 使用Register做为RegisterImpl的简称 class RegisterImpl; typedef RegisterImpl* Register;  class RegisterImpl: public AbstractRegisterImpl {  public:   enum {     number_of_registers      = 16,     number_of_byte_registers = 16   };   // ... }; 

对于64位来说,通用寄存器的位宽为64位,也可以将eax、ebx、ecx和edx部分用作8位寄存器,因此可以存储字节的寄存器数量为4。

在HotSpot VM寄存器的定义如下:

源代码位置:hotspot/src/cpu/x86/vm/register_x86.hpp  CONSTANT_REGISTER_DECLARATION(Register, noreg, (-1)); // noreg_RegisterEnumValue = ((-1)) CONSTANT_REGISTER_DECLARATION(Register, rax,    (0)); // rax_RegisterEnumValue = ((0)) CONSTANT_REGISTER_DECLARATION(Register, rcx,    (1)); // rcx_RegisterEnumValue = ((1)) CONSTANT_REGISTER_DECLARATION(Register, rdx,    (2)); // rdx_RegisterEnumValue = ((2)) CONSTANT_REGISTER_DECLARATION(Register, rbx,    (3)); // rbx_RegisterEnumValue = ((3)) CONSTANT_REGISTER_DECLARATION(Register, rsp,    (4)); // rsp_RegisterEnumValue = ((4)) CONSTANT_REGISTER_DECLARATION(Register, rbp,    (5)); // rbp_RegisterEnumValue = ((5)) CONSTANT_REGISTER_DECLARATION(Register, rsi,    (6)); // rsi_RegisterEnumValue = ((6)) CONSTANT_REGISTER_DECLARATION(Register, rdi,    (7)); // rdi_RegisterEnumValue = ((7)) CONSTANT_REGISTER_DECLARATION(Register, r8,     (8)); // r8_RegisterEnumValue = ((8)) CONSTANT_REGISTER_DECLARATION(Register, r9,     (9)); // r9_RegisterEnumValue = ((9)) CONSTANT_REGISTER_DECLARATION(Register, r10,   (10)); // r10_RegisterEnumValue = ((10)) CONSTANT_REGISTER_DECLARATION(Register, r11,   (11)); // r11_RegisterEnumValue = ((11)) CONSTANT_REGISTER_DECLARATION(Register, r12,   (12)); // r12_RegisterEnumValue = ((12)) CONSTANT_REGISTER_DECLARATION(Register, r13,   (13)); // r13_RegisterEnumValue = ((13)) CONSTANT_REGISTER_DECLARATION(Register, r14,   (14)); // r14_RegisterEnumValue = ((14)) CONSTANT_REGISTER_DECLARATION(Register, r15,   (15)); // r15_RegisterEnumValue = ((15)) 

宏CONSTANT_REGISTER_DECLARATION定义如下:

源代码位置:hotspot/src/share/vm/asm/register.hpp  #define CONSTANT_REGISTER_DECLARATION(type, name, value)   \   extern const type name;                                  \   enum { name##_##type##numValue = (value) }

经过宏扩展后如下:

extern const Register  rax;
enum { rax_RegisterEnumValue = ((0)) }
extern const Register  rcx;
enum { rcx_RegisterEnumValue = ((1)) }
extern const Register  rdx;
enum { rdx_RegisterEnumValue = ((2)) }
extern const Register  rbx;
enum { rbx_RegisterEnumValue = ((3)) }
extern const Register  rsp;
enum { rsp_RegisterEnumValue = ((4)) }
extern const Register  rbp;
enum { rbp_RegisterEnumValue = ((5)) }
extern const Register  rsi;
enum { rsi_RegisterEnumValue = ((6)) }
extern const Register  rsi;
enum { rdi_RegisterEnumValue = ((7)) }
extern const Register  r8;
enum { r8_RegisterEnumValue = ((8)) }
extern const Register  r9;
enum { r9_RegisterEnumValue = ((9)) }
extern const Register  r10;
enum { r10_RegisterEnumValue = ((10)) }
extern const Register  r11;
enum { r11_RegisterEnumValue = ((11)) }
extern const Register  r12;
enum { r12_RegisterEnumValue = ((12)) }
extern const Register  r13;
enum { r13_RegisterEnumValue = ((13)) }
extern const Register  r14;
enum { r14_RegisterEnumValue = ((14)) }
extern const Register  r15;
enum { r15_RegisterEnumValue = ((15)) }

如上的枚举类给寄存器指定了一个常量值。

在cpu/x86/vm/register_definitions_x86.cpp文件中定义的寄存器如下:

const Register  noreg = ((Register)noreg_RegisterEnumValue)
const Register  rax =   ((Register)rax_RegisterEnumValue)
const Register  rcx =   ((Register)rcx_RegisterEnumValue)
const Register  rdx =   ((Register)rdx_RegisterEnumValue)
const Register  rbx =   ((Register)rbx_RegisterEnumValue)
const Register  rsp =   ((Register)rsp_RegisterEnumValue)
const Register  rbp =   ((Register)rbp_RegisterEnumValue)
const Register  rsi =   ((Register)rsi_RegisterEnumValue)
const Register  rdi =   ((Register)rdi_RegisterEnumValue)
const Register  r8 =  ((Register)r8_RegisterEnumValue)
const Register  r9 =  ((Register)r9_RegisterEnumValue)
const Register  r10 = ((Register)r10_RegisterEnumValue)
const Register  r11 = ((Register)r11_RegisterEnumValue)
const Register  r12 = ((Register)r12_RegisterEnumValue)
const Register  r13 = ((Register)r13_RegisterEnumValue)
const Register  r14 = ((Register)r14_RegisterEnumValue)
const Register  r15 = ((Register)r15_RegisterEnumValue)

当我们需要使用通用寄存器时,通过rax、rcx等变量引用就可以了。

2、FloatRegisterImpl

在HotSpot VM中,使用FloatRegisterImpl来表示浮点寄存器,此类的定义如下:

源代码位置:hotspot/src/cpu/x86/vm/register_x86.hpp

// 使用FloatRegister做为简称
class FloatRegisterImpl;
typedef FloatRegisterImpl* FloatRegister;

class FloatRegisterImpl: public AbstractRegisterImpl {
 public:
  enum {
    number_of_registers = 8
  };
  // ...
}

浮点寄存器有8个,分别是st0~st7,这是8个80位寄存器。

这里需要注意的是,还有一种寄存器MMX,MMX并非一种新的寄存器,而是借用了80位浮点寄存器的低64位,也就是说,使用MMX指令集,会影响浮点运算!

3、MMXRegisterImpl

MMX 为一种 SIMD 技术,即可通过一条指令执行多个数据运算,共有8个64位寄存器(借用了80位浮点寄存器的低64位),分别为mm0 – mm7,他与其他普通64位寄存器的区别在于通过它的指令进行运算,可以同时计算2个32位数据,或者4个16位数据等等,可以应用为图像处理过程中图形 颜色的计算。

MMXRegisterImpl类的定义如下:

class MMXRegisterImpl;
typedef MMXRegisterImpl* MMXRegister;

MMX寄存器的定义如下:

CONSTANT_REGISTER_DECLARATION(MMXRegister, mnoreg , (-1));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx0 , ( 0));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx1 , ( 1));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx2 , ( 2));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx3 , ( 3));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx4 , ( 4));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx5 , ( 5));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx6 , ( 6));
CONSTANT_REGISTER_DECLARATION(MMXRegister, mmx7 , ( 7));

宏扩展后如下:

extern const MMXRegister  mnoreg;
enum { mnoreg_MMXRegisterEnumValue = ((-1)) }
extern const MMXRegister  mmx0;
enum { mmx0_MMXRegisterEnumValue = (( 0)) }
extern const MMXRegister  mmx1;
enum { mmx1_MMXRegisterEnumValue = (( 1)) }
extern const MMXRegister  mmx2;
enum { mmx2_MMXRegisterEnumValue = (( 2)) }
extern const MMXRegister  mmx3;
enum { mmx3_MMXRegisterEnumValue = (( 3)) }
extern const MMXRegister  mmx4;
enum { mmx4_MMXRegisterEnumValue = (( 4)) }
extern const MMXRegister  mmx5;
enum { mmx5_MMXRegisterEnumValue = (( 5)) }
extern const MMXRegister  mmx6;
enum { mmx6_MMXRegisterEnumValue = (( 6)) }
extern const MMXRegister  mmx7;
enum { mmx7_MMXRegisterEnumValue = (( 7)) }

MMX Pentium以及Pentium II之后的CPU中有从mm0到mm7共8个64位寄存器。但实际上MMX寄存器和浮点数寄存器是共用的,即无法同时使用浮点数寄存器和MMX寄存器。

cpu/x86/vm/register_definitions_x86.cpp文件中定义的寄存器变量如下:

const MMXRegister  mnoreg = ((MMXRegister)mnoreg_MMXRegisterEnumValue)
const MMXRegister  mmx0 =   ((MMXRegister)mmx0_MMXRegisterEnumValue)
const MMXRegister  mmx1 =   ((MMXRegister)mmx1_MMXRegisterEnumValue)
const MMXRegister  mmx2 =   ((MMXRegister)mmx2_MMXRegisterEnumValue)
const MMXRegister  mmx3 =   ((MMXRegister)mmx3_MMXRegisterEnumValue)
const MMXRegister  mmx4 =   ((MMXRegister)mmx4_MMXRegisterEnumValue)
const MMXRegister  mmx5 =   ((MMXRegister)mmx5_MMXRegisterEnumValue)
const MMXRegister  mmx6 =   ((MMXRegister)mmx6_MMXRegisterEnumValue)
const MMXRegister  mmx7 =   ((MMXRegister)mmx7_MMXRegisterEnumValue)

当我们需要使用MMX寄存器时,通过mmx0、mmx1等变量引用就可以了。

4、XMMRegisterImpl类

XMM寄存器是SSE指令用的寄存器。Pentium iii以及之后的CPU中提供了xmm0到xmm7共8个128位宽的XMM寄存器。另外还有个mxcsr寄存器,这个寄存器用来表示SSE指令的运算状态的寄存器。在HotSpot VM中,通过XMMRegisterImpl类来表示寄存器。这个类的定义如下:

源代码位置:hotspot/src/share/x86/cpu/vm/register_x86.hpp

// 使用XMMRegister寄存器做为简称
class XMMRegisterImpl;
typedef XMMRegisterImpl* XMMRegister;

class XMMRegisterImpl: public AbstractRegisterImpl {
 public:
  enum {
    number_of_registers = 16
  };
  ...
}

XMM寄存器的定义如下:

CONSTANT_REGISTER_DECLARATION(XMMRegister, xnoreg , (-1));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm0 ,   ( 0));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm1 ,   ( 1));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm2 ,   ( 2));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm3 ,   ( 3));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm4 ,   ( 4));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm5 ,   ( 5));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm6 ,   ( 6));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm7 ,   ( 7));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm8,      (8));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm9,      (9));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm10,    (10));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm11,    (11));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm12,    (12));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm13,    (13));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm14,    (14));
CONSTANT_REGISTER_DECLARATION(XMMRegister, xmm15,    (15));

经过宏扩展后为:

extern const XMMRegister  xnoreg;
enum { xnoreg_XMMRegisterEnumValue = ((-1)) }
extern const XMMRegister  xmm0;
enum { xmm0_XMMRegisterEnumValue = (( 0)) }
extern const XMMRegister  xmm1;
enum { xmm1_XMMRegisterEnumValue = (( 1)) }
extern const XMMRegister  xmm2;
enum { xmm2_XMMRegisterEnumValue = (( 2)) }
extern const XMMRegister  xmm3;
enum { xmm3_XMMRegisterEnumValue = (( 3)) }
extern const XMMRegister  xmm4;
enum { xmm4_XMMRegisterEnumValue = (( 4)) }
extern const XMMRegister  xmm5;
enum { xmm5_XMMRegisterEnumValue = (( 5)) }
extern const XMMRegister  xmm6;
enum { xmm6_XMMRegisterEnumValue = (( 6)) }
extern const XMMRegister  xmm7;
enum { xmm7_XMMRegisterEnumValue = (( 7)) }
extern const XMMRegister  xmm8;
enum { xmm8_XMMRegisterEnumValue = ((8)) }
extern const XMMRegister  xmm9;
enum { xmm9_XMMRegisterEnumValue = ((9)) }
extern const XMMRegister  xmm10;
enum { xmm10_XMMRegisterEnumValue = ((10)) }
extern const XMMRegister  xmm11;
enum { xmm11_XMMRegisterEnumValue = ((11)) }
extern const XMMRegister  xmm12;
enum { xmm12_XMMRegisterEnumValue = ((12)) }
extern const XMMRegister  xmm13;
enum { xmm13_XMMRegisterEnumValue = ((13)) }
extern const XMMRegister  xmm14;
enum { xmm14_XMMRegisterEnumValue = ((14)) }
extern const XMMRegister  xmm15;
enum { xmm15_XMMRegisterEnumValue = ((15)) }

在cpu/x86/vm/register_definitions_x86.cpp文件中定义的寄存器变量如下:

const XMMRegister  xnoreg = ((XMMRegister)xnoreg_XMMRegisterEnumValue)
const XMMRegister  xmm0 =   ((XMMRegister)xmm0_XMMRegisterEnumValue)
const XMMRegister  xmm1 =   ((XMMRegister)xmm1_XMMRegisterEnumValue)
const XMMRegister  xmm2 =   ((XMMRegister)xmm2_XMMRegisterEnumValue)
const XMMRegister  xmm3 =   ((XMMRegister)xmm3_XMMRegisterEnumValue)
const XMMRegister  xmm4 =   ((XMMRegister)xmm4_XMMRegisterEnumValue)
const XMMRegister  xmm5 =   ((XMMRegister)xmm5_XMMRegisterEnumValue)
const XMMRegister  xmm6 =   ((XMMRegister)xmm6_XMMRegisterEnumValue)
const XMMRegister  xmm7 =   ((XMMRegister)xmm7_XMMRegisterEnumValue)
const XMMRegister  xmm8 =   ((XMMRegister)xmm8_XMMRegisterEnumValue)
const XMMRegister  xmm9 =   ((XMMRegister)xmm9_XMMRegisterEnumValue)
const XMMRegister  xmm10 =  ((XMMRegister)xmm10_XMMRegisterEnumValue)
const XMMRegister  xmm11 =  ((XMMRegister)xmm11_XMMRegisterEnumValue)
const XMMRegister  xmm12 =  ((XMMRegister)xmm12_XMMRegisterEnumValue)
const XMMRegister  xmm13 =  ((XMMRegister)xmm13_XMMRegisterEnumValue)
const XMMRegister  xmm14 =  ((XMMRegister)xmm14_XMMRegisterEnumValue)
const XMMRegister  xmm15 =  ((XMMRegister)xmm15_XMMRegisterEnumValue)

当我们需要使用XMM寄存器时,直接通过xmm0、xmm1等变量引用就可以了。

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

x86的指令集可分为以下4种:

  1. 通用指令
  2. x87 FPU指令,浮点数运算的指令
  3. SIMD指令,就是SSE指令
  4. 系统指令,写OS内核时使用的特殊指令

下面介绍一些通用的指令。指令由标识命令种类的助记符(mnemonic)和作为参数的操作数(operand)组成。例如move指令:

指令 操作数 描述
movq I/R/M,R/M 从一个内存位置复制1个双字(64位,8字节)大小的数据到另外一个内存位置
movl I/R/M,R/M 从一个内存位置复制1个字(32位,4字节)大小的数据到另外一个内存位置
movw I/R/M, R/M 从一个内存位置复制2个字节(16位)大小的数据到另外一个内存位置
movb I/R/M, R/M 从一个内存位置复制1个字节(8位)大小的数据到另外一个内存位置

movl为助记符。助记符有后缀,如movl中的后缀l表示作为操作数的对象的数据大小。l为long的缩写,表示32位的大小,除此之外,还有b、w,q分别表示8位、16位和64位的大小。

指令的操作数如果不止1个,就将每个操作数以逗号分隔。每个操作数都会指明是否可以是立即模式值(I)、寄存器(R)或内存地址(M)。

另外还要提示一下,在x86的汇编语言中,采用内存位置的操作数最多只能出现一个,例如不可能出现mov M,M指令。

通用寄存器中每个操作都可以有一个字符的后缀,表明操作数的大小,如下表所示。

C声明 通用寄存器后缀 大小(字节)
char b 1
short w 2
(unsigned) int / long / char* l 4
float s 4
double l 5
long double t 10/12

注意:通用寄存器使用后缀“l”同时表示4字节整数和8字节双精度浮点数,这不会产生歧义,因为浮点数使用的是完全不同的指令和寄存器。

我们后面只介绍call、push等指令时,如果在研究HotSpot VM虚拟机的汇编遇到了callq,pushq等指令时,千万别不认识,后缀就是表示了操作数的大小。

下表为操作数的格式和寻址模式。

格式 操作数值 名称 样例(通用寄存器 = C语言)
$Imm Imm 立即数寻址 $1 = 1
Ea R[Ea] 寄存器寻址 %eax = eax
Imm M[Imm] 绝对寻址 0x104 = *0x104
(Ea) M[R[Ea]] 间接寻址 (%eax)= *eax
Imm(Ea) M[Imm+R[Ea]] (基址+偏移量)寻址 4(%eax) = *(4+eax)
(Ea,Eb) M[R[Ea]+R[Eb]] 变址 (%eax,%ebx) = *(eax+ebx)
Imm(Ea,Eb) M[Imm+R[Ea]+R[Eb]] 寻址 9(%eax,%ebx)= *(9+eax+ebx)
(,Ea,s) M[R[Ea]*s] 伸缩化变址寻址 (,%eax,4)= (eax4)
Imm(,Ea,s) M[Imm+R[Ea]*s] 伸缩化变址寻址 0xfc(,%eax,4)= (0xfc+eax4)
(Ea,Eb,s) M(R[Ea]+R[Eb]*s) 伸缩化变址寻址 (%eax,%ebx,4) = (eax+ebx4)
Imm(Ea,Eb,s) M(Imm+R[Ea]+R[Eb]*s) 伸缩化变址寻址 8(%eax,%ebx,4) = (8+eax+ebx4)

注:M[xx]表示在存储器中xx地址的值,R[xx]表示寄存器xx的值,这种表示方法将寄存器、内存都看出一个大数组的形式。

汇编根据编译器的不同,有2种书写格式:

(1)Intel : Windows派系 (2)AT&T: Unix派系

下面简单介绍一下两者的不同。

下面就来认识一下常用的指令。

下面我们以给出的是AT&T汇编的写法,这两种写法有如下不同。

1、数据传送指令

将数据从一个地方传送到另外一个地方。

1.1 mov指令

我们在介绍mov指令时介绍的全一些,因为mov指令是出现频率最高的指令,助记符中的后缀也比较多。

mov指令的形式有3种,如下:

mov   #普通的move指令
movs  #符号扩展的move指令,将源操作数进行符号扩展并传送到一个64位寄存器或存储单元中。movs就表示符号扩展 
movz  #零扩展的move指令,将源操作数进行零扩展后传送到一个64位寄存器或存储单元中。movz就表示零扩展

mov指令后有一个字母可表示操作数大小,形式如下:

movb #完成1个字节的复制
movw #完成2个字节的复制
movl #完成4个字节的复制
movq #完成8个字节的复制

还有一个指令,如下:

movabsq  I,R

与movq有所不同,它是将一个64位的值直接存到一个64位寄存器中。

movs指令的形式如下:

movsbw #作符号扩展的1字节复制到2字节
movsbl #作符号扩展的1字节复制到4字节
movsbq #作符号扩展的1字节复制到8字节
movswl #作符号扩展的2字节复制到4字节
movswq #作符号扩展的2字节复制到8字节
movslq #作符号扩展的4字节复制到8字节

movz指令的形式如下:

movzbw #作0扩展的1字节复制到2字节
movzbl #作0扩展的1字节复制到4字节
movzbq #作0扩展的1字节复制到8字节
movzwl #作0扩展的2字节复制到4字节
movzwq #作0扩展的2字节复制到8字节
movzlq #作0扩展的4字节复制到8字节

举个例子如下:

movl   %ecx,%eax
movl   (%ecx),%eax

第一条指令将寄存器ecx中的值复制到eax寄存器;第二条指令将ecx寄存器中的数据作为地址访问内存,并将内存上的数据加载到eax寄存器中。

1.2 cmov指令

cmov指令的格式如下:

cmovxx

其中xx代表一个或者多个字母,这些字母表示将触发传送操作的条件。条件取决于 EFLAGS 寄存器的当前值。

eflags寄存器中各个们如下图所示。

其中与cmove指令相关的eflags寄存器中的位有CF(数学表达式产生了进位或者借位) 、OF(整数值无穷大或者过小)、PF(寄存器包含数学操作造成的错误数据)、SF(结果为正不是负)和ZF(结果为零)。

下表为无符号条件传送指令。

指令对 描述 eflags状态
cmova/cmovnbe 大于/不小于或等于 (CF或ZF)=0
cmovae/cmovnb 大于或者等于/不小于 CF=0
cmovnc 无进位 CF=0
cmovb/cmovnae 大于/不小于或等于 CF=1
cmovc 进位 CF=1
cmovbe/cmovna 小于或者等于/不大于 (CF或ZF)=1
cmove/cmovz 等于/零 ZF=1
cmovne/cmovnz 不等于/不为零 ZF=0
cmovp/cmovpe 奇偶校验/偶校验 PF=1
cmovnp/cmovpo 非奇偶校验/奇校验 PF=0

无符号条件传送指令依靠进位、零和奇偶校验标志来确定两个操作数之间的区别。

下表为有符号条件传送指令。

指令对 描述 eflags状态
cmovge/cmovnl 大于或者等于/不小于 (SF异或OF)=0
cmovl/cmovnge 大于/不大于或者等于 (SF异或OF)=1
cmovle/cmovng 小于或者等于/不大于 ((SF异或OF)或ZF)=1
cmovo 溢出 OF=1
cmovno 未溢出 OF=0
cmovs 带符号(负) SF=1
cmovns 无符号(非负) SF=0

举个例子如下:

// 将vlaue数值加载到ecx寄存器中
movl value,%ecx 
// 使用cmp指令比较ecx和ebx这两个寄存器中的值,具体就是用ecx减去ebx然后设置eflags
cmp %ebx,%ecx
// 如果ecx的值大于ebx,使用cmova指令设置ebx的值为ecx中的值
cmova %ecx,%ebx 

注意AT&T汇编的第1个操作数在前,第2个操作数在后。

1.3 push和pop指令

push指令的形式如下表所示。

指令 操作数 描述
push I/R/M PUSH 指令首先减少 ESP 的值,再将源操作数复制到堆栈。操作数是 16 位的,则 ESP 减 2,操作数是 32 位的,则 ESP 减 4
pusha 指令按序(AX、CX、DX、BX、SP、BP、SI 和 DI)将 16 位通用寄存器压入堆栈。
pushad 指令按照 EAX、ECX、EDX、EBX、ESP(执行 PUSHAD 之前的值)、EBP、ESI 和 EDI 的顺序,将所有 32 位通用寄存器压入堆栈。

pop指令的形式如下表所示。

指令 操作数 描述
pop R/M 指令首先把 ESP 指向的堆栈元素内容复制到一个 16 位或 32 位目的操作数中,再增加 ESP 的值。如果操作数是 16 位的,ESP 加 2,如果操作数是 32 位的,ESP 加 4
popa 指令按照相反顺序将同样的寄存器弹出堆栈
popad 指令按照相反顺序将同样的寄存器弹出堆栈

1.4 xchg与xchgl

这个指令用于交换操作数的值,交换指令XCHG是两个寄存器,寄存器和内存变量之间内容的交换指令,两个操作数的数据类型要相同,可以是一个字节,也可以是一个字,也可以是双字。格式如下:

xchg    R/M,R/M
xchgl   I/R,I/R、  

两个操作数不能同时为内存变量。xchgl指令是一条古老的x86指令,作用是交换两个寄存器或者内存地址里的4字节值,两个值不能都是内存地址,他不会设置条件码。

1.5 lea

lea计算源操作数的实际地址,并把结果保存到目标操作数,而目标操作数必须为通用寄存器。格式如下:

lea M,R

lea(Load Effective Address)指令将地址加载到寄存器。

举例如下:

movl  4(%ebx),%eax
leal  4(%ebx),%eax  

第一条指令表示将ebx寄存器中存储的值加4后得到的结果作为内存地址进行访问,并将内存地址中存储的数据加载到eax寄存器中。

第二条指令表示将ebx寄存器中存储的值加4后得到的结果作为内存地址存放到eax寄存器中。

再举个例子,如下:

leaq a(b, c, d), %rax 

计算地址a + b + c * d,然后把最终地址载到寄存器rax中。可以看到只是简单的计算,不引用源操作数里的寄存器。这样的完全可以把它当作乘法指令使用。

2、算术运算指令

下面介绍对有符号整数和无符号整数进行操作的基本运算指令。

2.1 add与adc指令

指令的格式如下:

add  I/R/M,R/M
adc  I/R/M,R/M

指令将两个操作数相加,结果保存在第2个操作数中。

对于第1条指令来说,由于寄存器和存储器都有位宽限制,因此在进行加法运算时就有可能发生溢出。运算如果溢出的话,标志寄存器eflags中的进位标志(Carry Flag,CF)就会被置为1。

对于第2条指令来说,利用adc指令再加上进位标志eflags.CF,就能在32位的机器上进行64位数据的加法运算。

常规的算术逻辑运算指令只要将原来IA-32中的指令扩展到64位即可。如addq就是四字相加。

2.2 sub与sbb指令

指令的格式如下:

sub I/R/M,R/M
sbb I/R/M,R/M

指令将用第2个操作数减去第1个操作数,结果保存在第2个操作数中。

2.3 imul与mul指令

指令的格式如下:

imul I/R/M,R
mul  I/R/M,R

将第1个操作数和第2个操作数相乘,并将结果写入第2个操作数中,如果第2个操作数空缺,默认为eax寄存器,最终完整的结果将存储到edx:eax中。

第1条指令执行有符号乘法,第2条指令执行无符号乘法。

2.4 idiv与div指令

指令的格式如下:

div   R/M
idiv  R/M

第1条指令执行无符号除法,第2条指令执行有符号除法。被除数由edx寄存器和eax寄存器拼接而成,除数由指令的第1个操作数指定,计算得到的商存入eax寄存器,余数存入edx寄存器。如下图所示。

    edx:eax
------------ = eax(商)... edx(余数)
    寄存器

运算时被除数、商和除数的数据的位宽是不一样的,如下表表示了idiv指令和div指令使用的寄存器的情况。

数据的位宽 被除数 除数 余数
8位 ax 指令第1个操作数 al ah
16位 dx:ax 指令第1个操作数 ax dx
32位 edx:eax 指令第1个操作数 eax edx

idiv指令和div指令通常是对位宽2倍于除数的被除数进行除法运算的。例如对于x86-32机器来说,通用寄存器的倍数为32位,1个寄存器无法容纳64位的数据,所以 edx存放被除数的高32位,而eax寄存器存放被除数的低32位。

所以在进行除法运算时,必须将设置在eax寄存器中的32位数据扩展到包含edx寄存器在内的64位,即有符号进行符号扩展,无符号数进行零扩展。

对edx进行符号扩展时可以使用cltd(AT&T风格写法)或cdq(Intel风格写法)。指令的格式如下:

cltd  // 将eax寄存器中的数据符号扩展到edx:eax

cltd将eax寄存器中的数据符号扩展到edx:eax。

2.5 incl与decl指令

指令的格式如下:

inc  R/M
dec  R/M 

将指令第1个操作数指定的寄存器或内存位置存储的数据加1或减1。

2.6 negl指令

指令的格式如下:

neg R/M

neg指令将第1个操作数的符号进行反转。

3、位运算指令

3.1 andl、orl与xorl指令

指令的格式如下:

and  I/R/M,R/M
or   I/R/M,R/M
xor  I/R/M,R/M

and指令将第2个操作数与第1个操作数进行按位与运算,并将结果写入第2个操作数;

or指令将第2个操作数与第1个操作数进行按位或运算,并将结果写入第2个操作数;

xor指令将第2个操作数与第1个操作数进行按位异或运算,并将结果写入第2个操作数;

3.2 not指令

指令的格式如下:

not R/M

将操作数按位取反,并将结果写入操作数中。

3.3 sal、sar、shr指令

指令的格式如下:

sal  I/%cl,R/M  #算术左移
sar  I/%cl,R/M  #算术右移
shl  I/%cl,R/M  #逻辑左移
shr  I/%cl,R/M  #逻辑右移

sal指令将第2个操作数按照第1个操作数指定的位数进行左移操作,并将结果写入第2个操作数中。移位之后空出的低位补0。指令的第1个操作数只能是8位的立即数或cl寄存器,并且都是只有低5位的数据才有意义,高于或等于6位数将导致寄存器中的所有数据被移走而变得没有意义。

sar指令将第2个操作数按照第1个操作数指定的位数进行右移操作,并将结果写入第2个操作数中。移位之后的空出进行符号扩展。和sal指令一样,sar指令的第1个操作数也必须为8位的立即数或cl寄存器,并且都是只有低5位的数据才有意义。

shl指令和sall指令的动作完全相同,没有必要区分。

shr令将第2个操作数按照第1个操作数指定的位数进行右移操作,并将结果写入第2个操作数中。移位之后的空出进行零扩展。和sal指令一样,shr指令的第1个操作数也必须为8位的立即数或cl寄存器,并且都是只有低5位的数据才有意义。

4、流程控制指令

4.1 jmp指令

指令的格式如下:

jmp I/R

jmp指令将程序无条件跳转到操作数指定的目的地址。jmp指令可以视作设置指令指针(eip寄存器)的指令。目的地址也可以是星号后跟寄存器的栈,这种方式为间接函数调用。例如:

jmp *%eax

将程序跳转至eax所含地址。

4.2 条件跳转指令

条件跳转指令的格式如下:

Jcc  目的地址

其中cc指跳转条件,如果为真,则程序跳转到目的地址;否则执行下一条指令。相关的条件跳转指令如下表所示。

指令 跳转条件 描述 指令 跳转条件 描述
jz ZF=1 为0时跳转 jbe CF=1或ZF=1 大于或等于时跳转
jnz ZF=0 不为0时跳转 jnbe CF=0且ZF=0 小于或等于时跳转
je ZF=1 相等时跳转 jg ZF=0且SF=OF 大于时跳转
jne ZF=0 不相等时跳转 jng ZF=1或SF!=OF 不大于时跳转
ja CF=0且ZF=0 大于时跳转 jge SF=OF 大于或等于时跳转
jna CF=1或ZF=1 不大于时跳转 jnge SF!=OF 小于或等于时跳转
jae CF=0 大于或等于时跳转 jl SF!=OF 小于时跳转
jnae CF=1 小于或等于时跳转 jnl SF=OF 不小于时跳转
jb CF=1 大于时跳转 jle ZF=1或SF!=OF 小于或等于时跳转
jnb CF=0 不大于时跳转 jnle ZF=0且SF=OF 大于或等于时跳转

4.3 cmp指令

cmp指令的格式如下:

cmp I/R/M,R/M

cmp指令通过比较第2个操作数减去第1个操作数的差,根据结果设置标志寄存器eflags中的标志位。cmp指令和sub指令类似,不过cmp指令不会改变操作数的值。

操作数和所设置的标志位之间的关系如表所示。

操作数的关系 CF ZF OF
第1个操作数小于第2个操作数 0 0 SF
第1个操作数等于第2个操作数 0 1 0
第1个操作数大于第2个操作数 1 0 not SF

4.4 test指令

指令的格式如下:

test I/R/M,R/M

指令通过比较第1个操作数与第2个操作数的逻辑与,根据结果设置标志寄存器eflags中的标志位。test指令本质上和and指令相同,只是test指令不会改变操作数的值。

test指令执行后CF与OF通常会被清零,并根据运算结果设置ZF和SF。运算结果为零时ZF被置为1,SF和最高位的值相同。

举个例子如下:

test指令同时能够检查几个位。假设想要知道 AL 寄存器的位 0 和位 3 是否置 1,可以使用如下指令:

test al,00001001b    #掩码为0000 1001,测试第0和位3位是否为1

从下面的数据集例子中,可以推断只有当所有测试位都清 0 时,零标志位才置 1:

0  0  1  0  0  1  0  1    <- 输入值
0  0  0  0  1  0  0  1    <- 测试值
0  0  0  0  0  0  0  1    <- 结果:ZF=0

0  0  1  0  0  1  0  0    <- 输入值
0  0  0  0  1  0  0  1    <- 测试值
0  0  0  0  0  0  0  0    <- 结果:ZF=1

test指令总是清除溢出和进位标志位,其修改符号标志位、零标志位和奇偶标志位的方法与 AND 指令相同。

4.5 sete指令

根据eflags中的状态标志(CF,SF,OF,ZF和PF)将目标操作数设置为0或1。这里的目标操作数指向一个字节寄存器(也就是8位寄存器,如AL,BL,CL)或内存中的一个字节。状态码后缀(cc)指明了将要测试的条件。

获取标志位的指令的格式如下:

setcc R/M

指令根据标志寄存器eflags的值,将操作数设置为0或1。

setcc中的cc和Jcc中的cc类似,可参考表。

4.6 call指令

指令的格式如下:

call I/R/M

call指令会调用由操作数指定的函数。call指令会将指令的下一条指令的地址压栈,再跳转到操作数指定的地址,这样函数就能通过跳转到栈上的地址从子函数返回了。相当于

push %eip
jmp addr

先压入指令的下一个地址,然后跳转到目标地址addr。

4.7 ret指令

指令的格式如下:

ret

ret指令用于从子函数中返回。X86架构的Linux中是将函数的返回值设置到eax寄存器并返回的。相当于如下指令:

popl %eip

将call指令压栈的“call指令下一条指令的地址”弹出栈,并设置到指令指针中。这样程序就能正确地返回子函数的地方。

从物理上来说,CALL 指令将其返回地址压入堆栈,再把被调用过程的地址复制到指令指针寄存器。当过程准备返回时,它的 RET 指令从堆栈把返回地址弹回到指令指针寄存器。

4.8 enter指令

enter指令通过初始化ebp和esp寄存器来为函数建立函数参数和局部变量所需要的栈帧。相当于

push   %rbp
mov    %rsp,%rbp

4.9 leave指令

leave通过恢复ebp与esp寄存器来移除使用enter指令建立的栈帧。相当于

mov %rbp, %rsp
pop %rbp

将栈指针指向帧指针,然后pop备份的原帧指针到%ebp

5.0 int指令

指令的格式如下:

int I

引起给定数字的中断。这通常用于系统调用以及其他内核界面。

5、标志操作

eflags寄存器的各个标志位如下图所示。

操作eflags寄存器标志的一些指令如下表所示。

指令 操作数 描述
pushfd R PUSHFD 指令把 32 位 EFLAGS 寄存器内容压入堆栈
popfd R POPFD 指令则把栈顶单元内容弹出到 EFLAGS 寄存器
cld 将eflags.df设置为0

第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个逻辑。

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

标签: jl14系列连接器ax400系列变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台

字节码 助词符 指令含义
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 将double型0推送至栈顶
0x0f dconst_1 将double型1推送至栈顶
0x10 bipush 将单字节的常量值(-128~127)推送至栈顶
0x11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶
0x12 ldc 将int、float或String型常量值从常量池中推送至栈顶
0x13 ldc_w 将int,、float或String型常量值从常量池中推送至栈顶(宽索引)
0x14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引)
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 将栈顶引用型数值存入第二个本地变量