【JVM源碼解析】模板解釋器解釋執(zhí)行Java字節(jié)碼指令(上)

本文由HeapDump性能社區(qū)首席講師鳩摩(馬智)授權(quán)整理發(fā)布

第17章-x86-64寄存器

不同的CPU都能夠解釋的機(jī)器語(yǔ)言的體系稱為指令集架構(gòu)(ISA,Instruction Set Architecture)脖律,也可以稱為指令集(instruction set)冕杠。Intel將x86系列CPU之中的32位CPU指令集架構(gòu)稱為IA-32,IA是“Intel Architecture”的簡(jiǎn)稱酸茴,也可以稱為i386分预、x86-32。AMD等于Intell提出了x86系列的64位擴(kuò)展喳瓣,所以由AMD設(shè)計(jì)的x86系列的64位指令集架構(gòu)稱為AMD64翁巍。后來(lái)Intel在自己的CPU中加入和AMD64幾乎相同的指令集绞灼,稱為Intel 64的指令集痊剖。AMD64和Intel 64可以統(tǒng)稱為x86-64。

x86-64的所有寄存器都是與機(jī)器字長(zhǎng)(數(shù)據(jù)總線位寬)相同,即64位的,x86-64將x86的8個(gè)32位通用寄存器擴(kuò)展為64位(eax惯退、ebx队丝、ecx斩熊、edx愉阎、eci室埋、edi佛寿、ebp、esp)但壮,并且增加了8個(gè)新的64位寄存器(r8-r15)冀泻,在命名方式上,也從”exx”變?yōu)椤眗xx”蜡饵,但仍保留”exx”進(jìn)行32位操作弹渔,下表描述了各寄存器的命名和作用。

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

其中的%esp與%ebp有特殊用途溯祸,用來(lái)保存指向程序棧中特定位置的指針肢专。

另外還有eflags寄存器巾乳,通過(guò)位來(lái)表示特定的含義,如下圖所示鸟召。

image

在HotSpot VM中胆绊,表示寄存器的類都繼承自AbstractRegisterImpl類,這個(gè)類的定義如下:

源代碼位置:hotspot/src/share/vm/asm/register.hpp

class AbstractRegisterImpl;
typedef AbstractRegisterImpl* AbstractRegister;

class AbstractRegisterImpl {
 protected:
  int value() const  { return (int)(intx)this; }
}; 

AbstractRegisterImpl類的繼承體系如下圖所示欧募。

image

另外還有個(gè)ConcreteRegisterImpl類也繼承了AbstractRegisterImpl压状,這個(gè)灰與C2編譯器的實(shí)現(xiàn)有關(guān),這里不做過(guò)多講解跟继。

1种冬、RegisterImpl類

RegisterImpl類用來(lái)表示通用寄存器,類的定義如下:

源代碼位置:cpu/x86/vm/register_x86.hpp

// 使用Register做為RegisterImpl的簡(jiǎn)稱
class RegisterImpl;
typedef RegisterImpl* Register;

class RegisterImpl: public AbstractRegisterImpl {
 public:
  enum {
    number_of_registers      = 16,
    number_of_byte_registers = 16
  };
  // ...
};

對(duì)于64位來(lái)說(shuō)舔糖,通用寄存器的位寬為64位娱两,也可以將eax、ebx金吗、ecx和edx的一部分當(dāng)作8位寄存器來(lái)使用十兢,所以可以存儲(chǔ)字節(jié)的寄存器數(shù)量為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##EnumValue = (value) }

經(jīng)過(guò)宏擴(kuò)展后如下:

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)) }

如上的枚舉類給寄存器指定了一個(gè)常量值旱物。

在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)

當(dāng)我們需要使用通用寄存器時(shí),通過(guò)rax卫袒、rcx等變量引用就可以了宵呛。

2、FloatRegisterImpl

在HotSpot VM中夕凝,使用FloatRegisterImpl來(lái)表示浮點(diǎn)寄存器宝穗,此類的定義如下:

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

// 使用FloatRegister做為簡(jiǎn)稱
class FloatRegisterImpl;
typedef FloatRegisterImpl* FloatRegister;

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

浮點(diǎn)寄存器有8個(gè),分別是st0~st7码秉,這是8個(gè)80位寄存器逮矛。

這里需要注意的是,還有一種寄存器MMX泡徙,MMX并非一種新的寄存器橱鹏,而是借用了80位浮點(diǎn)寄存器的低64位,也就是說(shuō)堪藐,使用MMX指令集莉兰,會(huì)影響浮點(diǎn)運(yùn)算!

3礁竞、MMXRegisterImpl

MMX 為一種 SIMD 技術(shù)糖荒,即可通過(guò)一條指令執(zhí)行多個(gè)數(shù)據(jù)運(yùn)算,共有8個(gè)64位寄存器(借用了80位浮點(diǎn)寄存器的低64位)模捂,分別為mm0 – mm7捶朵,他與其他普通64位寄存器的區(qū)別在于通過(guò)它的指令進(jìn)行運(yùn)算蜘矢,可以同時(shí)計(jì)算2個(gè)32位數(shù)據(jù),或者4個(gè)16位數(shù)據(jù)等等综看,可以應(yīng)用為圖像處理過(guò)程中圖形 顏色的計(jì)算品腹。

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));

宏擴(kuò)展后如下:

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個(gè)64位寄存器。但實(shí)際上MMX寄存器和浮點(diǎn)數(shù)寄存器是共用的红碑,即無(wú)法同時(shí)使用浮點(diǎn)數(shù)寄存器和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)

當(dāng)我們需要使用MMX寄存器時(shí),通過(guò)mmx0析珊、mmx1等變量引用就可以了羡鸥。

4、XMMRegisterImpl類

XMM寄存器是SSE指令用的寄存器忠寻。Pentium iii以及之后的CPU中提供了xmm0到xmm7共8個(gè)128位寬的XMM寄存器惧浴。另外還有個(gè)mxcsr寄存器,這個(gè)寄存器用來(lái)表示SSE指令的運(yùn)算狀態(tài)的寄存器奕剃。在HotSpot VM中衷旅,通過(guò)XMMRegisterImpl類來(lái)表示寄存器。這個(gè)類的定義如下:

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

// 使用XMMRegister寄存器做為簡(jiǎn)稱
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));

經(jīng)過(guò)宏擴(kuò)展后為:

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)

當(dāng)我們需要使用XMM寄存器時(shí)祭饭,直接通過(guò)xmm0芜茵、xmm1等變量引用就可以了叙量。

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

x86的指令集可分為以下4種:

  1. 通用指令
  2. x87 FPU指令倡蝙,浮點(diǎn)數(shù)運(yùn)算的指令
  3. SIMD指令,就是SSE指令
  4. 系統(tǒng)指令绞佩,寫(xiě)OS內(nèi)核時(shí)使用的特殊指令

下面介紹一些通用的指令寺鸥。指令由標(biāo)識(shí)命令種類的助記符(mnemonic)和作為參數(shù)的操作數(shù)(operand)組成。例如move指令:

指令 操作數(shù) 描述
movq I/R/M,R/M 從一個(gè)內(nèi)存位置復(fù)制1個(gè)雙字(64位品山,8字節(jié))大小的數(shù)據(jù)到另外一個(gè)內(nèi)存位置
movl I/R/M,R/M 從一個(gè)內(nèi)存位置復(fù)制1個(gè)字(32位胆建,4字節(jié))大小的數(shù)據(jù)到另外一個(gè)內(nèi)存位置
movw I/R/M, R/M 從一個(gè)內(nèi)存位置復(fù)制2個(gè)字節(jié)(16位)大小的數(shù)據(jù)到另外一個(gè)內(nèi)存位置
movb I/R/M, R/M 從一個(gè)內(nèi)存位置復(fù)制1個(gè)字節(jié)(8位)大小的數(shù)據(jù)到另外一個(gè)內(nèi)存位置

movl為助記符。助記符有后綴肘交,如movl中的后綴l表示作為操作數(shù)的對(duì)象的數(shù)據(jù)大小笆载。l為long的縮寫(xiě),表示32位的大小涯呻,除此之外凉驻,還有b、w复罐,q分別表示8位涝登、16位和64位的大小。

指令的操作數(shù)如果不止1個(gè)效诅,就將每個(gè)操作數(shù)以逗號(hào)分隔胀滚。每個(gè)操作數(shù)都會(huì)指明是否可以是立即模式值(I)趟济、寄存器(R)或內(nèi)存地址(M)。

另外還要提示一下咽笼,在x86的匯編語(yǔ)言中顷编,采用內(nèi)存位置的操作數(shù)最多只能出現(xiàn)一個(gè),例如不可能出現(xiàn)mov M,M指令剑刑。

通用寄存器中每個(gè)操作都可以有一個(gè)字符的后綴勾效,表明操作數(shù)的大小,如下表所示叛甫。

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

注意:通用寄存器使用后綴“l(fā)”同時(shí)表示4字節(jié)整數(shù)和8字節(jié)雙精度浮點(diǎn)數(shù)层宫,這不會(huì)產(chǎn)生歧義,因?yàn)楦↑c(diǎn)數(shù)使用的是完全不同的指令和寄存器其监。

我們后面只介紹call萌腿、push等指令時(shí),如果在研究HotSpot VM虛擬機(jī)的匯編遇到了callq抖苦,pushq等指令時(shí)毁菱,千萬(wàn)別不認(rèn)識(shí),后綴就是表示了操作數(shù)的大小锌历。

下表為操作數(shù)的格式和尋址模式贮庞。

格式 操作數(shù)值 名稱 樣例(通用寄存器 = C語(yǔ)言)
$Imm Imm 立即數(shù)尋址 $1 = 1
Ea R[Ea] 寄存器尋址 %eax = eax
Imm M[Imm] 絕對(duì)尋址 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]表示在存儲(chǔ)器中xx地址的值,R[xx]表示寄存器xx的值究西,這種表示方法將寄存器窗慎、內(nèi)存都看出一個(gè)大數(shù)組的形式。

匯編根據(jù)編譯器的不同卤材,有2種書(shū)寫(xiě)格式:

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

下面簡(jiǎn)單介紹一下兩者的不同遮斥。

image

下面就來(lái)認(rèn)識(shí)一下常用的指令。

下面我們以給出的是AT&T匯編的寫(xiě)法扇丛,這兩種寫(xiě)法有如下不同术吗。

1、數(shù)據(jù)傳送指令

將數(shù)據(jù)從一個(gè)地方傳送到另外一個(gè)地方帆精。

1.1 mov指令

我們?cè)诮榻Bmov指令時(shí)介紹的全一些较屿,因?yàn)閙ov指令是出現(xiàn)頻率最高的指令,助記符中的后綴也比較多卓练。

mov指令的形式有3種隘蝎,如下:

mov   #普通的move指令
movs  #符號(hào)擴(kuò)展的move指令,將源操作數(shù)進(jìn)行符號(hào)擴(kuò)展并傳送到一個(gè)64位寄存器或存儲(chǔ)單元中昆庇。movs就表示符號(hào)擴(kuò)展 
movz  #零擴(kuò)展的move指令末贾,將源操作數(shù)進(jìn)行零擴(kuò)展后傳送到一個(gè)64位寄存器或存儲(chǔ)單元中。movz就表示零擴(kuò)展

