汇编语言学习笔记 上
本文是介绍汇编语言的基本组成和基本齐全的指令,以及帮助理解这些的背景知识
包括寄存器,汇编语言基本组成部分,数据传送指令,寻址指令,加减法指令,堆栈,过程,条件处理,整数运算
帮助读者在很短的时间内对汇编语言有一个总体的认知,学会查看汇编代码和掌握学习编写汇编程序的能力,深入学习汇编语言的相关知识可以查阅《汇编语言 基于x86处理器》
下一篇文章
目录
基于相等性的跳转,基于无符号数的跳转指令,基于有符号数比较的跳转
汇编语言
MASM是Microsoft Macro Assembler的缩写,Microsoft宏汇编器
汇编语言的特点
1.与机器语言很接近
2.需要了解计算机架构和操作系统,直接访问计算机硬件
3.学习汇编语言的基本语法,不需要深入了解操作系统和计算机架构,最好使用过一门编程语言进行编程,逻辑是相通的
4.汇编器将汇编语言转换成机器语言
5.链接器将汇编器生成的单个文件组合成一个可执行文件
6.汇编语言不可移植
汇编语言的使用领域
1.C或C++开发者需要了解汇编,很多编程错误在高级语言难以识别,需要深入到程序内部,理解内存,地址和指令是如何在底层工作的
2.编写嵌入式程序,例如显卡,声卡,打印机,硬盘驱动器
3.高级语言嵌入汇编,可以对程序进行优化
4.帮助理解计算机硬件,操作系统,应用程序之间的交互关系
5.进行逆向分析时候,分析反汇编器生成的汇编代码
汇编语言与机器语言,C++,Java,Python
汇编语言与机器语言
汇编语言和机器语言是一一对应的关系(one-to-one)
一句汇编语言对应一句机器语言
机器语言用数字编写,汇编语言使用助记符ADD,MOV,PUSH
汇编语言与C++,Java,Python
Java,Java,Python与汇编语言是一对多的关系(one-to-many)
高级语言是可以移植的,汇编语言不可移植
基本微机设计
CPU包含了有限数量的寄存器,一个高频时钟,一个控制单元CU,一个算数逻辑单元ALU
CPU通过主板上CPU插座的引脚和计算机其他部分相连
大部分引脚连接的是数据总线,控制总线和地址总线
总线
总线是一组并行线
分为四类总线:
数据类,I/O类,控制类,地址类
时钟
机器指令的时间单位是时钟周期
一个时钟周期是一个完整脉冲所需要的时间
一个机器指令最少需要一个时钟周期
内存
ROM 永久烧录在芯片上,不能擦除
EPROM 只能用强紫外线照射来擦除,可以重新编程
DRAM 最常用的一种电脑内存。它通常使用一个晶体管和一个电容器来代表一个比特。价格便宜,每毫秒需要刷新,避免丢失内容。
SRAM 静态随机存取存储器,所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持,不需要刷新。CPU的cache就是SRAM构成的。
VRAM 影像随机接达记忆器,作为影像绘图卡、显卡所使用的DRAM,就是广义上说的显存。
CMOS RAM 在系统主板,保存系统设置信息。由电池供电。计算机关闭后,CMOS RAM中的内容仍然能保留。
虚拟内存
虚拟内存是计算机系统内存管理的一种技术,他使计算机认为拥有连续的可用的内存。通常有多个物理内存碎片,还有部分在外部磁盘存储器上。大多数操作系统都使用了虚拟内存。虚拟内存可看作是一个存放在磁盘上的N个连续的字节大小的单元组成的数组。每个字节都有一个虚拟地址,作为到数组的索引。
寄存器
四类寄存器
通用寄存器
段寄存器
程序与控制寄存器
指令指针寄存器
32位机
EAX | (针对操作数和结果数据的)累加器 |
EBX | (DS段的数据指针)基址寄存器 |
ECX | (字符串和操作循环的)计数器 |
EDX | (I/O)指针数据寄存器 |
ESI | (字符串操作源指针)源变址寄存器 |
EDI | (字符串操作目标指针)目的变址寄存器 |
EBP | (SS段中栈内数据指针)扩展基址指针寄存器 |
ESP | (SS段中栈指针)栈指针寄存器 |
通用型的寄存器,用于传送和暂存数据
ESP PUSH,POP,CALL,RET可以直接用来操作ESP
EBP 函数调用存储ESP,函数返回把值返回ESP
CS | 代码段寄存器 |
SS | 栈段寄存器 |
DS | 数据段寄存器 |
ES | 附加数据段寄存器 |
FS | 数据段寄存器 |
GS | 数据段寄存器 |
每个16位
EFLAGS | 标志寄存器 |
32位,每个位置记录一种状态
EIP | 指令指针寄存器 |
CPU从中读取一条指令的地址,指令传送到缓存区后,EIP地址自动增加
64位机
十六位机中E改为R,增加了R8~R15八个通用寄存器
RAX | (针对操作数和结果数据的)累加器 |
RBX | (DS段的数据指针)基址寄存器 |
RCX | (字符串和操作循环的)计数器 |
RDX | (I/O)指针数据寄存器 |
RSI | (字符串操作源指针)源变址寄存器 |
RDI | (字符串操作目标指针)目的变址寄存器 |
RBP | (SS段中栈内数据指针)扩展基址指针寄存器 |
RSP | (SS段中栈指针)栈指针寄存器 |
R8~R15 |
补充
AL是AX寄存器的低八位
AH是AX寄存器的高八位
AX是EAX的低16位
汇编语言基本组成部分
整数常量
由三部分组成
可选前置符号(+/-) | 一个或多个数字 | 可选基数字符(指明基数) |
可选基数字符是指明进制类型,可选是因为没有基数的情况默认为十进制
十六进制 | h |
八进制 | q/o |
十进制 | d |
二进制 | b |
实数 | r |
例如:
26 ;十进制
3Ah ;十六进制
17o ;八进制
如图反汇编器生成的汇编代码中h基数结尾的表示16进制整数
整数常量表达式
运算符 | 名称 | 优先级 |
() | 圆括号 | 1 |
+,- | 一元加减 | 2 |
*,/ | 乘除 | 3 |
MOD | 取模 | 4 |
+,- | 一元加减 | 2 |
浮点数常量
real number literial
一个可选符号 | 一个整数 | 小数点 | 表示小数部分的一个整数 | 一个可选的指数 |
例如
5.
+2.0
26.E5
-78.2E+05
x86指令集是针对整数处理的,浮点数用的少
字符常量
用单引号和双引号包含的一个字符
"A"
'a'
使用ASCLL编码
"A"在内存中存放的形式65,41h
字符串常量
用单引号和双引号包含的一个字符串
"Hello world"
"Peter"
'Good'
可以用单引号嵌套双引号,也可以双引号嵌套单引号
"That's good"
'Say"Hello"'
保留字
有特殊意义并且只能在正确的上下文使用
- 指令助记符 例如ADD
- 寄存器名称
- 伪指令
- 属性 例如BYTE,DWORD
- 运算符
- 预定义符号
标识符
不区分大小写
第一个字符必须是字母,下划线,@,?,$
后面的字符可以是数字
伪指令
作用
定义宏,变量,子程序
由汇编器识别和运行,不在运行时执行
var dword 66 ;伪指令,为一个双字变量保留空间
mov eax,var ;指令,含义后面会讲
定义段
定义程序的段
例如
.data ;数据段,定义变量
.code ;code段包含了可执行的指令
.stack 100h ;定义了运行时的堆栈和大小
指令
标号
是一种标识符,是指令和数据的位置标记
数据标号
var DWORD 89 ;定义了一个变量var
代码标号
target:
mov ebp, esp
sub esp, 68h
push ebx
push esi
L1:mov ebp, esp
代码标号用于跳转和循环和目标
指令助记符
是标记一个指令的单词
例如ADD,MOV,JMP
注释
单行注释 使用;
;注释内容
块注释 使用COMMENT伪指令和一个用户定义的符号开始,相同的符号结束
COMMENT @
注释内容
@
汇编语言内部数据类型
BYTE 8 位无符号整数,B 代表字节
SBYTE 8 位有符号整数,S 代表有符号
WORD 16 位无符号整数
SWORD 16 位有符号整数
DWORD 32 位无符号整数,D 代表双(字)
SDWORD 32 位有符号整数,SD 代表有符号双(字)
FWORD 48 位整数(保护模式中的远指针)
QWORD 64 位整数,Q 代表四(字)
TBYTE 80 位(10 字节)整数,T 代表 10 字节
REAL4 32 位(4 字节)IEEE 短实数
REAL8 64 位(8 字节)IEEE 长实数
REAL10 80 位(10 字节)IEEE 扩展实数
数据定义语句例:count BYTE 10
数据传送指令,寻址指令,加减法指令
数据传送指令
MOV指令
将源操作数复制到目的操作数
MOV 目的操作数,源操作数
mov ebp, esp
逻辑相当于高级语言中的ebp=esp
XCHG指令
XCHG指令交换两个操作数的内容
XCHG eax,ebx
偏移量操作数
array BYTE 10h,20h,30h,40h,50h
mov eax,array ;eax=10h
mov eax,[array+1] ;eax=20h
mov eax,[array+2] ;eax=30h
NOP指令
是一个空操作
占用一个字节,不做任何操作,用于将代码对齐到有效的地址边界
加法指令和减法指令
INC指令
表示寄存器或内存操作数自减1
INC reg/men
DEC指令
表示寄存器或内存操作数自减1
DEC reg/men
ADD指令
将源操作数加在目的操作数上(可以是寄存器或者变量)
ADD eax,var1
SUB指令
将目的操作数减去源操作数,存在目的操作数中(可以是寄存器或者变量)
SUB eax,var1
NEG指令
取操作数的补码,存在操作数中
NEG eax
NEG var
与数据有关的运算符和伪指令
OFFSET运算符
返回一个变量与其所对在段起始地址之间的距离
ALIGN伪指令
将一个变量对齐到字节边界,字边界,双字边界或者段落边界
ALIGH bound
bound可以取值为1,2,4,8,16
定义后,变量地址将是bound的整数倍
PTR运算符
重写操作数默认的大小类型,必须和一个标准汇编数据类型一起使用
TYPE运算符
返回变量单个元素的大小
var1 BYTE ?
TYPE var1 ;值为1
var2 WORD ?
TYPE var2 ;值为2
LENGTHOF运算符
返回数组中元素的个数
SIZEOF运算符
返回数组占用的字节数
JMP和LOOP指令
JMP指令
无条件跳转到一个地址,这个地址可以使用代码标号
使用JMP可以创建循环
top:
...
jmp top
是一个死循环
LOOP指令
ECX计数器循环
ECX是计数器
第一步,ECX的数值减1
第二步,ECX与0比较,如果等于0循环终止
这个汇编程序会循环5次
汇编语言程序范例
.386伪指令表示这是一个32位程序,访问32位寄存器和地址
.model flat,stdcall表示内存模式是flat,子程序的调用规范是stdcall
.stack 4096 运行时堆栈保留了4096字节的存储空间
Exit是一个标准的Windows服务
main PROC表示要执行的第一个指令的位置
main ENDP表示一个过程的结束
END main表示汇编程序的结束
堆栈
堆栈数据结构
堆栈数据结构就是我们数据结构课中学到的栈
栈顶添加新元素,删除元素在栈顶删除
后进去先出FILO
本文要讲的是运行时堆栈
运行时堆栈
运行时堆栈是内存数组
ESP寄存器
ESP寄存器(extended stack pointer,扩展堆栈指针)
可以理解为抽象数据结构栈中指向栈顶的指针
存放某个位置的32位偏移量
基本上不会被程序员修改
用CALL,RET,PUSH,POP指令间接修改
入栈操作
32位入栈操作是把栈顶指针减4,再将数值复制到栈顶指针指向的位置
运行时堆栈在内存中是向下生长的,从高地址向低地址扩展
出栈操作
从堆栈删除元素,站定指针减小
PUSH指令
首先减少ESP的值,将操作数复制到堆栈
POP指令
将ESP指向的堆栈元素复制到一个操作数当中,增加ESP的值
PUSHFD指令
将32位EFLAGS寄存器的内容压入堆栈
POPFD指令
将栈顶元素弹出到32位EFLAGS寄存器
PUSHAD指令
按照EAX,ECX,EDX,EBX,ESP,EBP,ESI,EBI的顺序
将所有32位通用寄存器压入堆栈
POPAD指令
按照与PUSHAD相反的顺序将其弹出堆栈
过程
有过高级语言编程经验的话,我们知道为了增加一个程序的可读性,便于阅读,调试和合作,应该将程序封装成函数和方法
这些函数和方法就是一个子程序
在汇编语言中这些子程序叫做过程
过程是可以嵌套调用的
定义主过程
其他过程
全局标号
当跳转和循环时候
jmp destination
destination必须和JMP指令位于同一过程中
可以使用全局标号解决这一问题,在名字后面加双冒号::
CALL指令
调用一个过程
将返回地址压入堆栈,将被调用的过程地址复制到指令地址寄存器EIP
RET指令
结束一个过程调用
从堆栈将返回地址复制到指令地址寄存器EIP
向过程传递寄存器参数
在调用过程之前,将数值传递给寄存器
寄存器中保留传递的参数
条件处理
布尔操作和比较指令
AND指令,OR指令,XOR指令,NOT指令
分别将目的操作数与源操作数进行与,或,异或,非操作
将结果存放在目的操作数当中
TEST指令
进行与操作,更新CPU标志位
CMP指令
比较两个数字
隐含减法操作,但是不修改任何操作数
比较两个无符号数]
标志位在EFLAGS寄存器上
ZF是零标志位,CF是进位标志位,OF是溢出标志位
CMP结果 | ZF | CF |
目的操作数<源操作数 | 0 | 1 |
目的操作数>源操作数 | 0 | 0 |
目的操作数=源操作数 | 1 | 0 |
比较两个有符号数
CMP结果 | 标志位结果 |
目的操作数<源操作数 | SF不等于OF |
目的操作数>源操作数 | SF=OF |
目的操作数=源操作数 | ZF=1 |
我们可以看出当两个数字相等的时候,ZF是1
条件跳转
Jcond指令
JZ 为零跳转 ZF=1 JNO 无溢出跳转 OF=0
JNZ 非零跳转 ZF=0 JS 有符号跳转 SF=1
JC 进位跳转 CF=1 JNS 无符号跳转 SF=0
JNC 无进位跳转 CF=0 JP 偶校验跳转 PF=1
JO 溢出跳转 OF=1 JNP 奇校验跳转 PF=0
例
基于相等性的跳转,基于无符号数的跳转指令,基于有符号数比较的跳转
cmp leftOp,reghtOp
基于相等性的跳转
JE 相等跳转(leftOp = reghtOp)
JNE 不相等跳转(leftOp ≠ reghtOp)
JCXZ CX = 0 跳转
JECXZ ECX = 0 跳转
JRCXZ RCX = 0 跳转(64位模式)
基于无符号数的跳转指令
JA 大于跳转(若leftOp > reghtOp)
JNBE 不小于或不等于跳转(同JA)
JAE 大于或等于跳转(若leftOp ≥ rightOp)
JNB 不小于跳转(同JAE)
JB 小于跳转(若leftOp < reght)
JNAE 不大于或不等于跳转(同JB)
JBE 小于或等于跳转(若leftOp ≤ rightOp)
JNA 不大于跳转(同JBE)
基于有符号数比较的跳转
JG 大于跳转(若leftOp > reghtOp)
JNLE 不小于或不等于跳转(同JG)
JGE 大于或等于跳转(若leftOp ≥ rightOp)
JNL 不小于跳转(同JGE)
JL 小于跳转(若leftOp < reght)
JNGE 不大于或不等于跳转(同JL)
JLE 小于或等于跳转(若leftOp ≤ rightOp)
JNG 不大于跳转(同JLE)
整数运算
移位指令
算术移位和逻辑移位
逻辑移位是对无符号数进行的,左移和右移都是补零
算术移位是对有符号数进行的,空出来的符号位用原数据的符号位填充
SHL指令
逻辑左移指令,将目的操作数顺序左移1位或CL寄存器中指定的位数。左移一位时,操作数的最高位移入进位标志位CF,最低位补零。
SHR指令
逻辑右移指令,SHR指令将目的操作数顺序右移1位或CL寄存器指定的位数。逻辑右移1位时,目的操作数的最低位移到进位标志位
,最高位补零。SAL指令
算术左移指令,最高位移入CF,最低位补0。
SAR指令
算术右移指令
ROL指令
所有位都向左移,最高位复制到CF和空出来的最低位
ROR指令
所有位都向右移,最低位复制到CF和空出来的最低位
RCL指令
带进位循环左移,CF移动到空出来的最低位,最高位移动到CF
RCR指令
带进位循环右移,最低位移动到CF,CF移动到空出来的最高位
SHLD指令
双精度左移
SHLD dest,source,count
源操作数的最高位移动到目的操作数的最低位,目的操作数都左移,目的操作数的最高位移动到CF
SHRD指令
双精度右移
SHLD dest,source,count
源操作数最低位移动到目的操作数空出来的最高位,目的操作数最低位移动到CF,其余位右移
乘法指令
MUL指令(32位模式)
32位机中有三种乘法,一种乘法示例:
只有一个操作数,实现一个8位操作数和AL寄存器的乘法,存入AX
AL是AX寄存器的低八位
AH是AX寄存器的高八位
AX是EAX的低16位
范例
当AX的高半部分是0的时候,CF是0
被乘数 | 乘数 | 乘积 |
AL | reg/mem8 | AX |
AX | reg/mem16 | DX:AX |
EAX | reg/mem32 | EDX:EAX |
MUL指令(64位模式)
新增的类型
被乘数 | 乘数 | 乘积 |
RAX | reg/mem64 | RDX:RDX |
IMUL指令(32位模式)
有符号数乘法
X86支持三种类型的IMUL指令:
单操作数,双操作数,三操作数
单操作数格式与MUL一样
被乘数 | 乘数 | 乘积 |
AL | reg/mem8 | AX |
AX | reg/mem16 | DX:AX |
EAX | reg/mem32 | EDX:EAX |
双操作数格式
将两个操作数的乘积放在第一个操作数当中,第一个操作数必须是寄存器
三操作数格式
第一个操作数必须是一个16位寄存器
第二个操作数是一个16位置寄存器或者内存操作数
第三个操作数是一个8位或者16位的立即数
或者
第一个操作数必须是一个32位寄存器
第二个操作数是一个32位置寄存器或者内存操作数
第三个操作数是一个16位或者32位的立即数
IMUL指令(64位模式)
同MUL一样添加了64位数相乘的情况
DIV指令(32位模式)
被除数 | 除数 | 商 | 余数 |
AX | reg/mem8 | AL | AH |
DX:AX | reg/mem16 | AX | DX |
EDX:EAX | reg/mem32 | EAX | EDX |
DIV指令(64位模式)
被除数 | 除数 | 商 | 余数 |
RDX:RAX | reg/mem64 | RAX | RDX |
IDIV指令指令
有符号数除法
篇幅有点长,后续放在下一篇文章
- 点赞
- 收藏
- 关注作者
评论(0)