本文为学习了jiftle大神翻译并写下的通俗易懂的《X86汇编快速入门》后的记录

寄存器部分

如图,EAX用于计算,ECX用于循环变量计数,ESP指示栈指针(用于指示栈顶位置),EBP是基址指针(用于指示子程序或者函数调用的基址指针)。

并且,EAX,EBX, ECX和EDX的前两个高位字节和后两个低位字节可以独立使用,其中两位低字节又被独立分为H和L部分,这样做是考虑兼容16位程序。

应用寄存器时,名称大小写不敏感,eax与EAX无差别

内存和寻址方式

声明静态数据区

X86汇编语言中可以使用”.DATA”指令声明静态数据区(类似于全局变量),数据以单字节(DB),双字节(DW)或4字节(DD)存放,分别用DB,DW,DD指令表示声明内存的长度。在汇编语言中,相邻定义的标签在内存中连续存放

额外的,还可以声明连续的数据和数组,声明数组时使用DUP关键字

.DATA
标签指令作用
varDB 64声明一个字节,并将数值64放入此字节
var2DB ?声明一个未初始化的字节
DB 10声明一个没有标签的字节,值为10
XDW ?声明一个未初始化的双字节
YDD 30000声明一个4字节,其值为30000
ZDD 1, 2, 3声明三个4字节的值,分别初始化为1,2,3,地址为Z+8的值会是3
bytesDB 10 DUP(?)在bytes的位置处开始声明10个未初始化的字节
arrDD 100 DUP(0)在arr位置处开始声明100个4字节字符,全部初始化为0
strDB ‘hello’, 0在str地址处开始声明6个字节,初始化为hello字母的ASCII值和一个0

寻址方式

现代的x86处理器具有$2^{32}$字节的寻址空间,在上面的例子中,我们用标签(label)表示内存区域,这些标签在实际汇编时,均被32位的实际地址代替。

除了支持这种直接的内存区域描述,x86还提供了一种灵活的内存寻址方式,即利用最多两个32位的寄存器和一个32位的有符号常数相加计算一个内存地址,其中一个寄存器可以左移1、2或3位以表述更大的空间。例:

指令含义
mov eax, [ebx]将ebx值指示的内存地址中的4个字节传送到eax中
mov [var], ebx将ebx的内容传送到[var]的值指示的内存地址中
mov eax, [esi-4]将esi-4值指示的内存地址中的4个字节传送到eax中
mov [esi+eax], cl将cl的值传送到esi + eax的值指示的内存地址中
mov edx, [esi + 4*ebx]将esi + 4*ebx值指示的内存中的4个字节传送到edx

注意:方括号中的两个标签之间只能使用加法,并且最多只能有两个寄存器参与运算

长度规定

汇编中,一般用DB,DW,DD声明内存空间的大小

而在mov [edx], 2中,如果没有特殊的标识,就不确定常数2是单字节、双字节还是4字节,x86提供了三个指示规则标记,分别为BYTE PTR, WORD PTR,和DWORD PTR,上面的例子可以写成:

mov BYTE PTR [ebx], 2 ; mov WORD PTR [ebx], 2 ; mov DWORD PTR [ebx], 2,此时意思就非常清晰

汇编指令

汇编指令通常可以分为数据传送指令、逻辑计算指令和控制流指令。以下标记分别表示寄存器,内存和常数