mov指令后有一個(gè)字母可表示操作數(shù)大小整吆,形式如下:

movb #完成1個(gè)字節(jié)的復(fù)制
movw #完成2個(gè)字節(jié)的復(fù)制
movl #完成4個(gè)字節(jié)的復(fù)制
movq #完成8個(gè)字節(jié)的復(fù)制

還有一個(gè)指令拱撵,如下:

movabsq  I,R

與movq有所不同辉川,它是將一個(gè)64位的值直接存到一個(gè)64位寄存器中。

movs指令的形式如下:

movsbw #作符號(hào)擴(kuò)展的1字節(jié)復(fù)制到2字節(jié)
movsbl #作符號(hào)擴(kuò)展的1字節(jié)復(fù)制到4字節(jié)
movsbq #作符號(hào)擴(kuò)展的1字節(jié)復(fù)制到8字節(jié)
movswl #作符號(hào)擴(kuò)展的2字節(jié)復(fù)制到4字節(jié)
movswq #作符號(hào)擴(kuò)展的2字節(jié)復(fù)制到8字節(jié)
movslq #作符號(hào)擴(kuò)展的4字節(jié)復(fù)制到8字節(jié)

movz指令的形式如下:

movzbw #作0擴(kuò)展的1字節(jié)復(fù)制到2字節(jié)
movzbl #作0擴(kuò)展的1字節(jié)復(fù)制到4字節(jié)
movzbq #作0擴(kuò)展的1字節(jié)復(fù)制到8字節(jié)
movzwl #作0擴(kuò)展的2字節(jié)復(fù)制到4字節(jié)
movzwq #作0擴(kuò)展的2字節(jié)復(fù)制到8字節(jié)
movzlq #作0擴(kuò)展的4字節(jié)復(fù)制到8字節(jié)

舉個(gè)例子如下:

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

第一條指令將寄存器ecx中的值復(fù)制到eax寄存器拴测;第二條指令將ecx寄存器中的數(shù)據(jù)作為地址訪問(wèn)內(nèi)存乓旗,并將內(nèi)存上的數(shù)據(jù)加載到eax寄存器中。

1.2 cmov指令

cmov指令的格式如下:

cmovxx

其中xx代表一個(gè)或者多個(gè)字母集索,這些字母表示將觸發(fā)傳送操作的條件屿愚。條件取決于 EFLAGS 寄存器的當(dāng)前值。

eflags寄存器中各個(gè)們?nèi)缦聢D所示务荆。

image

其中與cmove指令相關(guān)的eflags寄存器中的位有CF(數(shù)學(xué)表達(dá)式產(chǎn)生了進(jìn)位或者借位) 妆距、OF(整數(shù)值無(wú)窮大或者過(guò)小)函匕、PF(寄存器包含數(shù)學(xué)操作造成的錯(cuò)誤數(shù)據(jù))娱据、SF(結(jié)果為正不是負(fù))和ZF(結(jié)果為零)。

下表為無(wú)符號(hào)條件傳送指令盅惜。

指令對(duì) 描述 eflags狀態(tài)
cmova/cmovnbe 大于/不小于或等于 (CF或ZF)=0
cmovae/cmovnb 大于或者等于/不小于 CF=0
cmovnc 無(wú)進(jìn)位 CF=0
cmovb/cmovnae 大于/不小于或等于 CF=1
cmovc 進(jìn)位 CF=1
cmovbe/cmovna 小于或者等于/不大于 (CF或ZF)=1
cmove/cmovz 等于/零 ZF=1
cmovne/cmovnz 不等于/不為零 ZF=0
cmovp/cmovpe 奇偶校驗(yàn)/偶校驗(yàn) PF=1
cmovnp/cmovpo 非奇偶校驗(yàn)/奇校驗(yàn) PF=0

無(wú)符號(hào)條件傳送指令依靠進(jìn)位中剩、零和奇偶校驗(yàn)標(biāo)志來(lái)確定兩個(gè)操作數(shù)之間的區(qū)別。

下表為有符號(hào)條件傳送指令抒寂。

指令對(duì) 描述 eflags狀態(tài)
cmovge/cmovnl 大于或者等于/不小于 (SF異或OF)=0
cmovl/cmovnge 大于/不大于或者等于 (SF異或OF)=1
cmovle/cmovng 小于或者等于/不大于 ((SF異或OF)或ZF)=1
cmovo 溢出 OF=1
cmovno 未溢出 OF=0
cmovs 帶符號(hào)(負(fù)) SF=1
cmovns 無(wú)符號(hào)(非負(fù)) SF=0

舉個(gè)例子如下:

// 將vlaue數(shù)值加載到ecx寄存器中
movl value,%ecx 
// 使用cmp指令比較ecx和ebx這兩個(gè)寄存器中的值结啼,具體就是用ecx減去ebx然后設(shè)置eflags
cmp %ebx,%ecx
// 如果ecx的值大于ebx,使用cmova指令設(shè)置ebx的值為ecx中的值
cmova %ecx,%ebx 

注意AT&T匯編的第1個(gè)操作數(shù)在前屈芜,第2個(gè)操作數(shù)在后郊愧。

1.3 push和pop指令

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

指令 操作數(shù) 描述
push I/R/M PUSH 指令首先減少 ESP 的值沸伏,再將源操作數(shù)復(fù)制到堆棧糕珊。操作數(shù)是 16 位的,則 ESP 減 2毅糟,操作數(shù)是 32 位的,則 ESP 減 4
pusha 指令按序(AX澜公、CX姆另、DX、BX坟乾、SP迹辐、BP、SI 和 DI)將 16 位通用寄存器壓入堆棧甚侣。
pushad 指令按照 EAX明吩、ECX、EDX殷费、EBX印荔、ESP(執(zhí)行 PUSHAD 之前的值)低葫、EBP、ESI 和 EDI 的順序仍律,將所有 32 位通用寄存器壓入堆棧嘿悬。

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

指令 操作數(shù) 描述
pop R/M 指令首先把 ESP 指向的堆棧元素內(nèi)容復(fù)制到一個(gè) 16 位或 32 位目的操作數(shù)中水泉,再增加 ESP 的值善涨。如果操作數(shù)是 16 位的,ESP 加 2草则,如果操作數(shù)是 32 位的钢拧,ESP 加 4
popa 指令按照相反順序?qū)⑼瑯拥募拇嫫鲝棾龆褩?/td>
popad 指令按照相反順序?qū)⑼瑯拥募拇嫫鲝棾龆褩?/td>

1.4 xchg與xchgl

這個(gè)指令用于交換操作數(shù)的值,交換指令XCHG是兩個(gè)寄存器炕横,寄存器和內(nèi)存變量之間內(nèi)容的交換指令娶靡,兩個(gè)操作數(shù)的數(shù)據(jù)類型要相同,可以是一個(gè)字節(jié)看锉,也可以是一個(gè)字姿锭,也可以是雙字。格式如下:

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

兩個(gè)操作數(shù)不能同時(shí)為內(nèi)存變量呻此。xchgl指令是一條古老的x86指令擂找,作用是交換兩個(gè)寄存器或者內(nèi)存地址里的4字節(jié)值屑迂,兩個(gè)值不能都是內(nèi)存地址疼阔,他不會(huì)設(shè)置條件碼撑蒜。

1.5 lea

lea計(jì)算源操作數(shù)的實(shí)際地址锦庸,并把結(jié)果保存到目標(biāo)操作數(shù)葡公,而目標(biāo)操作數(shù)必須為通用寄存器另患。格式如下:

lea M,R

lea(Load Effective Address)指令將地址加載到寄存器盏袄。

舉例如下:

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

第一條指令表示將ebx寄存器中存儲(chǔ)的值加4后得到的結(jié)果作為內(nèi)存地址進(jìn)行訪問(wèn)凭语,并將內(nèi)存地址中存儲(chǔ)的數(shù)據(jù)加載到eax寄存器中葱她。

第二條指令表示將ebx寄存器中存儲(chǔ)的值加4后得到的結(jié)果作為內(nèi)存地址存放到eax寄存器中。

再舉個(gè)例子似扔,如下:

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

計(jì)算地址a + b + c * d吨些,然后把最終地址載到寄存器rax中〕椿裕可以看到只是簡(jiǎn)單的計(jì)算豪墅,不引用源操作數(shù)里的寄存器。這樣的完全可以把它當(dāng)作乘法指令使用黔寇。

2偶器、算術(shù)運(yùn)算指令

下面介紹對(duì)有符號(hào)整數(shù)和無(wú)符號(hào)整數(shù)進(jìn)行操作的基本運(yùn)算指令。

2.1 add與adc指令

指令的格式如下:

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

指令將兩個(gè)操作數(shù)相加,結(jié)果保存在第2個(gè)操作數(shù)中屏轰。

對(duì)于第1條指令來(lái)說(shuō)颊郎,由于寄存器和存儲(chǔ)器都有位寬限制,因此在進(jìn)行加法運(yùn)算時(shí)就有可能發(fā)生溢出亭枷。運(yùn)算如果溢出的話袭艺,標(biāo)志寄存器eflags中的進(jìn)位標(biāo)志(Carry Flag,CF)就會(huì)被置為1叨粘。

對(duì)于第2條指令來(lái)說(shuō)猾编,利用adc指令再加上進(jìn)位標(biāo)志eflags.CF,就能在32位的機(jī)器上進(jìn)行64位數(shù)據(jù)的加法運(yùn)算升敲。

常規(guī)的算術(shù)邏輯運(yùn)算指令只要將原來(lái)IA-32中的指令擴(kuò)展到64位即可答倡。如addq就是四字相加。

2.2 sub與sbb指令

指令的格式如下:

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

指令將用第2個(gè)操作數(shù)減去第1個(gè)操作數(shù)驴党,結(jié)果保存在第2個(gè)操作數(shù)中瘪撇。

2.3 imul與mul指令

指令的格式如下:

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

將第1個(gè)操作數(shù)和第2個(gè)操作數(shù)相乘,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù)中港庄,如果第2個(gè)操作數(shù)空缺倔既,默認(rèn)為eax寄存器,最終完整的結(jié)果將存儲(chǔ)到edx:eax中鹏氧。

第1條指令執(zhí)行有符號(hào)乘法渤涌,第2條指令執(zhí)行無(wú)符號(hào)乘法。

2.4 idiv與div指令

指令的格式如下:

div   R/M
idiv  R/M

第1條指令執(zhí)行無(wú)符號(hào)除法把还,第2條指令執(zhí)行有符號(hào)除法实蓬。被除數(shù)由edx寄存器和eax寄存器拼接而成,除數(shù)由指令的第1個(gè)操作數(shù)指定吊履,計(jì)算得到的商存入eax寄存器安皱,余數(shù)存入edx寄存器。如下圖所示艇炎。

    edx:eax
------------ = eax(商)... edx(余數(shù))
    寄存器

