访问寄存器和内存
寄存器
8086 CPU
8086所有寄存器都是16位,可以存放两字节。8086的字长为16bit
一个字可以存在一个16位寄存器中,这个字的高位字节存放在这个寄存器的高8位寄存器,低位字节存放在低8位寄存器
共14个寄存器
- 通用寄存器:累加器AX、基址寄存器BX、基数寄存器CX、数据寄存器DX
- 变址寄存器:源变址寄存器SI、目的变址寄存器DI
- 指令寄存器:堆栈指针寄存器SP、基地址寄存器BP
- 指令指针寄存器:IP
- 段寄存器:代码段寄存器CS、栈段寄存器SS、数据段寄存器DS、附加段寄存器ES
- 标志寄存器:PSW
结尾H→16进制,D→10进制,B→二进制
兼容问题
8086的上一代8080、8088中寄存器是8位的(8088寄存器16位,但是对外的数据总线只有8条),8086的寄存器是16位的就会产生兼容问题。因此8086的通用寄存器均可以分为两个独立的8位寄存器使用
以AX为例,分为AH(高8位)和AL(低8位)
CS和IP
CS-代码段寄存器、IP-指令指针寄存器
CPU将内存中CS:IP指向的内容当作指令执行
工作过程:从CS:IP指向的内存单元读取指令,读取的指令进入缓冲器,IP+=所读取指令的长度从而指向下一条指令,执行指令然后重复以上过程
标志寄存器FLAGS
OF溢出-SF符号-ZF为零-AF辅助进位-PF奇偶-CF进位
内存
物理地址
CPU访问内存单元时要给出内存单元的地址,所有的内存单元构成的存储空间是一个一维的线性空间。每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址
8086有20位地址总线,可传送20位地址,寻址能力为220B=1M
但是8086是16位结构的CPU,运算器一次最多处理16位数据,寄存器的最大宽度为16位,8086内部处理、传输、暂存的地址也是16位,因此寻址能力只有216B=64KB
为解决两个寻址能力的差异,8086CPU用两个16位的地址(段地址和偏移地址)合成一个20位物理地址
物理地址=段地址×16+偏移地址,×16就是左移4位
总体流程:获得段地址和偏移地址→地址加法器计算物理地址→输入输出控制电路→通过地址总线传给内存
内存管理
内存本身没有分段,段的划分来自CPU
内存的分段表示
- 段地址×16一定是16的倍数,所以一个段的起始地址也一定是16的倍数
- 偏移地址16位,16位地址的寻址能力为64K,所以一个段的长度最大为64K
表示
- 数据存放在“内存段地址:偏移地址”单元中
- 数据存放在内存的“段地址”段中的“偏移地址”单元中
CPU中有专门的寄存器存放段地址(4个段寄存器):CS-代码段寄存器,DS-数据段寄存器,SS-栈段寄存器,ES-附加段寄存器
字的存储
8086中16位作为一个字,16位的字在内存中需要2个连续字节存储:低位字节存放在低地址单元,高位字节存放在高地址单元(也就是小端存储)
字单元:由两个连续的内存单元组成,存放一个字型数据(16位)
区分字型数据和字节型数据(前者的完整的16字节的数据,后者是一半的)
DS和代码段实现字的传送
利用DS和[address]配合实现字的传送,用DS寄存器存放要访问的数据的段地址,偏移地址用[…]形式直接给出。看到[…]格式时,默认段地址保存在DS里
将段地址送入DS
MOV BX,1000H //通过通用寄存器,8086CPU不支持将数据直接送入段寄存器
MOV DS,BX
访问段寄存器:数据放入一般寄存器,通过一般的寄存器来访问段寄存器
CPU可以一次性传送一个字(16位数据):寄存器和内存中的数据相互传送,会自动取两个8位的数据
段的总结
物理地址=段地址×16+偏移地址
编程时根据需要将一组内存单元定义为一个段,一个段需要是起始地址为16的倍数,长度为N(N≤64K)的一组地址连续的内存单元。段地址、偏移地址在程序中是可以由程序员安排的
各种段
- 数据段:段地址在DS中,用MOV、ADD、SUB等访问内存单元的指令时,CPU将我们定义的数据段中的内容当作数据段来访问
- 代码段:段地址在CS中,将段中第一条指令的偏移放在IP中
- 栈段:段地址在SS中,将栈顶单元的偏移地址放在SP中,CPU在进行栈操作时会将定义的栈段当作栈空间使用
三段是可以共享内存的
栈
以字为单位对栈进行操作
8086CPU中,有两个与栈相关的寄存器,通过这两个寄存器确认哪段空间被当作栈使用
- 栈段寄存器SS,存放栈顶的段地址
- 栈顶指针寄存器SP,存放栈顶的偏移地址
CPU只知道由SS:SP指示出的栈顶位置,不知道栈空间多大,因此可能发生栈顶超界问题,就要在编程时就防止越界,出栈时同理
指令
MOV、ADD
JMP:8086CPU不提供对CS和IP修改的指令,通过JMP修改CS、IP
JMP 段地址:偏移地址 //同时修改CS、IP内容
JMP 某一合法寄存器 //修改IP内容
汇编语言程序
汇编程序包括汇编指令和伪指令的文本
有固定的两行,是程序返回的固定模板,程序结束运行后,将CPU的控制权交还给使它得以运行的程序(DOS系统),例如
mov ax,4c00h
int 21h
伪指令
伪指令:没有对应的机器码的指令,最终不被CPU所执行。由编译器来执行,编译器根据伪指令来进行相关的编译工作
三种伪指令
- 段定义:一个汇编程序是由多个段组成,这些段被用来存放代码、数据或当作栈空间来使用。一个有意义的汇编程序中至少要有一个段,用来存放代码(代码段)。每个段都要有段名
段名 segment ;段开始
...
段名 ends ;段结束
- end:汇编程序的结束标记,若程序结尾不加end,编译器在编译时无法知道程序何处结束
- assume:假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联
assume cs:段名 ;CS寄存器与该段相关联,CS是代码段寄存器也就是该段会作为代码段
汇编程序
小程序可直接在Debug中编写,适用于功能简单、短小精悍的程序,只需要包含汇编指令即可
单独编写成源文件后再编译为可执行文件,适用与编写大程序,需要包含汇编指令、指导编译器工作的伪指令
示例:求23的值
assume cs:abc ;将段与段寄存器关联
abc segment
mov ax,2
add ax,ax
add ax,ax ;任务完成
mov ax,4c00h ;程序返回的代码
int 21h
abc ends
end
文本编辑(.asm)→编译得到目标文件(.obj)→连接得可执行文件(.exe)→运行
编译过程
- 目标文件(.OBJ)是对一个源程序进行编译要得到的最终结果
- 列表文件(.LST)是编译器将源程序编译为目标文件的过程中产生的中间结果
- 交叉引用文件(.CRF)和LST文件一样也是编译过程中的中间结果
连接过程
- 可执行文件(.EXE)是对一个程序进行连接要得到的最终结果
- 映像文件(.MAP)是连接程序将目标文件连接为可执行文件过程中产生的中间结果
- 库文件(.LIB)包含一些可以调用的子程序,如果程序中调用了某一个库文件的子程序,就需要再连接的时候将这个库文件和目标文件连接到一起生成可执行文件
- no stack segment “没有栈段”的告警错误,编写简单程序可以忽略
代码段
段前缀
针对一个“异常”现象
mov al,[0] ;会被编译成mov al,00 地址变成了值
在[idata]前显式地写上段寄存器以解决
mov al,ds:[0]
出现在访问内存单元的指令中,用于显示地指明内存单元的段地址的ds、cs、ss、es,在汇编语言中称为段前缀
Loop指令
cx中存放循环次数,还要定义一个标号
实现循环
- (cx)=(cx)-1
- 判断cx的值,不为0则转至标号处执行程序;为0则继续向下执行
示例
assume cs:code
code segment
mov ax,2
add ax,ax
mov cx,11
s: add ax,ax
loop s ;定义标号s,标号代表了一个地址
mov ax,4c00h
int 21h
code ends
end
Loop可以与[bx]结合
访问连续的内存单元时,对字节求和需要注意8位的数据相加造成进位丢失问题,应取出8位数据但是加到16位寄存器里
mov al,ds:[addr]
mov ah,0
add dx,ax
读取连续数据,循环中要访问的内存单元偏移地址放在bx中,随循环递增,访问连续的内存单元
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax ;设置段寄存器
mov bx,0
mov dx,0
mov cx,12
s: add al,[bx]
mov ah,0
add dx,ax
inc bx ;bx+1
loop s
mov ax,4c00h
int 21h
code ends
end
数据拷贝的示例
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov ax,0020h
mov es,ax ;es附加段地址寄存器
mov bx,0
mov cx,12
s: mov dl,[bx]
mov es:[bx],dl
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
loop的位移情况和8位位移一样
div指令
div除法
被除数:默认在AX或DX和AX中,所以调用之前要先把被除数放好
除数:8位或16位,在寄存器或内存单元中
8位的除数需要16位的被除数,结果8位商8位余数;16位除数需要32位被除数,因此用两个寄存器拼成32位
被除数 | AX | DX和AX |
---|---|---|
除数 | 8位内存或寄存器 | 16位内存或寄存器 |
商 | AL | AX |
余数 | AH | DX |
div bl ;(ax)÷(bl)商(al)余(ah)
div byte ptr ds:[0] ;(ax)÷((ds)×16+0)商(al)余(ah)
div bx ;(dx)×10000H+(ax)÷(bx)商(ax)余(dx)
div word ptr ds:[0] ;(dx)×10000H+(ax)÷((ds)×16+0)商(ax)余(dx)
要提前在默认寄存器中设置好被除数,且默认寄存器不作别的用处
示例:运算100001/100也就是186A1H,超过16位了,需要进行16位的除法,用dx和ax两个寄存器联合存放186A1H,bx存放除数100D=64H
mov dx,1
mov ax,86A1
mov bx,64
div bx
mul指令
mul 寄存器
mul 内存单元
8位乘法 | 16位乘法 | |
---|---|---|
被乘数 | AL | AX |
乘数 | 8位内存或寄存器 | 16位内存或寄存器 |
结果 | AX | DX高位,AX低位 |
示例
mov al,100 ;计算100×10
mov bl,10
mul bl ;结果(ax)=1000(03E8H)
mov ax,100 ;16位乘法
mov bx,10000
mul bx ;(dx)=00FH,(ax)=4240H,1000000=F4240H
dup设置内存空间
dup和db(字节型)、dw(字型)、dd(双字)等数据定义伪指令配合使用,用来进行数据的重复
db 重复次数 dup 重复的数据 ;dw、dd同理
db 3 dup (0) ;定义了3个字节,值都是0,即db 0,0,0
db 3 dup(0,1,2) ;定义了9个字节,由0、1、2重复三次构成,即db 0,1,2,0,1,2,0,1,2
db 3 dup('abc','ABC') ;定义了18个字节,即db 'abcABCabcABCabcABC'
代码段中使用数据
程序中直接写地址是危险的(上面的代码就是危险的),应当在程序的段中存放数据,运行时由操作系统分配空间。各种段中均可以有数据,可以在单个段中安置,也可以将数据、代码、栈放入不同的段中
assume cs:code
code segment
dw 0123H,0456H,0789H,0abcH ;累加这4个数据
start: mov bx,0 ;起始的第0个数据就是0123H
mov ax,0
mov cx,4
s: add ax,cs:[bx] ;数据定义在代码段里,这些数据的段地址就是代码段的段地址cs
add bx,2 ;2字节所以+2
loop s
mov ax,4c00h
int 21h
code ends
end start ;end除了告知编译器程序结束,还可以告知程序的入口位置
dw:define word,定义字型数据(定义一个字),也就是一个数据是一个字,2字节
db:定义一个字节
dd:定义一个双字
定义一个标号start用于定指定代码开始的位置,程序的开始是数据,不加start程序默认从头开始都是代码,造成混乱。这样程序加载后CS:IP指向第一条指令在start处
代码段中使用栈
栈需要内存空间,在程序中通过定义“空”数据来取得
assume cs:codesg ;利用栈将数据逆序存放
codesg segment
dw 0123H,0456H,0789H,0abcH
dw 0,0,0,0,0,0,0,0
start: mov ax,cs
mov ss,ax ;栈段寄存器ss和cs取值一样都是数据最开始
mov sp,18h ;数据+栈共12字,24字节,0x18,占了0~0x16
;入栈
;出栈
mov ax,4c00h
int 21h
codesg ends
end start
入栈
mov bx,0
mov cx,4
s: push cs:[bx]
add bx,2
loop s
出栈
mov bx,0
mov cx,8
s0: pop cs:[bx]
add bx,2
loop s0
不同段处理数据、代码、栈
assume cs:code,ds:data,ss:stack
data segment
dw 0123H,0456H,0789H,0abcH
data ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
code segment
start: mov ax,stack ;stack标号所对段地址
mov ss,ax
mov sp,10h
mov ax,data
mov ds,ax ;初始化各段寄存器,程序会自动给代码段cs赋值,不用自己初始化
;入栈
;出栈
mov ax,4c00h
int 21h
code ends
end start
入栈
mov bx,0
mov cx,8
s: push [bx]
add bx,2
loop s
出栈
mov bx,0
mov cx,8
s0: pop [bx] ;段地址默认ds
add bx,2
loop s0
易混/注意点
约定用idata表示常量
在汇编程序中,数据不能以字母开头,前面加0,比如FFFFH要写成0FFFFH
单引号括起来的字符处理是以ASCII码的形式存储
大小写字母的二进制只有第六位不一样,小写为1大写为0
- 小写→大写:b and 11011111 = B
- 大写→小写 :B or 00100000 = b
内存寻址方式
相关寄存器
BX-通用寄存器,在计算存储器地址时,常作为基址寄存器用
SI-源变址寄存器
DI-目标变址寄存器
SI和DI是8086CPU中和BX功能相近的寄存器,但SI和DI不能分成两个8位寄存器来使用
示例:将字符串复制到它后面的数据区中
assume cs:codesg,ds:dataseg
dataseg segment
db 'hello world!' ;12个字符
db '............' ;用来预留空间,几个.就留几个字符
data ends
codesg segment
...
codesg edns
end
源数据起始地址 dataseg:0,目标数据起始地址 dataseg:12
用ds:si指向要复制的原始字符串,用ds:di指向目的空间,利用循环完成复制
codesg segment
start: mov ax,dataseg
mov ds,ax
mov si,0
mov,di,12
mov cx,6 ;复制的是一个字,一次两个字符
s: mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s
mov ax,4c00h
int 21h
codesg edns
寻址方式
[idata]直接寻址
mov ax,[200]
[bx]寄存器间接寻址
mov bx,0
mov ax,[bx]
寄存器相对寻址
[bx+idata]类似数组,[bx+idata]表示一个内存单元,偏移地址为(bx)+idata,[bx+200]→((ds)×16+200+(bx))
mvo ax,[20+bx]
mov ax,200[bx]
mov ax,[bx].200
可以利用[bx+idata]在一个循环里同时处理两组字符,第二个字符串每个字符相对第一个是一样的偏移位置,就可以将分别处理两个字符串的循环合并成一个循环
基址变址寻址
[bx+si]和[bx+di]方式指定地址,[bx+si]表示的内存单元偏移地址为(bx)+(si),[bx+si]→ ((ds)×16+(bx)+(si))
mov ax,[bx+si]
mov ax,[bx][si]
si和di默认段地址是ds
相对基址变址寻址
[bx+si+idata]和[bx+di+idata]
[bx+si+idata]偏移地址为(bx)+(si)+idata,段地址在ds中,[bx+si+idata]→((ds)×16+(bx)+(si)+idata)
mov ax,[bx+si+200]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200
各种寻址方式利用
assume cs:codesg,ds:dataseg
dataseg segment
db 'abc ' ;16个字符
db 'def '
db 'hij '
db 'klm '
dataseg ends
codesg segment
start: ...
codesg ends
end start
将字母变成大写
mov ax,dataseg
mov ds,ax
mov bx,0
mov cx,4
s0: mov dx,cx ;多层循环但cx只有一个,可以利用dx将外层循环的cx值保存在dx中
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si ;下一列,先把一行的每一列遍历完
looop s
add bx,16 ;下一行
mov cx,dx
loop s0
但是寄存器数量有限,可以用固定的内存空间保存数据
mov ax,dataseg
mov ds,ax
mov bx,0
mov cx,4
s0: mov ds:[40H],cx ;把外层循环的cx值保存在dataseg:40H单元中
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
looop s
add bx,16
mov cx,ds:[40H] ;立即寻址
loop s0
直接确定内存空间保存数据需要避免该内存空间被正常程序占用,可以采用栈保存数据
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
...
mov ax,dataseg
mov ss,ax
mov sp,16
mov ds,ax
mov bx,0
mov cx,4
s0: push cx ;外层循环的cx值压栈
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
looop s
add bx,16
pop cx
loop s0
用于寻址的寄存器
BX-基址寄存器,SI、DI-变址寄存器,BP-也是基址寄存器
只有以上四个可以通过[…]对内存单元寻址,一般是把bx、bp当作基址,si、di当作变址。mov ax[bx+bp]不行
mov ax,[bx]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+di]
bx以外的通用寄存器、段寄存器不可以用在[…]中,如mov ax,[cx]就是错误的
bx默认ds段,bp默认ss段,给出段就忽略默认了
数据位置、长度
确定数据位置
- 立即数(idata):直接包含在机器指令中的数据,数据包含在指令中
- 寄存器:指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名
- 内存:段地址(SA)和偏移地址(EA),指令要处理的数据在内存中,有SA:EA确定内存单元,除了默认的段寄存器,也可以显性的给出存放段地址的寄存器
确定数据长度
- 字(word)操作:ax、bx
- 字节(byte)操作:al、bl
- 用word ptr和byte ptr指明,没有寄存器参与的内存单元访问指令中,有必要显性指明所访问的内存单元的长度,否则CPU不知道访问的单元是字还是字节单元
mov word ptr ds:[0],1 ;寻址找一个字
inc word ptr [bx] ;bx所指向的字+1
add byte ptr [bx],2
流程转移与子程序
转移
转移指令可以控制CPU执行内存中某处代码的指令,实质上是修改IP或同时修改CS和IP
转移指令分类
- 按转移行为
- 段内转移:只修改IP,jmp ax
- 段间转移:同时修改CS和IP,jmp 1000:0
- 根据指令对IP修改的范围不同
- 段内短转移:IP修改范围为-128~127(一个字节)
- 段内近转移:IP修改范围为-32768~32767(一个字/两个字节表示)
- 按转移指令:无条件转移指令(jmp)、条件转移指令(jcxz)、循环指令(loop)、过程、中断
操作符offset
操作符offset取得标号的偏移地址:offset 标号
codesg segment
start: mov ax,offset start ;相当于mov ax,0
s: mov ax,offset s ;相当于mov ax,3
codesg ends
示例,将s处的指令复制到s0处,即将cs:offset s处的数据复制到cs:offset s0处,要复制"mov ax,bx"长度为2个字节/1个字
assume cs:codesg
codesg segment
s: mov ax,bx
mov si,offset s
mov di,offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0: nop ;nop的机器码占一个字节,起占位作用
nop
codesg ends
end
jmp无条件转移
转移的距离
- 段间转移(远转移)
- 段内短转移:jmp short 标号,IP修改范围为-128~127,8位的位移
- 段内近转移:jmp near ptr 标号,IP修改范围-32768~32767,16位的位移
负是当前指令上面,正是当前指令下面
jmp short的机器指令中,包含的是跳到指令的相对位置,而不是转移的目标地址
jmp 标号
短转移:jmp short 标号,8位位移范围-128~127,用补码表示,8位不是人算出来的是编译程序时编译器算出来的。8位位移=“标号”处的地址-jmp指令后的第一个字节的地址
jmp short s ;short指明此处的位移为8位位移
add ax,1
s: inc ax ;实现的功能(IP)=(IP)+8
近转移:jmp near ptr 标号,和短转移同理
段内转移指明了相对于当前IP的转移位移,而不是转移的目的地址
远转移:jmp far ptr 标号,段间转移,指明跳转的目的地址,包含了标号的段地址CS和偏移地址IP
jmp 寄存器
jmp 16位寄存器
jmp ax ;IP=(ax)
jmp 内存单元
段内转移:从内存单元地址处开始存放着一个字,是转移的目的偏移地址
jmp word ptr 内存单元地址
mov ax,0123H
mov ds[0],ax
jmp word ptr ds:[0] ;执行使(IP)=0123H
段间转移:从内存单元地址处开始存放着两个字,高地址处的字的转移的目的段地址,低地址处是转移的目的的偏移地址
jmp dword ptr 内存地址
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0] ;执行后(CS)=0,(IP)=0123H,CS:IP指向0000:0123
源程序不允许使用jmp 2000:1000这样的立即数指令实现段间转移,Debug中可以用但是汇编编译器不认识这样的指令
jcxz指令
jcxz 标号
(cx)=0则转移到标号处执行;≠0则什么也不做,程序向下执行
8位位移=“标号”处地址-jcxz指令后第一个字节的地址,-128~127,补码表示,编译时算出
jcxz是有条件转移指令,所有的有条件转移指令都是短指令,对IP的修改范围都是-128~127,在对应的机器码中包含转移的位移,而不是目的地址
模块化程序设计
call和ret指令
call实现近转移:call将当前的IP或CS和IP压入栈中,然后转移到标号处执行指令,call标号是16位位移
(sp)=(sp)-1; ((ss)×16+(sp))=(IP); (IP)=(IP)+16位位移
call 标号
push IP ;call相当于push+jmp
jmp near ptr 标号
call实现段间转移
(sp)=(sp)-2; ((ss)×16+(sp))=(CS); (sp)=(sp)-2; ((ss)×16+(sp))=(IP);
call far ptr 标号
push CS ;相当于先压栈CS,再压栈IP
push IP
转移地址在寄存器中的call指令
(sp)=(sp)-2; ((ss)×16+(sp))=(IP); (IP)=(16位寄存器)
call 寄存器
push IP ;相当于
jmp 16w位寄存器
转移地址在内存中的call指令
call word ptr 内存单元地址
push IP ;相当于
jmp word ptr 内存单元地址
示例
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0] ;执行后(IP=0123h),(sp)=10h-2=0Eh
call dword ptr 内存单元地址,同理,四个字节中高地址放段地址,低地址放偏移地址
ret和retf,没有操作数的指令
- et指令:用栈中数据修改IP内容,从而实现近转移,相当于pop IP
- retf指令用栈中数据修改CS和IP内容,从而实现远转移,相当于pop IP; pop CS
没有call的时候也可以用ret
call和ret配合使用示例
assume cs:code,ss:stack
stack segment
db 8 dup (0)
db 8 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,1000
call s
mov ax,4c00h
int 21h
s: add ax,ax
ret
code ends
end
ret n
pop ip ;相当于先出栈后,再让sp指针越过n个单位,也就是把这n个单位出栈
add sp.n
call和ret这里还要再看看,没有看太清楚
模块化程序设计
子程序:根据提供的参数处理一定的事务,处理后将结果(返回值)提供给调用者
用寄存器来存储参数和结果是最常用的方法,但是寄存器有限,参数过多时寄存器不够用,可以采取用内存单元批量传递数据
用内存空间批量传递数据:将批量数据放到内存中,将他们所在内存空间的首地址放到寄存器中
用栈传递参数:调用者需要将传递给子程序的参数压入栈中,子程序从栈中取得参数
示例,将字符串转化为大写,利用字符串最后是0返回
assume cs:code
data segment
db 'conversation',0
data ends
code segment
start: mov ax,data ;设置字符串起始地址
mov ds,ax
mov si,0
call capital
mov ax,4c00h
int 21h
capital:mov cl,[si]
mov ch,0
jcxz ok ;如果[si]是0,低8位0,高8位赋值为0,cx就是0,直接跳转
and byte ptr [si],11011111b
inc si
jmp short capital
ok: ret
code ends
end start
子程序中如果有用到会产生冲突的寄存器,子程序开始前将子程序中使用的寄存器入栈,子程序结束后再出栈、返回
标志寄存器
标志寄存器是按位起作用,8086CPU中没有使用1、3、5、12、13、14、15位
标志寄存器用来存储相关指令的某些执行结果,为CPU执行相关指令提供行为依据,来控制CPU的相关方式
标志 | 值为1 | 值为0 | 意义 |
---|---|---|---|
OF | OV | NV | 溢出 |
DF | DN | UP | 方向 |
SF | NG | PL | 符号 |
ZF | ZR | NZ | 零值 |
PF | PE | PO | 奇偶 |
CF | CY | NC | 进位 |
直接访问标志寄存器
pushf:将标志寄存器入栈
popf:从栈中弹出数据送送入标志寄存器中
对标志位有影响大多数是运算类指令,传送类指令大都没影响
带进位的加法指令adc
adc 操作对象1,操作对象2
实现操作对象1=操作对象1+操作对象2+CF
mov al,98H
add al,al ;130H,但是只有8位所以舍去1,al=30H
adc al,3 ;30H+3+1=34H
大数相加
mov ax,001EH ;1EF000H+2010000H,结果高位放ax,低位放bx
mov bx,0F000H
add bx,1000H ;先将低16位相加,再将高16位和进位值相加
adc ax,0020H
看不下去了,先到这吧,回头记得去把剩下的网课补了