本文由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)表示特定的含義,如下圖所示鸟召。
在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類的繼承體系如下圖所示欧募。
另外還有個(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種:
- 通用指令
- x87 FPU指令倡蝙,浮點(diǎn)數(shù)運(yùn)算的指令
- SIMD指令,就是SSE指令
- 系統(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)單介紹一下兩者的不同遮斥。
下面就來(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所示务荆。
其中與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)志位如下圖所示。
操作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í)行的邏輯如下:
假設(shè)現(xiàn)在有個(gè)方法的字節(jié)碼指令流為連接3個(gè)iload指令所森,這3個(gè)iload指令前后都為非iload指令囱持。那么重寫(xiě)的過(guò)程如下:
匯編代碼在第一次執(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é)碼指令(下)》將在下篇中釋出