運(yùn)算時(shí)被除數(shù)酌伊、商和除數(shù)的數(shù)據(jù)的位寬是不一樣的,如下表表示了idiv指令和div指令使用的寄存器的情況冕臭。

數(shù)據(jù)的位寬 被除數(shù) 除數(shù) 余數(shù)
8位 ax 指令第1個(gè)操作數(shù) al ah
16位 dx:ax 指令第1個(gè)操作數(shù) ax dx
32位 edx:eax 指令第1個(gè)操作數(shù) eax edx

idiv指令和div指令通常是對(duì)位寬2倍于除數(shù)的被除數(shù)進(jìn)行除法運(yùn)算的腺晾。例如對(duì)于x86-32機(jī)器來(lái)說(shuō),通用寄存器的倍數(shù)為32位辜贵,1個(gè)寄存器無(wú)法容納64位的數(shù)據(jù),所以 edx存放被除數(shù)的高32位归形,而eax寄存器存放被除數(shù)的低32位托慨。

所以在進(jìn)行除法運(yùn)算時(shí),必須將設(shè)置在eax寄存器中的32位數(shù)據(jù)擴(kuò)展到包含edx寄存器在內(nèi)的64位暇榴,即有符號(hào)進(jìn)行符號(hào)擴(kuò)展厚棵,無(wú)符號(hào)數(shù)進(jìn)行零擴(kuò)展蕉世。

對(duì)edx進(jìn)行符號(hào)擴(kuò)展時(shí)可以使用cltd(AT&T風(fēng)格寫(xiě)法)或cdq(Intel風(fēng)格寫(xiě)法)。指令的格式如下:

cltd  // 將eax寄存器中的數(shù)據(jù)符號(hào)擴(kuò)展到edx:eax

cltd將eax寄存器中的數(shù)據(jù)符號(hào)擴(kuò)展到edx:eax婆硬。

2.5 incl與decl指令

指令的格式如下:

inc  R/M
dec  R/M 

將指令第1個(gè)操作數(shù)指定的寄存器或內(nèi)存位置存儲(chǔ)的數(shù)據(jù)加1或減1狠轻。

2.6 negl指令

指令的格式如下:

neg R/M

neg指令將第1個(gè)操作數(shù)的符號(hào)進(jìn)行反轉(zhuǎn)。

3彬犯、位運(yùn)算指令

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個(gè)操作數(shù)與第1個(gè)操作數(shù)進(jìn)行按位與運(yùn)算,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù)谐区;

or指令將第2個(gè)操作數(shù)與第1個(gè)操作數(shù)進(jìn)行按位或運(yùn)算湖蜕,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù);

xor指令將第2個(gè)操作數(shù)與第1個(gè)操作數(shù)進(jìn)行按位異或運(yùn)算宋列,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù)昭抒;

3.2 not指令

指令的格式如下:

not R/M

將操作數(shù)按位取反,并將結(jié)果寫(xiě)入操作數(shù)中炼杖。

3.3 sal灭返、sar、shr指令

指令的格式如下:

sal  I/%cl,R/M  #算術(shù)左移
sar  I/%cl,R/M  #算術(shù)右移
shl  I/%cl,R/M  #邏輯左移
shr  I/%cl,R/M  #邏輯右移

sal指令將第2個(gè)操作數(shù)按照第1個(gè)操作數(shù)指定的位數(shù)進(jìn)行左移操作坤邪,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù)中熙含。移位之后空出的低位補(bǔ)0。指令的第1個(gè)操作數(shù)只能是8位的立即數(shù)或cl寄存器罩扇,并且都是只有低5位的數(shù)據(jù)才有意義婆芦,高于或等于6位數(shù)將導(dǎo)致寄存器中的所有數(shù)據(jù)被移走而變得沒(méi)有意義。

sar指令將第2個(gè)操作數(shù)按照第1個(gè)操作數(shù)指定的位數(shù)進(jìn)行右移操作喂饥,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù)中消约。移位之后的空出進(jìn)行符號(hào)擴(kuò)展。和sal指令一樣员帮,sar指令的第1個(gè)操作數(shù)也必須為8位的立即數(shù)或cl寄存器或粮,并且都是只有低5位的數(shù)據(jù)才有意義。

shl指令和sall指令的動(dòng)作完全相同捞高,沒(méi)有必要區(qū)分氯材。

shr令將第2個(gè)操作數(shù)按照第1個(gè)操作數(shù)指定的位數(shù)進(jìn)行右移操作,并將結(jié)果寫(xiě)入第2個(gè)操作數(shù)中硝岗。移位之后的空出進(jìn)行零擴(kuò)展氢哮。和sal指令一樣,shr指令的第1個(gè)操作數(shù)也必須為8位的立即數(shù)或cl寄存器型檀,并且都是只有低5位的數(shù)據(jù)才有意義冗尤。

4、流程控制指令

4.1 jmp指令

指令的格式如下:

jmp I/R

jmp指令將程序無(wú)條件跳轉(zhuǎn)到操作數(shù)指定的目的地址。jmp指令可以視作設(shè)置指令指針(eip寄存器)的指令裂七。目的地址也可以是星號(hào)后跟寄存器的棧皆看,這種方式為間接函數(shù)調(diào)用。例如:

jmp *%eax

將程序跳轉(zhuǎn)至eax所含地址背零。

4.2 條件跳轉(zhuǎn)指令

條件跳轉(zhuǎn)指令的格式如下:

Jcc  目的地址

其中cc指跳轉(zhuǎn)條件腰吟,如果為真,則程序跳轉(zhuǎn)到目的地址徙瓶;否則執(zhí)行下一條指令毛雇。相關(guān)的條件跳轉(zhuǎn)指令如下表所示。

指令 跳轉(zhuǎn)條件 描述 指令 跳轉(zhuǎn)條件 描述
jz ZF=1 為0時(shí)跳轉(zhuǎn) jbe CF=1或ZF=1 大于或等于時(shí)跳轉(zhuǎn)
jnz ZF=0 不為0時(shí)跳轉(zhuǎn) jnbe CF=0且ZF=0 小于或等于時(shí)跳轉(zhuǎn)
je ZF=1 相等時(shí)跳轉(zhuǎn) jg ZF=0且SF=OF 大于時(shí)跳轉(zhuǎn)
jne ZF=0 不相等時(shí)跳轉(zhuǎn) jng ZF=1或SF!=OF 不大于時(shí)跳轉(zhuǎn)
ja CF=0且ZF=0 大于時(shí)跳轉(zhuǎn) jge SF=OF 大于或等于時(shí)跳轉(zhuǎn)
jna CF=1或ZF=1 不大于時(shí)跳轉(zhuǎn) jnge SF!=OF 小于或等于時(shí)跳轉(zhuǎn)
jae CF=0 大于或等于時(shí)跳轉(zhuǎn) jl SF!=OF 小于時(shí)跳轉(zhuǎn)
jnae CF=1 小于或等于時(shí)跳轉(zhuǎn) jnl SF=OF 不小于時(shí)跳轉(zhuǎn)
jb CF=1 大于時(shí)跳轉(zhuǎn) jle ZF=1或SF!=OF 小于或等于時(shí)跳轉(zhuǎn)
jnb CF=0 不大于時(shí)跳轉(zhuǎn) jnle ZF=0且SF=OF 大于或等于時(shí)跳轉(zhuǎn)

4.3 cmp指令

cmp指令的格式如下:

cmp I/R/M,R/M

cmp指令通過(guò)比較第2個(gè)操作數(shù)減去第1個(gè)操作數(shù)的差倍啥,根據(jù)結(jié)果設(shè)置標(biāo)志寄存器eflags中的標(biāo)志位禾乘。cmp指令和sub指令類似,不過(guò)cmp指令不會(huì)改變操作數(shù)的值虽缕。

操作數(shù)和所設(shè)置的標(biāo)志位之間的關(guān)系如表所示始藕。

操作數(shù)的關(guān)系 CF ZF OF
第1個(gè)操作數(shù)小于第2個(gè)操作數(shù) 0 0 SF
第1個(gè)操作數(shù)等于第2個(gè)操作數(shù) 0 1 0
第1個(gè)操作數(shù)大于第2個(gè)操作數(shù) 1 0 not SF

4.4 test指令

指令的格式如下:

test I/R/M,R/M

指令通過(guò)比較第1個(gè)操作數(shù)與第2個(gè)操作數(shù)的邏輯與,根據(jù)結(jié)果設(shè)置標(biāo)志寄存器eflags中的標(biāo)志位氮趋。test指令本質(zhì)上和and指令相同伍派,只是test指令不會(huì)改變操作數(shù)的值。

test指令執(zhí)行后CF與OF通常會(huì)被清零剩胁,并根據(jù)運(yùn)算結(jié)果設(shè)置ZF和SF诉植。運(yùn)算結(jié)果為零時(shí)ZF被置為1,SF和最高位的值相同昵观。

舉個(gè)例子如下:

test指令同時(shí)能夠檢查幾個(gè)位晾腔。假設(shè)想要知道 AL 寄存器的位 0 和位 3 是否置 1,可以使用如下指令:

test al,00001001b    #掩碼為0000 1001啊犬,測(cè)試第0和位3位是否為1

從下面的數(shù)據(jù)集例子中灼擂,可以推斷只有當(dāng)所有測(cè)試位都清 0 時(shí),零標(biāo)志位才置 1:

0  0  1  0  0  1  0  1    <- 輸入值
0  0  0  0  1  0  0  1    <- 測(cè)試值
0  0  0  0  0  0  0  1    <- 結(jié)果:ZF=0

0  0  1  0  0  1  0  0    <- 輸入值
0  0  0  0  1  0  0  1    <- 測(cè)試值
0  0  0  0  0  0  0  0    <- 結(jié)果:ZF=1

test指令總是清除溢出和進(jìn)位標(biāo)志位觉至,其修改符號(hào)標(biāo)志位剔应、零標(biāo)志位和奇偶標(biāo)志位的方法與 AND 指令相同。

4.5 sete指令

根據(jù)eflags中的狀態(tài)標(biāo)志(CF,SF,OF,ZF和PF)將目標(biāo)操作數(shù)設(shè)置為0或1语御。這里的目標(biāo)操作數(shù)指向一個(gè)字節(jié)寄存器(也就是8位寄存器峻贮,如AL,BL应闯,CL)或內(nèi)存中的一個(gè)字節(jié)纤控。狀態(tài)碼后綴(cc)指明了將要測(cè)試的條件。

獲取標(biāo)志位的指令的格式如下:

setcc R/M

指令根據(jù)標(biāo)志寄存器eflags的值碉纺,將操作數(shù)設(shè)置為0或1嚼黔。

setcc中的cc和Jcc中的cc類似细层,可參考表惜辑。

4.6 call指令

指令的格式如下:

call I/R/M

call指令會(huì)調(diào)用由操作數(shù)指定的函數(shù)唬涧。call指令會(huì)將指令的下一條指令的地址壓棧,再跳轉(zhuǎn)到操作數(shù)指定的地址盛撑,這樣函數(shù)就能通過(guò)跳轉(zhuǎn)到棧上的地址從子函數(shù)返回了碎节。相當(dāng)于

