hehe

王爽汇编全书知识点大纲


第一章 基础知识

  • 机器语言

机器语言是机器指令的集合,电子计算机的机器指令是一系列二进制数字.计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的.

  • 汇编语言的产生

由于机器语言指令都是由01组成,难以编写,记忆和维护程序.所以汇编语言为了解决这一问题产生.汇编语言又称为机器语言的助记符

  • 汇编语言的组成

汇编指令:有对应的机器码.机器码的助记符

伪指令:没有对应的机器码,由编译器执行,计算机并不执行.

其他符号:如+,-,*,/等,由编译器识别,没有对应的机器码

  • 存储器

  • 相当于大脑,程序指令和数据在存储器中都是以二进制数据形式存放,没有什么区别.且内存是动态存储数据的,程序或文件从磁盘加载到内存才能由cpu直接读取.断电后,内存中的数据就消失了.磁盘才是永久存储数据的地方.

  • 存储单元从0开始编号.能存储一个8位二进制数的单元,称为存储单元.为一个byte.大小,单位B. 位的单位为bit.向上为kb,mb,GB,TB,PB

  • cpu对存储器的读写

  • 必要的信息交互

(1). 存储单元的地址(地址信息),地址总线(cpu要从存储器中读取数据,首先要要确认,某个存储单元的地址.)

(2).器件的选择,读或写的命令(控制信息)控制总线(另外计算器中不只有存储器一种器件,其它器件,也有存储器.要确定从什么器件读取.)

(3).读或写的数据(数据信息),数据总线.

总线与信息传送 : 电子计算机能处理,传输的信息都是电信号,电信号当然通过导线传送,也就是总线(数根导线的集合).

读取例子:Mov Ax,[3]

CPU通过地址线将地址信息3发出cpu通过控制总线发出读命令,并选中存储器芯片,并通知他,将要从中取数据,存储器将3号单元中的数据8通过数据线送入cpu

  • 三大总线

  • 地址总线

地址总线:8根,表示能寻址0-1023的内存单元.能寻址2^8个内存单元,代表了cpu的寻址范围

  • 数据总线

假如数据总线有8根,那么每次只能传送1B数据.每次只能传送一个8位数.

  • 控制总线

假如控制总线位有8根,那么有2^8种控制方式,控制总线的宽度决定了CPU对外部器件的控制能力.

  • 内存地址空间

  • 每种器件cpu都通过总线将其连接,cpu在操控它们的时候,都把它们当成是内存来对待.(正如linux把每个硬件都当成文件来对待),操作显示器就是操作显存,显存就可以当文件一样进行读写操作.cpu操作硬件实际上就是操作硬件的内存或端口

  • 0-7fffh的32kb空间为主随机存储器的地址空间.地址8000h-9fffh的8kb空间为显存地址空间A000h~ffffh的24kb空间为各个rom的地址空间.(写入显存地址空间,就会显示在显示器上,写入只读rom地址空间,那么没有任何效果,我们基于一个计算机硬件系统编程的时候,必须知道这个系统中的内存地址空间分配情况).

  • BIOS有一个只读room,是硬件和操作系统的接口.

第二章 寄存器

  • cpu的基本结构

  • 运算器:运算器进行信息处理

  • 寄存器:寄存器进行信息存储

  • 控制器:控制器控制各种器件进行工作

  • 内部总线:用来连接cpu内部各种元器件的导线集合

  • 寄存器:AX,BX,CX,DX, SI,DI,SP,BP,IP, CS,SS,DS,ES, PSW

  • 通用寄存器

为了和上一代cpu兼容可以拆分16位通用寄存器,拆分为两个8位寄存器.从右向左,以0开始编号.

  • AX可拆分为AH和AL

  • BX可拆分为BH和BL

  • CX可拆分为CH和CL

  • DX可拆分为DH和DL

  • 字的存储和数制表示

  • 字在寄存器中的存储:高8位放在寄存器的高8位中,低8位放在寄存器的低8位中

  • 字在内存中的存储:低位存储在低地址中,高位存储在高地址中.称为小尾存储.

  • 数制的表示:源代码中默认数制是十进制,在数字后面添加h表示16进制,如果16进制最高位为字符,那么必须在前面添0, 如果数字后面加b后缀表示二进制数字. 特别注意,在debug中数制默认是16进制.

  • 几条汇编指令

  • 汇编指令默认不区分大小写

  • 假设ax和bx中的值为8226h,add ax,bx执行后,相加的结果为1044ch,但是ax存储不下,最后ax中的值为044ch

  • 假设ax中的值为00c5h,add al,85h执行结果为158h,al存储不下会设置标志位.千万不要认为高位进位会存储在ah中,这是错误的想法,最后ax中的结果为0058h.

  • 下面的指令是错误指令:

mov ax,bl //两个寄存器尺寸不一致

mov bh,ax //两个寄存器尺寸大小不一致

mov al 20000 //20000在al中不能存储

add al 100H //100h超过al中存储的最大范围

  • 计算2^4:

Mov ax,2

ADD AX,AX

ADD AX,AX

ADD AX,AX

  • 8086物理地址

  • 内存空间是一个线性空间.每一个内存单元在这个空间中都有唯一的地址,我们将这个唯一的地址称为物理地址.

  • 16位机的含义:

  • 运算器一次最多处理 16 位的数据

  • 寄存器最大宽度为 16 位

  • 寄存器和运算器之间通路为 16 位,即数据总线 16 条

  • 物理地址的计算:为了用16位地址寻址更多的内存,8086采取用连个16位寄存器合成一个20位地址,即

