8086汇编学习笔记
1. 基础知识
- 1.1 机器语言:机器指令的集合,机器可以正确执行的指令
- 1.2 汇编语言是机器指令便于记忆的书写形式,寄存器是CPU中存储数据的器件
- 1.3 汇编语言的组成:汇编指令+伪指令+其他符号;汇编语言的核心是汇编指令,其决定了汇编语言的特性
- 1.4 存储器:存储指令与数据、即所谓内存;数据或程序不读入内存就无法被CPU使用
- 1.5 指令和数据:没有区别,只是应用上的概念不同
- 1.6 存储单元:存储器被划分为若干个存储单元(0~n-1)
- 1.7 CPU读写存储器需要得知三个信息,即存储单元的地址(地址信息)、器件选择和读写命令(控制信息)、读或写的数据(数据信息),分别由总线中的地址总线、数据总线、控制总线提供。
- 1.8 地址总线:CPU通过地址总线指定存储单元,CPU有N根地址总线,即地址总线宽度为N,可寻址2n个内存单元
- 1.9 数据总线:CPU通过数据总线传送数据,数据总线宽度决定了CPU和外界的数据传送速度
- 1.10 控制总线:CPU通过控制总线控制外部器件、有几条控制总线就可以控制几个部件
- 1.11 内存地址空间:2n可寻址的内存空间
- 1.12 主板:PC上的主板
- 1.13 接口卡:CPU通过接口控制设备
- 1.14 各类存储器芯片:根据读写属性可分为随机存储器(RAM)和只读存储器(ROM),根据功能和连接可分为随机存储器(RAM)、装有BIOS的ROM和接口卡的RAM
- 1.15 内存地址空间在物理上各组件独立,但在逻辑上都与CPU总线相连,CPU通过控制线进行控制,对于8086CPU而言,00000-9FFFF为主存储器地址空间(RAM)、A0000-BFFFF为显存地址空间、C0000-FFFFF为各类ROM地址空间
2. 寄存器(CPU工作原理)
CPU由运算器、控制器和寄存器等组成,8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。8086CPU寄存器都是16位的,可存放两个字节
- 2.1 通用寄存器:AX、BX、CX、DX,一个16位寄存器可以存储一个16位的数据,最大为216-1,AX可分为AH(8-15)和AL(0-7),BX、CX、DX也是。
- 2.2 字在寄存器中的存储:1word=2Byte=16bit
- 2.3 汇编语言不区分大小写,能看懂简单的汇编语言
- 2.4 物理地址:所有的内存单元构成的存储空间是一个一维的线性空间,即物理地址
- 2.5 16位结构CPU:运算器一次最多可以处理16位的数据,寄存器的最大宽度为16位,寄存器与运算器之间的通路是16位
- 2.6 8086给出物理地址:20位地址总线,传送20位地址,可寻址220=1M;内部为16位结构,只能传送16位地址,寻址能力为216=64K。解决此冲突的方式:合并两个16位为一个20位(段地址+偏移地址),物理地址=段地址*16+偏移地址
- 2.7 物理地址=段地址*16+偏移地址=基础地址+偏移地址
- 2.8 段的概念:内存没有分段,段的划分来自于CPU,段就是连续的内存单元,注意以下几点,段的起始地址一定是16的倍数,一个段的最大长度为64,CPU可以用不同的段地址和偏移地址形成同一物理地址
- 2.9 段寄存器:8086有4个段寄存器:CS、DS、SS、ES
- 2.10 CS与IP:CS为代码段寄存器,IP为指令指针寄存器。
- 2.11 CPU执行过程如下:(1)从CS:IP中所指内存单元读指令存入指令缓冲器(2)IP=IP+指令长度(3)执行指令并转入步骤(1)
- 2.12 初始时,CS=FFFFH、IP=0000H;FFFF0H中的指令是8086PC开机后执行的第一条指令,CPU将CS和IP中的内容当作段地址和偏移地址合并为物理地址
- 2.13 修改CS:IP的指令:MOV不能修改CS、IP的值,修改使用JMP指令
- 2.14 代码段:将一组内存单元定义为一个段,CPU只认CS:IP指向的内存单元中的内容为指令
3. 寄存器(内存访问)
- 3.1 内存中字的存储:区分字和字节;大端序为高位低地址低位高地址,小端序为高位高地址低位低地址
- 3.2 DS和[address]:CPU要读取内存单元,必须给出地址,DS通常存放要访问的数据的段地址;执行指令时,8086CPU会自动取DS中的数据为内存单元地址,8086CPU不支持将数据直接送入段寄存器
- 3.3 字的传送:8086有16根数据线,可一次传送16位数据,即一次性传送一个字
- 3.4 MOV、ADD、SUB指令的详细用法
- 3.5 数据段:根据需要,将一组内存单元定义为一个段,数据段的起始地址为16的倍数,长度为n(n<=64K),数据段的访问需要借助DS来进行
- 3.6 栈:先入后出,后入先出,基本操作是入栈和出栈
- 3.7 CPU提供的栈机制:PUSH是压栈、POP是出栈,8086CPU的入栈和出栈都是以字为单位的,栈是从高地址(栈底)到低地址(栈顶)增长的,8086CPU中,段寄存器ss存放的是栈顶的段地址,sp存放的是栈顶的偏移地址。在任意时刻,SS:SP始终指向的是栈顶元素
- 3.8 栈顶超界问题:栈满push或者栈空pop都会导致溢出,8086CPU未检查溢出问题
- 3.9 push和pop:可以在寄存器和内存之间进行数据传送;push入栈,即改变sp,后向ss:sp传送;pop出栈,向ss:sp传送,改变sp
- 3.10 栈段:将长度为n(n<=64K)的一组地址连续、起始地址为16的倍数的内存单元当作栈使用,即栈段
4. 第一个程序
- 4.1 一个源程序从写出到执行的过程:编写->编译链接->执行;可执行文件包含两部分:(1)程序和数据,(2)相关的描述信息
- 4.2 源代码:汇编指令+伪指令
1
2
3
4
5
6
7
8
9
10assume cs:codesg
codesg segment
start: mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end start - 4.3 编写源程序
- 4.4 编译:masm 1.asm
- 4.5 连接:link 1.obj
- 4.6 简化编译连接:ml 1.asm
- 4.7 执行exe:1.exe或1
- 4.8 可执行文件中的程序装入内存并运行的原理:(1)主进程将程序1.exe装入内存;(2)CS:IP指向程序1.exe的入口;(3)1.exe执行完成,返回主进程
- 4.9 exe文件前面有一段PSP占用256字节
5. [BX]和loop指令
- 5.1 [bx]:bx中存放的数据作为偏移地址EA、段地址SA放在ds中,将SA:EA处的数据送入ax中
- 5.2 loop:格式:loop 标号;执行过程为:(cx)=(cx)-1,判断cx的值,不为零则跳至标号处,为零则向下执行。循环要点如下:cx寄存器中存放循环次数,loop标号先定义后使用,循环执行的程序段,写在标号和loop指令之间
1
2
3mov cx,循环次数
s:循环执行的代码段
loop s - 5.3 Debug跟踪用loop指令实现循环
- 5.4 debug与msam对指令处理的不同
- 5.5 loop和[bx]联合使用来处理地址连续的内存单元
- 5.6 段前缀:默认ds为段前缀,但也可进行显式指定
- 5.7 一段安全的空间:8086直接写入数据有可能会破坏掉其他程序数据;检查方式:DOS模式下用debug查看0:200-0:2FF,如果数据为0则程序是安全的
- 5.8 段前缀的使用:可以用于优化计算过程
6. 包含多个段的程序
- 6.1 在代码段中时使用数据:dw=defind word;end:通过编译器的结束位置及入口位置
- 6.2 在代码段中使用栈:定义dw作为栈使用
- 6.3 将数据、代码、栈放入不同段
1
2
3
4
5
6
7
8
9
10
11
12
13assume cs:code,ds:date,ss:stack
data segment
xxx
data ends
stack segment
xxx
stack ends
code segment
start:xxx
code ends
end start
7. 更灵活定位内存地址
- 7.1 and和or指令:and全1才1,or有1则1;利用and和or可进行掩码操作,and用于置0,or用于置1
- 7.2 ASCII码
- 7.3 以字符形式给出数据:db ‘str’
- 7.4 大小写转换的问题:大小写的ASCII码不同,相差32,即相差0X20H,可以通过掩码来实现大小写转换。
- 7.5 [bx+idata]:[bx]可表示偏移地址,[bx+idata]=([bx])+idata=idata[bx]=[bx].idata
- 7.6 [bx+idata]来处理数组问题:字符串看作数组来处理,如[bx+0],[bx+5]等
- 7.7 SI和DI:功能类似于bx,但不能分为两个8位寄存器可用,SI和DI常用来拷贝字符串
- 7.8 [bx+si]和[bx+di]:取得(bx的值+si/di中的值)作为地址,指明内存单元方式为[bx(si或di)]、[bx(si或di)+idata]或[bx+si或di]
- 7.9 [bx+si/di+idata]:表示一个内存单元,偏移地址为(bx)+(si)+idata
- 7.10 不同的寻址方式的灵活应用:[idata]、[bx]、[bx+idata]、[bx+si]、[bx+si+idata]
- 7.11 可以用内存变量/栈解决变量冲突问题
8. 数据处理的两个基本问题
8.1 (1)bx、si、di、bp:只有这4个寄存器可以用于[]中进行内存单元寻址 (2)四种组合方法:bx和si、bx和di、bp和si、bp和di (3)[]中使用bp,段地址默认在ss中
8.2 机器指令处理的数据所在位置:(1)处理分三类:读取、写入、运算 (2)数据存放在三个地方:CPU内部、内存、端口
8.3 汇编语言中数据位置的表述:立即数、寄存器、偏移地址和段地址
8.4 寻址方式:定位内存单元的方法
- 直接寻址:[idata]
- 寄存器间接寻址:[bx]、[si]、[di]、[bp]
- 寄存器相对寻址:[bx/si/di/bp+idata]
- 基址变址寻址:[bx/bp+si/di]
- 相对基址变址寻址:[bx/bp+si/di+idata]
8.5 指令要处理的数据有多长:8086CPU可以处理byte和word,因此要指明,进行的操作是字操作还是字节操作
- 通过寄存器名指明要处理的数据的尺寸
- 在没有寄存器名存在的情况下,用操作符x ptr指明内存单元长度,x在汇编指令中可以为word或byte
- 其他方法:有些指令默认了访问的是字单元还是字节单元,如push指令只进行字操作
8.6 寻址方式的综合利用:我们通常可以用[bx+idata+si]的方式来访问结构体中的数据,即用bx定位整个结构体,用idata定位结构体中的某一个数据项,用si定位数组项中的每个元素,如[bx].idata[si]
8.7 div:除法指令;
- 除数为8位或16位,存放在寄存器或内存单元中,被除数默认存放在AX或DX和AX中;
- 运算结果:8位运算商存于AL中、余数存于AH中;16位运算商存于AX,余数存于DX中
- 格式:div reg/内存单元
8.8 伪指令dd
- db:定义字节型数据(8)
- dw:定义字型数据(16)
- dd:定义双字型数据(32)
8.9 dup:是一个操作符,同db、dw、dd一样,由编译器识别处理,配合db、dw、dd使用,用来进行数据重复,格式为db/dw/dd n dup(value)
9. 转移指令的原理
8086CPU的转移指令分为:无条件转移指令(jmp)、条件转移指令、循环指令、过程、中断
- 9.1 offset 操作符:由编译器处理,取得符号的偏移地址
- 9.2 jmp:无条件转移,可以只修改IP,也可以同时修改CS:IP,jmp指令要给出两种信息,即转移的目的地址和转移的距离(段间距离、段内短转移、段内近转移)
- 9.3 根据位移进行转移的jmp指令
- (1)jmp short 标号:转到某标号处执行指令,指令实现的是段内短转移,它对IP的修改范围是-128~127(即1B范围),(IP)=(IP)+8,机器语言不需要知道偏移IP距离(标号S地址-jmp后第一字节地址)
- (2)jmp near ptr 标号:实现的是段内近转移,功能为(IP)=(IP)+16位位移,范围为-32769~32767
- 9.4 转移的目的地址在指令中的jmp指令:jmp far ptr 标号,实现的是段间转移,又称为远转移
- 9.5 转移地址在寄存器中的jmp指令:jmp 16位寄存器,实现的是段内近转移或段内短转移
- 9.6 转移地址在内存中的jmp指令:
- jmp word ptr 内存单元地址,实现的是段内转移
- jmp dword ptr 内存单元地址,实现的是段间转移
- 9.7 jxcz:有条件转移指令,短转移,机器码包含转移位移而不是目的地址,IP修改范围为-128~127,使用格式为jxcz 标号,若cx=0,则跳转到标号处执行并将IP值增加8,否则什么都不做
- 9.8 loop指令:短转移,机器码包含转移的位移而不是地址,当cx不等于0时,cx=cx-1,(IP)=(IP)+8,执行循环,否则什么都不做。
- 9.9 根据位移进行转移的意义:机器码只包含位移而不包含地址,是为了方便程序段在内存中的浮动转配,位移是可以计算出来的
- 9.10 编译器会对转移位移超界进行检测
10. call和ret指令
- 10.1 ret和retf
- ret指令用栈中数据修改IP的内容,实现近转移,执行ret时,(IP)=((SS)*16+(SP)),SP=SP+2
- retf用栈中数据修改CS和IP,实现远转移,执行retf时,(IP)=((SS)*16+(SP)),SP=SP+2
- 以汇编语言解决,ret相当于POP IP,retf相对于POP IP,POP CS
- 10.2 call指令,call指令经常和ret配合使用,执行call指令时,进行两步操作,首先将当前IP或CS:IP压栈,转移(jmp),call指令除了不能实现短转移,其他方法原理与jmp相同。
- 格式:call 标号;具体为(SP)=(SP)-2、((SS)*16+SP)=(IP)、(IP)=(IP)+16
- 10.3 根据位移进行的call指令:以汇编解释call指令时,即push IP;jmp near ptr 标号;
- 10.4 转移的目的地址在指令中的call指令,格式为call far ptr 标号,实现的是段间转移,即(SP)=(SP)-2,(CS)=((SS)16+(SP)),(SP)=(SP)-2,(IP)=((SS)16+(SP)),CS为标号所在的段地址,IP为标号所在的偏移地址。从汇编的角度看,即push CS、push IP、jmp for ptr 标号
- 10.5 转移地址在寄存器中的call指令:指令格式:call 16位寄存器,即(SP)=(SP)-2、(IP)=((SS)*16+(SP))、(IP)=16位寄存器,以汇编角度解释,即push IP和jmp 16位寄存器
- 10.6 转移地址在内存中的call指令
- call word ptr 内存单元地址:push IP、jmp word ptr 内存单元地址
- call dword ptr 内存单元地址:push CS、push IP、jmp dword ptr 内存单元地址
- 10.7 call和ret指令配合使用实现子程序机制
- 10.8 mul指令:乘法指令,要么8位乘8位,要么16位乘16位,8位存储在AL中和8位寄存器或内存单元中,16位存储在AX中和16位寄存器或内存字单元中,结果:8位存储在AX中,16位存储在DX(高位)和AX(低位)中。使用格式为:mul reg/内存单元
- 10.9 模块化程序设计:利用call和ret指令,我们可以用简洁方法实现多个互相联系,功能独立的子程序来解决一个复杂的问题。
- 10.10 参数和结果传递的问题:如何存储子程序需要的参数和产生的返回值:用寄存器bx存储/用栈存储。调用者和子程序对于参数寄存器与结果寄存器读写顺序相反
- 10.11 批量数据的传递:将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序,除此之外,也可以通过栈来实现。
- 10.12 寄存器冲突的问题:暂时保存寄存器值,子程序完成后进行恢复
11. 标志寄存器
- 8086CPU的标志寄存器有16位,存储的信息通过叫作程序状态字(PSW)。
- 8086CPU的flag寄存器的结构:
- 11.1 ZF标志:零标志位:结果为0,ZF=1;结果不为0,ZF=0;
- 11.2 PF标志:奇偶标志位:结果二进制位1的个数为偶数时PF=1,奇数时PF=0
- 11.3 SF标志:符号标志位:结果为负,SF=1;结果为正,SF=0;
- 11.4 CF标志:进位标志位:进位CF=1;不进位CF=0(无符号数)
- 11.5 OF标志:溢出标志位:溢出OF=1;不溢出OF=0(有符号数)
- 11.6 几种常见的影响标志寄存器的指令
- 指令adc标志:带进位加法指令,利用了CF位上记录的进位值,格式:adc 操作对象1,操作对象2;即adc ax,bx为(ax)=(ax)+(bx)+CF
- 指令sbb标志:带借位减法指令,利用了CF位上记录的借位值,格式:sbb
操作对象1,操作对象2;即sbb ax,bx为(ax)=(ax)-(bx)-CF
- 指令sbb标志:带借位减法指令,利用了CF位上记录的借位值,格式:sbb
- 指令cmp标志:比较指令,功能相当于减法指令,只是不保存结果,格式:cmp 操作对象1,操作对象2,只关注cmp操作后标志寄存器的值,计算时要先看OF,再看SF/ZF
- 11.7 检测比较结果的条件转移指令:
- je:等于则跳转:ZF=1
- jne:不等于则跳转:ZF=0
- jb:低于则转移:CF=1
- jnb:不低于则转移:CF=0
- ja:高于则转移:CF=0,ZF=0
- jna:不高于则转移:CF=1或ZF=1
- 11.8 DF:方向标志位,控制SI和DI的增减,当DF=0时,SI和DI加一,当DF=1,SI和DI减一
- 11.9 串传送指令:movsb,即
- ((es)16+(di))=((ds)16+(si))
- 若DF=0,则(SI)=(SI)+1,(DI)=(DI)+1;若DF=1,则(SI)=(SI)-1,(DI)=(DI)-1
- movsw与其类似,只不过偏移量为2
- cld指令将DF置0,std指令将DF置1
- rep movsb/movsw:根据cx的值重复移动
- 11.10:pushf将标志寄存器的值压栈,popf将栈中弹出数据,送入标志寄存器中
12. 内中断
- 12.1 中断是CPU处理外部突发事件的一个重要技术
- 12.2 中断优先权从高到低:除法错/溢出中断/软件中断、不可屏蔽中断、可屏蔽中断、单步中断
- 12.3 CPU通过中断类型码(8位)来定位中断处理程序,通过中断向量表来得到中断处理程序的段地址和偏移地址
- 12.4 中断向量表即中断向量的列表,用于索引,8086CPU的中断向量表放在地址0处,0000:0000-0000:03FF的1024个单元中,1024=4*28(安全内存)
- 12.5 中断过程:CPU硬件遇到中断时用中断类型码找到中断向量,并用其设置CS:IP,并执行中断处理程序,过程如下:
- 从中断信息中取得中断类型码
- 标志寄存器的值入栈(保护标志位)
- 设置标志寄存器的第8位TF和第9位IF为0
- CS的内容入栈、IP的内容入栈
- 从内存地址为4×中断类型码和4×中断类型码+2的两个字单元中读取中断处理程序的入口地址设置IP和CS
- 12.6 单步中断:类似debug的T,每执行一条指令中断一次,条件:TF=1,产生单步中断,过程如下
- 取得中断类型码为1
- flag入栈,设置TF、IF=0
- CS、IP入栈
- IP=1×4,CS=1×4+2
本文链接: https://yd0ng.github.io/2020/10/29/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!