push %eip
jmp addr

先壓入指令的下一個(gè)地址,然后跳轉(zhuǎn)到目標(biāo)地址addr抵卫。

4.7 ret指令

指令的格式如下:

ret

ret指令用于從子函數(shù)中返回狮荔。X86架構(gòu)的Linux中是將函數(shù)的返回值設(shè)置到eax寄存器并返回的。相當(dāng)于如下指令:

popl %eip

將call指令壓棧的“call指令下一條指令的地址”彈出棧介粘,并設(shè)置到指令指針中殖氏。這樣程序就能正確地返回子函數(shù)的地方。

從物理上來(lái)說(shuō)姻采,CALL 指令將其返回地址壓入堆棧雅采,再把被調(diào)用過(guò)程的地址復(fù)制到指令指針寄存器。當(dāng)過(guò)程準(zhǔn)備返回時(shí)慨亲,它的 RET 指令從堆棧把返回地址彈回到指令指針寄存器婚瓜。

4.8 enter指令

enter指令通過(guò)初始化ebp和esp寄存器來(lái)為函數(shù)建立函數(shù)參數(shù)和局部變量所需要的棧幀。相當(dāng)于

push   %rbp
mov    %rsp,%rbp

4.9 leave指令

leave通過(guò)恢復(fù)ebp與esp寄存器來(lái)移除使用enter指令建立的棧幀刑棵。相當(dāng)于

mov %rbp, %rsp
pop %rbp

將棧指針指向幀指針巴刻,然后pop備份的原幀指針到%ebp

5.0 int指令

指令的格式如下:

int I

引起給定數(shù)字的中斷。這通常用于系統(tǒng)調(diào)用以及其他內(nèi)核界面蛉签。

5胡陪、標(biāo)志操作

eflags寄存器的各個(gè)標(biāo)志位如下圖所示。

image

操作eflags寄存器標(biāo)志的一些指令如下表所示碍舍。

指令 操作數(shù) 描述
pushfd R PUSHFD 指令把 32 位 EFLAGS 寄存器內(nèi)容壓入堆棧
popfd R POPFD 指令則把棧頂單元內(nèi)容彈出到 EFLAGS 寄存器
cld 將eflags.df設(shè)置為0

第19篇-加載與存儲(chǔ)指令(1)

TemplateInterpreterGenerator::generate_all()函數(shù)會(huì)生成許多例程(也就是機(jī)器指令片段柠座,英文叫Stub),包括調(diào)用set_entry_points_for_all_bytes()函數(shù)生成各個(gè)字節(jié)碼對(duì)應(yīng)的例程乒验。

最終會(huì)調(diào)用到TemplateInterpreterGenerator::generate_and_dispatch()函數(shù)愚隧,調(diào)用堆棧如下:

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

調(diào)用堆棧上的許多函數(shù)在之前介紹過(guò),每個(gè)字節(jié)碼都會(huì)指定一個(gè)generator函數(shù)锻全,通過(guò)Template的_gen屬性保存狂塘。在TemplateTable::generate()函數(shù)中調(diào)用。_gen會(huì)生成每個(gè)字節(jié)碼對(duì)應(yīng)的機(jī)器指令片段鳄厌,所以非常重要荞胡。

首先看一個(gè)非常簡(jiǎn)單的nop字節(jié)碼指令。這個(gè)指令的模板屬性如下:

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

nop字節(jié)碼指令的生成函數(shù)generator不會(huì)生成任何機(jī)器指令了嚎,所以nop字節(jié)碼指令對(duì)應(yīng)的匯編代碼中只有棧頂緩存的邏輯泪漂。調(diào)用set_vtos_entry_points()函數(shù)生成的匯編代碼如下:

// 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

// 接下來(lái)為取指邏輯廊营,開(kāi)始的地址為0x00007fffe1027c30

可以看到,由于tos_in為vtos萝勤,所以如果是aep露筒、bep、cep敌卓、sep與iep時(shí)慎式,直接使用push指令將%rax中存儲(chǔ)的棧頂緩存值壓入表達(dá)式棧中。對(duì)于fep趟径、dep與lep來(lái)說(shuō)瘪吏,在棧上開(kāi)辟對(duì)應(yīng)內(nèi)存的大小,然后將寄存器中的值存儲(chǔ)到表達(dá)式的棧頂上蜗巧,與push指令的效果相同掌眠。

在set_vtos_entry_points()函數(shù)中會(huì)調(diào)用generate_and_dispatch()函數(shù)生成nop指令的機(jī)器指令片段及取下一條字節(jié)碼指令的機(jī)器指令片段。nop不會(huì)生成任何機(jī)器指令幕屹,而取指的片段如下:

// movzbl 將做了零擴(kuò)展的字節(jié)傳送到雙字蓝丙,地址為0x00007fffe1027c30
0x00007fffe1027c30: movzbl  0x1(%r13),%ebx       

0x00007fffe1027c35: inc %r13 

0x00007fffe1027c38: movabs $0x7ffff73ba4a0,%r10 

// movabs的源操作數(shù)只能是立即數(shù)或標(biāo)號(hào)(本質(zhì)還是立即數(shù)),目的操作數(shù)是寄存器 
0x00007fffe1027c42: jmpq *(%r10,%rbx,8)

r13指向當(dāng)前要取的字節(jié)碼指令的地址香嗓。那么%r13+1就是跳過(guò)了當(dāng)前的nop指令而指向了下一個(gè)字節(jié)碼指令的地址迅腔,然后執(zhí)行movzbl指令將所指向的Opcode加載到%ebx中。

通過(guò)jmpq的跳轉(zhuǎn)地址為%r10+%rbx*8靠娱,關(guān)于這個(gè)跳轉(zhuǎn)地址在前面詳細(xì)介紹過(guò)沧烈,這里不再介紹。

我們講解了nop指令像云,把棧頂緩存的邏輯和取指邏輯又回顧了一遍锌雀,對(duì)于每個(gè)字節(jié)碼指令來(lái)說(shuō)都會(huì)有有棧頂緩存和取指邏輯,后面在介紹字節(jié)碼指令時(shí)就不會(huì)再介紹這2個(gè)邏輯迅诬。

加載與存儲(chǔ)相關(guān)操作的字節(jié)碼指令如下表所示腋逆。

字節(jié)碼 助詞符 指令含義
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 將單字節(jié)的常量值(-128~127)推送至棧頂
0x11 sipush 將一個(gè)短整型常量值(-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 將第一個(gè)int型本地變量推送至棧頂
0x1b iload_1 將第二個(gè)int型本地變量推送至棧頂
0x1c iload_2 將第三個(gè)int型本地變量推送至棧頂
0x1d iload_3 將第四個(gè)int型本地變量推送至棧頂
0x1e lload_0 將第一個(gè)long型本地變量推送至棧頂
0x1f lload_1 將第二個(gè)long型本地變量推送至棧頂
0x20 lload_2 將第三個(gè)long型本地變量推送至棧頂
0x21 lload_3 將第四個(gè)long型本地變量推送至棧頂
0x22 fload_0 將第一個(gè)float型本地變量推送至棧頂
0x23 fload_1 將第二個(gè)float型本地變量推送至棧頂
0x24 fload_2 將第三個(gè)float型本地變量推送至棧頂
0x25 fload_3 將第四個(gè)float型本地變量推送至棧頂
0x26 dload_0 將第一個(gè)double型本地變量推送至棧頂
0x27 dload_1 將第二個(gè)double型本地變量推送至棧頂
0x28 dload_2 將第三個(gè)double型本地變量推送至棧頂
0x29 dload_3 將第四個(gè)double型本地變量推送至棧頂
0x2a aload_0 將第一個(gè)引用類型本地變量推送至棧頂
0x2b aload_1 將第二個(gè)引用類型本地變量推送至棧頂
0x2c aload_2 將第三個(gè)引用類型本地變量推送至棧頂
0x2d aload_3 將第四個(gè)引用類型本地變量推送至棧頂
0x2e iaload 將int型數(shù)組指定索引的值推送至棧頂
0x2f laload 將long型數(shù)組指定索引的值推送至棧頂
0x30 faload 將float型數(shù)組指定索引的值推送至棧頂
0x31 daload 將double型數(shù)組指定索引的值推送至棧頂
0x32 aaload 將引用型數(shù)組指定索引的值推送至棧頂
0x33 baload 將boolean或byte型數(shù)組指定索引的值推送至棧頂
0x34 caload 將char型數(shù)組指定索引的值推送至棧頂
0x35 saload 將short型數(shù)組指定索引的值推送至棧頂
0x36 istore 將棧頂int型數(shù)值存入指定本地變量
0x37 lstore 將棧頂long型數(shù)值存入指定本地變量
0x38 fstore 將棧頂float型數(shù)值存入指定本地變量
0x39 dstore 將棧頂double型數(shù)值存入指定本地變量
0x3a astore 將棧頂引用型數(shù)值存入指定本地變量
0x3b istore_0 將棧頂int型數(shù)值存入第一個(gè)本地變量
0x3c istore_1 將棧頂int型數(shù)值存入第二個(gè)本地變量
0x3d istore_2 將棧頂int型數(shù)值存入第三個(gè)本地變量
0x3e istore_3 將棧頂int型數(shù)值存入第四個(gè)本地變量
0x3f lstore_0 將棧頂long型數(shù)值存入第一個(gè)本地變量
0x40 lstore_1 將棧頂long型數(shù)值存入第二個(gè)本地變量
0x41 lstore_2 將棧頂long型數(shù)值存入第三個(gè)本地變量
0x42 lstore_3 將棧頂long型數(shù)值存入第四個(gè)本地變量
0x43 fstore_0 將棧頂float型數(shù)值存入第一個(gè)本地變量
0x44 fstore_1 將棧頂float型數(shù)值存入第二個(gè)本地變量
0x45 fstore_2 將棧頂float型數(shù)值存入第三個(gè)本地變量
0x46 fstore_3 將棧頂float型數(shù)值存入第四個(gè)本地變量
0x47 dstore_0 將棧頂double型數(shù)值存入第一個(gè)本地變量
0x48 dstore_1 將棧頂double型數(shù)值存入第二個(gè)本地變量
0x49 dstore_2 將棧頂double型數(shù)值存入第三個(gè)本地變量
0x4a dstore_3 將棧頂double型數(shù)值存入第四個(gè)本地變量
0x4b astore_0 將棧頂引用型數(shù)值存入第一個(gè)本地變量
0x4c astore_1 將棧頂引用型數(shù)值存入第二個(gè)本地變量
0x4d astore_2 將棧頂引用型數(shù)值存入第三個(gè)本地變量
0x4e astore_3 將棧頂引用型數(shù)值存入第四個(gè)本地變量
0x4f iastore 將棧頂int型數(shù)值存入指定數(shù)組的指定索引位置
0x50 lastore 將棧頂long型數(shù)值存入指定數(shù)組的指定索引位置
0x51 fastore 將棧頂float型數(shù)值存入指定數(shù)組的指定索引位置
0x52 dastore 將棧頂double型數(shù)值存入指定數(shù)組的指定索引位置
0x53 aastore 將棧頂引用型數(shù)值存入指定數(shù)組的指定索引位置
0x54 bastore 將棧頂boolean或byte型數(shù)值存入指定數(shù)組的指定索引位置
0x55 castore 將棧頂char型數(shù)值存入指定數(shù)組的指定索引位置
0x56 sastore 將棧頂short型數(shù)值存入指定數(shù)組的指定索引位置
0xc4 wide 擴(kuò)充局部變量表的訪問(wèn)索引的指令

我們不會(huì)對(duì)每個(gè)字節(jié)碼指令都查看對(duì)應(yīng)的機(jī)器指令片段的邏輯(其實(shí)是反編譯機(jī)器指令片段為匯編后惩歉,通過(guò)查看匯編理解執(zhí)行邏輯),有些指令的邏輯是類似的俏蛮,這里只選擇幾個(gè)典型的介紹撑蚌。

1、壓棧類型的指令

(1)aconst_null指令

aconst_null表示將null送到棧頂搏屑,模板定義如下:

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

指令的匯編代碼如下:

// xor 指令在兩個(gè)操作數(shù)的對(duì)應(yīng)位之間進(jìn)行邏輯異或操作争涌,并將結(jié)果存放在目標(biāo)操作數(shù)中
// 第1個(gè)操作數(shù)和第2個(gè)操作數(shù)相同時(shí),執(zhí)行異或操作就相當(dāng)于執(zhí)行清零操作
xor    %eax,%eax 

由于tos_out為atos辣恋,所以棧頂?shù)慕Y(jié)果是緩存在%eax寄存器中的亮垫,只對(duì)%eax寄存器執(zhí)行xor操作即可模软。