段寄存器中的地址*16+16位偏移地址

  • 一个段的起始地址必然是16的倍数

  • 因为偏移寄存器只有16位大小,所以给定一个段,它最大能在当前段寻址64kb

  • 寻址过程:段地址和偏移地址通过内部总线送入一个称为地址加法器的部件,地址加法器通过内部总线将20位物理地址送入输入输出控制电路.输入输出控制电路将20位物理地址送上地址总线.20位物理地址总线传送到存储器.

  • 一个地址可以用不同的段形式表示.

  • CS和IP

  • 段寄存器:CS,DS,ES,SS

  • CS位代码段寄存器,IP位代码偏移地址寄存器,cpu把任何时刻cs和ip指向的地址处的数据当做指令执行.

  • 指令的读取和执行:

  • 假设地址2000:0~2000:2中存储着指令mov ax,123h

  • 初始状态cs=2000h,ip=0,将从内存2000h:0中读取指令执行

  • 将CS和IP的值送上地址加法器合成一个20位的物理地址

  • 地址加法器将物理地址送入输入输出电路

  • 输入输出电路将物理地址20000h送上地址总线

  • 从地址20000h中读取数据通过数据总线送入cpu

  • 输入输出电路将指令mov ax,123h送入指令缓冲器

  • 读取一条指令后ip的值自动增加,指向可以读取的下一条指令

  • 执行控制器执行指令mov ax,123h

  • 然后重复上面的步骤

  • 8086cpu加电启动或复位后,cs和ip被设置为cs=ffffh,ip=0000h,cpu从内存FFFF0h单元中读取指令并执行.

  • 修改cs和ip的指令:

  • 通过修改cs和ip的指向,起到控制cpu执行流程和跳转等功能.在debug中可以通过mov指令修改cs的值,但是不能用mov修改ip的值

  • 在cpu中程序员能够用指令读写的部件只有寄存器

  • mov指令不能用于设置cs:ip的值.原因很简单,因为没有这个功能.但是debug中可以用mov cs,xxx

  • 想同时修改 CS,IP

jmp 1000:0 则 CS=1000H,IP=0000H //这条指令只在debug中有效

仅修改 IP 的内容

jmp 某一合法寄存器 //在源代码中也有效

  • debug调试指令

  • 用 R(register) 命令查看或改变 cpu 寄存器内容

  • r ax/其它寄存器 功能:修改 ax 寄存器内容

  • 直接r, 查看各个寄存器的值.

  • 用 D(dump)命令查看内存中内容

  • d 1000:0 会显示10000:0地址开始的128个内存单元的内容

  • D 段地址:开始偏移地址 结束偏移地址 可以查看指定内存单元的内容例子如下:

  • D 1000:0 f (1000:0 ~ 1000:f内存单元的数据) 再次输入D会把后续的128个内存单元内容显示出来.

  • 用 E (enter)命令改写内存中的内容

  • 采用“e 起始段地址:偏移地址 数据 数据 数据..." e 1000:0 0 1 2 3 4 5 6 7 写机器码.这是一次性输入

  • 用一个一个的输入方式,改写内存的内容 (按空格键陆续输入,敲回车结束输入.) 例如 e 1000:0,以向内存中写入字符 e 1000:0 1 'a' 2 'b' 3 'c' 写机器码和字符.

注:e命令写进去的机器码数值16进制形式,所以数字不能超过两位十六位数字,eg:111这是错误的(因为debug中默认是16进制数制).

  • 用 U(unassemble) 查看内存中机器码含义,用 T 执行cs:ip指向的指令.

  • U 段地址:偏移地址

  • 查看指定地址间的汇编指令:U 段地址:偏移地址 结束地址

  • 用 A(assemble) 以汇编指令形式在内存中写入机器指令. A 段地址:偏移地址

  • 用g(go) 命令跳到某指令,g 地址

eg:g 0012(表示从当前cs:ip指向的指令执行,一直到(ip) = 0012为止.) 用 t(trace) 命令单步执行 用 p (proceed)命令跳跃循环

  • q退出debug

  • dosbox中可以按alt+enter键实现全屏.

  • debug界面认识

  • 习题讲解:下面的代码几次修改IP的值

mov ax,bx

sub ax,ax

jmp ax

答:一共4次,第一次:读取完mov ax,bx后.第二次:读取完sub ax,bx后.第三次:读取完jmp ax后.第四次:执行完jmp ax后.

第三章 寄存器内存访问

  • 字在内存中的存储

  • 低位字节存放在低地址单元中,高位字节存放在高地址单元中.

  • 字单元:两个字节联合起来的16位.存储单元.起始地址为N的字单元简称为N地址字单元

  • DS和[ADRESS]访问内存单元

  • ds寄存器用来存放要访问的内存单元的段地址

  • 8086硬件设计的问题,不能将立即数直接用mov指令传送到段寄存器中

  • 在源代码中使用[idata]时,[idata]会被当成idata.

调试的时候,[idata]才会被翻译成内存单元.源代码中需要使用[bx/si/di..]等

表示内存单元.也可以使用段前缀, 段寄存器 : [idata]

  • 字的传送

  • 只要在mov指令中给出的寄存器是16位,就会对字进行操作.

  • 也可以用尺寸指针指明要操作的内存单元大小,byte ptr [],word ptr [],dword ptr[]

  • mov/add/sub指令详解

  • mov指令有以下几种格式:


mov 寄存器,数据

mov 寄存器,寄存器

mov 寄存器,内存单元

mov 内存单元,寄存器

mov 段寄存器,寄存器

mov 寄存器,段寄存器 //这个是自己实验添加的

mov 段寄存器,内存单元//这个是自己实验添加的

mov 内存单元,段寄存器//这个是自己实验添加的

注意事项:

  1. 从这几种格式可以看出,操作数不能同时为段寄存器
  1. 也不能同时为内存操作数.
  1. 不能将立即数直接送入段寄存器.
  1. 立即数只能放在右边,如果此时左边操作数为内存单元,需要指明内存单元尺寸
  1. 操作数不能出现ip寄存器
  • add指令有以下几种格式:

add 寄存器,数据

add 寄存器,寄存器

add 寄存器,内存单元

add 内存单元,寄存器

add byte/word/... ptr 内存单元,数据

注意事项:

  1. add指令中不能出现段寄存器操作数
  1. add中操作数不能同时为内存单元
  1. 第一个操作数必须是寄存器或者内存单元
  1. add指令中也不能出现ip寄存器
  • sub指令,格式和使用方式同add指令

  • 数据段

  • 我们可以将一组已知起始地址的内存单元定义为数据段,该段<= 64kb

  • 然后将ds设置为起始地址,然后该地址开始的段就可以当做数据段使用

  • 同理可以设置ss:sp的指向,来构造栈段

  • 代码段是通过,end 标号 指明起始地址(标号就是代码段开始处)

  • 后进先出数据结构,最初栈指针指向高地址,越压栈,sp寄存器指针越往低地址移动.

  • 通过ss:sp指定和初始化栈.

  • push和pop指令出栈和入栈

  • push 寄存器/内存单元/段寄存器 : sp=sp-2 mov (sp),(寄存器/内存单元/段寄存器)

  • pop 寄存器/内存单元/段寄存器: mov (寄存器/内存单元/段寄存器),(sp) sp=sp+2

  • 注意事项:

操作数不能是ip寄存器,栈每次操作一个字

  • 栈可以超界,且没有任何保护机制

  • 栈环绕问题

如果将10000H~1FFFFH这段空间当做栈段,初始状态是空的,此时ss=1000h,sp=?

答:sp=0,因为此时会发生栈环绕问题,FFFF的底端无法寻址,所以就会循环跑到另一头0上面去.,此时压栈会发生,sp=0-2=0FFFEH

  • 编程例子:

编程:将10000h~1000Fh这段空间当做栈,初始状态是空的,设置ax=001ah,bx=001bh,将ax,bx入栈,然后将ax,bx清零,从栈中恢复ax,bx的值.

解答:


mov ax,1000h

mov ss,ax

mov sp,10h

mov ax,001ah

mov bx,001bh

push ax

push bx

sub ax,ax

sub bx,bx

pop bx

pop ax

利用栈交换ax,bx内容


push ax

push bx

pop ax

pop bx

....

  • 实验2

  • 可以在调试命令中需要段地址的地方,使用段寄存器.例子如下:

  • d 1000:0 会将段地址1000送入ds中.

  • e ds:0 0 1 2 3 4

  • u cs:0 ;以汇编指令形式,显示当前代码

  • a ds:0

  • ss:sp的设置指令应该放在一起紧挨着,单步中断并不会中断,这两条 指令会连续执行,因为中断也设计压栈操作,那么假设如果在sp还未设置时,发生了中断,那么中断的压栈操作将会压入不正确的栈顶,紧接着又设置了新的sp值,那么程序就不会正确运行.

第4章 第一个程序

  • 源程序到可执行程序

  • 源程序文本文件-->编译链接--->生成可执行程序

  • 可执行程序包含从源程序中翻译过来的机器码和数据,还有相关的描述信息(比如,程序有多大,要占用多少内存空间等)

  • 操作系统根据可执行文件的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化,并进行相关的初始化(比如设置CS:IP指向第一条指令),然后cpu执行程序.

  • 如果不用end指明程序入口,那么编译成程序时,cpu会自动将cs,ip指向改程序数据开头.如果指定了标号,那么cpu会将cs和ip指向标号地址处.

  • 源程序


assume cs:code

code segment

mov ax,0123h

mov bx,0456h

add ax,bx

add ax,ax

mov ax, 4c00h

int 21h

code ends

end

  • xxx segment ....xxx ends伪指令是定义一个段.标号就是偏移地址,不需要用offset获取

  • end伪指令 : 表示结束一个汇编源程序的编译,end 后面可以接一个标号来指明可执行代码入口.

  • assume伪指令 : 它假设程序的某个段寄存器与某个段标号相关联.通过这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段想联系.

  • 程序返回 : mov ax,4c00h int 21h程序返回.告诉运行本程序的程序,运行结束后的结果状态.ah=4c,传递中断子程序功能号,表示调用21中断的4c号子程序. Al=0表示返回值为0.如果不正确返回

