8086汇编语言
1.8086汇编语言
部分寄存器对照(8086和win32(对应x86))
通用寄存器:
ax:累加器,用于算数和逻辑操作 eax:和8086相同
bx:基址寄存器,存储内存地址 ebx:和8086相同
cx:计数器,用于循环和计数操作 ecx:相同
dx:数据寄存器,用于存储数据和i/o操作 edx:相同
段寄存器:(两者同名同作用)
cs:代码段寄存器,存储指令代码的段地址
ds:数据段寄存器,存储数据的段地址
ss:堆栈段寄存器:存储堆栈的段地址
es:附加段寄存器:可用于存储额外的数据段地址
指针寄存器:
si:源索引寄存器:用于字符串操作 esi:相同
di:目的索引寄存器:用于字符串操作 edi:相同
基址指针寄存器:
bp:基址指针寄存器,指向当前栈帧的基址 ebp:相同
栈指针寄存器
sp:指向当前栈顶的地址 esp:相同
指令指针寄存器
ip:存储下一条要执行的指令的地址 eip:相同
导入
为什么学习汇编?
- 开发软件的核心部件,快速执行和实时响应
- 涉及底层,如操作系统内核和嵌入式
- 加深对计算机原理和操作系统的理解
- 底层的调试和分析
要学的内容:8088\8086指令集(现在的计算机系统过于复杂,直入x86难度高,以简单的先引入)
学习的方法:
- 贯穿实践
- 观察机器内部状态
- 学习进程
机器指令到汇编指令
机器语言是机器指令的集合
指令是机器可直接执行的命令,由一串二进制数表示
汇编指令是机器指令的助记符(便于记忆的书写格式)
不用了解具体电子结构
使用汇编的工作流程
- 编写汇编指令
- 通过编译生成可执行文件
- 直接执行
汇编语言:汇编指令,伪指令,和其他符号
计算机组成
1.主板
其中有cpu,总线,内存条,扩展槽(接外部设备).总线分为地址总线,数据总线,和控制总线
2.存储指令
指令和数据都存储在内存和磁盘中,开始运行时由外存到内存,表示数据和指令时多用十六和十进制..
3.存储单元
从0开始编号,FFFFF对应1mb的最后一位
4.总线
对应芯片的引脚,链接cpu和其他芯片
地址总线随cpu性能提高而增宽,决定可寻址的存储单元大小,
cpu对内存读写
三类信息交互:存储单元的地址,器件的选择(读或写命令),读或写的数据
对应着地址信息,控制信息,数据信息
MOV AL,[3]//从三号单元读取数据送入寄存器AL
首先由地址线把要读的三号单元,由地址线传到内存
再由控制线从cpu发出读的信号
最后由数据线从内存处读出三号单元
内存地址空间
8086为20宽的地址总线,可寻址1mb的内存单元,内存空间为1mb
从cpu的角度看,地址空间包含ram(随机,能读写,易失数据,动态)和rom(只读,静态)(如今也可擦除了),它们统一编址,把两种看成一个逻辑存储器,每一个物理存储器都在逻辑存储器中占有一个地址段,即一段地址空间
操纵对应地址空间可实现对应功能(如显卡和显示内容相关)
内存地址空间的分配,以8086pc机为例
在各个机器内应有该内容
实践,环境搭建,常用命令
在dos系统环境下,最好的是dos模拟器
命令:(不包含已有可执行文件)
- dir:查看当前文件目录
访问寄存器和内存
寄存器及信息存储
cpu由运算器,寄存器和控制器组成
运算器进行信息处理,寄存器进行信息存储,控制器进行器件协调工作总线实现cpu内各器件的联系
寄存器(8086)(16位两字节)(16位字长 )
通用寄存器:AX,BX,CX,DX
16位最大可存2的16次方减一
例:存储18D,转为12H,即10010B(前面全0)
变址寄存器:SI,DI,
指针寄存器:SP,BP
指令指针寄存器:IP
段寄存器:CS,SS,DS,ES
标志寄存器:PSW
寄存器从8位到16位的兼容性8086中把AX分为AH(高位)和AL,平分使用(bx,cx,dx一样)
mov和add指令
mov ax,78//ax=78
mov ah,18//ah=18
add ax 8//ax=ax+8
add ax,bx //ax=ax+bx
低位相加溢出时,不会给到高位
确定物理地址的方法(地址加法器)
每一个内存单元都有唯一的物理地址,存储空间构成一个一维线性的空间:
8086中有20位的地址总线,寻址能力1m
8086是16位结构的cpu
运算器最多可以处理16位的数据,寄存器最大宽度为16位
在8086内部处理的,运输的,暂存的地址也是16位,寻址能力只有64kb(矛盾)
解决方法:用两个16位地址:段地址和偏移地址合成一个20位的物理地址.即:
物理地址=段地址*16(此时16位+4位)+偏移地址
物理地址对应的段地址不唯一
偏移地址为汇编程序直接使用的地址
相关拓展博客:物理地址和逻辑地址
地址分段管理内存
内存没有分段,cpu划分段,同一段内存可有不同分段方案.
debug使用
调试,观察cpu内存和寄存器情况
命令:
- R:查看改变cpu寄存器内容
- D:查看内存内容
- E:改变内存内容
- U:将内存中的机器指令改为汇编指令
- A:以汇编指令的形式在内存中写入机器指令
- T:执行机器指令
多多实操
cs和ip关键寄存器
cs:代码段寄存器
ip:指令指针寄存器
任意时刻,cs和ip指向的内容都将被cpu当作指令执行(提供cpu要使用的所有指令)
ffff0h为8086pc机开机后的第一条指令
修改cs,ip指令
使用比如mov的传送指令,可改变大多数寄存器的值(cs,ip除外)
可改变cs和IP的都被称为转移指令,如jmp
若想同时修改cs和ip的内容,可使用”jmp 段地址:偏移地址”的指令
这将使cs的值等于段地址,ip的值等于偏移地址
只改变ip的内容,则可用形如”jmp 某一合法寄存器”的指令完成(即不改变cs的值,将被选中寄存器的值赋给ip)
2.寄存器和内存访问
内存中字的存储
cpu中使用16位寄存器存储一个字,高8位存放高位字节,低8位存放低位字节
而在内存中,一个字要用两个地址连续的内存单元存储,低位存储在低地址单元,高位字存储在高地址单元中
字单元的概念:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成
将起始地址为N的字单元简称为N地址字单元,如2地址字单元由2,3两个内存单元组成
DS和[address]
mov bx,1000H
mov ds,.bx
mov al,[0]
上面三条命令将内存10000H中的数据读到al中
ds的值将自动被8086取为内存的段地址,而[内]表示内存单元的偏移地址
反之则可将字存入内存中
字的传送
mov ax,data(一个字)//ax可换为任意一个16位寄存器或内存字单元
mov,add,sub指令
已知mov指令的使用方法:
MOV:
- 寄存器,(数据/寄存器/内存单元)
- 内存单元,寄存器
- 段寄存器,寄存器
推测:
mov 寄存器,段寄存器
mov 内存单元,段寄存器
- mov 段寄存器,内存单元
对add和sub也可推出并使用debug验证
数据段
可根据需要将一组内存单元定义为一个段,如123b0H~123b9H,定义了一个段地址为1232BH,长度10字节的数据段
栈
相关指令:push,pop
相关寄存器:ss,sp
- push指令的使用(push ax为例)
- sp=sp-2,指向新栈顶
- 将ax的值赋给ss:sp的内存单元
- pop指令的使用(pop ax为例)
- ss:sp的值赋给ax
- sp+=2
栈顶越界
8086cpu无记录栈底和栈顶的寄存器,无法保证不会越界
栈段
push和pop仅改变sp
而sp取值范围为0~FFFFH(64KB)
若sp=0时仍然压栈,栈顶将环绕
3.汇编程序的编写
程序编写过程
大体和高级语言相同(编译\链接\可执行+描述)
使用的代码分为伪指令和汇编指令
伪指令是由编译器执行,用于执行编译工作
伪指令例子:
XXX segment&XXX ends
这是在写可被编译的汇编程序时,必须用到的一组伪指令,用于定义一个段来存放代码,一个有意义的汇编程序必有至少一个段
end
该指令标记着汇编程序的结束
assume
将某一段寄存器和程序中的某一个segment定义的段相关联
debug a指令可执行简单的汇编程序
注释:使用;在一行末尾,;后即注释
报错:语法和逻辑错误
形成可执行文件
文本->源程序.asm->目标文件.obj->可执行文件.exe
- 编译
- 链接
- 运行
编译
指定目标文件(.obj),列表文件(.lst),交叉引用文件(.crf)
编译时如果没有warnin errors和severe errors,则完美通过编译
具体报错:
链接
debug装载文件
debug 可执行.exe
常用命令:
- p命令:类似t,逐条执行,但在子程序,中断时,将直接执行然后显示结果
- g命令:从 指定地址处开始运行程序,直到遇到断点或 程序正常结束
[…]和(…)的约定
以及idata约定为常量
LOOP指令
类似goto和go-while
mov cx,11
s: add ax,ax
loop s
此时执行loop的操作:
- (cx)=(cx)-1
- 判断cx的值,非0则循环,0则向下执行
debug和汇编编译器对masm对指令的不同处理
example:
mov ax,[0];表示将ds:0处的数据传入ax中(ax)=((ds)*16+0)
mov ax,0;实际运行的代码(ax)=0
为避免以上歧义情况,应在[0]前加上段前缀ds:
8位数据在16位寄存器上的计算
提出问题:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中.
考虑的情况:
会溢出嘛?
12个8位数据累加不会溢出
可否直接累加进入dx中?
8位数据无法直接加到16位寄存器中
可否将每一个累加数先存入al中,在设ah=00H,执行累加?
程序过于冗长
可否使用loop
每个循环中使用的命令如下
mov al,ds:[X] ;X递增
mov ah,0
add dx,ax
使用循环表示:
mov cx,12 ;此之前ds=ffffH,bx=0,dx=0
s: mov al,[bx] ;最好表示为ds:[bx]
mov ah,0
add dx,ax
inc bx
loop s
一段安全的空间
8086中随意写入一段内存空间是很危险的,因为此时此段空间内可能存有重要的系统数据或代码
mov ax,1000H
mov ds,ax
mov al,0
mov ds:[0],al
此时1000段上的数据未经验证,可能将引发错误
8086cpu仍然可操作本应该收到保护的系统数据,但在现有的系统中的cpu保护模式下,不理会操作系统,直接操作真实的硬件是不可能的.因此我们应当使用安全分配的内存,比如dos系统中因避免使用0:200~0:2ff空间的内存.
4.包含多个段的程序
在操作系统允许的情况下,程序可取得任意容量的空间.
程序取得所需的空间有两个方法,其一是在加载程序的时候为其分配,再一则是程序在执行时向系统申请.如果要一个程序在被加载时取得空间,需要在源程序中进行说明,即所谓定义段
在代码段中使用数据
程序第一行的dw含义是定义字型数据.(define word),此处定义了八个字型数据(以逗号分隔),共占16字节
他们的地址在cs中,从0开始,即cs:0,cs:2~cs:e,代码段中的代码则改为从16之后开始(执行时需要说明)
start将指定cs:ip所指向的第一行指令,所以start之前可以存放数据,
start属于可执行文件的描述信息,应用于编译,链接程序对源程序中相关伪指令的处理所得到的信息
同样的end也可指明cpu从何处开始执行程序
在代码中使用栈
应在数据区域提前用dw申请内存,用来指明栈的段内存和栈空间大小,再将ss和sp使用寄存器正确赋值
将数据,代码,栈放入不同的段
5.灵活地定位内存地址的方法
处理字符问题
汇编程序中,用”……”是方式指明数据是以字符的方式给出的,编译器将把他们转为响应的ascii码
此段代码中,’a’变成可赋值的数据
大小写的区别在6位是否为1(加减32)
如果要统一转为大写或小写,则需要位与(and)运算,将第6位取为0或1
即and dest,src(取1101 1111时为大写)
同样有位或运算,or dest,src(取0010 0000时为小写)
[(bx)+idata]方式寻址
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
以上指令表示相同含义,即(ax)=(ds)*16+200+(bx)
非常类似c中的数组(实际上区别不大)
尝试使用以上技巧处理字符串??
si和di变址寄存器
si:source index,源变址寄存器
di:destination index,目标变址寄存器
si和di不能分成两个8位寄存器使用
[bx+si],[bx+di]
猜猜小标题是什么意思?
这种方法称为基址+变址的寻址方法
[bx+si+idata],[bx+di+idata]
dup指令
重复定义一系列数据的伪指令
db 3 dup (0) ;三个字节的0
db 3 dup (0,1,2) ;九个字节,由0,1,2重复三次
db 3 dup (‘abc’,’ABC’) ;定义18个字节,构成‘abcABC’*3
即db 重复的次数 dup 重复的字节型/字型/双字符数据
6.流程转移和子程序
转移!
分为 段内转移和段间转移(只修改ip或同时修改cs,ip)
分为段内短转移和近转移(范围分别为-128,127或-32768,32767)
分为条件转移、无条件转移、循环、过程、中断等
offset取得标号的偏移地址
可通过mov和offset配合做到复制某处的指令
jmp指令
无条件转移,可以只修改ip,也可同时修改cs和ip
用伪指令标记段内指令地址时,底层是用立即数实现的
assume cs:codesg
codeseg segment
start: mov ax,0
jmp short s
add ax,1
s: inc ax
codesg ends
end start
dbg对应数据如图
左侧机器码(EB03)中传入jmp的值为03,实际上跳转的地址为03+下一条将执行的ip偏移地址05=跳转到的08(在dbg中被最终显示)
远转移:jmp far ptr s ;对应机器码实例:EA+016A+07(目标地址[016A+cs]:07+ip)
转移地址在寄存器中的jmp指令
如jmp ax
其他跳转
jcxz指令、loop(在32位以上很少出现,依赖cx的值进行条件跳转)
flag寄存器
内中断
cpu具备中断能力,即当检测到外部或内部的一种特殊信息后,立即对此信息进行处理的能力。对于8086cpu内部,有以下中断信息:
- 除法错误,如div指令产生的除法溢出
- 单步执行
- 执行into指令
- 执行int指令
对应中断类型码:0、1、4、传入int 指令的值
端口
(施工ing)