(2)iconst_m1指令

iconst_m1表示將-1壓入棧內(nèi),模板定義如下:

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

生成的機(jī)器指令經(jīng)過(guò)反匯編后饮潦,得到的匯編代碼如下:

mov    $0xffffffff,%eax 

其它的與iconst_m1字節(jié)碼指令類似的字節(jié)碼指令燃异,如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           );

可以看到特铝,生成函數(shù)都是同一個(gè)TemplateTable::iconst()函數(shù)。

iconst_0的匯編代碼如下:

xor    %eax,%eax

iconst_@(@為1壹瘟、2、3鳄逾、4稻轨、5)的字節(jié)碼指令對(duì)應(yīng)的匯編代碼如下:

// 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

如果看過(guò)我之前寫(xiě)的文章枚抵,那么如上的匯編代碼應(yīng)該能看懂线欲,我在這里就不再做過(guò)多介紹了。

(3)bipush

bipush 將單字節(jié)的常量值推送至棧頂汽摹。模板定義如下:

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

指令的匯編代碼如下:

// %r13指向字節(jié)碼指令的地址李丰,偏移1位
// 后取出1個(gè)字節(jié)的內(nèi)容存儲(chǔ)到%eax中
movsbl 0x1(%r13),%eax 

由于tos_out為itos,所以將單字節(jié)的常量值存儲(chǔ)到%eax中逼泣,這個(gè)寄存器是專門用來(lái)進(jìn)行棧頂緩存的趴泌。

(4)sipush

sipush將一個(gè)短整型常量值推送到棧頂,模板定義如下:

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

生成的匯編代碼如下:

// movzwl傳送做了符號(hào)擴(kuò)展字到雙字
movzwl 0x1(%r13),%eax 
// bswap 以字節(jié)為單位拉庶,把32/64位寄存器的值按照低和高的字節(jié)交換
bswap  %eax     
// (算術(shù)右移)指令將目的操作數(shù)進(jìn)行算術(shù)右移      
sar    $0x10,%eax    

Java中的短整型占用2個(gè)字節(jié)嗜憔,所以需要對(duì)32位寄存器%eax進(jìn)行一些操作。由于字節(jié)碼采用大端存儲(chǔ)氏仗,所以在處理時(shí)統(tǒng)一變換為小端存儲(chǔ)吉捶。

2、存儲(chǔ)類型指令

istore指令會(huì)將int類型數(shù)值存入指定索引的本地變量表皆尔,模板定義如下:

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

生成函數(shù)為TemplateTable::istore()呐舔,生成的匯編代碼如下:

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

由于棧頂緩存tos_in為itos,所以直接將%eax中的值存儲(chǔ)到指定索引的本地變量表中床佳。

模板中指定ubcp滋早,因?yàn)樯傻膮R編代碼中會(huì)使用%r13,也就是字節(jié)碼指令指針砌们。

其它的istore杆麸、dstore等字節(jié)碼指令的匯編代碼邏輯也類似搁进,這里不過(guò)多介紹。

第20篇-加載與存儲(chǔ)指令之ldc與_fast_aldc指令(2)

ldc指令將int昔头、float饼问、或者一個(gè)類、方法類型或方法句柄的符號(hào)引用揭斧、還可能是String型常量值從常量池中推送至棧頂莱革。

這一篇介紹一個(gè)虛擬機(jī)規(guī)范中定義的一個(gè)字節(jié)碼指令ldc,另外還有一個(gè)虛擬機(jī)內(nèi)部使用的字節(jié)碼指令_fast_aldc讹开。ldc指令可以加載String盅视、方法類型或方法句柄的符號(hào)引用,但是如果要加載String旦万、方法類型或方法句柄的符號(hào)引用闹击,則會(huì)在類連接過(guò)程中重寫(xiě)ldc字節(jié)碼指令為虛擬機(jī)內(nèi)部使用的字節(jié)碼指令_fast_aldc。下面我們?cè)敿?xì)介紹ldc指令如何加載int成艘、float類型和類類型的數(shù)據(jù)赏半,以及_fast_aldc加載String、方法類型或方法句柄淆两,還有為什么要進(jìn)行字節(jié)碼重寫(xiě)等問(wèn)題断箫。

1、ldc字節(jié)碼指令

ldc指令將int秋冰、float或String型常量值從常量池中推送至棧頂仲义。模板的定義如下:

def(Bytecodes::_ldc , ubcp|____|clvm|____, vtos, vtos, ldc ,  false );

ldc字節(jié)碼指令的格式如下:

// index是一個(gè)無(wú)符號(hào)的byte類型數(shù)據(jù),指明當(dāng)前類的運(yùn)行時(shí)常量池的索引
ldc index 

調(diào)用生成函數(shù)TemplateTable::ldc(bool wide)丹莲。函數(shù)生成的匯編代碼如下:

第1部分代碼:

// movzbl指令負(fù)責(zé)拷貝一個(gè)字節(jié)光坝,并用0填充其目
// 的操作數(shù)中的其余各位,這種擴(kuò)展方式叫"零擴(kuò)展"
// ldc指定的格式為ldc index甥材,index為一個(gè)字節(jié)
0x00007fffe1028530: movzbl 0x1(%r13),%ebx // 加載index到%ebx

// %rcx指向緩存池首地址盯另、%rax指向類型數(shù)組_tags首地址
0x00007fffe1028535: mov    -0x18(%rbp),%rcx
0x00007fffe1028539: mov    0x10(%rcx),%rcx
0x00007fffe102853d: mov    0x8(%rcx),%rcx
0x00007fffe1028541: mov    0x10(%rcx),%rax


// 從_tags數(shù)組獲取操作數(shù)類型并存儲(chǔ)到%edx中
0x00007fffe1028545: movzbl 0x4(%rax,%rbx,1),%edx

// $0x64代表JVM_CONSTANT_UnresolvedClass,比較洲赵,如果類還沒(méi)有鏈接鸳惯,
// 則直接跳轉(zhuǎn)到call_ldc
0x00007fffe102854a: cmp    $0x64,%edx
0x00007fffe102854d: je     0x00007fffe102855d   // call_ldc

// $0x67代表JVM_CONSTANT_UnresolvedClassInError,也就是如果類在
// 鏈接過(guò)程中出現(xiàn)錯(cuò)誤叠萍,則跳轉(zhuǎn)到call_ldc
0x00007fffe102854f: cmp    $0x67,%edx
0x00007fffe1028552: je     0x00007fffe102855d  // call_ldc

// $0x7代表JVM_CONSTANT_Class芝发,表示如果類已經(jīng)進(jìn)行了連接,則
// 跳轉(zhuǎn)到notClass
0x00007fffe1028554: cmp    $0x7,%edx
0x00007fffe1028557: jne    0x00007fffe10287c0  // notClass

// 類在沒(méi)有連接或連接過(guò)程中出錯(cuò)苛谷,則執(zhí)行如下的匯編代碼
// -- call_ldc --

下面看一下調(diào)用call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::ldc), c_rarg1)函數(shù)生成的匯編代碼辅鲸,CAST_FROM_FN_PTR是宏,宏擴(kuò)展后為( (address)((address_word)(InterpreterRuntime::ldc)) )。

在調(diào)用call_VM()函數(shù)時(shí)漏麦,傳遞的參數(shù)如下:

  • %rax現(xiàn)在存儲(chǔ)類型數(shù)組首地址,不過(guò)傳入是為了接收調(diào)用函數(shù)的結(jié)果值
  • adr是InterpreterRuntime::ldc()函數(shù)首地址
  • c_rarg1用rdi寄存器存儲(chǔ)wide值攀隔,這里為0刻炒,表示為沒(méi)有加wide前綴的ldc指令生成匯編代碼

生成的匯編代碼如下:

第2部分:

// 將wide的值移到%esi寄存器决采,為后續(xù)
// 調(diào)用InterpreterRuntime::ldc()函數(shù)準(zhǔn)備第2個(gè)參數(shù)
0x00007fffe102855d: mov $0x0,%esi 
// 調(diào)用MacroAssembler::call_VM()函數(shù),通過(guò)此函數(shù)來(lái)調(diào)用HotSpot VM中用
// C++編寫(xiě)的函數(shù)坟奥,通過(guò)這個(gè)C++編寫(xiě)的函數(shù)來(lái)調(diào)用InterpreterRuntime::ldc()函數(shù)

0x00007fffe1017542: callq  0x00007fffe101754c 
0x00007fffe1017547: jmpq   0x00007fffe10175df // 跳轉(zhuǎn)到E1

// 調(diào)用MacroAssembler::call_VM_helper()函數(shù)
// 將棧頂存儲(chǔ)的返回地址設(shè)置到%rax中树瞭,也就是將存儲(chǔ)地址0x00007fffe1017547
// 的棧的slot地址設(shè)置到%rax中
0x00007fffe101754c: lea 0x8(%rsp),%rax


// 調(diào)用InterpreterMacroAssembler::call_VM_base()函數(shù)
// 存儲(chǔ)bcp到棧中特定位置
0x00007fffe1017551: mov %r13,-0x38(%rbp)

// 調(diào)用MacroAssembler::call_VM_base()函數(shù)
// 將r15中的值移動(dòng)到rdi寄存器中,也就是為函數(shù)調(diào)用準(zhǔn)備第一個(gè)參數(shù)
0x00007fffe1017555: mov   %r15,%rdi
// 只有解釋器才必須要設(shè)置fp
// 將last_java_fp保存到JavaThread類的last_java_fp屬性中
0x00007fffe1017558: mov   %rbp,0x200(%r15)  
// 將last_java_sp保存到JavaThread類的last_java_sp屬性中 
0x00007fffe101755f: mov   %rax,0x1f0(%r15)   

