王爽-汇编语言 万字学习总结
前言
这是我个人在学习王爽老师的《汇编语言》的一些学习笔记,注意,实验很重要,能锻炼我们的逻辑和编程能力,书本上很多实验这里没有记录下来,我自己除了最后一个实验都做了,实验和工具打包到百度网盘了,地址在文章的最后。对文章和代码有任何问题欢迎在评论区留言提问。
基础知识
CPU需要和内存交互,用到的是外部总线
前提须知:我们对总线描述的多少位、多少宽度指的是CPU对其组件交互的一种能力,是一种能力,外部总线由三部分组成:
地址总线
地址总线有N条地址线,地址总线宽度为N,拥有最多寻找2^N个地址的能力
数据总线
数据总线的宽度为N,则可以一次性传输 N / 8 个Byte的数据,比如8086数据宽度为16,则一次可最多传输2个字节的数据
控制总线
CPU对外部器件的控制是通过控制总线进行的,控制总线是一个总称,总线是一些不同控制线的集合。有多少根控制线总线,就意味着CPU提供了对外多少种控制。所以控制总线的宽度决定了CPU对外部器件的控制能力
内存地址空间:假如CPU寻址能力为1024,则1024个内存地址构成了内存地址空间。存储器空间分为ROM,RAM,我们常说的主存即主存地址空间对应硬件内存条,但是地址空间不止这点,还要加上其他硬件的RAM,比如显卡的RAM,这段地址空间会加在主存后面,CPU通过对这段RAM读写数据,显卡就实现相应画面。
CPU如何控制外设:CPU不能直接控制外设,能直接控制外设的是接口卡,CPU通过控制接口卡,接口卡再控制外设。
寄存器
CPU由运算器、寄存器、控制器组成
- 运算器进行数据处理
- 寄存器进行数据存储
- 控制金控制器件工作
- 内部总线连接各个器件,在他们之间进行数据的传送
不同CPU寄存器数量不一样,8086有14个寄存器,分别是AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW
常见寄存器是ax,bx,cx,dx,都是16位寄存器,能最大表示2^16 - 1的数字保存一般数据。
常用寄存器分高位和低位,比如ah表示高8位即左边八位,al表示低八位即右边八位(8位存储最大255的数)。
注意:如果对高位或低位单独操作,比如add al,al,如果最后加和得的值为185H,则最后al的值会是85H,前面的1不会进位到ah里,cpu会对al的操作视为一个单独的寄存器。
by the way,对寄存器的操作需要位数匹配,否则会报错,比如:
mov ax,bx
mov al,bh 都可以
mov ax,10101H
mov ax,bl 都会报错
一个16CPU表示什么意思?
- 运算器一次最多处理16位数据
- 寄存器最大宽度16位
- 寄存器和运算器之间的通路为16位
8086是16位CPU,即可以一次处理、运输、暂时存储最大16位数据(地址)。
但是8086地址总线有20位,也就是说可以访问最大1MB大小的地址,而16位8086最多表示64KB大小的数据,是怎么访问1MB的大小的地址呢?
这个问题可以翻译成8086是怎么得出物理地址的
用两个16位地址表示一个20位的地址,即段地址和偏移地址
这里CPU用到了段的概念,物理地址 = 段地址 * 16 + 偏移地址(其中段地址必须是16的倍数)
引用书中原文对这个计算的本质描述: “CPU在访问内存时,用一个基础地址(段地址*16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址”。
其中可以得出几个结论:
- CPU可以用不同的段地址和偏移地址形成同一个物理地址
- 可以根据需要,将地址连续、起始地址为16位的倍数的一组内存单元定义为一个段。
这种分段的机制是CPU干的,不是内存被分成段了
段寄存器
8086段寄存器有四个段寄存器CS,DS,SS,ES。
这里讨论一下CS寄存器。
CS是代码寄存器,IP是指针寄存器
CS段寄存器和IP偏移地址配合,CS:IP对应物理地址CS*16+IP。
CPU通过访问CS*16+IP地址读取指令运行
可以通过jmp改变IP的值:jmp bx,但CS段寄存器不变
实验一
使用DEBUG命令查看CPU寄存器的相关信息
什么是DEBUG
Debug是DOS、Windows都提供的实模式(8086方式)程序的调试工具,使用它以查看CPU各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。
不过现在windows7及以上都不支持debug的命令了,做这个实现需要下载额外的软件配置,具体步骤看下面的链接
https://blog.csdn.net/weixin_34216107/article/details/86259971
常用功能
Debug有很多功能,这里只说常用的命令
r 查看或修改寄存器的内容
查看寄存器中的内容
修改寄存器中的内容
r后面跟寄存器的名字,中间有没有空格都无所谓,后面介绍的所以命令开始的参数都可以不跟空格,不过后面的参数都需要用空格分割,rcs后输入修改后的地址回车即可
d 查看内存中的内容
d后面直接跟段地址:偏移地址,此命令会列出这个地址开始到后面128个字节的内容,即默认查看给出地址后面128个字节内容
也可以只查看一小段内容,在后面加个偏移量即可
继续使用d命令不跟参数则默认看后面的内容
e 改写内存中的内容
e后面直接键入地址,默认从该地址逐字节修改,空格跳到下一个,回车结束修改
也可修改为字符或字符串
u 将机器指令翻译成汇编代码
t 命令执行一行机器指令
注意观察寄存器值的变化
a 命令以汇编代码格式在内存写入机器指令
a后面跟地址,可以不断键入汇编指令,以回车结束
寄存器(内存访问)
字的概念
一个字==在内存中连续的两个字节,即16位
8086在高八位存储高位字节,第八位存储低位字节。
在内存中前面的是低位字节,后面的是高位字节,故如果存储9630H在一个字中的话,在内存中是30 96,反过来也是一样,如果内存里是56 78 ,对这两个连续字节类型看成一个字的话,读出来的数据应该是7856H
为什么在这里要说字的概念?
因为8086是16位的,对16位寄存器操作都相当于在对字进行操作
用汇编获取地址里的内容
如标题所说,用下面代码获取地址中的值
mov al,[0]
方括号表示包着的是内存地址,里面内容是偏移地址。
也可以这么写 mov [0],al
意思是将al的值放入ds:0000地址里面
默认的数据段寄存器是DS
故这段语句相当于 mov al,[ds:0]
,当然这是伪码
对了,CPU硬件设计不支持直接像操作通用寄存器一样直接赋值给段寄存器,只能通过寄存器到寄存器
mov ds,1000 # 非法
mov ax,1000
mov ds,ax # 合法
栈
栈是一段连续的内存地址,编程时默认保存数据,汇编通过push、pop对栈进行操作,比如
push ax # 向栈中填ax的值
pop ax # 弹出栈顶的值,放入ax中
8086使用SS作为栈的段寄存器,SP作为偏移寄存器
SS:SP始终指向栈顶
如果我们把2000:1 ~ 2000:F 这段地址空间作为栈空间的话,当其位空我们执行pop就会越界,同时,栈为空时,sp等于0010,也就是指向底部的下一个内存单元,当其为满时执行push栈也会越界,需要注意的是8086没有防止越界的机制,只能我们自己注意进行判断
栈的操作都是以字为单位,可以有
- push 通用寄存器 | 段寄存器 | 地址空间 ([xxx])
- pop 通用寄存器 | 段寄存器 | 地址空间 ([xxx])
需要注意的是,地址空间是向下增加的,也就是上面地址小,下面地址大,同样,栈底在下面,栈顶在上面,入栈时sp减小,出栈时sp增大
d 额外支持的用法
d 段寄存器:offset offset_2
d 通用寄存器:offset offset_2
注意:当出现对ss寄存器进行操作时,比如 mov ss,ax; mov ss,[0];pop ss
,其下面的一条语句也会同时运行,这涉及到中断。
第一个程序
用记事本编写1.asm
,功能是计算2^3,内容如下
assume cs:abcabc segmentmov ax,2add ax,axadd ax,axmov ax,4C00Hint 12H
abc ends
end
源程序由伪指令
和汇编代码
构成
汇编代码解释:
前面三句不用说,主要是最后两句,这两句是固定的,而且必须要有,相当于return。
伪指令功能介绍:
assume
将有特殊用途的段和相关的寄存器关联起来,这里将代码段和cs寄存器关联在一起,不太重要,这里甚至可以不用写这句伪指令
xxx segment
xxx是自定义的标签,表示定义一个段
xxx ends 这里对应上面的xxx segment,表示这个段的结束,ends可以理解为end segment
end
表示程序的结束,给编译器说,必须要有
伪指令不会转换为机器码,主要编译器进行识别,转换为机器码的只有里面的汇编代码部分。
将源程序转换为可执行程序分两步:
编译
将源码转换为目标文件 x.obj
MASM 1.asm
链接
将目标文件链接成可执行文件,下面引用原书对链接描述:
1、当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成目标文件后,再用链接程序将他们连接到一起,生成一个可执行文件
2、程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件
3、一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
注意:对于连接的过程,可执行文件是我们要得到的最终结果
LINK 1.obj
可执行文件的加载过程
首先操作系统会分配给程序一段内容空间,起始地址是 SA:0000,这段内存前面256个字节创建程序段前缀(PSP)的数据区,DOS要利用PSP来和加载程序进行通信。程序加载后ds保存所在内存去的段地址,即ds保存SA的值
然后在256个字节紧跟着后面是程序的入口地址 SA+10H:0000,即设置CS为SA+10,IP为0000,
SA + 10H的来由:SA x 16+256=(SA+16)x 16 = SA+10:0000
使用debug 1.exe可以跟踪程序的运行,需要用``p`命令运行int 21指令
注:PSP的头两个字节是 CD 20
[ BX ] 和 loop指令
要完整描述一个内存单元,需要两种信息:1、内存单元的地址;2、内存单元难度长度(类型)
[ bx ] 和 [0]类似,段地址都是ds,只不过前者偏移地址变成bx,后者偏移地址是0,这里得到了内存地址,那么大小由寄存器来判断,比如mov ax,[bx]
就是一个字大小;mov al,[bx]
就是一个字节大小
loop 指令
先看一段loop演示代码,功能是计算2^3
assume codecode segmentmov ax,2mov cx,3
s: add ax,axloop smov ax,4c00hint 12h
code ends
end
loop功能描述:1、先cx寄存器自减一;2、如果不为0就跳转到标记点(在这里是s),如果为0则向下运行
所以遇到loop的功能框架如下
mov cx,循环次数
s: ;这里是s,自己编写可以取别的标签循环执行的程序段loop s
问题:在跟踪loop的程序时,单步t命令跟踪太慢了,如果循环次数多会很麻烦。
解答:这时可以使用g 偏移地址
,这里的偏移地址就是u命令查看汇编代码前面的xxxx:偏移地址这个地址,命令会一次性直接运行到这里来,严谨点说是让设置这个偏移地址处的指令为下一个运行的语句。还可以使用p
命令一次性运行完loop,不过只限在下一个指令是loop的时候使用。
tips:编写源程序时数据不能以字母开头,也就是说
mov ax,ffffH ;报错
mov ax,0ffffH ;安全
编译器的一个特性
在debug里可以用mov ax,[0]
得到偏移地址0里的内容,但是如果源文件里写这句会被转换为mov ax,0
,所以源程序要有同样的效果只能:
mov ax,ds:[0] # 显式的在前面加ds
mov ax,[bx] # 用其他寄存器代替
段前缀:
mov ax,ds:[bx]
mov ax,cs:[bx]
mov ax,ss:[bx]
mov ax,es:[bx]
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的ds: \ cs: \ ss: \ es:
,在汇编语言中称为段前缀。
注意:不能随意对某个地址的值进行覆盖,因为其可能保存其他程序的重要数据,在DOS模式下,一般0:200~0:2ff这256个字节没有被操作系统或其他程序使用,意思是以后实验可以安全(任意)使用这段地址。
注意:debug x.exe 程序加载程序后cx保存的是程序的大小,单位是字节
包含多个段的程序
直接上代码
assume cs:code,ss:stack,ds:data
data segmentdw 1234h,5678h,1231h,0235h
data ends
stack segmentdw 0,0,0,0,0,0,0,0
stack ends
code segment
start:mov ax,stack ;这里不能直接mov,ss,stackmov ss,ax ; 因为编译器认为段名是一个数值类型... ; 而段寄存器不能直接赋予数值类型mov ax,datamov ds,ax...mov ax,4c00hint 21h
code ends
end start
为什么需要多个段?
- 解决数据、栈、代码堆积在一起混乱的问题
- 解决数据、栈、代码放一个段导致空间不够用的问题,一个段大小只有64KB
知识点:
- assume多出来的ss:stack,ds:data给程序员看的,增加可读性,为了让段寄存器和相关段关联起来还是要在代码段里手动设置
- 新关键字dw(define word)定义一个字型数据
- 程序开头不再是代码段了,所以需要手动指定程序的入口地址是说明,这就要设置start标签(标签名可以随便取,这里设置start为了增加可读性),编译器通过
end start
识别到程序的入口地址,从而编译的时候设置cs:ip
更灵活的定位内存地址的方法
定义字符串
db 'hello' ;编译器会自动转换为对应的ASCII编码
一个位运算技巧
and al,11011111B ; 将一个字母变为大写'a' > 'A'; 'A' > 'A'
or al,00100000B ; 将一个字母变为小写 'A' > 'a'; 'a' > 'a'
[bx + idata]
idata表示一个数值
以前用[bx]表示一个内存单元,现在可以用更灵活的方式表示
- [bx + 200] 表示偏移地址是:bx的内容加上200,下面一样的功能
- [200+ bx]
- 200[bx]
- [bx].200
SI 和 DI 寄存器
si 和 di是8086CPU中和bx功能相近的寄存器,si和di都不能分成两个八位寄存器来使用(ax可以分为ah、al两个八位寄存器,而si和di这两个不能)。
为什么说和bx功能相近?
因为汇编中规定ax作为段地址赋值的中间地址
bx、si、di 作为取地址空间值的偏移地址(或作为偏移地址的基地址),放在[]里来使用,或者ds:si,ds:di
si、di还有一种用法就是:[bx+si]
还有一种同样的写法:[bx][si]
(常用)
此时还有一种表现形式
[bx+si+idata]
,以下和这句一样的效果
[bx+idata+si]
[200+bx+si]
200[bx][si]
[bx].200[si]
[bx][si].200
不禁感叹一句,内存地址的表示显示真tm多 !
一种编程技巧
如果要使用双重循环时,可以开辟一段空间作为栈使用,然后将栈里的内容弹出来给cx
pop cx
数据处理的两个基本问题
数据指令处理前数据在哪
- 数据寄存器里
mov ax,bx
- 数据在指令缓冲区里(立即数)
mov ax,0
- 数据在内存里
mov ax,[0]
寻址寄存器:bx、si、di、bp
8086CPU规定用上面的四个寄存器寻址(也就是用在"[…]"里)
不过使用也有规定(按一定的组合使用,但四个寄存器单独使用都是可以的),下面是cpu的一些规定:
;下面是正确的
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]
;下面是错误的
mov ax,[bx+bp]
mov ax,[si+di];也就是说bx和bp是大哥,不能同时有两个大哥(一山不容二虎)
;si和di是大哥的老婆,两个女人也不能碰面(碰到要掐架)
注意:当"[…]"里出现bp
寄存器的时候,默认的段寄存器变成ss
指令要处理的数据有多长
通过要操作的寄存器自动判断,如:
mov ax,0 ;2个字节 mov al,0 ;1一个字节
如果出现
mov [bx],2
这个语句就无法判断是多少个字节了这种情况要用到
X ptr
指定数据类型,X在汇编指令种可以为 word 或 byte,如:mov byte ptr [0],1 ;覆盖一个字节 mov word ptr [0],2 ;覆盖一个字
新的指令:
div 除法指令
用法:
div reg 或 内存单元
(div 除数)注意点:
1、被除数:默认在ax或dx和ax中,如果除数为8位,那么被除数为16位,默认在ax中;如果除数为16位,那么被除数为32位,默认在dx和ax中,dx存高16位,ax存低16位。
2、结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
伪指令dd
dd即define dword(double word)定义双字
总结前面提到的定义数据的伪指令:
db 定义字节
dw 定义字
dd 定义双字
伪指令dup
说明:结合上面定义数据指令使用,初始化大量空间很有用
用法:db 重复次数 dup(重复内容)
举例:
db 3 dup(0) ;相当于db 0,0,0 db 3 dup('123') ;相当于db '123123123'
转移指令的原理
转移指令:可以修改IP,或同时修改CS和IP的指令。概括的讲,转移指令就是可以控制CPU执行内存中某处代码的指令。
废话不多说,直接上新指令:
操作符
offset
可以获取标签的偏移地址,如
code segment start:mov ax,offset s (相当于mov ax,3 ;因为这条指令长度是3,所以偏移为3)mov ax,offset satrt (相当于mov ax,0)
jmp short 标号(转移到标号处执行指令)
段内短转移指令,根据标号出的地址和这句地址相减得出位移大小,位移范围 -128 ~ 127 (八位补码的范围)
注意,机器码EB后面的那个字节是转移的位移数,编译器会自己算位移大小放进去,这里是00,即IP = IP + 00,如果远一点会加一个别的数
jmp near 标号(转移到标号处执行指令)
段内近转移指令,原理同上,位移范围是 -32768 ~ 32767(16位补码范围)
jmp far ptr 标号
断间转移,又称为远转移,可以跳转到别的段运行指令,far ptr指明了指令用标号的段地址和偏移地址修改CS和 IP。如
codesg segment start:mov ax,0mov bx,0jmp far ptr sdb 256 dup (0) s: add ax,1inc ax codesg ends
注意jmp那段指令的机器码,段地址在后面,偏移地址在前面
jmp 16位reg
只不过用寄存器作为偏移地址
功能:IP = 16位reg
jmp word ptr 内存单元地址(段内转移)
功能:从内存单元地址开始处拿一个字当偏移地址,比如
mov ax,1234h mov ds:[0],ax mov word ptr ds:[0] ; IP = 1234h
jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。如:
mov ax,0123h mov ds:[0],ax mov word ptr ds:[2],0 jmp dword ptr ds:[0] ; CS=0000,IP=0123h
jcxz 标号
有条件转移指令,所有有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128 ~ 127
功能:判断cx是否为0,为0则跳转到标号去,和下面的C类似:
if(cx==0)jmp short 标号
说明一下,loop指令是循环指令,也是短转移
短转移的好处就是跳转的距离是相对的位移,所以换一块内存空也能使用。
注意:如果位移的范围越界了的话,编译器在编译的时候会报错。
CALL和RET指令
RET指令
ret指令相对简单,执行下面两步,变换IP
1、IP = SS*16+SP
2、SP = SP + 2
相当于 pop IP
retf指令则变换IP和CS,执行下面四部
1、IP = SS*16+SP
2、SP = SP + 2
3、CS = SS*16+SP
4、SP = SP + 2
相当于 pop ip,pop cs
CALL指令
call指令就相对多了
CALL 标号(段内转移)
执行下面两步
1、SP = SP - 2
2、SS*16+SP = IP
3、IP = IP + 16位位移
相当于:
push ip jmp near 标号
CALL far ptr 标号(段间转移)
执行下面几步
1、SP = SP - 2
2、SS*16+SP = CS
3、SP = SP - 2
4、SS*16+SP = IP
相当于:
push cs push ip jmp far ptr 标号
CALL 16位reg
执行下面几步
1、SP = SP - 2
2、SS*16+SP = IP
3、IP = 16位reg
相当于:
push ip jmp 16位reg
call word ptr 内存单元地址
相当于:
push ip jmp word ptr 内存单元地址
call dword ptr 内存单元地址
相当于:
push cs push ip jmp dword ptr 内存单元地址
mul指令
乘法指令,和div指令类似。
用法:mul 寄存器或内存单元地址
另一个乘数默认是AL或AX,取决于另一个乘数的位数。如果是八位结果保存在AX中;如果是16位,结果高位保存在DX中,低位保存在AX中。
利用RET和CALL可以实现模块化编程,如
主程序:??call 子程序子程序:??ret
不过这样就涉及到多参数传递和寄存器冲突的问题,解决方法:
多参数传递
用栈来传递多参数
寄存器冲突解决
在子程序里把要用到的寄存器先入栈(保存现场),在返回之前出栈(还原现场),如:
主程序:??call 子程序; 这里用到两个寄存器 子程序:;保存现场push cxpush axmov cx,0mov ax,0;还原现场pop axpop cxret
标志寄存器
一种特殊的寄存器,不同处理机,个数和结构都可能不同,具有以下3种作用:
- 用来存储相关指令的某些执行结果
- 用来为CPU执行相关指令提供行为根据
- 用来控制CPU的相关工作方式。
8086中标志寄存器有16位,叫flag寄存器,存储的信息通常称为程序状态字(PSW)。flag寄存器按位起作用,但不是每一个位都用。
话不多说,直接介绍:
ZF标志
flag的第6位,零标志位,记录相关指令执行后,结果是否为0,为0则zf=1;结果不为0,zf=0
mov ax,1 sub ax,1 ;zf=1 mov ax,1 add ax,1 ;zf=0
注意:主要针对的运算执行如add,or,mul等,mov,push等传送指令对标志寄存器没影响。
PF标志
flag的第2位,奇偶标志位。记录相关指令执行后,计算结果bit位1的个数,如果个数为偶数pf=1,如果为奇数pf=0
mov ax,1 sub ax,1 ;pf=1 mov ax,2 sub ax,1 ;pf=0
SF标志
flag的第七位,符号标志位,记录相关指令执行后,结果是否为负,为负sf=1,非负sf=0
mov ax,0 sub ax,1 ;sf=1 mov ax,1 sub ax,1 ;sf=0
注意:将数据当成有符号数使用时,sf才有意义,虽然当成无符号数也会影响sf的值
CF标志
flag第0位,进位标志位。对无符号数来说
一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
;相加的溢出位 mov al,98h add al,al ;cf=1,al=30h ;al+al=130;相减的借位 mov al,97h sub al,98h ;cf=1 ;197-98=ff
OF标志
flag第11位,进位标志位。对有符号数来说
操作结果溢出了of=1,没溢出of=0
mov al,0F0h add al,88h ;of=1
adc指令
带位加法指令,利用CF位上记录的进位值
指令格式:adc 操作对象1,操作对象2
功能:操作对象1=操作对象1+操作对象2+CF
mov ax,2 mov bx,1 sub bx,ax adc ax,1 ;ax=2+1+cf=4
sbb指令
带位减法指令,同样利用cf值,不过换成了减.
指令格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF
计算003E1000H-00202000H
mov bx,1000h mov ax,003eh sub bx,2000h sbb ax,0020h
cmp指令
格式:cmp ax,bx
功能:计算
ax - bx
,但不保存结果,结果影响标志位变化,通过je
,jl
等指令跳转,类似if
判断无符号数判断总结(书上截图)
有符号数判断总结
zf=1,说明ax=bx
sf=1,of=0,说明ax<bx
sf=1,of=1,说明ax>bx
sf=0,of=1,说明ax<bx
sf=0,of=0,说明ax>=bx
条件跳转指令
无符号数跳转总结
有符号数跳转跟无符号数跳转指令相同,只是检测的标志位不同
指令一般配合cmp使用,由上图可知指令跳转是根据标志位跳转的。所以理论上指令add改变了标志位,紧跟着je也能跳转。
DF 标志位
flag的第十位是DF,一般用作串处理。
命令:movsb
作用:
mov es:[di], ptr byte ds:[si] ;8086不支持这种写法,只是演示
如果DF=0,inc si,inc di
如果DF=1,dec si,dec di
同样有:movsw
作用:
mov es:[di], ptr word ds:[si] ;8086不支持这种写法,只是演示
如果DF=0,add si,2,add di,2
如果DF=1,add si,2,add di,2
一般上面两个指令结合指令rep
来用
用法:rep movsb / movsw
作用:
s:movsb
loop s
同样,也可以通过两个命令更改DF的值
cld ;df=0
std ;df=1
pushf 和 popf
两个指令分别将标志寄存器入栈和出栈
标准寄存器在DEBUG中的显示
如图
中断
中断:发生中断的话无论做什么都会保留现场去执行发生中断的处理程序。
内中断:当CPU内部发生这4个就是内中断
1、除法错误,比如div除法溢出
; 执行下面三条语句就会发生除法错误中断
mov ax,1000h
mov bh,1
div bh
2、单步执行
3、执行into指令
4、执行int指令
中断会发生的事
中断发生会转去执行中断处理程序,这时就需要一个地址,即CS:IP,这些地址的集合叫做中断向量表。
8086的中断向量表存在0:0-0:400出,共1KB的大小。每个中断都有CS:IP,各占一个字,高字保存CS,低字保存IP。
但是0:200后面的512字节的空间是空闲的,所以我们写程序可以利用。
提一点:根据书中的实验11做完,发现0号中断程序被彻底改变(除法中断=0号中断),也就是说除法中断彻底被改变。
单步执行
发生中断后会:
pushf
push cs
push ip
一般搭配指令 iret
使用
iret指令做的事:
pop ip
pop cs
popf
可以看到DEBUG程序是可以单步执行的,每执行一下指令t
就显示寄存器的状态,然后暂停等待输入。
单步执行是CPU提供的一种供程序单步调试的功能。
一个新知识点:
当执行 mov ss,xxxxh
时,为什么会连带下面一条语句一起运行。
在前面看到,中断会入栈保存现场,所以当一个程序运行到对栈顶寄存器赋值时,还需要对sp赋值栈才完整,随意CPU会自动多运行一条语句,所以我们一般将mov ss 和mov sp写在一起,即当程序运行到mov ss时发生中断也不会跳转到中断处理程序,还会再运行一条语句再跳过去,目的是将栈搞完整。
int 指令
功能
CPU执行int n指令,引发一个n号中断,执行下面4部。
1、取中断类型码
2、标志寄存器入栈,IF=0,TF=0
3、CS、IP入栈
4、IP=n*4,CS=n*4+2
安装一个中断
安装一个7c号中断,安装在内存0:200处
功能是实现一个字型的平方,参数是ax,结果高位在dx,低位在ax
assume cs:codecode segment
start:mov ax,csmov ds,axmov si,offset sqr ;ds:si指向源地址mov ax,0mov es,axmov di,200h ;设置es:di指向目的地址mov cx,offset sqrend-offset sqr ;设置cx为传输长度cld ;设置传输方向为正rep movsbmov ax,0mov es,axmov word ptr es:[7ch*4],200hmov word ptr es:[7ch*4+2],0mov ax,4c00hint 21h
sqr:mul axiret ; 注意中断最后要写iret,而不是mov ax,4c00h和int 21h
sqrend:nopcode ends
end start
BIOS 中断例程应用
一个中断往往提供多个子程序调用,BIOS和DOS提供的中断例程都是用ah
来传递内部子程序的编号。
下面看一下int 10h
中断例程的设置光标位置功能。
mov ah,2 ;置光标
mov bh,0 ;第0页
mov dh,5 ;dh放行号
mov dl,12 ;dl放列号
int 10h
下面看一下int 10h
中断例程的在光标位置显示字符功能。
mov ah,9 ;在光标处显示字符
mov al,'a' ;字符
mov bl,7 ;颜色
mov bh,0 ;第0页
mov cx,3 ;字符重复个数
int 10h
DOS 中断例程应用
int 21h
是DOS提供的中断,其中包含了很多子程序
前面使用的int 21h
使用的4ch
号中断,即程序返回功能,如下:
mov ah,4ch ;程序返回
mov al,0 ;返回值
int 21h
下面调用int 21h
的在光标处显示字符串的功能。
mov ah,2 ;置光标
mov bh,0 ;第0页
mov dh,5 ;行号
mov dl,12 ;列号
int 10h;ds:dx指向字符串首地址,字符串末尾用$标识
mov ah,9
int 21h
端口
在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有已下3种芯片:
1、各种接口卡(比如网卡、显卡)上的接口芯片,它们控制接口卡进行工作;
2、主板上的接口芯片,CPU通过它们对部分外设进行访问;
3、其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理。
CPU对以上3种芯片数据的读写就是通过端口实现的。
汇编对端口的读写只能用in和out
,且读入数据只能保存在ax寄存器,16位在ax,8位在al中。
例如:
in al,20h ;从20h端口读入一个字节
out 20h,ah ;向20h端口写入一个字节
shl 和 shr 指令
shl 是逻辑左移指令
shr 是逻辑右移指令
例子:
shl al,1 ;al左移一位
shr al,1 ;al右移一位mov cl,2 ;移位大于1必须保存在cl中
shl al,cl ;al左移2位
移位的最后一位移出位保存在CF中
例如:AL=0100 0000,左移一位CF=0,左移两位CF=1
下载地址
链接:https://pan.baidu.com/s/1Vay7PFF2WadtIDgC8rLgbQ
提取码:ddmj
王爽-汇编语言 万字学习总结相关推荐
- 8086汇编学习小记-王爽汇编语言实验12
8086汇编学习小记-王爽汇编语言实验12 0号中断处理程序,开始安装在0000:0200处的程序最后用死循环导致显示不出'divided error',改成直接退出就正常显示了.注意修改ss,sp之 ...
- 王爽 汇编语言第二版 课程设计2
王爽汇编语言 课程设计2 掌握一门编程语言最重要的就是实践,王爽老师的课程设计2,如果完整写出来要400行至800行代码,独立完成这个课程设计,会使你熟练16位汇编,掌握8086汇编精髓.这个课程设计 ...
- (王爽)汇编语言-课程设计二完整版
王爽汇编语言-课程设计二 前言 特别感谢 实验结果 实验思路 如何优化调试流程以节省时间 源码 未完成部分 前言 刚刚把lab2调试完,2021年10月12日 16:26:36,本来是打算在oneno ...
- 王爽汇编语言检测点1.1(含详细解题步骤)
检测点1.1(王爽汇编语言(第3版)) (1)1个CPU的 寻址能力为8KB,那么它的地址总线宽度为_____. 分析:答案:13 2^N=8KB=8*1024B=2^13B,N为地址总线宽度,N=1 ...
- 王爽汇编语言指令大全
最近学习了王爽的<汇编语言>,收获很多,趁热在此记录汇编语言常用指令,方便以后查找使用 注:以下代码中凡是以h结尾的数均为16进制数,以b结尾的均为2进制数 一.寄存器 通用寄存器:可用于 ...
- 王爽 汇编语言第三版 课程设计 1
From:https://www.cnblogs.com/Since-natural-ran/p/6938133.html 汇编语言-课程设计1: https://www.cnblogs.com/ts ...
- 王爽 汇编语言第三版 第10章 call 和 ret 指令 以及 子程序设计
第10章 call 和 ret 指令 10.1 ret 和 reft 指令 call 和 ret 指令都是转移指令,他们都修改 IP,或同事修改 CS 和 IP .他们经常被共同来实现子程序的设计. ...
- 王爽 汇编语言第三版 第7章 --- 更灵活的定位内存地址的方法(可以理解为 数组形式的内存定位)
汇编语言(第三版)王爽著 的十二个实验:https://blog.csdn.net/OrangeHap/article/details/89791064 大小端 字节对齐 对于 arm,intel 这 ...
- 王爽汇编语言第三版答案
转载自 https://blog.csdn.net/modiz/article/details/88776695 部分加上自己的分析,感谢 Modiz 汇编语言答案(王爽) 检测点1.1 (1)1个C ...
最新文章
- 安防行业为何缺少真正适用的AI芯片?
- 2018python培训-2018python深度学习核心技术培训班
- vue学习笔记——路由
- python 多进程共享变量manager_python 进程间共享数据 multiprocessing 通信问题 — Manager...
- 使用PaddleFluid和TensorFlow训练序列标注模型
- 进程间基于共享存储区的通信_IPC(进程间通讯):inter process communication
- 自适应网页设计/响应式Web设计 (Responsive Web Design)
- Java 7 对ArrayList和HashMap的性能的提升
- k8s | 搞不明白为什么大家都在学习 k8s
- java.lang.Math类的API介绍
- Spark 基础 —— Map 容器
- SQL学习精粹之内外连接以及where和on条件的区别
- mysql批量插入跟更新_Mysql批量插入和更新的性能-问答-阿里云开发者社区-阿里云...
- out在matlab中,在仿真模型中添加一个输出端口模块(Out模块),能够将结果输出到MATLAB工作空间中。...
- 地理信息安全在线培训考试-判断题
- 调用链监控 - Tracing - APM
- Scrum: 谁是利益相关者?
- 耀世升级发布!阿里第三版Java多线程核心技术手册PDF全彩版
- Linux攻关之基础模块四 命令初识
- Oracle11G完全卸载步骤
热门文章
- 网络安全基础——ARP欺骗
- 7-2 实验二 银行利息结算
- 开源sso单点登陆系统推荐
- windows“运行”中的快捷命令
- Unity实现的汽车方向盘转动效果[完整案例]
- WebView的白屏检测与处理
- docker-compose 部署prometheus+grafana+alertmanager+chronograf+prometheus-webhook-dingtalk+loki
- Java 读取Word文本框中的文本、图片、表格
- Depix:还原马赛克工具的试用及总结
- 如何备份SolidWorks网络版服务器