CSAPP要点总结 第3章 程序的机器级表示 part2 汇编寻址与运算指令

隐藏

ATT和Intel汇编代码格式

ATT是根据AT&T命名的代码格式,是GCC、OBJDUMP等工具的默认格式, 而Microsoft工具以及来自Intel的文档采用Intel格式。 王爽的《汇编语言》就是Intel格式

GCC也可以通过命令行选项,生成Intel格式汇编:

~ $ gcc -O1 -S -masm=intel code.c

虽然格式不同,但内容一样,CSAPP以ATT格式来讲解内容。

以下将总结ATT的汇编语法:

数据格式

以下表格给出C语言基本数据类型对应的IA32格式:

C声明 Intel 数据类型 汇编代码后缀 大小 (bytes)
char Byte b 1
short Word w 2
int Double word l 4
long int Double word l 4
long long int 4
char * Double word l 4
float Single precision s 4
double Double precision l 8
long double Extended precision t 10/12

之所以32位后缀用l是因为对于早期16位CPU,例如8086,32位就是long了, 所以这一习惯沿用至今。

注意char*是双字哦

访问信息

如下图为IA32的整数寄存器,共8个:

IA32的整数寄存器

对比给出x86-64的寄存器:

x86-64的整数寄存器

在平坦寻址中,对于%ax,&al这样的特殊寄存器的需求已经极大降低了。主要为了向后兼容。

他们都是通用寄存器,但功能上有些区别:

其中%eax、%ecx、%edx一组

%ebx、%edi、&esi一组,

%esp栈指针%ebp帧指针保存程序栈的重要位置指针, 要用栈管理的标准惯例使用。

操作数

指令的操作数(operand)有三类,IA32格式如下:

立即数Immediate

例如 $-577、$0x1F

寄存器Register

例如 %eax, %ax

存储器Memory

例如 4(%eax)、9(%eax, edx)、0xF9(%eax, edx, 4) 含寄存器就带括号的。 还有绝对地址的写法,例如 0x104

其中0xF9(%eax, edx, 4)中的 4 是变址的比例因子,必须是1、2、4或者8。 通常引用中有两个寄存器适合于二维的数组或者表。

操作数格式汇总如下:

操作数格式

数据传送指令

数据传送指令

需要注意的是零扩展和符号扩展,类似于右移中的逻辑扩展和算术扩展。

跟8086一样,IA32也不支持内存到内存,还是要内存到寄存器再到内存。

关于pushl,popl中数据与指针执行顺序可以参考汇编语言第三单元 寄存器中关于弹夹的比喻。

此外,对于IA32,入栈、出栈猜测似乎只能用于双字, 那么访问栈元素是4的倍数,例如4(%esp), 同时,指令似乎只有pushl和popl一种写法,而没有pushb、popw之类的。

对于一个实际的C函数,指针实际就是将地址保持到寄存器,调用指针就是操作寄存器值对应的内存地址。 函数中的某个局部变量通常保存在寄存器中,因为寄存器较快。

算术与逻辑操作

基本的算术与逻辑操作汇总如下图:

基本的算术与逻辑操作

上图中 S代表Source,D代表Destination,k代表一个数或者%cl

可见基本的算术逻辑操作分为四类:加载有效地址,一元操作,二元操作和移位

加载有效地址

leal是加载有效地址(load effective address)实际上是movl的变形 类似于C语言中的取地址,为内存引用产生指针。

关于leal,还有特殊用法跟取地址无关。 例如:

leal 7(%edx, %edx, 4), %eax

这条指令中,7(%edx, %edx, 4) = (%edx + 4%edx + 7) = (5 * %edx + 7) 再取其地址,则实际上是将5 * %edx + 7赋值给%eax。 所以leal在此处实际上用作计算。 编译器中经常能看到leal的灵活用法。

一元操作和二元操作

需要注意的是二元操作的两个操作数与mov一样不能都是存储器位置, 对于两个存储器的数据交换必须通过寄存器。

这从CPU执行指令的过程很好理解, 总线可以是外部数据总线到寄存器 或者 寄存器到外部总线, 而外部内存空间数据之间没有直接数据线相连(不知道其他计算机构架有没有直连的,猜测计算显卡可能会有吧)。

移位操作

移位量用单个byte也只考虑byte内的低5位(IA-32)。

还记得第二章第一小节说到的,当移位的位数大于数的宽度的情况么?对移位数取余。 这里取低5位实际上就是对32位取余。 可以想到,对于64位数,取低6位; 16位数,取低4位

移位量k可以是立即数,或者cl寄存器(正好是1个byte)。这跟8086的机制很相似。 被执行移位的可以是寄存器,或者虚拟内存地址(指向的虚拟内存)。

注意SHR和SAR(shift algorithm right)的区别。 SHR逻辑右移,右补0,用在无符号 SAR算术右移,正数右补0,负数右补1,用在补码。

讨论

上述运算中,除了位移运算需要区分无符号数和补码数外, 其他运算对两者运算都没区别,这也正是补码的优点!

特殊的算术操作

在基本的算术与逻辑操作汇总图中的乘法imul有两个操作数, 而实际上,还有一个操作数的乘除法, 乘法扩展到64位,而除法,可得到余数, 这与古老的8086的乘除法方式一致。具体如下:

基本的算术与逻辑操作

其中cltd即convert long to double,用来辅助补码除法计算的。 (Intel汇编格式对应命令为cdq convert double to quard)

对于除法,为何要区分补码和无符号呢? 一个原因是,对于64位被除数是先由32位扩展而来, 那么,就有逻辑扩展(对应无符号)和算术扩展(对应补码)

所以要区分div(无符号除法)和idiv(补码除法)

对于乘法为何区分补码和无符号还没想清楚,感觉一样啊。

-----EOF-----

Categories: csapp Tags: 汇编 computer system