IA32的整数寄存器
在IA32中央处理单元(CPU)中,包含了8个32位整数寄存器(如下图)。从图中可以看到在每个32位寄存器的名字前面都会有一个%e,在这里可以把e理解成extended(扩展的),因为早期的8086寄存器是16位,所以加e之后就变成32位的了。
虽然现在的寄存器是32位的了,但仍然不影响我们对16位寄存器的使用,图中的ax,cx,dx,bx,si,di,sp,bp均是16位寄存器,并且也可用通过ah访问ax的高8位,al访问ax的低8位(cx,dx,bx访问方法类似)。
一般而言,前面6个32位寄存器都是通用的,除开某些特定操作会用到特定的寄存器之外,你可以随意使用它们。除此之外,在过程(functions)处理中,针对前3个寄存器的处理会不同于后3个,但目前来说,你并不需要关心这些差异。
但值得注意的是:最后两个寄存器是不能随便乱动的,因为它们保存着指向程序栈重要位置的指针。
下面做一个简单的总结,让条理清晰一点:
1、IA32中总共有8个32位寄存器(请不要太过关注这些寄存器奇怪的名字),如果要访问寄存器中32位数据的值,你需要使用%e??这种形式,如果要访问某个寄存器低16位的值你需要使用%?x, %?i 或者 %?p 这种形式,如果你需要访问高8位需要使用 %?h这种形式,如果要访问低8位,则需要使用%?l这种形式。
2、前6个寄存器是通用的(在遵循一定的使用规则前提下,可以随意更改寄存器中的值),后两个寄存器是不能随意更改的。
3、IA32寄存器被划分为了8个32位寄存器,8个16位寄存器,以及8个8位寄存器。但实质上只有8个32位寄存器,无外乎玩了一场位游戏。
示例:
假设32位寄存器%eax存储的值如下:
%eax : aa bb cc dd
那么下面寄存器对应的值为
%ax:cc dd
%ah:cc
%al::dd
因为第一个%ax代表取寄存器低16位的值,%ah代表取低16位中的高8位值,%al代表取低16位中的低8位值。
操作数指示符
大多数指令有一个或者多个操作数,指示出执行一个操作中要引用的源数据值以及放置结果的目标位置。
操作数的类型:
(1)立即数
书写方式:‘ ′后面跟一个用标准C表示法表示的整数,比如 -123, $0x1f.
任何能放进一个32位的字里的数值都可以用作立即数
(2)寄存器
表示某个寄存器的内容,对于双字操作来说,可以是8个32位寄存器的一个(例如,%eax),对于字操作来说,可以是8个单字节寄存器元素中的一个(例如,%al)。
用符号Ea来表示任意寄存器a,用引用R[Ea]来表示他的值,这是将寄存器集合看成一个数组R,用寄存器标识符作为索引。
(3)存储器引用
它会根据计算出来的地址(通常称为有效地址)访问某个寄存器位置。
数据传送指令
将数据从一个位置复制到另一个位置的指令使用数据传送指令。
a. MOV指令
MOV类指令将源操作数的值复制到目的操作数中。源操作数指定的值是一个立即数,存储在寄存器或者存储器中。目的操作数指定一个位置,要么是一个寄存器,要么是一个存储器地址。
IA32有限制,传送指令的两个操作数不能都指向存储器位置。
将一个值从一个存储器位置复制到另一个存储器位置需要两条指令——第一条指令将源值加载到寄存器中,第二条指令将该寄存器写入目的位置。
b. MOVS指令和MOVZ指令
MOVS指令和MOVZ指令类都是讲一个较小的源数据复制到一个较大的数据位置,高位用符号扩展(MOVS)或者零扩展(MOVZ)进行填充。
用符号位扩展,目的位置的所有高位用源值的最高位数值进行填充,用零扩展,所有高位都用零填充。
这两个类中每个都有三条指令,包括了所有源大小为1个和2个字节,目的大小为2个和4个的情况(省略了冗余的组合movsww和movzww)。
示例:
源代码:
int exchange(int *p, int y)
{
int x = p;
*p = y;
return x;
}
前三条指令说明了如何用MOV指令从存储器中读值到寄存器,第四条指令说明了如何从寄存器写到存储器。
字节传送指令比较
仔细观察可以发现,三个字节传送指令movb, movsb1和movzbl之间有细微的差别。
//假设%dh = CD, %eax = 98765432
mov %dh, %al %eax = 987654CD
movsbl %dh, %eax %eax = FFFFFFCD
movzbl %dh, %eax %eax = 000000CD
例子中都是将寄存器%eax的低位字节设置成%edx的第二个字节。movb指令不改变其他三个字节。根据源字节的最高位,movsbl指令将其他三个字节设为全1或全0。movzbl指令无论如何都是将其他三个字节设置为全0.
栈操作:压栈是减少栈指针(寄存器%esp)的值,并将数据存放到存储器中,而出栈是从存储器中读,并增加栈指针的值
pushl指令的功能是把数据压入到栈上,popl指令时弹出数据,
这些指令只有一个操作数。
将一个双字值压入栈中,首先要把栈指针减4,然后将值写入到新的栈顶地址。
pushl %ebp 等价于
subl $4 , %ebp
movl %ebp , (%esp)
区别:
在目标代码中1pushl指令编码为1字节,而以上两条指令应该需要6字节。
弹出一个双字的操作包括从栈顶位置读出数据,然后将栈指针加4.
popl %eax等价于
movl (%ebp), %eax
addl $4, %esp