程序就会运行不正常.

  • 链接

  • 当源程序很大的时候,我们可以将源程序分成多个程序,然后编译成多个obj文件.然后连接在一起,

  • 程序中调用了某个库文件中子程序,需要将这个库文件和程序生成的目标文件连接到一起,生成一个可执行文件.

  • 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接生成可执行文件.连接程序将这些内容处理为最终可执行信息.所在,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件.

  • dos程序的加载流程

  • dos中直接运行exe文件时,首先加载入内存,设置cs:ip的入口(end指定的标号处).

  • 找到一段起始地址为SA:0000(即起始地址的偏移地址为0)的容量足够的空闲内存区

  • 前256个字节中创建一个段前缀psp的数据区,dos利用psp来和被加载程序进行通信.ds首先初始化时,是指向psp数据区的.

  • 程序放在SA+10H:0地址处,CS:IP指向这里

  • 实验3

  • 杂项知识:将程序加载入内存执行,cx存储的是程序的长度

第五章 [bx]和loop

  • [bx]

  • 要完整描述一个内存单元需要两种信息:@内存单元地址@内存单元长度

  • 表示段地址在ds中,bx中存放的是该段的偏移地址.

  • Loop指令

  • (cx)=(cx) - 1

  • 判断cx中的值是否为零,非零继续循环,零则退出循环.

  • 循环例子程序:计算2^12次方


assume cs:code

code segment

mov ax,2

mov cx,11

s:

add ax,ax

loop s

mov ax, 4c00h

int 21h

code ends

end

例子2:计算123*236


assume cs:code

code segment

mov ax,0

mov cx,123

s:

add ax,236

loop s

mov ax, 4c00h

int 21h

code ends

end

  • inc/dec 寄存器或

  • inc/dec (byte /word/..) ptr [内存单元]

  • 自增或自减指令

  • 杂项

  • 在汇编源程序中,数据不能以字母开头需要在前面添加一个0.

  • 在 dos 下,一般情况 0:200-0:2ff 空间中没有系统或其他程序的数据或代码

  • 遇到loop指令时,用p,可以跳过循环.遇到int 21是也可以用p命令结束程序

  • g 地址偏移 ,直接跳转到该地址处执行

  • loop和bx 的联合应用

  • 计算ffff:0~ffff:b每个内存单元中的数据相加的和,结果存储在dx中


assume cs:code

code segment

mov ax,0ffffh

mov ds,ax

mov bx,0

mov dx,0

mov cx,12

mov ah,0

s:

mov al,[bx]

add dx,ax

inc bx

loop s

mov ax, 4c00h

int 21h

code ends

  • 段前缀

  • 可以在[idata]或[bx]前面加上cs,ds,es,ss等段前缀来指明段地址

  • cs/ds/es/ss : []

  • 段前缀的使用

  • 编程:将内存ffff:0ffff:b单元的数据复制到0:200020b中


assume cs:code

code segment

mov ax,0ffffh

mov ds,ax

mov ax,0020h

mov es,ax

mov bx,0

mov cx,6

s:

mov ax,[bx]

mov es:[bx],ax

add bx,2

loop s

mov ax, 4c00h

int 21h

code ends

end

  • 实验4 bx和loop的使用

  • 编程,向内存0:200~0:23f依次传送数据0-63(3fh)


assume cs:code

code segment

mov ax,0020h

mov ds,ax

mov bx,0

mov cx,64

s:

mov [bx],bl

inc bx

loop s

mov ax, 4c00h

int 21h

code ends

end

第六章 包含多个段的程序

  • 在代码中使用数据

  • 程序获得所需空间的方法有两种

  • 是在程序加载的时候为程序分配

  • 是在程序运行过程中向系统申请.我们只讨论前者.因为后者属于动态分配内存.这两种方式都是由操作系统分配.

  • 还有就是在dos时代,可以直接将数据送入内存.当数据有很多时,这种方法存储明显不现实

  • db定义字节, dw定义字数据,dd定义双字,dq8字节,dt十字节

  • 程序框架


assume cs:code

code segment

start:

程序代码

mov ax, 4c00h

int 21h

code ends

end start

end代表程序源代码结束,并指明可执行代码入口地址.

如果指明入口地址,那么从程序的首地址开始执行,不管起始地址处是否是可执行代码.

  • CPU根据什么设置CS:IP指向程序的第一条要执行的指令?是由可执行文件中的描述信息指明的.描述信息则主要是编译,连接程序对源程序中相关伪指令进行处理所得到的信息.

  • 即使我们用了类似Assume cs:code,ds:data这样的伪指令,在执行的时候,Cpu也不会把他们的段寄存器指向相应的内容,cpu在加载程序运行把CS:IP指向我们Start入口处,我们在这里调整ss,ds等段寄存器的指向.

  • 在代码中使用栈

  • 利用栈将程序中的数据逆序存放


assume cs:code

code segment

dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h

dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0

start:

mov ax,cs

mov ss,ax

mov sp,30h

mov bx,0

mov cx,8

s:

push cs:[bx]

add bx,2

loop s

mov bx,0

mov cx,8

s1:

pop cs:[bx]

add bx,2

loop s1

mov ax, 4c00h

int 21h

code ends

end  start

  • 栈使用技巧:在内存之间传递数据,连个变量之间的传递,可以先用栈做中转,即先压栈变量1

然后弹出来给变量2

  • 将数据,代码,栈放入不同的段

  • 实验5

  • 非常重要:如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为 ((N+15)/16)_16 。

解析:

N分为被16整除和不被16整除。

当N被16整除时: 占有的空间为(N/16)_16

当N不被16整除时: 占有的空间为(N/16+1)_16,N/16得出的是可以整除的部分,还有一个余数,余数肯定小于16,加上一个16。

程序加载后分配空间是以16个字节为单位的,也就是说如果不足16个字节的也分配16个字节。

