汇编基础

DinS          Written on 2018/5/2

一、概述

见题图说明情况。

汇编语言的位置就在这里。

绝大多数程序员不会进行汇编编程,不过还是有必要了解汇编的基础知识,这样有助于加深对高级语言的理解。

本专题将简要概述汇编相关的基础和重要知识,理解即可。以C语言为蓝本。

二、指令集简介

编译的环节都很熟,平常写完代码后都需要编译一下,这个编译就是告诉编译器,让其把高级语言转换成汇编语言,并在过程中进行一定优化。

那么指令是什么呢?指令系统是处理器对外提供的主要接口与规格,汇编语言可视为其助记符。可以这样来理解:计算机的核心就是处理器,我们需要一种方式告诉处理器进行哪些操作,这个方式就是处理器的指令集,由处理器生产商提供。比如上图那个“addl”就是一条指令,看名字跟加法有关。

指令系统分成两大类:CISC(复杂指令系统)和RISC(精简指令系统)。

众所周知的x86就属于CISC,代表厂商是Intel和AMD。所谓复杂指令就是处理器提供了许多种指令,其产生背景是在计算机发展的初期编译器很不发达,所以处理器厂家就提供了多种指令方便开发人员编程。

RISC的代表是MIPS处理器。所谓简单指令就是处理器只提供基本的操作,其产生背景是到了1980s编译器逐渐发展成熟,设计CISC的初衷发生了变化,复杂指令占用了80%的资源但是应用频率很低,简单指令占20%资源但是高频使用。于是RISC的思路是把复杂指令踢出处理器,重点优化简单指令,将其余的任务交给编译器处理。

RISC的另一个代表是ARM,具有压缩指令集,更精简功耗更低,在移动计算领域大获成功。

不同的指令系统其指令不同,这里以x86为例。写法基本上都是Instruction Source, Destination。几种常见的指令如下:

大致了解常见的几种即可,如果真的需要可以去查指令语法。

但是图中的指令是“addl”不是“add”,怎么回事?“l”后缀表示双字运算,即32位数据,这个接下来讲解。

三、x86体系结构

再上一张图说明。

处理器里有3部分:Registers表示寄存器,Conditional Codes表示条件码,也就是跟上面cmp和set相关的那个条件码,PC表示program counter,指令寄存器,用于存储下一条指令的地址。有了这三部分处理器就可以不停地运转了。

处理器之外的就是内存了,里面有各种东西,其中栈跟汇编有很大关系,会详细讲解。

所谓寄存器,就是暂时寄存数据的元件,在因为在CPU中所以访问速度极快。32位处理器共有8个寄存器,分别是:%eax, %ebx, %ecx, %edx, %esi, %edi, %esp, %ebp。汇编里主要就是在各种寄存器之间来回捣腾。

于是现在应该能够猜出“addl  8(%ebp),  %eax”到底是什么意思了,即将寄存器%eax里面的数据取出,加上一个跟寄存器%ebp有关的数据,结果放入寄存器%eax中。如果直接是%ebp那么很简单,那个8()是什么意思?这个是寻址模式。寄存器里可以直接放数值,比如123,也可以放地址,比如0x120,因为地址本质上也是个数值。如果放地址就对应C语言里面的指针,指向一块内存。在C语言里面可以对指针解引用,得到指针对应的值,这个操作在汇编里面就是(),所以(%ebp)表示的是取出%ebp里存储的内存地址里面的值。那个8表示什么呢?我们知道内存在微观上是一块一块存储的,32位处理器下一块内存对应4字节,即32比特。8表示该内存块之后8字节的位置。于是8(%ebp)的含义是取出%ebp地址指向的内存块之后第2块内存里的数值。

画个图表示一下。

如果是这种情况,那么8(%ebp)取出的就是数值789。

现在顺带说一下32位和64位到底是什么意思。这需要补充一个概念:机器字长(machine word)。

机器字长一般指计算机进行一次整数运算所能处理的二进制数据的位数,通常也包括数据地址的长度。这个概念对处理器很重要,我们可以想象有一大长串指令,处理器接收一条,执行,然后接收下一条,执行,如此往复。但是这里的问题是一次能够处理的数据规模存在上限,当这个上限是32比特,4字节时,这个处理器就是32位的;如果是64比特,8字节,处理器就是64位的。再说的直白一点,如果寄存器只能够装载32位数据,就是32位处理器;如果寄存器能够装载64位数据,就是64位处理器。

我们知道寄存器里也装载地址用于在内存里寻址,如果是32位那么能够表示的最大整数大约是232 ≈ 4 GB,这就是为什么32位的机器能够利用的内存最大只有4GB。64位机器理论上也有内存使用极限,但是那就十分遥远了,现在x86-64位的机器一般也就使用了48位,可以支持256TB的内存。另外这个事实也有助于我们理解为什么32位程序的指针都是4字节,因为寄存器里放的都是32位数据。

64位处理器共有16个寄存器,分别是:%rax, %rbx, %rcx, %rdx, %rsi, %rdi, %rsp, %rbp,%r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15。寄存器多了一倍,效率自然高,而且指针是8字节,寻址空间大大提高。64位处理器与32位的另一个显著区别在于生成的汇编代码差异较大,64位的汇编代码会尽量利用寄存器而不是内存。

以上就是与汇编香相关的基础知识,接下来让我们看看一次完整的函数调用对应的汇编代码是什么样子的。见《函数调用过程——汇编的视角》。