// ... 省略調(diào)用MacroAssembler::call_VM_leaf_base()函數(shù)

// 重置JavaThread::last_java_sp與JavaThread::last_java_fp屬性的值
0x00007fffe1017589: movabs $0x0,%r10
0x00007fffe1017593: mov %r10,0x1f0(%r15)
0x00007fffe101759a: movabs $0x0,%r10
0x00007fffe10175a4: mov %r10,0x200(%r15)

// check for pending exceptions (java_thread is set upon return)
0x00007fffe10175ab: cmpq  $0x0,0x8(%r15)
// 如果沒(méi)有異常則直接跳轉(zhuǎn)到ok
0x00007fffe10175b3: je    0x00007fffe10175be
// 如果有異常則跳轉(zhuǎn)到StubRoutines::forward_exception_entry()獲取的例程入口
0x00007fffe10175b9: jmpq  0x00007fffe1000420

// -- ok --
// 將JavaThread::vm_result屬性中的值存儲(chǔ)到%rax寄存器中并清空vm_result屬性的值
0x00007fffe10175be: mov     0x250(%r15),%rax
0x00007fffe10175c5: movabs  $0x0,%r10
0x00007fffe10175cf: mov     %r10,0x250(%r15)

// 結(jié)束調(diào)用MacroAssembler::call_VM_base()函數(shù)


// 恢復(fù)bcp與locals
0x00007fffe10175d6: mov   -0x38(%rbp),%r13
0x00007fffe10175da: mov   -0x30(%rbp),%r14


// 結(jié)束調(diào)用MacroAssembler::call_VM_helper()函數(shù)

0x00007fffe10175de: retq  
// 結(jié)束調(diào)用MacroAssembler::call_VM()函數(shù)

下面詳細(xì)解釋如下匯編的意思爱谁。

call指令相當(dāng)于如下兩條指令:

push %eip
jmp  addr

而ret指令相當(dāng)于:

 pop %eip

所以如上匯編代碼:

0x00007fffe1017542: callq  0x00007fffe101754c 
0x00007fffe1017547: jmpq   0x00007fffe10175df // 跳轉(zhuǎn)
...
0x00007fffe10175de: retq 

調(diào)用callq指令將jmpq的地址壓入了表達(dá)式棧晒喷,也就是壓入了返回地址x00007fffe1017547,這樣當(dāng)后續(xù)調(diào)用retq時(shí)管行,會(huì)跳轉(zhuǎn)到j(luò)mpq指令執(zhí)行厨埋,而jmpq又跳轉(zhuǎn)到了0x00007fffe10175df地址處的指令執(zhí)行。

通過(guò)調(diào)用MacroAssembler::call_VM()函數(shù)來(lái)調(diào)用HotSpot VM中用的C++編寫(xiě)的函數(shù)捐顷,call_VM()函數(shù)還會(huì)調(diào)用如下函數(shù):

MacroAssembler::call_VM_helper
   InterpreterMacroAssembler::call_VM_base()
       MacroAssembler::call_VM_base()
            MacroAssembler::call_VM_leaf_base()

在如上幾個(gè)函數(shù)中,最重要的就是在MacroAssembler::call_VM_base()函數(shù)中保存rsp雨效、rbp的值到JavaThread::last_java_sp與JavaThread::last_java_fp屬性中迅涮,然后通過(guò)MacroAssembler::call_VM_leaf_base()函數(shù)生成的匯編代碼來(lái)調(diào)用C++編寫(xiě)的InterpreterRuntime::ldc()函數(shù),如果調(diào)用InterpreterRuntime::ldc()函數(shù)有可能破壞rsp和rbp的值(其它的%r13徽龟、%r14等的寄存器中的值也有可能破壞叮姑,所以在必要時(shí)保存到棧中,在調(diào)用完成后再恢復(fù)据悔,這樣這些寄存器其實(shí)就算的上是調(diào)用者保存的寄存器了)传透,所以為了保證rsp、rbp极颓,將這兩個(gè)值存儲(chǔ)到線程中朱盐,在線程中保存的這2個(gè)值對(duì)于棧展開(kāi)非常非常重要,后面我們會(huì)詳細(xì)介紹菠隆。

由于如上匯編代碼會(huì)解釋執(zhí)行兵琳,在解釋執(zhí)行過(guò)程中會(huì)調(diào)用C++函數(shù),所以C/C++棧和Java棧都混在一起骇径,這為我們查找?guī)?lái)了一定的復(fù)雜度躯肌。

調(diào)用的MacroAssembler::call_VM_leaf_base()函數(shù)生成的匯編代碼如下:

第3部分匯編代碼:

// 調(diào)用MacroAssembler::call_VM_leaf_base()函數(shù)
0x00007fffe1017566: test  $0xf,%esp          // 檢查對(duì)齊
// %esp對(duì)齊的操作,跳轉(zhuǎn)到 L
0x00007fffe101756c: je    0x00007fffe1017584 
// %esp沒(méi)有對(duì)齊時(shí)的操作
0x00007fffe1017572: sub   $0x8,%rsp
0x00007fffe1017576: callq 0x00007ffff66a22a2  // 調(diào)用函數(shù)破衔,也就是調(diào)用InterpreterRuntime::ldc()函數(shù)
0x00007fffe101757b: add   $0x8,%rsp
0x00007fffe101757f: jmpq  0x00007fffe1017589  // 跳轉(zhuǎn)到E2
// -- L --
// %esp對(duì)齊的操作
0x00007fffe1017584: callq 0x00007ffff66a22a2  // 調(diào)用函數(shù)清女,也就是調(diào)用InterpreterRuntime::ldc()函數(shù)

// -- E2 --

// 結(jié)束調(diào)用
MacroAssembler::call_VM_leaf_base()函數(shù)

在如上這段匯編中會(huì)真正調(diào)用C++函數(shù)InterpreterRuntime::ldc(),由于這是一個(gè)C++函數(shù)晰筛,所以在調(diào)用時(shí)嫡丙,如果要傳遞參數(shù)拴袭,則要遵守C++調(diào)用約定,也就是前6個(gè)參數(shù)都放到固定的寄存器中迄沫。這個(gè)函數(shù)需要2個(gè)參數(shù)稻扬,分別為thread和wide,已經(jīng)分別放到了%rdi和%rax寄存器中了羊瘩。InterpreterRuntime::ldc()函數(shù)的實(shí)現(xiàn)如下:

// ldc負(fù)責(zé)將數(shù)值常量或String常量值從常量池中推送到棧頂
IRT_ENTRY(void, InterpreterRuntime::ldc(JavaThread* thread, bool wide))
  ConstantPool* pool = method(thread)->constants();
  int index = wide ? get_index_u2(thread, Bytecodes::_ldc_w) : get_index_u1(thread, Bytecodes::_ldc);
  constantTag tag = pool->tag_at(index);

  Klass* klass = pool->klass_at(index, CHECK);
  oop java_class = klass->java_mirror(); // java.lang.Class通過(guò)oop來(lái)表示
  thread->set_vm_result(java_class);
IRT_END

函數(shù)將查找到的泰佳、當(dāng)前正在解釋執(zhí)行的方法所屬的類存儲(chǔ)到JavaThread類的vm_result屬性中。我們可以回看第2部分匯編代碼尘吗,會(huì)將vm_result屬性的值設(shè)置到%rax中逝她。

接下來(lái)繼續(xù)看TemplateTable::ldc(bool wide)函數(shù)生成的匯編代碼,此時(shí)已經(jīng)通過(guò)調(diào)用call_VM()函數(shù)生成了調(diào)用InterpreterRuntime::ldc()這個(gè)C++的匯編睬捶,調(diào)用完成后值已經(jīng)放到了%rax中黔宛。

// -- E1 --  
0x00007fffe10287ba: push   %rax  // 將調(diào)用的結(jié)果存儲(chǔ)到表達(dá)式中
0x00007fffe10287bb: jmpq   0x00007fffe102885e // 跳轉(zhuǎn)到Done

// -- notClass --
// $0x4表示JVM_CONSTANT_Float
0x00007fffe10287c0: cmp    $0x4,%edx
0x00007fffe10287c3: jne    0x00007fffe10287d9 // 跳到notFloat
// 當(dāng)ldc字節(jié)碼指令加載的數(shù)為float時(shí)執(zhí)行如下匯編代碼
0x00007fffe10287c5: vmovss 0x58(%rcx,%rbx,8),%xmm0
0x00007fffe10287cb: sub    $0x8,%rsp
0x00007fffe10287cf: vmovss %xmm0,(%rsp)
0x00007fffe10287d4: jmpq   0x00007fffe102885e // 跳轉(zhuǎn)到Done
 
// -- notFloat --
// 當(dāng)ldc字節(jié)碼指令加載的為非float,也就是int類型數(shù)據(jù)時(shí)通過(guò)push加入表達(dá)式棧
0x00007fffe1028859: mov    0x58(%rcx,%rbx,8),%eax
0x00007fffe102885d: push   %rax

// -- Done --

由于ldc指令除了加載String外擒贸,還可能加載int和float臀晃,如果是int,直接調(diào)用push壓入表達(dá)式棧中介劫,如果是float徽惋,則在表達(dá)式棧上開(kāi)辟空間,然后移到到這個(gè)開(kāi)辟的slot中存儲(chǔ)座韵。注意险绘,float會(huì)使用%xmm0寄存器。

2誉碴、fast_aldc虛擬機(jī)內(nèi)部字節(jié)碼指令

下面介紹_fast_aldc指令宦棺,這個(gè)指令是虛擬機(jī)內(nèi)部使用的指令而非虛擬機(jī)規(guī)范定義的指令。_fast_aldc指令的模板定義如下:

def(Bytecodes::_fast_aldc , ubcp|____|clvm|____, vtos, atos, fast_aldc ,  false );

生成函數(shù)為TemplateTable::fast_aldc(bool wide)黔帕,這個(gè)函數(shù)生成的匯編代碼如下:

// 調(diào)用InterpreterMacroAssembler::get_cache_index_at_bcp()函數(shù)生成
// 獲取字節(jié)碼指令的操作數(shù)代咸,這個(gè)操作數(shù)已經(jīng)指向了常量池緩存項(xiàng)的索引,在字節(jié)碼重寫(xiě)
// 階段已經(jīng)進(jìn)行了字節(jié)碼重寫(xiě)
0x00007fffe10243d0: movzbl 0x1(%r13),%edx

// 調(diào)用InterpreterMacroAssembler::load_resolved_reference_at_index()函數(shù)生成

// shl表示邏輯左移蹬屹,相當(dāng)于乘4,因?yàn)镃onstantPoolCacheEntry的大小為4個(gè)字
0x00007fffe10243d5: shl    $0x2,%edx