两种情况总结成一个通用的公式:((N+15)/16)_16

  • 每个段(例如 数据段,代码段)必须是 16字节的整数倍(系统规定)所以小于16个字节的会自动的变成16个字节

  • 程序开始时ds指向psp比cs大10h,cx中存放着程序的长度

  • 编写代码,将a段和b段的数据相加,保存在c段中.


assume cs:code

a segment

db 1, 2, 3, 4, 5, 6, 7, 8

a ends

b segment

db 1, 2, 3, 4, 5, 6, 7, 8

b ends

c segment

db 0, 0, 0, 0, 0, 0, 0, 0

c ends

code segment

start:  mov ax, a

mov ds, ax

mov ax, b

mov es,ax

mov ax, c

mov ss,ax

mov ax,0

mov bx,0

mov cx,8

s: mov al,ds:[bx]

add al,es:[bx]

mov ss:[bx],al

inc bx

loop s

mov ax, 4c00h

int 21h

code ends

end start

第7章 更灵活的定位内存的方法

  • and和or 指令

  • and指令,按位进行与运算,or按位进行或运算.

  • 第一个操作数必须寄存器或内存单元(内存单元必须指明尺寸)

  • 操作数不能是段寄存器,或ip寄存器

  • 两个操作数的尺寸必须一致

  • 操作数不能同时是两个内存单元.

  • 关于ascii码

  • 大写字母在6590(41h5Ah)(0100000101011010)开始,小写字母97122(61h-7Ah)(01100001~01111010)开始.

  • 大写字母第6位为0,小写字母第6位为1(索引从1开始算)

  • 重点是了解了字符的ascii码的规律后,可以利用and和or指令进行大小写转换

  • 数字的ascii码4857(30h-49h)(00110000b00111001b).

  • 数字字符,跟二进制数字的区别是数字字符第5位和第6位都为1而,数字值第7位和第8位都为0

  • 有助于理解ascii码压缩和非压缩指令

  • 编码方案:在显示器上呈现的文字等都是文本字符串,在计算机内部是以二进制形式存储,通过解码,显示器在屏幕上画出文本,其中一种编码方案是ascii码.

  • 文本处理过程例子:我们按下键盘a键,这个按键的信息被送入计算机,计算机用ascii吗的规则对其进行编码,将其转化为61h存储在内存的指定空间中,文本编辑软件从内存中取出61h,将其送到显卡上的显存中;工作在文本模式下的显卡,用ascii码的规则解释显存中的内容.61h被当作字符"a".显卡驱动显示器,将字符"a"的图像画在屏幕上.

  • db 'unIX' <=> db 75h,6eh, 49h,58h.

mov al, 'a' <=> mov al,97

  • 大小写转换问题例子代码

  • 写一段程序将 'BaSic'字符串转化为大写,将'iNfOrMaTiOn'转换为小写


assume cs:code, ds:data

data segment

db 'BaSic'

db 'iNfOrMaTiOn'

data ends

code segment

start:

mov ax, data

mov ds, ax

mov bx,0

mov cx,5

s0:

mov al,[bx]

and al,11011111b

mov [bx],al

inc bx

loop s0

mov cx,11

mov bx,5

s1:

mov al,[bx]

or al,00100000b

mov [bx], al

inc bx

loop s1

mov ax, 4c00h

int 21h

code ends

end  start

  • 更灵活的寻址

  • [bx+idata] <=> idata[bx],[bx].idata

  • 可以用于数组寻址,例子如下,将数据段中第一个字符串转换为大写,第二个转换为小写


assume cs:code, ds:data

data segment

db 'BaSic'

db 'MinIX'

data ends

code segment

start:

mov ax, data

mov ds, ax

mov bx,0

mov cx,5

s0:

mov al,[bx]

and al,11011111b

mov [bx],al

mov al,[bx+5]

or al,00100000b

mov [bx+5],al

inc bx

loop s0

mov ax, 4c00h

int 21h

code ends

end  start

  • 技巧:因为第一个字符串和第二个字符串一样长,我们就可以用bx+idata寻址,且可以用一个循环就可以达到转换目的,因为长度一样长.

  • C语言:a[i]<=>idata[i]

  • [bx+si],[bx+di]

  • [bx+si+idata],[bx+di+idata]默认段地址在ds中.

  • [bp+si+idata],[bp+di+idata] 如果bp没有给出段前缀,默认段地址在ss中.

  • 注意事项 : bp和bx不能同时出现,di和si也不能同时出现.且di和si不能拆分成8位寄存器.

  • 寻址综合应用

  • 将data段中的每个单词的首字母大写.每个字符串占16个字节.


assume cs:code, ds:data

data segment

db '1\. file        '

db '2\. edit        '

db '3\. search      '

db '4\. view        '

db '5\. options      '

db '6\. help        '

data ends

code segment

start:

mov ax, data

mov ds, ax

mov bx,0

mov cx, 6

s0:

mov al,[bx+3]

and al,11011111b

mov [bx+3],al

add bx,16

loop s0

mov ax, 4c00h

int 21h

code ends

end  start

  • 将data段中的每个单词改为大写字母,由于每个字符串一样长,我们可以用嵌套循环

assume cs:codesg, ds:datasg

datasg segment

db 'ibm            '

db 'dec            '

db 'dos            '

db 'vax            '

datasg segment

codesg segment

start:mov ax,datasg

mov ds,ax

mov bx,0

mov cx,4

mov ax,0

s0: mov dx,cx

