本文为学习了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 | ||
|---|---|---|
| 标签 | 指令 | 作用 |
| var | DB 64 | 声明一个字节,并将数值64放入此字节 |
| var2 | DB ? | 声明一个未初始化的字节 |
| DB 10 | 声明一个没有标签的字节,值为10 | |
| X | DW ? | 声明一个未初始化的双字节 |
| Y | DD 30000 | 声明一个4字节,其值为30000 |
| Z | DD 1, 2, 3 | 声明三个4字节的值,分别初始化为1,2,3,地址为Z+8的值会是3 |
| bytes | DB 10 DUP(?) | 在bytes的位置处开始声明10个未初始化的字节 |
| arr | DD 100 DUP(0) | 在arr位置处开始声明100个4字节字符,全部初始化为0 |
| str | DB ‘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的值