注:以下所有内容均来自开源学习组织DataWhale
**leaq S, D → \rightarrow → Load Effective Address **
注:x86-64位处理器地址长度都是64,因此都是leaq
例如如下指令:
leaq 7(%rdx, %rdx, 4), %rax
:表示把有效地址复制到寄存器rax中。
其中7(%rdx, %rdx, 4)
表示有效地址(计算方式见程序的机器级表示(一)中4.0.1):形式符合公式
I
m
m
(
r
b
,
r
i
,
s
)
→
I
m
m
+
R
[
r
b
]
+
R
[
r
i
]
×
s
Imm(r_b, r_i, s)\rightarrow Imm+R[r_b]+R[r_i]\times s
Imm(rb,ri,s)→Imm+R[rb]+R[ri]×s;假设rdx中保存的数值为
x
x
x,那么根据公式则可计算得到有效地址的值为:
7
+
%
r
d
x
+
%
r
d
x
×
4
=
7
+
5
x
7+\%rdx+\%rdx \times 4=7+5x
7+%rdx+%rdx×4=7+5x
注意:leaq指令不是去内存地址 7 + 5 x 7+5x 7+5x处读数据,而是将 7 + 5 x 7+5x 7+5x这个值直接写入目的寄存器rax
例如有C代码如下:
long scale(long x, long y, long z){ long t = x + 4 * y + 12 * z; return t; }
编译后汇编代码如下:
scale: leaq (%rdi, %rsi, 4), %rax leaq (%rdx, %rdx, 2), %rdx leaq (%rax, %rdx, 4), %rax ret
根据寄存器使用惯例,x、y、z分别保存在寄存器rdi、rsi、rdx中。
根据有效地址计算方式可分析上述汇编代码:
leaq (%rdi, %rsi, 4), %rax
→
\rightarrow
→
%
r
d
i
+
4
×
%
r
s
i
=
x
+
4
×
y
\%rdi+4\times\%rsi=x+4\times y
%rdi+4×%rsi=x+4×y(
x
+
4
×
y
x+4\times y
x+4×y存入了rax)
leaq (%rdx, %rdx, 2), %rdx
→
\rightarrow
→
%
r
d
x
+
2
×
%
r
d
x
=
z
+
2
×
z
\%rdx+2\times\%rdx=z+2\times z
%rdx+2×%rdx=z+2×z (
3
z
3z
3z存入了rdx,此时
z
=
3
z
z=3z
z=3z)
leaq (%rax, %rdx, 4), %rax
→
\rightarrow
→
%
r
a
x
+
4
×
%
r
d
x
\%rax+4\times\%rdx
%rax+4×%rdx(这一步x+4y和3z都被视作整体,得到的结果存到rax)
注:比例因子只能是1,2,4,8中的一个,所以要分解12
一元操作指令只有一个操作数,因此该操作数既是源操作数又是目的操作数,操作数可以是寄存器,也可以是内存地址。
指令 | 影响 | 描述 |
---|---|---|
INC D | D ← \leftarrow ← D + 1 | 加1 |
DEC D | D ← \leftarrow ← D - 1 | 减1 |
NEG D | D ← − \leftarrow - ←−D | 取负 |
NOT D | D ← ∼ \leftarrow \sim ←∼D | 取补 |
注:取补即按位取反,然后加1。
二元操作指令包含两个操作数,第一个操作数是源操作数,这个操作数可以是立即数、寄存器或内存地址;第二个操作数既是源操作数也是目的操作数,这个操作数可以是寄存器或者内存地址,但不能是立即数。
指令 | 影响 | 描述 |
---|---|---|
ADD S, D | D ← \leftarrow ← D + S | 加 |
SUB S, D | D ← \leftarrow ← D - S | 减 |
IMUL S, D | D ← \leftarrow ← D * S | 乘 |
XOR S, D | D ← \leftarrow ← D ^ S | 异或 |
OR S, D | D ← \leftarrow ← D | S | 或 |
AND S, D | D ← \leftarrow ← D & S | 与 |
一开始内存及寄存器中保存的数据如下图所示:
有如下指令:
addq %rcx, (%rax) subq %rdx, 8(%rax) incq 16(%rax) subq %rdx, %rax
addq %rcx, (%rax)
:将内存地址0x100内的数据与寄存器rcx相加,二者之和再存储到内存地址0x100处。这条指令执行完毕后,内存地址0x100处所存的数据由0xFF变为0x100
subq %rdx, 8(%rax)
:将内存地址0x108(0x108=0x100+8)内的数据减去寄存器rdx内的数据,得0xAB - 0x3 = 0xA8,存到内存地址0x108处。这时0x108处数据由0xAB变为0xA8。
incq 16(%rax)
:将内存地址0x110(0x100+10(这个10是16进制的,对应十进制的16))的值加1,即0x13+1=0x14。此时,0x110地址处值变为0x14。
subq %rdx, %rax
:将寄存器rax内的值减寄存器rdx内的值,即0x100 - 0x3 = 0xFD,存入寄存器寄存器rax内,此时,寄存器rax内值由0x100变为0xFD。
左移指令:SAL
和SHL
,二者效果相同,都是在右边填零
右移指令:
算术右移:SAR
,左边填符号位
逻辑右移:SHR
,左边填零
指令 | 影响 | 描述 |
---|---|---|
SAL k, D | D ← \leftarrow ← D << k | 左移 |
SHL k, D | D ← \leftarrow ← D << k | 左移,等同于SAL |
SAR k, D | D ← \leftarrow ← D >> A _A A k | 算术右移 |
SHR k, D | D ← \leftarrow ← D >> L _L L k | 逻辑右移 |
移位量k:可以是一个立即数,或者是放在寄存器cl中的数。(移位指令只允许以特定的寄存器cl作为操作数)
寄存器cl的长度为8,原则上移位量的编码范围可达 2 8 − 1 2^8-1 28−1 。实际上对于 w 位的操作数进行移位操作,移位量是由寄存器 cl 的低 m 位来决定。
对于指令
salb
,当目的操作数为8位,移位量由cl的低3位决定。
b → \rightarrow → byte,为8位,即 2 3 位 2^3位 23位,对应2进制3位
对于指令
salw
,移位量由cl的低4位决定w → \rightarrow → word,为16位,即 2 4 2^4 24,对应2进制4位
例如有C代码:
long arith(long x, long y, long z){ long t1 = x ^ y; long t2 = z * 48; long t3 = t1 & 0xF0F0F0F; long t4 = t2 - t3; return t4; 7 }
其中long t2 = z * 48;
的汇编指令对应:
leaq (%rdx, %rdx, 2), %rax salq $4, %rax
leaq (%rdx, %rdx, 2), %rax
:即rdx中的值×3放到rax中
salq $4, %rax
:rax左移四位,即rax中的值×16
通过这两条指令就实现了×48的操作。
指令 | 描述 |
---|---|
imulq S | 有负号全乘法 |
mulq S | 无符号全乘法 |
cqto | 转化为八字 |
idivq S | 有符号除法 |
divq S | 无符号除法 |
ALU 除了执行算术和逻辑运算指令外,还会根据该运算的结果去设置条件码寄存器。如图中所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-necvrnJP-1640185893504)(D:\DataWhale\ComputerSystem\Task 03:程序的机器级表示\条件码.png)]
条件码寄存器由CPU维护,长度是单个比特位。
CF(Carry Flag,进位标志):当CPU最近执行的一条指令最高位发生进位时,CF会被置为1。
ZF(Zero Flag,零标志):当操作结果等于零时,ZF会被置为1。
SF(Sign Flag,符号标志):当操作结果小于零时,SF会被置为1,大于零会被置为0。
OF(Overflow Flag,溢出标志):针对有符号数,最近的操作导致正溢出或负溢出时,OF会被置为1。
条件码寄存器的值ALU在执行算术和运算指令时写入,下表中这些算术和逻辑运算指令都会改变条件码寄存器的内容。
Unary Operations | Binary Operations | Shift Operations |
---|---|---|
INC D | ADD S, D | SAL k, D |
DEC D | SUB S, D | SHL k, D |
NEG D | IMUL S, D | SAR k, D |
OR S, D | SHR k, D | |
XOR S, D | ||
AND S, D |
例如:
xor指令:进位标志(CF)和溢出标志(OF)会置0
inc和dec:溢出标志(OF)和零标志(ZF)会被设置,但不会改变进位标志(CF)
此外还有两类指令可以设置条件码寄存器,但不会改变目的寄存器的值:
有C代码如下:
int comp(long a, long b){ return (a == b); }
对应汇编代码如下(a in %rdi, b in %rsi):
comp: cmpq %rsi, %rdi sete %al movzbl %al, %eax ret
cmpq %rsi, %rdi
:当a-b=0时,指令cmp会将零标志位(ZF)设置为1。
sete %al
:这条指令意思是将条件码寄存器内ZF的值赋给寄存器al。
最后al赋值给eax并返回。
有C代码如下(a in %dil; b in %sil):
int comp(char a, char b){ return (a < b); }
对应汇编指令如下:
comp: cmpq %sil, %dil setl %al movzbl %al, %eax ret
setl %al
:如果a<b,则寄存器al置为1。(setl的l是less的意思)
cmp
指令判断小于情况如下:
注:t=a-b:当a<b,t结果为负,符号位SF会被置为1;当出现正溢出或负溢出时,OF会被置为1。
Case1: a<b t<0 → \rightarrow → SF=1 OF=0 SF^OF=1(未发生溢出)
Case2: a>b t>0 → \rightarrow → SF=0 OF=0 SF^OF=0(未发生溢出)
Case3: a<b t>0 → \rightarrow → SF=0 OF=1 SF^OF=1(发生负溢出)
Case3: a>b t<0 → \rightarrow → SF=1 OF=1 SF^OF=1(发生正溢出)
因此,只有当SF^OF结果为1时,a<b才为真。
对于其他情况,同样可以通过条件码的组合来判断:
对于无符号数,选择的是进位标志(CF)和零标志(ZF)的组合来判断:
有如下C代码:
long absdiff_se(long x, long y){ long result; if(x < y){result = y - x;} else{result = x - y;} return result; }
对应汇编代码如下:
absdiff_se: cmpq %rsi, %rdi jl.L4 movq %rdi, %rax subq %rsi, %rax ret .L4: movq %rsi, %rax subq %rdi, %rax ret
jl.L4
会根据上一条cmpq指令设置的符号位标志SF和溢出标志OF的异或结果来判断是否顺序执行或者跳转到L4处执行,是一种跳转指令。当x>y时,指令顺序执行,当x<y时,跳转到L4处执行。
在现代处理器上,if-else机制比较简单通用,但执行效率可能会比较低。因此有另一种替代策略,C代码如下:
long comvdiff_se(long x, long y){ long rval = y - x; long eval = x - y; long ntest = x >= y; if(ntest){rval = eval;} return rval; }
其对应汇编指令如下:
cmovdiff_se: movq %rsi, %rdx subq %rdi, %rdx movq %rdi, %rax subq %rsi, %rax cmpq %rsi, %rdi cmovge %rdx, %rax ret
其中cmovge
是根据条件码的某种组合来进行有条件的传送数据,是一种条件传送指令。当x>y时,才会执行这一条指令。
while、do-while、for循环都是通过条件测试指令(如cmpq)和跳转指令组合来实现的。
switch语句是通过跳转表来实现的。
例如:C代码如下:
void switch_eg(long x, long n, long *dest){ long val = x; switch(n){ case 0: val *= 13; break; case 2: val += 10; break; case 3: val += 11; break; case 4: case 6: val += 11; break; default: val = 0; } *dest = val; }
其中switch部分对应跳转表:
case4和case6情况相同:
case1和case5情况相同:
switch相比使用一组很长的if-else效率更高。