本次笔记内容:
07.寻址模式与数据传输指令等-2
文章目录
变址寻址
承接上次笔记内容,对变址寻址进行讨论:
D(Rb, Ri, S) : Mem[Reg[Rb] + S * Reg[Ri]]
- D为常量(地址偏移量);
- Rb为基址寄存器:8个通用寄存器其之一;
- Ri为索引寄存器:%esp不作为索引寄存器,一般%ebp也不做这个用途;
- S为比较因子1, 2, 3或8。
其他变形还有:
(Rb, Ri) : Mem[Reg[Rb] + Reg[Ri]]
D(Rb, Ri) : Mem[Reg[Rb] + Reg[Ri] + D]
(Rb, Ri, S) : Mem[Reg[Rb] + S * Reg[Ri]]
寻址模式实例
首先,已知%edx = 0xf000,%ecx = 0x100.
Expression | Computation | Address |
---|---|---|
0x8(%edx) | 0xf000 + 0x8 | 0xf008 |
(%edx, %ecx) | 0xf000 + 0x100 | 0xf100 |
(%edx, %ecx, 4) | 0xf000 + 4*0x100 | 0xf400 |
0x80 (, %edx, 2) | 2*0xf000 + 0x80) | 0x1e080) |
总结mov指令
MOV S, D 表示将S移动到D(在AT&T汇编格式中):
- movb:传送字节;
- movw:传送字;
- movl:传送双字。
MOVS S, D 表示将符号扩展的S移动到D(在AT&T汇编格式中):
- movsbw:将做了符号扩展的字节传送到字;
- movsbl:将做了符号扩展的字节传送到双字;
- movswl:将做了符号扩展的字传送到双字。
MOVZ S, D 表示将零扩展的S移动到D(在AT&T汇编格式中):
- movzbw:将做了零扩展的字节传送到字;
- movzbl:将做了零扩展的字节传送到双字;
- movzwl:将做了零扩展的字传送到双字。
pushl S 表示将双字压栈;popl D 表示将双字出栈。之后的课程会具体讨论。
地址计算指令 lea
leal Src, Dest
- Src是地址计算表达式;
- 计算出来的地址赋给Dest。
使用实例:
- 地址计算(无需访存)如:translation of p = %x[i];
- 进行x + k * y这一类型的整数运算:k = 1, 2, 4 or 8。
整数计算指令
双操作数指令:
Format | Computation |
---|---|
addl Src, Dest | Dest = Dest + Src |
subl Src, Dest | Dest = Dest - Src |
imull Src, Dest | Dest = Dest * Src |
sall Src, Dest | Dest = Dest << Src 与shll等价 |
sarl Src, Dest | Dest = Dest >> Src 算数右移 |
shrl Src, Dest | Dest = Dest >> Src 逻辑右移 |
xorl Src, Dest | Dest = Dest ^ Src 异或 |
andl Src, Dest | Dest = Dest & Src |
orl Src, Dest | Dest = Dest |
单操作数指令:
Format | Computation |
---|---|
incl Dest | Dest = Dest + 1 |
decl Dest | Dest = Dest - 1 |
negl Dest | Dest = - Dest |
notl Dest | Dest = ~ Dest |
将leal指令用于计算
实例1
int arith(int x, int y, int z)
{
int t1 = x + y;
int t2 = z + t1;
int t3 = x + 4;
int t4 = y * 48;
int t5 = t3 + t4;
int rval = t2 * t5;
return rval;
}
arith:
pushl %ebp
movl %esp, %ebp
# codes above are for Set up
movl 8(%ebp), %eax
movl 12(%ebp), %edx
leal (%edx, %eax), %ecx
leal (%edx, %edx, 2), %edx
sall $4, %edx
addl 16(%ebp), %ecx
leal 4(%edx, %eax), %eax
imull %ecx, %eax
# codes above are for Body
movl %ebp, %esp
popl %ebp
ret
# Finish
在进入Body之前,获得如下结构:
其中,ebp表示基址。栈中,返回地址为ebp + 4,x、y、z分别也被压入栈中(符合函数调用传参规范)。
1 movl 8(%ebp), %eax # eax = x
2 movl 12(%ebp), %edx # edx = y
3 leal (%edx, %eax), %ecx # ecx = x + y
4 leal (%edx, %edx, 2), %edx # edx = 3 * y
5 sall $4, %edx # edx = 48 * y (t4)
6 addl 16(%ebp), %ecx # ecx = z + t1 (t2)
7 leal 4(%edx, %eax), %eax # eax = 4 + t4 + x (t5)
8 imull %ecx, %eax # eax = t5 * t2 (rval)
对于第3条指令,用lea指令将x+y值放在ecx中。相当于c语言过程的局不变量。如果寄存器够用,将局部变量放在寄存器中,否者放在栈中。
对于第4、5条指令,首先乘3,已达到最终乘以48的效果:
- 如何乘3?即将edx加edx乘以2,放在edx中,以实现edx乘以3。
- 之后,左移4位,即乘16。
注意到,第3条指令计算t1,第4、5条t4,第6条t2,第7条t5,与c中的顺序并不一致。
实例2
int logical(int x, int y)
{
int t1 = x^y;
int t2 = t1 >> 17;
int mask = (1<<13) - 7;
int t4 = y * 48;
int rval = t2 & mask;
return rval;
}
logical:
pushl %ebp
movl %esp, %ebp
# codes above are for Set up
movl 8(%ebp), %eax
xorl 12(%ebp), %eax
sarl $17, %eax
andl $8185, %eax
# codes above are for Body
movl %ebp, %esp
popl %ebp
ret
# Finish
只看Body,先不讨论栈的分配与回收:
1 movl 8(%ebp), %eax eax = x
2 xorl 12(%ebp), %eax eax = x ^ y (t1)
3 sarl $17, %eax eax = t1 >> 17 (t2)
4 andl $8185, %eax eax = t2 & 8185
- 第2行,进行异或运算;
- 第3行,t1是一个带符号数,因此右移使用sarl进行算数右移,而非逻辑右移;
- 2 13 − 7 = 8185 2^{13} - 7 = 8185 213−7=8185,编译器将常数mask计算了出来,而非交给机器去做。
c中,return一个32位整型,放在eax中;如果32位中return一个long long类型(64位),放在edx + eax中。如果在64位机器中,则直接放在rax中。
x86-32与x86-64的数据类型宽度
Size of C Objects (in Bytes)
C Data Type | Typical 32-bit | Intel IA32 | x86-64 |
---|---|---|---|
unsigned | 4 | 4 | 4 |
int | 4 | 4 | 4 |
long int | 4 | 4 | 8 |
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | 8 | 10/12 | 16 |
char * | 4 | 4 | 8 |
如果返回数在32位及以下,那么一个eax就够了。
x86-64的通用寄存器
64位进行了扩展:
- 一方面,扩展现有的,增加了8个新的寄存器;
- 另一方面,寄存器宽度扩展了,但eax等名称还可以继续使用;
- %ebp/%rbp不再是专用寄存器。
x86-32与x86-64下的swap对比
void swap (int *xp, int *yp)
{
int t0 = *xp;
int t1 = *yp;
*xp = t1;
*yp = t0;
}
32位下:
pushl %ebp
movl %esp, %ebp
pushl %ebx
// 以上为Set Up
movl 12(%ebp), %ecx
movl 8(%ebp), %edx
movl (%ecx), %eax
movl (%edx), %ebx
movl %eax, (%edx)
movl %ebx, (%ecx)
// 以上为Body
movl -4(%ebp), %ebx
movl %ebp, %esp
popl %ebp
ret
// Finsih
将两个地址取出,放在ecx与edx中,再将ecx与edx的地址的数取出,放在eax和ebx中,之后在eax和ebx中的数取出,放在edx与ecx中,即可返回。
而64位下:
movl (%rdi), %edx
movl (%rsi), %eax
movl %eax, (%rdi)
movl %edx, (%rsi)
retq
注意到64位中该功能非常简洁,可以注意到64位情况下传参是用过寄存器来传递的,而32位中寄存器不够用,参数用栈Stack来传递。
64位中,减少访问内存,使用寄存器来传参数。
但最多传6个参数,超过6个部分,依此从右往左压入栈中。
注意到上例中,被操作数仍然是32位,因此使用寄存器%eax和%edx,以及movl指令。
而x86-64下long int类型的swap如下。
void swap_l (long int *xp, long int *yp)
{
long int t0 = *xp;
long int t1 = *yp;
*xp = t1;
*yp = t0;
}
汇编代码如下:
swap_:
movq (%rdi), %rdx
movq (%rsi), %rax
movq %rax, (%rdi)
movq %rdx, (%rsi)
retq
被操作数是64位:
- 所以使用寄存器%rax与%rdx;
- 以及movq指令,q表示4字。
小结:x86指令的特点
- 支持多种类型的指令操作数,包括立即数、寄存器和内存数据;
- 算逻辑指令可以以内存数据作为操作数;
- 支持多种内存地址计算模式,包括Rb + S * Ri + D,也可用于整数计算如leal指令;
- 变成指令from 1 to 15 bytes。
扩展:x86汇编的格式
Intel/Microsoft Format
(-masm = intel, Intel 语法)
lea eax, [ecx + ecx * 2]
sub esp, 8
cmp dword ptr [ebp-8], 0
mov eax, dword ptr [eax * 4 + 100h]
AT&T Format
leal (%ecx + %ecx * 2), %eax
subl $8, %esp
cmpl $0, -8(%ebp)
movl $0x100 (, %eax, 4), %eax
Intel/Microsoft Differs from GAS
- Operands listed in opposite order
-
- mov Dest, Src
-
- movl Src, Dest
- Constants not preceded by ‘$’, Denote hex with ‘h’ at end
-
- 100h
-
- $0x100
- Operand size indicated by operands rather than operator suffix
-
- sub
-
- subl
- Addressing format shows effective address computation
-
- [eax * 4 + 100]
-
- $0x100 (, %eax, 4)
练习题
一个函数的原型为:
int decode2(int x, int y, int z);
汇编代码为:
movl 16(%ebp), %edx
subl 12(%ebp), %edx
movl %edx, %eax
sall $15, %eax
sarl $15, %eax
xorl 8(%ebp), %edx
imull %edx, %eax
另外,有:
x at %ebp + 8, y at %ebp + 12, z at %ebp + 16
参数x、y和z存放在存储器中相对于寄存器%ebp中地址偏移量为8、12和16的地方,代码将返回值放在寄存器%eax中,写出等价于汇编代码的decode2的c代码。
答案为:
int decode2(int x, int y, int z)
{
int t1 = z - y;
int t2 = (t1 << 15) >> 15;
int t3 = x ^t1;
int t4 = t2 * t1;
return t4;
答疑:movl与leal中地址表达式
movl中出现地址表达式是用要去访问相应内存的,而leal中是用于算数的。