1,机器语言与汇编语言一一对应
2,汇编指令:Mov AX,BX 将寄存器BX,移入AX
3,寄存器:CUP中的存储器,注意不是CUP的缓存,
4,汇编--》编译器---》机器码01
5,汇编语言组成
6,一串机器码,可以使指令,也可以是数据,就看是cd,ds那个指向他
7,地址信息,读或写,数据信息
cup与所有内存之间:地址总线,数据总线,控制总线,每条线对应不同信息,指令与数据分开
8,总线
9,例
地址:0-7FFFH,为RAM
地址:8000H-9FFFH,为显存地址:当数据写入此处,就会显示
地址:A000H-FFFFH为各个ROM
10,8086CPU如下,8086,8088,龙芯,酷睿等都是类似分配
1,CPU:运算器,控制器,寄存器,内部总线相连接
2,8086cpu寄存器:共14个,有8个通用寄存器
寄存器中数据大多是n*8bit
1,一次16bit数据
2,寄存器最大宽度16bit
3,寄存器运算器之间通路16bit
1,20位地址总线,寻址1m
2,内部16位结构,寻址64kb
方法:
3,物理地址=段地址*16+偏移地址 因为是2进制
1,内存并没有分段,分段是因为cup,内部不足,但因此使用分段管理内存
2,偏移地址16bit,因此每段64KB
3,CUP可以不痛段地址与偏移构成相同的物理地址
8086:CS,DS,SS,ES
cs:指令段寄存器,ip指令偏移寄存器 ----》段,偏移
1,只能操作寄存器控制cpu,因此需要控制cs,ip
2,mov 传送指令,可修改ax,但不能cs,ip
3,jmp 2ae3:3 跳转的意思 此时cs 为2ae3,ip为3
将n<64kb的数据 ---》代码段
将cs,ip指向该代码段,会认为是指令,才会执行,执行后ip = ip + 指令长度
1,例:数字2000 (4E20H)
任何两个连续单元,N,N+1,可以看做两个内存单元,也可以看做地址为N的字单元中的高位字节单元,低位字节单元
DS:存放的数据的段地址,
1,执行指令时,自动取DS中数据为内存单元的段地址
2,mov al,[0] 将ds:0 内存单元中的数据移入al
3,但不能直接把值移入ds ,mov ds 1000H:错误的 8086cpu不支持放入段寄存器,硬件问题。只能数据 --》通用寄存器--》段寄存器
1,mov
push ax:将ax放入栈内存中
sp =sp-2
ss:sp 指向新地址
pop ax:从栈内存取数据到ax
1,cup如何知道某段内存是栈内存?
ss,栈顶段寄存器,sp栈顶偏移 ss:sp指向栈顶,栈为空时指向栈顶下一位,
出栈时只是修改索引,数据还未覆盖,由此可见,硬盘类似
2,cpu如何知道哪个是栈顶,栈底?从大地址向小地址写数据
3,语言中的函数,调用时就是申请一块栈内存,执行完,栈内存元素全部出栈,因此局部变量失效,栈内存由编译器管理,堆内存有程序员管理
1,cpu关心栈只关心栈顶指针在何处,当前要执行的指令是哪个
2,c,c++常会溢出
1,栈空间,一段固定读取格式的内存
1,可以将小于64kb的内存当做栈
2,认为10010H,1001FH 当做16字节栈,但cup只关心栈顶,不会关心栈段的大小
汇编---》可执行
1,流程
2,可执行程序组成
3,执行过程
4,汇编指令,伪指令
1,一个标号只带一个地址
2,codesg:给段的命名,最后会变为地址
3,程序返回,
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
abc ends
end
masm 编译,link 链接 生成exe文件
“;”可以简化masm,link
ml:两部合起来
# 有入口的文件
assume cs:abc
abc segment
start:mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
abc ends
end start
1,exe文件加载过程
程序加载后,内存地址为ds:0
其中前256是dos与程序通信的,256向后是程序
注意:在debug中[0]表示段地址的偏移地址,在masm中表示数值0
mov bx ,0
mov ax,[bx] ,此时[bx]表示以bx内容位偏移地址
1,将bx中的数据作为偏移地址
inc bx # 自加
1,loop 标号
2,2^1000
assume cs:code
code segment
mov ax,2
mov cx,11
s:add as,ax
loop s # s就是标号 判断cx,是否跳到标号
3,越位赋值会可能出错 mov ax,[bx]
mov al,[bx]
mov ah,0
4,汇编语言中数字以字母开头,例如大于9fffh的,Afffh,需要在前边加0
5,使用deubg追踪循环
g 命令地址:相当于 debug中的段点
p :在循环时使用,会直接跳到循环结束,即自动完成循环过程
在masm中
mov al,[0] 将0移入al
mov al,ds:[0] ,偏移为0的地址中内容放入al
mov al,[bx],将bx为偏移地址的地址中的内容放入al
mov al,ds:[bx],同上
内存数值相加时的问题
例:ffff:0---ffff:b 12个字节相加,
问题:放入16位ax,会不会越界,8位如何放入16位寄存器
解决方法:使用中介,将8位数据放入16位中介寄存器ax,再相加
assume cs:code
code segment
mov,ax,0ffffh
mov,ds,ax # 设置段地址
mov dx,0 # 初始化累加寄存器
mov al,ds:[0] # 赋值给中间寄存器
mov ah,0 # 设置高位为0,此时ax为0
add dx,ax # 累加
...
mov ax,4c00h
int 21h
\(sum =\sum\limits_{X=0}^{0bh}(ffffh*10h+X)\)
# 优化:使用循环,偏移地址应该递增
asume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0
mov dx,0
mov cx,12
s: mov al,[bx]
mov ah,0
add dx,ax
int bx
loop s
mov ax,4c00h
int 21h
code ends
end
ds就是段地址,也称为段前缀,ds是默认的段前缀,其他还有cs,es等
在PC中,0:200到0:2ff是安全的空间
段前缀的使用
在不同的段中操作时,一个ds需要多次更改,可能需要多个段寄存器,例如用es替代。效果更好
dw 0123h,0456h 定义字符型数据
db 45h,78h 定义字节型数据
当上面定义在cs中时,数据段地址就是代码段的段地址
dw在第一行定义,数据地址偏移为0,2,4,6....
assume cs:code
code segment
dw 0123h,0456h,0789h
start:mov bx,0
mov ax,0
mov cx,0
s:add ax,cs:[bx]
add bx 2
loop s
mov ax 4c00h
int 21h
code ends
end start
# cup读指令的时候会从start开始,若不添加start,应为dw的存在,存放的是数据,无法转化为机器指令,程序无法执行
# end的作用:通知编译器程序结束,告诉编译器程序入口在哪里,当不指定入口时,会按照上到下执行,因此若现定义数据,会把数据当做指令执行
问题:将上面程序中的数据逆序存放
assume cs:code
code:segment
dw 0123h,0456h,0789h
dw 0,0,0 # 定义数据当做栈空间
start :mov ax,cs
mov ss,ax # 指定栈段
mov sp,32 # 指定栈偏移,栈指针,操作时指针自动变
mov bx,0
mov cx,3 # 循环次数
s:push cs:[bx] # 入栈数据
add bx,2
loop s
mov bx,0
mov cx,3
s0:pop cs:[bx]
add bx,2
loop s0
mov 4c00h
int 21h
code ends
end start
上面的逆序排放
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h
data ends
stack segment
dw 0,0,0
stack ends
code segment
start:mov ax,stack # cs,ss,da 系统加载程序时会有系统指定加载的位置,cs 确定后,ds应该就是cs-2,ss=cs-1
mov ss,ax
mov sp,6 # 指定栈指针
mov ax,data
mov ds,ax # 数据段地址
mov bx,0
mov cx,3
s:pop [bx]
add bx,2
loop s
mov bx,0
mov cs,8
s0:pull [bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
code ends
end start
键盘按下a键:键盘产生61h,放入内存空间,编辑器软件从指定内存读取,送到显存中
'....'指明数据是以字符形式给出,编译器会转化为ASCII
assume ds:data
data segment
db 'unix' # db 75H,6EH,49H,58H
db 'fork' # ...
data ends
code segment
start:mov al,'a' # mov al,61h
mov b1,'b'
mov ax,4c00h
int 21h
code ends
end start
A:65,a:97
大写:第五位为0
小写:第五位为1
and 小写,11011111b
or 大写,00100000b
mov ax,[bx+200]:将地址内数据放入ax,这个地址是bx中的值+200
mov ax,200[bx] mov ax,[bx].200
assume cs:code,ds:data
data segment
db 'BaSiC'
db 'MinIX'
data ends
code segment
start:mov as,data
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx]
and al,11011111b
mov [bx],al
mov al,[5+bx] # 5[bx]
or al,00100000b
mov [5+bx],al
inc bx
loop s
code ends
end start
与bx类似的功能,但不能分为2个8位的使用,
bx不够用的问题
# 将数据复制到后边地址
# ds:si 指向原数据
# ds:di 指向目的地址
assume cs:code,ds:data
data segment
db 'welcome to masm!'
db '...............'
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cs,8
s:mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s
mov ax,4c00h
int 21h
code ends
end start
两个功能类似
[bx+si] 表示一个内存单元,偏移地址为bx值+si值
表示内存单元,偏移地址,
例:将数据段单词,开头变为大写
assume cs:code,ds:data
data segment
db '1.file '
db '2.editable'
db '3.help '
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov cx,3
s:mov al,[bx+2] # 把第二个字母放入al
add al,11011111b # 修改字母,变为大写
mov [bx+3],al # 放回
add bx,10
loop s
mov ax,4c00h
int 21h
code ends
end start
对于多层循环,需要其它寄存器记下外层cx的值,内层循环结束后恢复,不然会遇到死循环
当寄存器不够时,需要使用内存了,如使用栈,每层cx入栈 push cx,结束时出栈pop cx
寄存器:ax,bx,cx,dx ah,al,bh,bj,ch,cl,dh,dl
sp(栈指针),bp, si(原数据地址),di(目的数据地址)
段寄存器:ds(数据段),ss(栈段),cs(代码段),es(扩展段)
指令执行前,数据所在位置:CUP,内存,端口
8086可操作byte,word,在机器指令中需要指明进行操作的是字符还是字节
8086CPU转移指令分类
由编译器处理的符号,取得标号的偏移地址吗,就标号所在地址
assume cs:code
code segment
start:mov ax.offset start # 相当于mov ax,0
s:mov ax,offset s # 相当于mov ax,3
code ends
end start
movax,0123 # B8 23 01
mov as,ds:[0123]# A1 23 01
push ds:[0123] # FF 36 23 01
jmp指令对应的机器码中没有目的地址,是相对于jmp所在指令的偏移
jmp 16位寄存器
jmp ax
使用位移可使程序在内存中任意位置调用
编译时编译器会报错
显存地址:B8000H-BFFFFH dos系统的显存地址
都是修改cs,ip
call far ptr 标号
sp = sp -2
ss*16+sp=cs
sp = sp -2
ss*16+sp=ip
cs=标号所在段地址
ip=标号所在偏移地址
相当于push cs,push ip,jmp fat ptr 标号
sp =sp -2
ss *16+sp =ip
ip=16位寄存器
相当于push ip,jmp 16位寄存器
可以使用call与ret配合写子程序,就像是高级语言中的方法,使用call跳转,ret返回
如何存储子程序参数,与返回值
用寄存器存储参数与结果最常用的方法
传递的数据多的时候怎么办?使用内存,或者栈,高级语言就是栈
assume cs:code
data segment
db 'conversation'
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0 ;ds:si指向字符串所在空间首地址
mov cx,12 ;cx存放字符串长度
call capital
mov ax,4c00h
int 21h
capital:and byte ptr [si]:11011111b
inc si
loop capital
ret
code ends
end start
上面的程序,当不知道字符串长度时,如何做
db 'conversation','0'
capital:mov cl,[si]
mov ch,0
jcxz ok ;若果cx=0,结束,若不是,接着处理
and byte ptr [si],11011111b
inc si
jmp short capital
ok:ret
计算机中的数据可以看做是有符号数,也可以是无符号数
00000001B # 可看做无符号数1,或有符号数+1
10000001B # 可看做无符号数129,或有符号数-127
flag标志寄存器与其它不同,其他是用来存放数据的,flag是按位起作用
1,3,5,12,13,14,15在8086中没有使用
存储上一条指令执行后是否有数据进位,或借位(减 法)
减法
超出了机器的范围
对于cpu的计算结果,若做有符号位观察,OF,SF,若看作无符号位观察CF
总之,cpu计算时,不管有无符号,adc指令:结合符号位获取正确值
mov ax,98d ; 0110 0010
add ax,99d ; 0110 0011
;1100 0101 C5 SF=1,OF=1,CF=0
;当做无符号位相加,C5
;当有符号位,首位为1,SF=1表示是负数,是-59的补码,
带进位加法指令,利用CF位记录值
adc ax,bx ax = ax+bx+CF
add,配合adc可以解决这些问题
;1EF000h+201000h 高16位ax,低16位bx
mov ax,001EH
mov bx,0f000H
add bx,1000H ; 0000H cf=1
adc ax,0020H ;
adc执行后也会可能产生进位
;1E F000 1000h+ 20 1000 1EF0h 高位16ax,次高位16bx,cx低16位
;思路 低16位相加,然后次16位adc,高16位adc
mov ax,001EH
mov bx,0f000H
mov cx,1000H
add cx,1ef0h
adc bx,1000h
adc ax,0020h
更大的数据可以放在栈或内存中
带位减法指令
sbb ax,bx = > ax= ax-bx-cf
与加法类似
相当于减法指令,但不保存结果,仅仅对标志位进行设置
cmp ax,ax ===> 此时ax不变,仅影响flag ---》ZF=1,PF=1,SF=0,CF=0,OF=0
有符号位比较
ZF
需要考虑OF,SF
与cmp配合
cmp ah,bh
je s
add ah,bh
jmp short ok
s:add ah,ah
ok:ret
pushf:将标志寄存器的值压入栈中
popf:弹出到标志寄存器,出栈
外部中断:外部设备
内部中断:内部错误
软件中断,int 21h等
CPU中断优先权:
内存中有中断处理程序
找到中断程序地址,CPU设置cs,ip该过程为中断过程
中断过程:
iret:pop ip;pop cs;popf
iret和硬件自动完成中断过程配合使用
发生除法溢出时,即结果比寄存器范围大,Cpu将转换为处理中断程序
中断向量表中该错误终端地址为0号地址
可以自定义中断程序,修改中断向量表,此时会执行自己的中断程序
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset do0;设置自定义终端程序位置
mov ax,0
mov es,ax
mov di,200h ;设置es:di位置
mov cx offset do0end- offset do0
cld rep movsb ;中断程序写入内存
;设置中断向量表
mov ax,4c00h
int 21h
do0:
xxxxx
mov ax,4c00h
int 21h
do0end:nop ;占一个字节,无作用
code ends
end start
CPU在执行一条指令后检测到TF=1,就会引发中断过程
单步中断的类型码为1,debug就是改变了1号中断的程序,改为debug的程序
有时候遇到中断信号CPU也不会响应,
例如:
与call类似,int调用中断程序
mov ah,2 ;2号子程序 mov bh,0 ;0页 mov dh,5 ;5行 mov dl,12 ;12列 int 10h
外设的出入不是直接送入内存,而是相关接口芯片的端口中
assume cs:code
code segment
a:db 1,2,3,4,5,6,
b:dw 0
start :mov si,offset a;将a的偏移地址给si
code ends
end start
;若标号后边没有“:”他们可以同时描述内存地址与单元长度
有“:"的地址标号只能在代码段使用
若要使用数据标号访问数据,需要assume 将寄存器与段对应
例如上面的数据,访问12345678中的1 需要设置ds,si
若使用a,则mov ax,[0]即可不用管ds,因为assume ds:data
键盘缓区15个字单元,存储扫描码,ascii
mov ah,0
int 16h
;从键盘缓冲区读取一个,并删除它
; ah=扫描码,al=Ascii
int 16h的0号功能
int 9向缓冲区写数据,int16h读数据,编程接受用户输入时,就是int16h
柱面号,磁道号,扇区号
读取:
ah = int 13h ;功能号,2表示读取,3表示写入
al=读取扇区数
ch=磁道号
cl=扇区号
dh=磁头号
dl=驱动号 0:软驱A,1:软驱B 80h:硬盘C,81h:硬盘D
ex:bx 向此区域读入数据
ah=0,al = 读入的扇区数
ah=出错代码
写入同上