指令含义
32位寄存器(EAX, EBX, ECX, EDX, ESI, EDI, ESP or EBP)
16位寄存器(AX, BX, CX, or DX)
8位寄存器(AH, BH, CH, DH, AL, BL, CL or DL)
任何寄存器
内存地址(例如 [eax], [var + 4] 或者dword ptr [eax+ebx]
32位常数
16位常数
8位常数
任何8位,16位或32位常数

数据传送指令

mov ——移动(复制)

(操作码:88, 89,8A,8B,8C,8E,…)

mov指令将第二个操作数(可以是寄存器的内容、内存中的内容或值)复制到第一个操作数(寄存器或内存)。mov 不能用于直接从内存复制到内存,其语法如下所示:

mov <reg>, <reg>
mov <reg>, <mem>
mov <mem>,<reg>
mov <reg>,<const>
mov <mem>,<const>

比如:
mov eax,ebx —— 将ebx的值拷贝到eax
mov byte ptr[var],5 ——将5保存到var指示内存中的一个字节中

push——入栈

(操作码:FF,89.8A,8B,8C,8E,…)

push指令将操作数压入内存的栈中,栈主要用于函数调用过程中,其中ESP只是栈顶。在压栈前,首先将ESP值减4(X86栈增长方向与内存地址编号增长方向相反),然后将操作数内容压入ESP指示的位置。语法如下:

push <reg32>
push <mem>
push <con32>

比如:
push eax ——将eax的内容压栈
push [var] ——将var指示的4字节内容压栈

pop —— 出栈

pop指令与push指令相反,它执行的是出栈的工作。它首先将ESP指示的地址中的内容出栈,然后将ESP值加4,语法如下:

pop <reg32>
pop <mem>

比如:
pop edi —— 将栈顶的元素出栈到edi中
pop [edx] ——将栈顶元素出栈到从EBX位置开始的4个字节处

lea ——载入有效地址

lea实际上是一个载入有效地址指令,将第二个操作数表示的地址载入到第一个操作数(寄存器)中,其语法如下所示:

lea <reg32>,<mem>

比如:
lea eax,[var]——将var指示的地址载入eax中
lea edi,[ebx+4*esi]——将ebx+4*esi表示的地址载入到edi中,这实际上是上面所说的寻址模式的一种表示方式

算术和逻辑指令

add —— 整数加法

add指令将两个操作数相加,且将相加后的结果保存到第一个操作数中。语法如下:

add <reg>,<reg>
add <reg>,<mem>
add <mem>,<reg>
add <reg>,<con>
add <mem>,<con>

比如:
add eax,10 —— EAX ←EAX + 10
add BYTE PTR [var], 10 —— 10与var指示的内存中的一个byte的值相加,并将结果保存在指示的内存中

sub —— 整数减法

sub指令指示第一个操作数减去第二个操作数,并将相减后的值保存在第一个操作数,其语法如下所示:

sub <reg>,<reg>
sub <reg>,<mem>
sub <mem>,<reg>
sub <reg>,<con>
sub <mem>,<con>

比如:
sub al, ah —— AL←AL-AH
sub eax, 216 —— eax中的值减26,并将计算值保存在eax中

inc, dec——自增自减

inc, dec分别表示将操作数自加1,自减1,其语法如下:

inc <reg>
inc <mem>
dec <reg>
dec <mem>

比如:
dec eax —— eax中的值自减1
inc DWORD PTR [var] —— var指示内存中的一个4字节值自加1

imul —— 整数相乘

整数相乘指令,它有两种指令格式,一种位两个操作数,将两个操作数的值相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器;第二种格式为三个操作数,其语义为:将第二个和第三个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器,其语法如下所示:

imul <reg32>,<reg32>
imul <reg32>,<mem>
imul <reg32>,<reg32>,<con>
imul <reg32>,<mem>,<con>

比如:
imul eax, [var]——eax→eax * [var]
imul esi,edi, 25——ESI→EDI * 25

下面不是很需要仔细品懂的地方开始省略

idiv —— 整数除法

and, or, xor —— 按位逻辑与,或 以及 异或

not —— 按位逻辑非

neg ——取负指令

shl, shr —— SHift left, Shift Right,左移右移

shl <reg>,<con8>
shl <mem>,<con8>
shl <reg>,<sl>
shl <mem>,<cl>

;shr同理
比如:
shl eax, 1 —— 将eax的值乘2(左移位数n,相当于乘2^n)
shr ebx, cl ——将ebx的地板值除以2^cl的值

控制转移指令