// 獲取Method*
0x00007fffe10243d8: mov    -0x18(%rbp),%rax
// 獲取ConstMethod*
0x00007fffe10243dc: mov    0x10(%rax),%rax
// 獲取ConstantPool*
0x00007fffe10243e0: mov    0x8(%rax),%rax
// 獲取ConstantPool::_resolved_references屬性的值侣背,這個(gè)值
// 是一個(gè)指向?qū)ο髷?shù)組的指針
0x00007fffe10243e4: mov    0x30(%rax),%rax

// JNIHandles::resolve(obj)
0x00007fffe10243e8: mov    (%rax),%rax

// 從_resolved_references數(shù)組指定的下標(biāo)索引處獲取oop,先進(jìn)行索引偏移
0x00007fffe10243eb: add    %rdx,%rax

// 要在%rax上加0x10慨默,是因?yàn)閿?shù)組對(duì)象的頭大小為2個(gè)字贩耐,加上后
// %rax就指向了oop
0x00007fffe10243ee: mov    0x10(%rax),%eax

獲取_resolved_references屬性的值,涉及到的2個(gè)屬性在ConstantPool類中的定義如下:

// Array of resolved objects from the constant pool and map from resolved
// object index to original constant pool index
jobject              _resolved_references; // jobject是指針類型
Array<u2>*           _reference_map;

關(guān)于_resolved_references指向的其實(shí)是Object數(shù)組厦取。在ConstantPool::initialize_resolved_references()函數(shù)中初始化這個(gè)屬性潮太。調(diào)用鏈如下:

ConstantPool::initialize_resolved_references()  constantPool.cpp    
Rewriter::make_constant_pool_cache()  rewriter.cpp  
Rewriter::Rewriter()                  rewriter.cpp
Rewriter::rewrite()                   rewriter.cpp
InstanceKlass::rewrite_class()        instanceKlass.cpp 
InstanceKlass::link_class_impl()      instanceKlass.cpp

后續(xù)如果需要連接ldc等指令時(shí),可能會(huì)調(diào)用如下函數(shù):(我們只討論ldc加載String類型數(shù)據(jù)的問(wèn)題,所以我們只看往_resolved_references屬性中放入表示String的oop的邏輯铡买,MethodType與MethodHandle將不再介紹更鲁,有興趣的可自行研究)

oop ConstantPool::string_at_impl(
 constantPoolHandle this_oop, 
 int    which, 
 int    obj_index, 
 TRAPS
) {
  oop str = this_oop->resolved_references()->obj_at(obj_index);
  if (str != NULL)
      return str;

  Symbol* sym = this_oop->unresolved_string_at(which);
  str = StringTable::intern(sym, CHECK_(NULL));

  this_oop->string_at_put(which, obj_index, str);

  return str;
}

void string_at_put(int which, int obj_index, oop str) {
  // 獲取類型為jobject的_resolved_references屬性的值
  objArrayOop tmp = resolved_references();
  tmp->obj_at_put(obj_index, str);
}

在如上函數(shù)中向_resolved_references數(shù)組中設(shè)置緩存的值。

大概的思路就是:如果ldc加載的是字符串奇钞,那么盡量通過(guò)_resolved_references數(shù)組中一次性找到表示字符串的oop澡为,否則要通過(guò)原常量池下標(biāo)索引找到Symbol實(shí)例(Symbol實(shí)例是HotSpot VM內(nèi)部使用的、用來(lái)表示字符串)景埃,根據(jù)Symbol實(shí)例生成對(duì)應(yīng)的oop媒至,然后通過(guò)常量池緩存下標(biāo)索引設(shè)置到_resolved_references中。當(dāng)下次查找時(shí)谷徙,通過(guò)這個(gè)常量池緩存下標(biāo)緩存找到表示字符串的oop拒啰。

獲取到_resolved_references屬性的值后接著看生成的匯編代碼,如下:

// ...
// %eax中存儲(chǔ)著表示字符串的oop
0x00007fffe1024479: test   %eax,%eax
// 如果已經(jīng)獲取到了oop完慧,則跳轉(zhuǎn)到resolved
0x00007fffe102447b: jne    0x00007fffe1024481

// 沒(méi)有獲取到oop谋旦,需要進(jìn)行連接操作,0xe5是_fast_aldc的Opcode
0x00007fffe1024481: mov    $0xe5,%edx  

調(diào)用call_VM()函數(shù)生成的匯編代碼如下:

// 調(diào)用InterpreterRuntime::resolve_ldc()函數(shù)
0x00007fffe1024486: callq  0x00007fffe1024490
0x00007fffe102448b: jmpq   0x00007fffe1024526

// 將%rdx中的ConstantPoolCacheEntry項(xiàng)存儲(chǔ)到第1個(gè)參數(shù)中

// 調(diào)用MacroAssembler::call_VM_helper()函數(shù)生成
0x00007fffe1024490: mov    %rdx,%rsi
// 將返回地址加載到%rax中
0x00007fffe1024493: lea    0x8(%rsp),%rax

// 調(diào)用call_VM_base()函數(shù)生成
// 保存bcp
0x00007fffe1024498: mov    %r13,-0x38(%rbp)

// 調(diào)用MacroAssembler::call_VM_base()函數(shù)生成

// 將r15中的值移動(dòng)到c_rarg0(rdi)寄存器中屈尼,也就是為函數(shù)調(diào)用準(zhǔn)備第一個(gè)參數(shù)
0x00007fffe102449c: mov    %r15,%rdi
// Only interpreter should have to set fp 只有解釋器才必須要設(shè)置fp
0x00007fffe102449f: mov    %rbp,0x200(%r15)
0x00007fffe10244a6: mov    %rax,0x1f0(%r15)

// 調(diào)用MacroAssembler::call_VM_leaf_base()生成
0x00007fffe10244ad: test   $0xf,%esp
0x00007fffe10244b3: je     0x00007fffe10244cb
0x00007fffe10244b9: sub    $0x8,%rsp
0x00007fffe10244bd: callq  0x00007ffff66b27ac
0x00007fffe10244c2: add    $0x8,%rsp
0x00007fffe10244c6: jmpq   0x00007fffe10244d0
0x00007fffe10244cb: callq  0x00007ffff66b27ac
0x00007fffe10244d0: movabs $0x0,%r10
// 結(jié)束調(diào)用MacroAssembler::call_VM_leaf_base()

0x00007fffe10244da: mov    %r10,0x1f0(%r15)
0x00007fffe10244e1: movabs $0x0,%r10

// 檢查是否有異常發(fā)生
0x00007fffe10244eb: mov    %r10,0x200(%r15)
0x00007fffe10244f2: cmpq   $0x0,0x8(%r15)
// 如果沒(méi)有異常發(fā)生册着,則跳轉(zhuǎn)到ok
0x00007fffe10244fa: je     0x00007fffe1024505
// 有異常發(fā)生,則跳轉(zhuǎn)到StubRoutines::forward_exception_entry()
0x00007fffe1024500: jmpq   0x00007fffe1000420

// ---- ok ----

// 將JavaThread::vm_result屬性中的值存儲(chǔ)到oop_result寄存器中并清空vm_result屬性的值
0x00007fffe1024505: mov    0x250(%r15),%rax
0x00007fffe102450c: movabs $0x0,%r10
0x00007fffe1024516: mov    %r10,0x250(%r15)

// 結(jié)果調(diào)用MacroAssembler::call_VM_base()函數(shù)

// 恢復(fù)bcp和locals
0x00007fffe102451d: mov    -0x38(%rbp),%r13
0x00007fffe1024521: mov    -0x30(%rbp),%r14

// 結(jié)束調(diào)用InterpreterMacroAssembler::call_VM_base()函數(shù)
// 結(jié)束調(diào)用MacroAssembler::call_VM_helper()函數(shù)

0x00007fffe1024525: retq   

// 結(jié)束調(diào)用MacroAssembler::call_VM()函數(shù)脾歧,回到
// TemplateTable::fast_aldc()函數(shù)繼續(xù)看生成的代碼指蚜,只
// 定義了resolved點(diǎn)

// ---- resolved ----  

調(diào)用的InterpreterRuntime::resolve_ldc()函數(shù)的實(shí)現(xiàn)如下:

IRT_ENTRY(void, InterpreterRuntime::resolve_ldc(
 JavaThread* thread, 
 Bytecodes::Code bytecode)
) {
  ResourceMark rm(thread);
  methodHandle m (thread, method(thread));
  Bytecode_loadconstant  ldc(m, bci(thread));
  oop result = ldc.resolve_constant(CHECK);

  thread->set_vm_result(result);
}
IRT_END

這個(gè)函數(shù)會(huì)調(diào)用一系列的函數(shù),相關(guān)調(diào)用鏈如下:

ConstantPool::string_at_put()   constantPool.hpp
ConstantPool::string_at_impl()  constantPool.cpp
ConstantPool::resolve_constant_at_impl()     constantPool.cpp   
ConstantPool::resolve_cached_constant_at()   constantPool.hpp   
Bytecode_loadconstant::resolve_constant()    bytecode.cpp   
InterpreterRuntime::resolve_ldc()            interpreterRuntime.cpp   

其中ConstantPool::string_at_impl()函數(shù)在前面已經(jīng)詳細(xì)介紹過(guò)涨椒。

調(diào)用的resolve_constant()函數(shù)的實(shí)現(xiàn)如下:

oop Bytecode_loadconstant::resolve_constant(TRAPS) const {
  int index = raw_index();
  ConstantPool* constants = _method->constants();
  if (has_cache_index()) {
    return constants->resolve_cached_constant_at(index, THREAD);
  } else {
    return constants->resolve_constant_at(index, THREAD);
  }
}

調(diào)用的resolve_cached_constant_at()或resolve_constant_at()函數(shù)的實(shí)現(xiàn)如下:

oop resolve_cached_constant_at(int cache_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, _no_index_sentinel, cache_index, THREAD);
}

oop resolve_possibly_cached_constant_at(int pool_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, pool_index, _possible_index_sentinel, THREAD);
}

調(diào)用的resolve_constant_at_impl()函數(shù)的實(shí)現(xiàn)如下:

oop ConstantPool::resolve_constant_at_impl(
 constantPoolHandle this_oop,
 int index,
 int cache_index,
 TRAPS
) {
  oop result_oop = NULL;
  Handle throw_exception;

  if (cache_index == _possible_index_sentinel) {
    cache_index = this_oop->cp_to_object_index(index);
  }

  if (cache_index >= 0) {
    result_oop = this_oop->resolved_references()->obj_at(cache_index);
    if (result_oop != NULL) {
      return result_oop;
    }
    index = this_oop->object_to_cp_index(cache_index);
  }

  jvalue prim_value;  // temp used only in a few cases below

  int tag_value = this_oop->tag_at(index).value();

  switch (tag_value) {
  // ...
  case JVM_CONSTANT_String:
    assert(cache_index != _no_index_sentinel, "should have been set");
    if (this_oop->is_pseudo_string_at(index)) {
      result_oop = this_oop->pseudo_string_at(index, cache_index);
      break;
    }
    result_oop = string_at_impl(this_oop, index, cache_index, CHECK_NULL);
    break;
  // ...
  }

  if (cache_index >= 0) {
    Handle result_handle(THREAD, result_oop);
    MonitorLockerEx ml(this_oop->lock());  
    oop result = this_oop->resolved_references()->obj_at(cache_index);
    if (result == NULL) {
      this_oop->resolved_references()->obj_at_put(cache_index, result_handle());
      return result_handle();
    } else {
      return result;
    }
  } else {
    return result_oop;
  }
}

