看了看书发现书上的一些概念容易忘记,就顺手打下来,考虑到篇幅过长,分为前后两个部分。
第一章:基础知识
汇编指令是机器指令的助记符,同机器指令一一对应。
每一种CPU都有自己的汇编指令集
在存储器中指令和数据没有任何区别,都是二进制信息。
CPU可以直接使用的信息在存储器中存放。
存储器单元从零开始顺序编号。
一个存储单元可以存储8 bit,即8个二进制位。
1Byte = 8bit….
每一个CPU芯片都有许多管脚,它们与总线相连。其分为三类:
- 地址总线的宽度决定了CPU的寻址能力
- 数据总线的宽度决定了CPU与其它器件进行数据传送时一次数据传送量
- 控制总线的宽度决定了CPU对系统中其它器件的控制能力
内存地址空间:最终运行程序的是CPU,对CPU来讲,系统中所有的存储器的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力(读取内存地址的范围)的限制。这个逻辑存储器即内存地址空间。
第二章:寄存器
8086CPU所有的寄存器都是16位的,可以存放两个字节。
AX、BX、CX、DX这四个寄存器用来存放一般性数据,称为通用寄存器。为了兼容,通常把它们掰为两半来使用,它们可以独立使用:
- AX → AH + AL
- BX → BH + BL
- CX → CH + CL
- DX → DH + DL
在进行数据传送或运算时,指令的两个操作对象的位数应当是一致的。
8086cpu一次性能处理16位地址,但有20位地址总线,于是采用两个16位地址合成来形成一个20位的物理地址。
物理地址 = 段地址 * 16 + 偏移地址,其含义是用一个基础地址(段地址 * 16)和一个相对于基础地址的偏移地址相加得到物理地址。
将若干地址连续的内存单元看作是一个段,段地址就是该段的起始地址,用偏移地址定位段中的具体位置。其中段地址必然是16的倍数,偏移地址为16位,所以寻址能力为64K,故一个段的长度最大为64K
段寄存器:CS、DS、SS、ES、
8086pc机中,设CS中的内容为M(段地址),IP中的内容为N(偏移地址),任意时刻,CPU将从M * 16 + N内存单元读取指令执行。
“ jmp 某一合法寄存器” :用寄存器中的值修改 IP。
Debug基本命令:
命令 功能 格式 R(Register) 查看、改变CPU寄存器中的内容 ①查看:直接输入r,②修改寄存器值:r ax,按enter然后输入数据 D(Dump) 查看内存中的内容 d 段地址:偏移地址 或者 d 段地址:偏移地址段 或d 起始位置 L长度 E(Enter) 改写内存中的内容 e 起始地址 数据 数据 … 空格键表示处理完成 U(Unassemble) 将内存中的机器指令翻译为汇编指令 u 段地址:偏移地址 T(Trace) 执行一条机器指令 输入t ,CPU执行CS:IP指向的指令 A(Assemble) 以汇编指令的格式在内存中写入一条机器指令 a 段地址:偏移地址,接着输入汇编指令
第三章:寄存器(内存访问)
8086CPU用16位来存储一个字,高8位存放在高位字节,低8位存放在低位字节。一个字用两个地址连续的内存单元存放,低位字节在低位地址,高位字节在高位地址。
DS寄存器通常用来存放要要访问的数据的段地址。不支持直接将数据送入段寄存器。
mov、add、sub指令
指令 格式 示例 mov 寄存器,数据 mov ax,6 mov 寄存器,寄存器 mov ax,bx mov 寄存器,内存单元 mov ax,[8] mov 内存单元,寄存器 mov [9],ax mov 段寄存器,寄存器 mov ds,ax add 寄存器,数据 add ax,0 add 寄存器,寄存器 add ax,bx add 寄存器,内存单元 add ax,[0] add 内存单元,寄存器 add [0],ax sub 寄存器,数据 sub ax,0 sub 寄存器,寄存器 aub ax,bx sub 寄存器,内存单元 sub ax,[9] sub 内存单元,寄存器 sub [9],ax 入栈:将新元素放到栈顶,出栈:从栈顶取出一个元素。遵循先入后出原则(LIFO)
PUSH(入栈),POP(出栈),以字为单位进行,
任意时刻,SS:SP指向栈顶元素,push和pop执行时,cpu从ss:sp中得到栈顶地址。
执行PUSH指令时,SP=SP-2,表示栈顶向上移动;执行pop指令时,SP=SP+2,表示栈顶向下移动;当栈为空时,SS:SP指向栈顶内存地址+2的内存单元。
在使用栈的时候,应当注意不要出现越界。
出栈的顺序应该和入栈的顺序相反,这与栈后入先出的特性相关。
PUSH过程:①CPU先改变SP=SP+2,②向SS:SP处传送;
POP过程:①CPU先向SS:SP读取数据,②改变SP的值SP=SP-2。
push和pop指令操作栈的时候,修改的只是SP。
栈的栈顶变化范围:0~FFFFH,所以一个栈段最大容量为64K.
第四章:第一个程序
伪指令:由编译器执行的指令,CPU是不知道它们的,由编译器执行它们
指令 功能 段名 segment … 段名 ends 定义一个段,segment说明一个段开始,ends说明一个段结束 ebd 汇编程序结束的标志 assume assume cs:code,将段code与寄存器cs关联起来 mov ax,4c00H int 21H 两行汇编指令,实现程序返回 连接(LINK)作用:
- 当源程序很大时,可以将它分为几个源程序文件来编译,每个源程序编译成为目标文件后,再用连接程序将他们连接到一起,生成一个可执行文件;
- 程序中调用来某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件;
- 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
EXE文件加载过程:找到一段起始地址为0的空闲区,创建一个程序段前缀(PSP),在PSP后256字节开始,即SA+10H:0为段地址载入ds寄存器中,并初始化CS:IP指向这个地址。
第五章:[BX]和loop指令
[BX]:
指令 功能 mov ax,[bx] bx中存放的数据作为偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax中,即(ax) = ((ds)*16 + (bx)) mov [bx],ax bx中存放的数据作为偏移地址EA,段地址SA默认在ds中,将ax中的数据送入SA:EA处,即((ds)*16 + (bx)) = (ax) loop指令:
指令 格式 功能 loop loop 标号 实现循环功能,cx中存放着循环次数,就相当于for,cx为0向下执行,不为0循环 使用方法: ①在cx中存放循环次数②loop指令中的标号所标识地址在前面③循环执行的程序段在标号和lpp指令之间 loop指令框架:
mov cx,循环次数 s: 循环执行的程序段 loop s
在汇编源程序中,数据不能以字母开头。所以大于9FFFH的数要在前面加0。
loop指令的每一次都先判断cx的值,不为0则CS:IP指向下一步循环指令的地址,CPU执行。
在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须使用 “[…]” 来表示内存单元,如果在 “[]” 里用一个常量 idata 直接给出内存单元的偏移地址,就要在 “[]” 的前面显示的给出段地址所在的段寄存器。
mov al,ds:[0] //正确的表示 mov al,[0] //masm就将[idata] 解释为 idata ,而不是一个内存单元地址
用于显示的指明内存单元的段地址的 “ds:” 、”cs:”、”ss:”、”es:”,在汇编语言中成为段前缀。
在pc机中,一般 0:200~0:2ff 的256个字节的空间是安全的,即没有其它应用使用这段内存单元。
第六章:包含多个段的程序
在操作系统环境中,合法的通过操作系统取得的空间都是安全的,需要在汇编源程序中做出说明
dw(define word) 关键字的意思是定义字型数据,数据之间使用逗号分隔。
将 start 放在第一条指令前面,然后在伪指令 end 后面加上 start,表示程序第一条指令从前面的 start 开始。
内存空间的开辟:使用 dw 关键字定义一些字型数据,通常为 0,然后将这一段空间当作栈段来使用。
使用内存 0:0~0:15 单元中的内容改写为程序中的数据:
assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h start: mov ax,0 mov ds,ax mov bx,0 ;ds = 0,bx = 0 mov cx,8 ;循环8次 s: mov ax,[bx] ;(ax) = ((ds)*16 + (bx)),将对应的数据送入ax mov cs:[bx],ax ;将ax中的数据放入内存地址 ((cs)*16 + (bx)) 处,[bx]为偏移地址,段地址在cs中 add bx,2 ;bx + 2 loop s mov ax,4c00h int 21h codesg ends end start
使用内存 0:0~0:15 单元中的内容改写为程序中的数据(数据的传送用栈来进行。栈空间设置在程序内):
assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h dw 0,0,0,0,0,0,0,0,0,0 start: mov ax,cs mov ss,ax mov sp,24h ;或mov sp, 36 ,ss:sp = cs:24h mov ax,0 mov ds,ax ;ds = 0 mov bx,0 ;bx = 0 mov cx,8 ;循环8次 s: push [bx] ;将[bx]中的数据压入栈中 pop cs:[bx] ;或 pop ss:[bx], add bx,2 ;bx + 2 loop s mov ax,4c00h int 21h codesg ends end start
在汇编程序中,一个段的段名就代表了这个段的段地址,而偏移地址需要根据段中的数据来确定。
8086CPU不允许直接将一个数值送入段寄存器,应该由一个寄存器比如 ax 来中转。
汇编语言实验五:
(1)将下面的程序编译连接,用Debug加载、跟踪,然后回答问题
assume cs:code,ds:data,ss:stack data segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data ends stack segment dw 0,0,0,0,0,0,0,0 stack ends code segment start: mov ax,stack mov ss,ax mov sp,16 mov ax,data mov ds,ax push ds:[0] push ds:[2] pop ds:[2] pop ds:[0] mov ax,4c00h int 21h code ends end start
①CPU执行程序,程序返回前,data段中的数据 不变 。
②CPU执行程序,程序返回前,CS= 0C88H ,SS= 0C87H ,DS= 0C86H 。
③设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X-2 ,STACK段的段地址为 X-1 。
(2)将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h
data ends
stack segment
dw 0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
mov ds,ax
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax,4c00h
int 21h
code ends
end start
①CPU执行程序,程序返回前,data段中的数据 不变 。
②CPU执行程序,程序返回前,CS= 0C88H ,SS= 0C87H ,DS= 0C86H 。
③设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X-2 ,STACK段的段地址为 X-1 。
④对于如下定义的段:
name segment
……
name ends
如果段中的数据占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
(3)将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。
assume cs:code,ds:data,ss:stack
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
mov ds,ax
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax,4c00h
int 21h
code ends
data segment
dw 0123h,0456h
data ends
stack segment
dw 0,0
stack ends
end start
①CPU执行程序,程序返回前,data段中的数据 不变 。
②CPU执行程序,程序返回前,CS= 0C86H ,SS= 0C8AH ,DS= 0C89H 。
③设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X+3 ,STACK段的段地址为 X+4 。
(4)如果将(1)、(2)、(3)题中的最后一条伪指令“end start”改为“end”(也就是说,不指明程序的入口),则哪个程序仍然可以正确执行?请说明原因。
答:start 作为汇编程序的一个标号,定义了程序的入口,如果不指名入口,程序会从加载进内存的第一个单元起开始执行,前二个题中,前面定义的是数据段、栈段,CPU 能够执行,但在整个程序上来讲,逻辑顺序错误,这样会导致空间二次分配等等情况。
如果指明了程序的入口,CPU会直接从入口处开始执行真正的机器码,直到遇到中断指令返回,比如指令进行到需要栈段空间的时候,CPU 会跳到前面定义的栈段得到分配空间的地址。此种方式能够确保程序逻辑上的正确。因此有必要为程序来指明入口。
综上,只有(3)可以在没有指明程序入口的情况下正确执行。
(5)程序如下,编写code段中代码,将 a 段和 b 段中的数据依次相加,将结果存到 c 段中。
assume cd: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 ;ds段寄存器地址指向a
mov ax,b
mov es,ax ;es段寄存器地址指向b
mov ax,c
mov ss,ax ;ss段寄存器地址指向c
mov bx,0 ;偏移地址为0
mov cx,8 ;循环8次
s: mov ax,[bx] ;(ax) = ((ds)*16 + (bx)),将对应的数据送入ax
mov ss:[bx],ax ;将ax中的数据放入内存地址 ((ss)*16 + (bx)) 处,[bx]为偏移地址,段地址在ss中
mov ax,es:[bx] ;(ax) = ((es)*16 + (bx)),将对应的数据送入ax
mov ss:[bx],ax ;将ax中的数据放入内存地址 ((ss)*16 + (bx)) 处,[bx]为偏移地址,段地址在ss中
inc bx ;bx + 1
loop s
mov ax,4c00h
int 21h
code ends
end start
(6)程序如下,编写 code 段中代码,用 push 指令将 a 段中的前8个字型数据,逆序存储到 b 段中。
assume cs:code
a segment
dw 1,2,3,4,5,6,7,8
a ends
b segment
dw 0,0,0,0,0,0,0,0
b ends
code segment
start: mov ax,a
mov ds,ax ;ds指向a段
mov ax,b
mov bx,0 ;ds:bx 指向a段的第1个单元
mov ss,ax
mov sp,16 ;设置栈顶指向 b:16
mov cx,8 ;循环8次
s: push [bx]
add bx,2 ;bx + 2
loop s ;将a段中0~16个单元逆次入栈
code ends
end start