一、X86 寻址方式
x86的通用寄存器有8个。这些寄存器在大多数指令中是可以任意选用的,比如movl 指令可以把一个立即数传送到eax 中,也可传送到ebx 中。但也有一些指令规定只能用其中某个寄存器做某种用途,例如除法指令idivl 要求被除数在eax 寄存器中,edx 寄存器必须是0,而除数可以在任意寄存器中,计算结果的商数保存在eax 寄存器中(覆盖原来的被除数),余数保存在edx 寄存器中。也就是说,通用寄存器对于某些特殊指令来说也不是通用的。
介绍x86常用的几种寻址方式(Addressing Mode)。内存寻址在指令中可以表示成如下的通用格式:
ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)
它所表示的地址可以这样计算出来:
FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX
注:实际上 final address 也只是逻辑地址中的32位偏移量部分,需要使用段选择符找到段描述符,进而得到段基地址,两者相加才是线性地址,但在Linux实现中段基地址都为0,故偏移量可以直接当作线性地址,再经过分页转换就是真正的物理地址,也就是说final address 是程序中访问的地址。具体参见 《 80386分段分页机制 》
其中ADDRESS_OR_OFFSET 和 MULTIPLIER 必须是常数, BASE_OR_OFFSET 和 INDEX 必须是寄存器。
在有些寻址方式中会省略这4项中的某些项,相当于这些项是0。
直接寻址(Direct Addressing Mode)。只使用ADDRESS_OR_OFFSET寻址,例如movl ADDRESS, %eax 把ADDRESS地址处的32位数传送到eax 寄存器。
变址寻址(Indexed Addressing Mode) 。movl data_items(,%edi,4), %eax 就属于这种寻址方式,用于访问数组元素比较方便。
间接寻址(Indirect Addressing Mode)。只使用BASE_OR_OFFSET寻址,例如movl (%eax), %ebx ,把eax 寄存器的值看作地址,把内存中这个地址处的32位数传送到ebx 寄存器。注意和movl %eax, %ebx 区分开。
基址寻址(Base Pointer Addressing Mode)。只使用ADDRESS_OR_OFFSET和BASE_OR_OFFSET寻址,例如movl 4(%eax), %ebx ,用于访问结构体成员比较方便,例如一个结构体的基地址保存在eax 寄存器中,其中一个成员在结构体内的偏移量是4字节,要把这个成员读上来就可以用这条指令。
立即数寻址(Immediate Mode)。就是指令中有一个操作数是立即数,例如movl $12,%eax 中的$12, 这其实跟寻址没什么关系,但也算作一种寻址方式。
寄存器寻址(Register Addressing Mode)。就是指令中有一个操作数是寄存器, 例如movl $12, %eax 中的%eax ,这跟内存寻址没什么关系,但也算作一种寻址方式。在汇编程序中寄存器用助记符来表示,在机器指令中则要用几个Bit表示寄存器的编号,这几个Bit也可以看作寄存器的地址,但是和内存地址不在一个地址空间。
在Intel 的语法中,寄存器和和立即数都没有前缀。但是在AT&T 中,寄存器前冠以“%”, 而立即数前冠以“$”。在Intel 的语法中,十六进制和二进制立即数后缀分别冠以“h”和 “b”,而在AT&T 中,十六进制立即数前冠以“0x”,如表2.2 所示给出几个相应的例子。
2.操作数的方向
Intel 与AT&T 操作数的方向正好相反。在Intel 语法中,第一个操作数是目的操作数, 第二个操作数是源操作数。而在AT&T 中,第一个数是源操作数,第二个数是目的操作数。由 此可以看出,AT&T 的语法符合人们通常的阅读习惯。
例如:在Intel 中,mov eax,[ecx]
在AT&T 中,movl (%ecx),%eax
3.内存单元操作数
从上面的例子可以看出,内存操作数也有所不同。在Intel 的语法中,基寄存器用“[]” 括起来,而在AT&T 中,用“()”括起来。
例如: 在Intel 中,mov eax,[ebx+5]
在AT&T,movl 5(%ebx),%eax
4.间接寻址方式
与Intel 的语法比较,AT&T 间接寻址方式可能更晦涩难懂一些。Intel 的指令格式是segreg:[base+index*scale+disp],而AT&T 的格式是%segreg:disp(base,index,scale)。 其中index/scale/disp/segreg 全部是可选的,完全可以简化掉。如果没有指定scale 而指 定了index,则scale 的缺省值为1。segreg 段寄存器依赖于指令以及应用程序是运行在实 模式还是保护模式下,在实模式下,它依赖于指令,而在保护模式下,segreg 是多余的。在 AT&T 中,当立即数用在scale/disp 中时,不应当在其前冠以“$”前缀,表2.3 给出其语法 及几个相应的例子。
从表中可以看出,AT&T 的语法比较晦涩难懂,因为[base+index*scale+disp]一眼就可 以看出其含义,而disp(base,index,scale)则不可能做到这点。 这种寻址方式常常用在访问数据结构数组中某个特定元素内的一个字段,其中,base 为 数组的起始地址,scale 为每个数组元素的大小,index 为下标。如果数组元素还是一个结构,则disp 为具体字段在结构中的位移。
5.操作码的后缀
在上面的例子中你可能已注意到,在AT&T 的操作码后面有一个后缀,其含义就是指出 操作码的大小。“l”表示长整数(32 位),“w”表示字(16 位),“b”表示字节(8 位)。而 在Intel 的语法中,则要在内存单元操作数的前面加上byte ptr、word ptr 和dword ptr,
“dword”对应“long”。表2.4 给出了几个相应的例子。
三、AT&T 汇编语言相关知识
在Linux 源代码中,以.S 为扩展名的文件是“纯”汇编语言的文件。这里,我们结合具 体的例子再