前言
感觉汇编挺有意思的,平时写代码的时候没考虑这么多,以下示例皆为8086CPU
基础知识
存储器
8bit(位)=1Byte(字节)
2Byte=1word
1024Byte(字节)=1KB
存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号,大小为1bit,表现为2位的16进制数
为方便调用,8086CPU在内部用段地址和偏移地址最终形成物理地址即
段地址*16+偏移地址=物理地址
总线
CPU要对数据进行读写,必须和外部器件进行以下三类信息的交互:
1、存储单元的地址(地址信息);
2、器件的选择、读或写命令(控制信息);
3、读或写的数据(数据信息) 。
总线是连接CPU和其他芯片的导线,逻辑上分为地址总线、数据总线、控制总线。
CPU从内存单元中读写数据的过程:
1、CPU通过地址线将地址信息发出;
2、CPU通过控制线发出内存读命令,选中存储器芯片,并通知它将要从中读或写数据;
3、存储器将相应的地址单元中的数据通过数据线送入CPU或CPU通过数据线将数据送入相应的内存单元。
8086外部数据总线宽度为16位,它既能处理16位数据,也能处理8位数据。可寻址的内存空间为1mb。
CPU是通过地址总线指定存储单元,地址总线传送的能力决定了CPU对存储单元的寻址能力。
8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力,但8086CPU内部数据总线宽度是16位,表现出来的寻址能力只有64kb。8086CPU采用一种在内部用用两个16位地址合成的方法来形成一个20位的物理地址。
寄存器
寄存器是cpu完成操作的载体
8086 CPU 中寄存器总共为 14 个,且均为 16 位。
即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。而这 14 个寄存器按照一定方式又分为了通用寄存器,控制寄存器和段寄存器。
通用寄存器
通用寄存器的每一个16位寄存器就可以当做2个独立的8位寄存器来使用,如AX寄存器可以分为两个独立的8位的AH和AL寄存器;
- AX (Accumulator)——累加寄存器,也称之为累加器;
AX寄存器还具有的特殊用途是在使用DIV和MUL指令时使用,
在mul使用时al或ax作为被乘数,积保留在ax或积低16位保留在ax
在div使用时ax作为被除数或被除数后16位,商保留在al或ax - bx——based register——基地址寄存器
BX主要还是用于其专属功能–寻址(寻址物理内存地址)上,BX寄存器中存放的数据一般是用来作为偏移地址使用的。 - cx——count register——计数器
当在汇编指令中使用循环LOOP指令时,可以通过CX来指定需要循环的次数 - dx——data registered——数据寄存器
dx可与ax配合使用,常作为ax的扩展
在执行MUL指令时,如果两个相乘的数都是16位的话,那么相乘后产生的结果显然需要32位来保存,而这32位的结果的高16位就是存放在DX寄存器中。
如除数为16位时,被除数将会是32位,而被除数的高16位就是存放在DX中,而且执行完DIV指令后,本次除法运算所产生的余数将会保存在DX中段寄存器
cs——code segment——代码段
ds——data segment——数据段
ss——stack segment——栈段寄存器
es——extra segment——附加段寄存器变址寄存
si——source index——源变址寄存器
di——destination index——目的变址寄存器指针寄存器
ip——instructor point——指令指针寄存器
sp——stack point——堆栈指针寄存器
bp——base point——基础指针状态标志寄存器
OF(11位-overflow flag-溢出标志位)——OV(overflow-溢出)——NV(not overflow-没溢出)
DF(10位-direction flag-方向标志位)——DN(down-下方)——UP(up-上方)
IF(9位-interrupt flag-中断标志位)——EI(enable interrupt-允许中断)——DI(disabled interrupt-不允许中断)
TF(8位-trap flag-陷阱标志位)——
SF(7位-sign flag-负号标志位)——NG(negative-负)——PL(plus-正)
ZF(6位-zero flag-零值标志位)——ZR(zero-为零)——NZ(not zero-不为0)
AF(4位-auxiliary carray flag-辅助进位标志位)——AC(auxiliary carry-有辅助进位)NA(not auxiliary carry-没有辅助进位)
PF(2位-parity flag-奇偶标志位)——PE(parity even-偶)——PO(parity odd-奇)
CF(0位-carry flag-进位标志位)——CY(carried-有进位)——NC(not carried-没进位)
汇编指令
数据传送指令
数据传送指令可以利用立即数和寄存器来完成存储单元的数据传输,没有程序转移的功能,不能改变cs:ip的值
通用数据传送指令。
MOV 传送指令 MOV DEST,SRC
XCHG 交换指令 XCHG OPER1,OPER2堆栈和堆栈操作指令。
PUSH 进栈指令 PUSH SRC
POP 出栈指令 POP DEST目的地址传送指令。
LEA 取偏移地址指令 LEA REC,OPRD
LDS 取指针内容指令 例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 取指针内容指令 例: LES DI,string ;把段地址:偏移地址存到ES:DI.符号拓展指令
CBW 字节转化为字指令 把寄存器AL中的值符号拓展到寄存器AH
CWD 字转化为双字指令 把寄存器AX中的值符号拓展到寄存器DX算术运算指令
加减
ADD 加法指令 ADD DEST,SRC DEST<=DEST SRC 两数相加
SUB 减法指令 SUB DEST,SRC DEST<=DEST-SRC 两数相减
INC 加1指令 INC DEST DEST<=DEST 1
DEC 减1指令 DEC DEST DEST<=DEST-1
ADC 带进位加法指令 ADC DEST,SRC DEST<=DEST SRC CF 与add指令不同之处是要再加上进位标志cf的值
SBB 带借位减法 SBB DEST,SRC DEST<=DEST-(SRC CF) 与sub指令不同之处是要再减上借位标志cf的值乘除
MUL 无符号数乘法指令
IMUL 有符号数乘法指令
DIV 无符号数除法指令
IDIV 有符号数除法比较
CMP 比较指令 CMP DEST,SRC 根据dest-src的差影响各状态标志寄存器 不把dest-src的结果送入dest相反数
NEG 取补指令 NEG OPRD OPRD=0-OPRD 对操作数取补(相反数)逻辑运算指令
逻辑运算
NOT 否运算指令 NOT OPRD 把操作数OPRD按位取反,然后送回OPRD AND 与运算指令 AND DEST,SRC 把两个操作数进行与运算之后结果送回DEST 同1得1,否则得0 OR 或运算指令 OR DEST,SRC 把两个操作数进行或运算之后结果送回DEST 同0得0,否则得1 XOR 异或运算 XOR DEST,SRC 把两个操作数进行异或运算之后结果送回DEST 相同得0不同得1 TEST 测试指令 TEST DEST,SRC 将两个操作数进行按位AND,设结果是TEMP,但是结果不送回DEST,仅影响状态位标志,指令执行后,ZF、PF、SF反映运算结果,CF和OF清零 如:TEMP是0,ZF位置1,如果TEMP不是0,ZF位置0 SAL 算术左移 SAL OPRD,count 把操作数oprd左移count位,右边补0 与shl指令一样 通过截取count的低5位,实际的移位数被限于0到31之间。 SHL 逻辑左移 SHL OPRD,count 把操作数oprd左移count位,右边补0 与sal指令一样 通过截取count的低5位,实际的移位数被限于0到31之间。 SAR 算术右移 SAR OPRD,count 把操作数oprd右移count位,同时每右移一位,左边补符号位,移出的最低位进入标志位CF 通过截取count的低5位,实际的移位数被限于0到31之间。 SHR 逻辑右移 SHR OPRD,count 把操作数oprd右移count位,左边补0,移出的最低位进入标志位CF 通过截取count的低5位,实际的移位数被限于0到31之间。 ROL 左循环移位指令 ROL OPRD,count 通过截取count的低5位,实际的移位数被限于0到31之间。 ROR 右循环移位指令 ROR OPRD,count 通过截取count的低5位,实际的移位数被限于0到31之间。 RCL 带进位左循环移位 RCL OPRD,count 相当于CF在最高位参与循环移位 大循环左移 通过截取count的低5位,实际的移位数被限于0到31之间。 RCR 带进位右循环移位 RCR OPRD,count 相当于CF在最高位参与循环移位 大循环右移 通过截取count的低5位,实际的移位数被限于0到31之间。
程序转移指令
转移指令
JMP 无条件段内直接转移指令 JMP LABEL 使控制无条件地转移到标号为label的位置 无条件转移指令本身不影响标志 JA/JNBE > JAE/JNB ≥ JB/JNAE < JBE/JNA ≤ 以上四条,测试无符号整数运算的结果(标志C和Z)。 JG/JNLE > JGE/JNL ≥ JL/JNGE < JLE/JNG ≤ 以上四条,测试有符号整数运算的结果(标志S,O和Z)。 JE/JZ ZF=1 JNE/JNZ ZF=0 JC CF=1 JNC JO OF=1 JNO JS SF=1 JNS
- 循环指令
LOOP 计数循环指令 LOOP LABEL 使CX的值减1,当CX的值不为0的时候跳转至LABEL,否则执行LOOP之后的语句
- 子程序指令
CALL 过程调用指令 CALL LABEL 段内直接调用LABEL 与jmp的区别在于call指令会在调用label之前保存返回地址(call 中return之后主程序还可以继续执行,jmp 当label执行完毕后不能返回主程序继续执行) RET 段内过程返回指令 RET 使子程序结束,继续执行主程序
变量属性
SEG 得到变量名或标号名的段基址
OFFSET 得到变量名或标号名的偏移量
TYPE 在变量名前,返回值为1(字节)、2(字)、4(双字)
LENGTH 用在变量前面,对于变量使用DUP进行定义的情况,汇编程序将回送分配给该变量的单元数。对于其他情况则回送1
SIZE 用在变量前面,汇编程序将回送分配给该变量的字节数,值为 LENGTH 与 TYPE 值的乘积
PTR 指定类型汇编程序
一个汇编语言程序从写出到最终执行的简要过程:
编写 -> 编译连接 -> 执行
在Win 64位系统下实现Debug汇编的方法。
1 | mount c d:\ 回车 |
如果源程序没有错误,则自动生成.OBJ 文件和.EXE 可执行文件。
结构
1 | assum cs:codesg |
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。segment和ends的功能是定义一个段,segment说明一个段开始,ends 说明一个段结束。
assume对除了CS以外的其它段寄存器,仅仅只是关联了段名,以便在访问段内变量时程序可以知道用哪个段寄存器,并没有在程序加载时将段地址装入段寄存器。所以,将段地址装入段寄存器的工作,必须由用户在程序中自己编写代码,并在程序开始运行时执行代码完成装入工作。
内存并没有分段,段的划分来自于CPU。由于8086CPU用“基础地址(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
数据段:存放数据的段。使用时候,用DS寄存器。
程序段:用来存放程序的段。使用的时候,用CS和IP寄存器。
栈段: 是一个栈。使用时,初始设置SS和SP寄存器。
输入
字符输入
mov ah, 1;输入后,收到的值存放在AL中
int 21h字符串输入
BUFFER DB 20 ;预定义20字节的空间DB ? ;待输入完成后,自动获得输入的字符个数 DB 20 DUP(0) LEA DX,BUFFER ;接收字符串 MOV AH, 0AH INT 21H
输出
字符输出
mov ah,02h mov dl,0dh ;回车键 int 21h mov ah,02h mov dl,0ah ;换行键 int 21h
字符串输出
INPUT DB “Please input a string: “,’$’LEA DX, INPUT ;打印提示输入信息 MOV AH, 09H INT 21H