mov si,0

mov cx,3

s1:mov al,[bx+si]

and al,11011111b

mov [bx+si], al

inc si

loop s1

add bx,16

mov cx,dx

loop s0

codesg ends

end start

这里注意保存外层循环的cx值.可以用其它寄存器保存,也可以保存在内存单元中,也可以保存在栈中

  • 将data段中的前四个字母改为大写.

assume cs:codesg, ds:datasg

datasg segment

db '1\. display      '

db '2\. brows        '

db '3\. replace      '

db '4\. modify      '

datasg segment

codesg segment

start:

mov ax, datasg

mov ds,ax

mov bx,0

mov cx,4

s1:

push cx

mov cx,4

mov si,0

s2:

mov al,[bx+si+3]

and al,11011111b

mov [bx+si+3],al

inc si

loop s2

add bx, 16

pop cx

loop s1

mov ax, 4c00h

int 21h

codesg ends

end start

注意:si需要在循环中每次重置为0

  • 实验6

  • 实践本单元所有程序

第八章 数据处理的两个基本问题

  • 两个基本问题

  • 处理的数据在什么地方

  • cpu内部(寄存器,缓存)

  • 端口

  • 内存

  • 数据的尺寸有多长

  • 寄存器的大小指明尺寸

  • 用byte/word/dword ptr [寻址寄存器或段前缀]来指明.

  • 数据的位置表达

  • 立即数:直接包含在指令中的立即数在执行前是在cpu内部的指令缓冲器中.

  • 寄存器

  • 内存单元:bx系列寻址组合默认段地址在ds中,bp系列组合默认地址在ss中

  • 寻址的综合应用

  • 关于DEC公司的一条记录(1982年)如下:

公司名称:DEC

总裁姓名:Ken Olsen

排名:137

收入: 40(40亿美元)

著名产品:PDP(小型机)


mov ax,seg

mov ds,ax

mov bx,60h

mov word ptr [bx+0ch],38

mov word ptr [bx+0eh],70

mov byte ptr [bx+10h],'V'

inc bx

mov byte ptr [bx+10h],'A'

inc bx

mov byte ptr [bx+10h],'X'

  • 几条指令

  • div指令

  • 格式:

div reg

div 内存单元 //必须指明内存单元尺寸

  • 指令说明

  • 除数:有8位和16位两种在reg或内存单元中.

  • 被除数:放ax或ax和dx中,如果除数为8位,则被除数为16位,默认在ax中存放,如果除数为16位,则被除数则为32位.在dx和ax中存放,dx存放高16位,ax存放低16位

  • 结果:如果除数为8位,al存放商,AH存放余数,如果除数为16位,则AX存放除法的商,dx存放除法操作的余数..

  • 例子:利用除法计算1000001/100

因为100001大于65536,化成16进制为186A1


mov dx,1

mov ax,86A1h

mov bx,100

div bx

  • 例子:编程,利用除法指令计算1001/100

mov ax,1001

mov bl,100

div bl

  • 用div计算data段中的第一个数据除以第二个数据后的结果,商存在第三个数据的存储单元中

assume cs:code, ds:data

data segment

dd 100001

dw 100

dw 0

data ends

code segment

start:

mov ax, data

mov ds,ax

mov bx,0

mov ax,[bx]

mov dx,[bx+2]

div word ptr [bx+4]

mov [bx+6],ax

mov ax, 4c00h

int 21h

code ends

end  start

  • dd伪指令:定义双字型数据. 还有db/dw/dq(八字节)/dt(十字节)

  • dup伪指令:db/dw/dd 重复次数 dup (需要重复的数据列表)

eg:db 3 dup (0,1,2) 一共占用3*3=9个字节

  • 实验7

  • 代码如下:


assume cs:code,ds:data,es:table

data segment

db '1975','1976','1977','1978','1979','1980','1981','1982','1983'

db '1984','1985','1986','1987','1988','1989','1990','1991','1992'

db '1993','1994','1995'

dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514

dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000

dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226

dw 11452,14430,15257,17800

data ends

table segment

db 21 dup ('year summ ne ?? ')

table ends

code segment

start:

mov ax,data

mov ds,ax

mov ax,table

mov es,ax

mov bx,0

mov si,0

mov di,0

mov cx,21

s1:

mov ax,[bx]

mov es:[di],ax

mov ax,[bx+2]

mov es:[di+2],ax

mov byte ptr [di+4],' '

mov ax,84[bx]

mov es:[di+5],ax

mov ax,84[bx+2]

mov es:[di+7],ax

mov byte ptr [di+9],' '

mov ax,168[si]

mov es:[di+10],ax

mov byte ptr [di+12],' '

mov dx,86[bx]

mov ax,84[bx]

div word ptr 168[si]

mov es:[di+13],ax

mov es:[di+15],' '

add bx,4

add si,2

add di,16

loop s1

mov ax, 4c00h

int 21h

code ends

end start

第九章 转移指令的原理

  • offset操作符

  • 操作符offset在汇编语言中是由编译器处理的符号.功能是获取标号的偏移地址

  • 段标号名称就代表地址,所以不需要offset 操作符.

  • Nop指令占用一个字节

  • 转移指令分类

  • 可以修改ip或同时修改CS或IP的指令统称为转移指令:

(1).只修改ip:段内转移

  • 8位数值范围的修改IP,称为短转移

  • 修改范围为16位的称为近转移