通過(guò)常量池的tags數(shù)組判斷,如果常量池下標(biāo)index處存儲(chǔ)的是JVM_CONSTANT_String常量池項(xiàng)绽媒,則調(diào)用string_at_impl()函數(shù)蚕冬,這個(gè)函數(shù)在之前已經(jīng)介紹過(guò),會(huì)根據(jù)表示字符串的Symbol實(shí)例創(chuàng)建出表示字符串的oop是辕。在ConstantPool::resolve_constant_at_impl()函數(shù)中得到oop后就存儲(chǔ)到ConstantPool::_resolved_references屬性中囤热,最后返回這個(gè)oop,這正是ldc需要的oop获三。

通過(guò)重寫(xiě)fast_aldc字節(jié)碼指令旁蔼,達(dá)到了通過(guò)少量指令就直接獲取到oop的目的,而且oop是緩存的疙教,所以字符串常量在HotSpot VM中的表示唯一棺聊,也就是只有一個(gè)oop表示。

C++函數(shù)約定返回的值會(huì)存儲(chǔ)到%rax中贞谓,根據(jù)_fast_aldc字節(jié)碼指令的模板定義可知限佩,tos_out為atos,所以后續(xù)并不需要進(jìn)一步操作。

HotSpot VM會(huì)在類的連接過(guò)程中重寫(xiě)某些字節(jié)碼祟同,如ldc字節(jié)碼重寫(xiě)為fast_aldc作喘,還有常量池的tags類型數(shù)組、常量池緩存等內(nèi)容在《深入剖析Java虛擬機(jī):源碼剖析與實(shí)例詳解》中詳細(xì)介紹過(guò)晕城,這里不再介紹泞坦。

第21篇-加載與存儲(chǔ)指令之ldc與_fast_aldc指令(3)

iload會(huì)將int類型的本地變量推送至棧頂。模板定義如下:

def(Bytecodes::_iload , ubcp|____|clvm|____, vtos, itos, iload , _ );

iload指令的格式如下:

iload index

index是一個(gè)無(wú)符號(hào)byte類型整數(shù)砖顷,指向局部變量表的索引值贰锁。

生成函數(shù)為TemplateTable::iload(),反編譯后的匯編代碼如下:

// 將%ebx指向下一條字節(jié)碼指令的首地址
0x00007fffe1028d30: movzbl 0x2(%r13),%ebx
// $0x15為_(kāi)iload指令的操作碼值
0x00007fffe1028d35: cmp $0x15,%ebx 
// 當(dāng)下一條指令為iload時(shí)择吊,直接跳轉(zhuǎn)到done
0x00007fffe1028d38: je 0x00007fffe1028deb // done

// 0xdf為_(kāi)fast_iload指令的操作碼值
0x00007fffe1028d3e: cmp $0xdf,%ebx
// 將_fast_iload2指令移動(dòng)到%ecx
0x00007fffe1028d44: mov $0xe0,%ecx
0x00007fffe1028d49: je 0x00007fffe1028d5a // rewrite

// 0x34為_(kāi)caload指令的操作碼
// _caload指令表示從數(shù)組中加載一個(gè)char類型數(shù)據(jù)到操作數(shù)棧
0x00007fffe1028d4b: cmp $0x34,%ebx
// 將_fast_icaload移動(dòng)到%ecx中
0x00007fffe1028d4e: mov $0xe1,%ecx
0x00007fffe1028d53: je 0x00007fffe1028d5a // rewrite

// 將_fast_iload移動(dòng)到%ecx中
0x00007fffe1028d55: mov $0xdf,%ecx

// -- rewrite --

// 調(diào)用patch_bytecode()函數(shù)
// 重寫(xiě)為fast版本李根,因?yàn)?cl中存儲(chǔ)的是字節(jié)碼的fast版本,%ecx的8位叫%cl 
0x00007fffe1028de7: mov %cl,0x0(%r13)

// -- done --

// 獲取字節(jié)碼指令的操作數(shù)几睛,這個(gè)操作數(shù)為本地變量表的索引
0x00007fffe1028deb: movzbl 0x1(%r13),%ebx
0x00007fffe1028df0: neg %rbx
// 通過(guò)本地變量表索引從本地變量表中加載值到%eax中房轿,
// %eax中存儲(chǔ)的就是棧頂緩存值,所以不需要壓入棧內(nèi)
0x00007fffe1028df3: mov (%r14,%rbx,8),%eax

執(zhí)行的邏輯如下:

image

假設(shè)現(xiàn)在有個(gè)方法的字節(jié)碼指令流為連接3個(gè)iload指令所森,這3個(gè)iload指令前后都為非iload指令囱持。那么重寫(xiě)的過(guò)程如下:

image

匯編代碼在第一次執(zhí)行時(shí),如果判斷最后一個(gè)_iload之后是非_iload指令焕济,則會(huì)重寫(xiě)最后一個(gè)_iload指令為_(kāi)fast_iload纷妆;第二次執(zhí)行時(shí),當(dāng)?shù)?個(gè)字節(jié)碼指令為_(kāi)iload晴弃,而之后接著判斷為_(kāi)fast_iload時(shí)掩幢,會(huì)更新第2個(gè)_iload為_(kāi)fast_iload2。

執(zhí)行_fast_iload和執(zhí)行_fast_iload2都可以提高程序執(zhí)行的效率上鞠,_fast_icaload指令也一樣际邻,下面詳細(xì)介紹一下這幾個(gè)指令。

1芍阎、_fast_iload指令

_fast_iload會(huì)將int類型的本地變量推送至棧頂世曾。模板定義如下:

def(Bytecodes::_fast_iload , ubcp|____|____|____, vtos, itos, fast_iload , _ );

生成函數(shù)為TemplateTable::fast_iload() ,匯編代碼如下:

0x00007fffe1023f90: movzbl 0x1(%r13),%ebx
0x00007fffe1023f95: neg %rbx
0x00007fffe1023f98: mov (%r14,%rbx,8),%eax

匯編代碼很簡(jiǎn)單谴咸,這里不再過(guò)多說(shuō)轮听。

執(zhí)行_fast_iload指令與執(zhí)行_iload指令相比,不用再進(jìn)行之前匯編介紹的那么多判斷岭佳,也沒(méi)有重寫(xiě)的邏輯血巍,所以會(huì)提高執(zhí)行效率。

2驼唱、_fast_iload2指令

_fast_iload2會(huì)將int類型的本地變量推送至棧頂藻茂。模板定義如下:

def(Bytecodes::_fast_iload2 , ubcp|____|____|____, vtos, itos, fast_iload2 , _ );

生成函數(shù)為TemplateTable::fast_iload2() ,匯編代碼如下:

0x00007fffe1024010: movzbl 0x1(%r13),%ebx
0x00007fffe1024015: neg %rbx
0x00007fffe1024018: mov (%r14,%rbx,8),%eax
0x00007fffe102401c: push %rax
0x00007fffe102401d: movzbl 0x3(%r13),%ebx
0x00007fffe1024022: neg %rbx
0x00007fffe1024025: mov (%r14,%rbx,8),%eax

可以看到,此指令就相當(dāng)于連續(xù)執(zhí)行了2次iload指令辨赐,省去了指令跳轉(zhuǎn)优俘,所以效率要高一些。

3掀序、_fast_icaload指令

caload指令表示從數(shù)組中加載一個(gè)char類型數(shù)據(jù)到操作數(shù)棧帆焕。

_fast_icaload會(huì)將char類型數(shù)組指定索引的值推送至棧頂。模板定義如下:

def(Bytecodes::_fast_icaload , ubcp|____|____|____, vtos, itos, fast_icaload , _ );

生成函數(shù)為TemplateTable::fast_icaload()不恭,生成的匯編代碼如下:

0x00007fffe1024090: movzbl 0x1(%r13),%ebx
0x00007fffe1024095: neg %rbx
// %eax中存儲(chǔ)著index
0x00007fffe1024098: mov (%r14,%rbx,8),%eax
// %rdx中存儲(chǔ)著arrayref
0x00007fffe102409c: pop %rdx 
// 將一個(gè)雙字?jǐn)U展后送到一個(gè)四字中,%rax中存儲(chǔ)著index 
0x00007fffe102409d: movslq %eax,%rax 
// %rdx指向數(shù)組對(duì)象的首地址叶雹,偏移0xc后獲取length屬性的值 
0x00007fffe10240a0: cmp 0xc(%rdx),%eax 
0x00007fffe10240a3: mov %eax,%ebx
// 如果數(shù)組索引index等于數(shù)組的長(zhǎng)度或大于數(shù)組長(zhǎng)度時(shí),那么跳轉(zhuǎn)
// 到_throw_ArrayIndexOutOfBoundsException_entry拋出異常
0x00007fffe10240a5: jae 0x00007fffe100ff20
// 在指定數(shù)組arrayref中加載指定索引處index的值
0x00007fffe10240ab: movzwl 0x10(%rdx,%rax,2),%eax

可以看到换吧,此指令省去了指令跳轉(zhuǎn)折晦,所以效率要高一些。

由于字?jǐn)?shù)限制沾瓦,《模板解釋器解釋執(zhí)行Java字節(jié)碼指令(下)》將在下篇中釋出

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末满着,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贯莺,更是在濱河造成了極大的恐慌风喇,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缕探,死亡現(xiàn)場(chǎng)離奇詭異魂莫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)爹耗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門耙考,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人潭兽,你說(shuō)我怎么就攤上這事琳骡。” “怎么了讼溺?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)最易。 經(jīng)常有香客問(wèn)我怒坯,道長(zhǎng),這世上最難降的妖魔是什么藻懒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任剔猿,我火速辦了婚禮,結(jié)果婚禮上嬉荆,老公的妹妹穿的比我還像新娘归敬。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布汪茧。 她就那樣靜靜地躺著椅亚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舱污。 梳的紋絲不亂的頭發(fā)上呀舔,一...
    開(kāi)封第一講書(shū)人閱讀 49,856評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音扩灯,去河邊找鬼媚赖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛珠插,可吹牛的內(nèi)容都是我干的惧磺。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捻撑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼磨隘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起布讹,我...
    開(kāi)封第一講書(shū)人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琳拭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后描验,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體白嘁,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年膘流,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了絮缅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呼股,死狀恐怖耕魄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情彭谁,我是刑警寧澤吸奴,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布,位于F島的核電站缠局,受9級(jí)特大地震影響则奥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狭园,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一读处、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唱矛,春花似錦罚舱、人聲如沸井辜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粥脚。三九已至,卻和暖如春渐北,著一層夾襖步出監(jiān)牢的瞬間阿逃,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工赃蛛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恃锉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓呕臂,卻偏偏與公主長(zhǎng)得像破托,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歧蒋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容