(2).同时修改Cs和IP时,称为段间转移.

  • 8086CPU的转移指令分为以下几类:

(1)无条件转移指令(如:jmp)

(2)条件转移指令

(3)循环指令

(4)过程

(5)中断

  • 无条件跳转jmp指令

  • 跳转到标号:

  • 短转移:jmp sort 标号 (机器码中包含的是8位位移)

  • 近转移:jmp near ptr 标号(机器码中包含16位位移)

位移计算:要跳转的标号的偏移地址 减掉 跳转指令的下一条指令的偏移地址,位移在编译时由汇编器算出.

  • 远转移:jmp far ptr 标号,cs=标号段地址,ip=标号偏移地址,该指令机器码中不是用的位移表示.而是包含了转移地址4个字节.机器码的高位字单元是段地址,低位时偏移地址

  • 转移地址在寄存器:jmp 16位寄存器

  • 转移地址在内存中的jmp指令

  • 段内转移:jmp word ptr 内存单元地址

  • 段间转移:jmp dword ptr 内存单元地址(高字单元是段地址,低地址字单元是偏移地址)

  • 条件转移指令jcxz

  • 所有条件转移都是短转移.,在对应的机器码中包含转移的位移.

  • 格式:jcxz 标号,当cx=0时就跳转到标号处.

  • 例子程序:补全程序,利用jcxz指令,实现在内存2000h段中,查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中


assume cs:code

code segment

start:

mov ax,2000h

mov ds,ax

mov bx,0

s:

mov cx, 0

mov cl,[bx]

jcxz ok

inc bx

jmp short s

ok:

mov dx, bx

mov ax, 4c00h

int 21h

code ends

end  start

  • loop指令

  • 所有的循环指令都是短转移.对应的机器码包含的是位移.

  • 例子程序:例子程序:补全程序,利用loop指令,实现在内存2000h段中,查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中


assume cs:code

code segment

start:  mov ax,2000h

mov ds,ax

mov bx,0

s:mov cl,[bx]

mov ch,0

inc cx

inc bx

loop s

ok:dec bx

mov dx,bx

mov ax,4c00h

int 21h

code ends

end start

inc cx是防止当cx为0时,loop指令会首先cx=cx-1会变成0ffffh,就会循环ffff次.

  • 实验

  • 实验8:


assume cs:codesg

codesg segment

mov ax,4c00h

int 21h

start: mov ax,0            ax=0

s: nop                占一字节,机器码90

nop                占一字节,机器码90

mov di,offset s    (di)=s偏移地址

mov si,offset s2    (si)=s2偏移地址

mov ax,cs:[si]      (ax)=jmp short s1指令对应的机器码EBF6

mov cs:[di],ax      jmp short s1覆盖s处指令2条nop指令

s0: jmp short s        执行到这里,不会再继续向下执行,直接跳回mov ax,4c00h了

s1: mov ax,0

int 21h

mov ax,0

s2: jmp short s1

nop

codesg ends

end start

因为jmp short s1<=>EB 位移.位移等于S1标号偏移地址减去nop指令的偏移地址大约为-10.当跳转到s处执行jmp short s1,实际上是执行EB -10.会向前跳转10个字节,也就是说会执行程序返回的代码.

  • 实验9:根据材料编程:在屏幕中间分别显示绿色,绿底红色,白色蓝底的字符串’welcome to masm!’

  • 显示字符相关知识: B8000h-bffffh共32kb的空间,为80*25彩色字符模式的显示缓存区.一个字符占两个字节,后面的字节是该字符的一些颜色属性信息.所以一屏幕内容占缓冲区4000b约等于4kb


assume cs:code, ds:data

data segment

db 'welcome to masm!'

data ends

code segment

start:

mov ax, data

mov ds, ax

mov bx,0

mov ax,0b800h

mov es, ax

mov di,11*160+72

mov cx,16

s:

mov al,[bx]

mov es:[di], al

mov byte ptr es:[di+1],00000010b

mov es:160[di],al

mov byte ptr es:161[di],00100100b

mov es:320[di],al

mov byte ptr es:321[di],01110001b

add di,2

inc bx

loop s

mov ax, 4c00h

int 21h

code ends

end  start

第十章 call和ret指令

  • ret和retf指令

  • ret<=>pop ip<=>(ip)=((ss)*16+(sp)),(sp)=(sp)+2

  • retf<=>pop ip,pop cs <=>(ip)=((ss)_16+(sp)),(sp)=(sp)+2

(cs)=((ss)_16+(sp)),(sp)=(sp)+2

  • call指令

  • call指令执行的步骤

  • 将当前call指令后的下一条指令的ip,或cs和ip压入栈中

  • 跳转到目标地址执行

(sp)=(sp) - 2

((ss)*16+(sp)) = (ip)

(ip)=(ip)+16位位移

  • call指令不能实现短转移,此外转移的方法同jmp指令一样.

  • call 标号:16位位移=标号处的地址-call指令后的第一个字节的地址, 位移范围为-32768~32767

有等同于 push ip,jmp near ptr 标号

  • call far ptr 标号:实现的是段间转移.

push cs,push ip, jmp far ptr 标号

  • 转移地址在寄存器中的call指令:call 16位reg

push ip, jmp reg

  • 转移地址在内存中的call指令:

  • call word ptr 内存单元地址

push ip, jmp word ptr 内存单元地址

  • call dword ptr 内存单元地址

push cs, push ip,jmp dword ptr 内存单元地址

  • call和ret的配合使用

  • 子程序框架:


assume cs:code

code segment

main:

call sub1

:

:

call sub2

:

:

mov ax, 4c00h

int 21h

sub1:

:

:

sub2:

:

code ends

所以c语言中的函数名其实就是一个标识子程序的地址

  • mul指令

  • 要求:

  • 两个相乘的数,要么都是8位,要么都是16位,如果是8位,那么一个乘数默认放在AL中,另一个放在8位reg或内存单元中,如果是16位,一个默认在ax中,另一个放在16位reg或内存单元中

  • 结果:如果是8位乘法,结果默认放在AX中,如果是16位乘法,结果高位默认放在dx中,低位放在ax中.

  • 格式:mul reg/内存单元(必须指明内存单元尺寸)

  • 例子:计算100*10,因为两个乘数都是8位,所以用8位乘法


mov al,100

mov bl,10

mul bl

计算 100 * 10000,必须用16位乘法


mov ax,100

mov bx,10000

mul bx

  • 模块化程序设计

  • 参数和结果传递问题:

  • 用寄存器传递参数和用寄存器存储函数结果值

  • 例子:计算n的立方,用bx传递参数dx和ax返回结果值


cube:

mov ax,bx

mul bx

mul bx

ret

  • 参数的批量传递:可以将参数放在一组内存单元中,然后用si等间接寄存器指向它.返回值也可以这样做.

  • 例子:将一个si指向的全是字母的字符串转换为大写


capital:

and byte ptr [si], 11011111b

inc si

loop capital

ret

  • 用栈来传递参数

  • 寄存器冲突问题:

  • 例子程序:将一个全是字母,以0结尾的字符串,转化为大写si指向该字符串


capital:mov cl, [si]

mov ch,0

jcxz ok

and byte ptr [si],11011111b

inc si

jmp short capital

ok : ret

如果调用该子程序中的代码中使用了cx寄存器,将产生错误.解决办法是在该子程序开头压栈保存需要使用的寄存器,在返回前弹出该寄存器的值.改正如下


capital:push cx

push si

change:

mov cl, [si]

mov ch,0

jcxz ok

and byte ptr [si],11011111b

inc si

jmp short change

ok :pop si

pop cx

ret

  • 所以子程序框架如下:

子程序标号:

子程序中使用的寄存器入栈保存

子程序指令标号:

....

子程序返回标号:

子程序中保存的寄存器出栈恢复

子程序返回(ret,retf)

  • 实验10 编写子程序

  • 显示字符串子程序:show_str

  • 功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串

  • 参数:(dh)=行号(取值范围024),(dl)=列号(取值范围是079).(cl)=颜色,ds:si指向字符串的首地址

  • 返回值:无


show_str:

push ax

push bx

push cx

push dx

push es

push si

push di

begn:

mov ax,0b800h

mov es,ax

mov al,160

mul dh

mov bx,ax

mov al,2

mul dl

add bx,ax

mov di,bx

mov al,cl

mov ch,0

s:

mov cl,[si]

jcxz retn

mov es:[di],cl

mov es:[di+1],al

inc si

add di,2

jmp short s

retn:

pop di

pop si

pop es

pop dx

pop cx

pop bx

pop ax

ret

  • 解决触发溢出问题子程序:

  • 名称:divdw

  • 功能:进行不会产生溢出的除法运算,被除数位dword型,除数位word型.结果为dword型

  • 参数:(ax)=dword型数据的低16位,(dx)=dword型数据的高16位,(cx)=除数

  • 返回:(dx)=结果的高16位,(ax)=结果的低16位,(cx)=余数


divdw:

push ax

push bx

push cx

push dx

begn:

mov bx,ax

mov ax,dx

mov dx,0

div cx

push ax

mov ax,bx

div cx

mov cx,dx

pop dx

retn:

pop dx

pop cx

pop bx

pop ax

ret

  • 将数值以十进制形式显示:

  • 名称:dtoc

  • 功能:将word型数据转变为表示十进制数的字符串,字符串以0结尾

  • 参数:(ax)=word型数据,ds:si指向字符串首地址

  • 返回值:无


dtoc:

push ax

push bx

push cx

push dx

push ds

push si

push 0

begn:

mov dx,0

mov bx,10

div bx

mov cx,ax

jcxz sw1

add dx,30h

push dx

jmp short begn

sw1:add dx,30h

push dx

sw2:

pop cx

jcxz retn

mov [si],cl

inc si

jmp short sw2

retn:

mov [si+1],0

pop si

pop ds

pop dx

pop cx

pop bx

pop ax

ret

推荐阅读更多精彩内容

  • 8086汇编 本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,在此感谢他和像他一样...
    Gibbs基阅读 14,227评论 5 81
  • 计算机通过执行指令序列来使机器得以工作,所以对于每一系列的计算机都有指定的一组指令集供计算机使用,这组指令...
    国才阅读 2,657评论 1 9
  • 汇编总结 汇编的发展史 机械语言 由0和1组成的机器指令(如:0101 0001 1101 0110) 汇编语言(...
    iChuck阅读 452评论 1 8
  • ![Uploading 图片_484415.png . . .] 校验和格式化工具:JSONLint 用jQuer...
    Hathaway_桉阅读 26评论 0 0
  • 你来 我欣喜 并 赴汤蹈火 你走 我恋之不得 惟 再无所求 仿佛一场花开 在梦中 梦醒时 却 冷痛 无以复加
    未可_Win阅读 42评论 3 9