8000 GitHub - yonh/asm: 王爽汇编
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

yonh/asm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

第7章

and和or

如果想要某一位变为0,利用0与其进行与运算
如果想要某一位变为1,利用1与其进行或运算
1111 and 1011 => 1011
0000 or 0100 => 0100

大小写转换的问题

  • 将大写+20h可转换为小写, 将小写-20h可转换为大写
  • 利用and或or运算,更改第五位(索引从0开始), 要获得大写改为0, 要获得小写则改为1

内存寻址

  • [bx+idata]

第8章

关于偏移地址的表示

  • 在8086CPU中,能够用于偏移地址的寄存器只有 bx, si, di, bp
  • 在表示偏移地址的组合中,只有bx和si, bx和di, bp和si, bp和di这四种组合
  • 如果使用了bp而又没有显式的给出段地址,那么段地址就是ss, 如
    mov ax, [bp] <=> mov ax, ss:[bp]
    mov ax, [bp+idata] <=> mov ax, ss:[bp+idata]

寻址方式总结

  • 直接寻址
  • 寄存器间接寻址
  • 寄存器相对寻址
  • 基址变址寻址
  • 相对基址变址寻址

处理的数据的长度是多少

  • 如果使用到具体寄存器,则操作数据的大小等同于寄存器
  • 没有寄存器的情况下可使用X ptr表示,X可为word,byte
  • 其他,如push操作数据的大小只能是word

mov word ptr ds:[0], 1
mov byte ptr ds:[0], 1

div

; div reg / div [...]
div ax
div byte ptr ds:[0]

div是一个除法指令,除法指令执行我们需要知道的信息有

  • 除数的大小(8/16位)
  • 被除数的最大值
  • 被除数在哪
  • 商被保存在哪
  • 余数被保存在哪
  • 被除数比除数大一倍, 若除数是8位,被除数则为16位
除数的大小 被除数 余数
8位 AX AL AH
16位 DX(高),AX(低) AX DX

使用div计算的时候需要注意,不能让商溢出, 8位除数商最大值为255, 16位除数商最大值65535 对应的被除数最大值为65279(FEFF 8位), 4294901759(FFFE FFFF 16位)

程序示例
除数为8位: 8_div_8.asm 除数为16位: 8_div_16.asm

div byte ptr [0]
(al) = (ax) / ((ds)*16 + 0)的商
(ah) = (ax) / ((ds)*16 + 0)的余数

div word ptr [0]
(ax) = (ds)*10000h + (ax) / ((ds) * 16 + 0)的商
(dx) = (ds)*10000h + (ax) / ((ds) * 16 + 0)的余数

db,dw和dd

db: 定义字节数据, 每个占一个字节
dw: 定义字数据, 每个占两个字节
dd: 定义双字数据, 每个占四个字节

dup

配合db,dw,dd使用,定义连续n个相同的数据

db 重复次数 dup (重复的字节型数据)
dw 重复次数 dup (重复的字型数据)
dd 重复次数 dup (重复的双字型数据)

第9章

本章总结

  • 只要是段内转移,并不包含真实地址,只是通过偏移量跳转
  • 所有的有条件转移指令都是短转移,对IP的修改范围-128~127
  • 所有的循环指令都是短转移, 对IP的修改范围为-128~127
  • 位移距离使用补码表示

转移指令汇总

  • jmp 无条件跳转
  • jcxz cx为零则跳转
  • loop cx不等于0则跳转
  • je 等于则跳转 (zf=1)
  • jne 不等于则跳转 (zf=0)
  • jb 低于则跳转 (cf=1)
  • jnb 不低于则跳转 (cf=0)
  • ja 高于则跳转 (cf=0且zf=0)
  • jna 不高于则跳转 (cf=1或zf=1)

几个名词记录

## 短转移
不包含目的地地址, 只是获得转移的位移,只修改IP, 范围-128~127

转移指令

可以修改IP或同时修改CS和IP的指令统称为转移指令 8086CPU的转移指令分为以下几类

  • 无条件转移(如jmp)
  • 条件转移
  • 循环 (loop)
  • 过程
  • 中断

offset

offset的作用是获取标号的偏移地址

start: mov ax, offset s ; 获取标号s的偏移地址,送入ax
end: mov ax, offset end ; 获取标号end的偏移地址,送入ax

回顾指令执行步骤

  1. 从CS:IP指向的单元读取指令到指令缓冲器
  2. IP = IP+指令的长度
  3. 执行指令, 回到第一步重复这个过程

jmp指令

修改CS:IP或IP使程序跳转都某处开始执行代码

转移位移的计算方法

位移 = 标号位置 - jmp指令的下一条指令的位置

; 几条转移指令
jmp 标号
jmp short 标号 		; 8位位移 (范围-128~127)
jmp near ptr 标号 	; 此处的位移是16位 (范围-32768~32767)
jmp far ptr 标号	; 实现段间转移, (不存在转移范围只能是 -32768~32767的问题)
jmp 寄存器			; IP = 寄存器的值
jmp word ptr 内存单元地址  ; 段内转移
jmp dword ptr 内存单元地址 ; 段间转移, 高位存储段地址, 低位存储偏移地址
jcxz 标号 			; jmp if cx is zero的缩写(我猜),用C表示就是 if (0==cx) jmp short 标号

使用汇编显示字符

在内存地址中B8000~BFFFF,共32kib的空间为80x25(列x行)彩色字符模式的显示缓冲区,在这里写入数据将会显示出来
每个字符占2个字节,分别是字符的ASCII码(低位), 字符的属性(高位)

字符的属性有: 前景色,背景色,闪烁,高亮, (闪烁的效果必须在DOS全屏的方式才能看到?)

闪烁 背景 高亮 前景
BL R G B I R G B
7 6 5 4 3 2 1 0

第10章

本章总结

  • ret => pop ip
  • retf => pop ip, pop cs
  • call不能实现短转移
  • 子程序中使用了寄存器,可能主程序也使用了,从而造成寄存器冲突

ret和retf

ret指令利用栈实现修改IP的值,从而实现近转移
retf利用栈实现修改CS和IP的值,从而实现远转移

; 执行ret的时候
(ip) = ((ss)*16+ (sp))
(sp) = (sp)+2
; 相当于汇编指令
pop ip


; 执行retf的时候
(ip) = ((ss)*16 + (sp))
(sp) = (sp)+2
(cs) = ((ss)*16 + (sp))
(sp) = (sp)+2
; 相当于汇编指令
pop ip
pop cs 

call

执行call会将下一条指令的IP或CS:IP入栈,然后跳转
需要关注call后的参数,会导致IP或CS:IP入栈

几种调用call的例子

  • call 标号
  • callfar ptr 标号
  • call 16位寄存器
  • call word ptr 内存单元地址
  • call dword ptr 内存单元地址

call指令执行段内转移过程

  1. 将call指令的下一条指令压栈
  2. 程序跳转到标号处执行 用汇编解释
    push ip
    jmp near ptr 标号

call指令执行段间转移过程

  1. 将call指令的下一条指令的CS压栈
  2. 将call指令的下一条指令的IP压栈
  3. 跳转到标号处执行 用汇编解释
    push cs
    push ip
    jmp far ptr 标号

mul乘法指令

和除法指令相同,乘法指令需要用到ax和dx,同时2个数需要同时是8位或16位
若是8位,则一个数存放在al,另一个数存放在8位寄存器或内存单元中, 结果存于ax
若是16位,则一个数存放在ax,另一个数存放在16位寄存器或内存单元中,结果存于ax(低位)和dx(高位)中

; 格式如下
; mul 寄存器
; mul 内存单元

mul byte ptr ds:[0]
mul word ptr ds:[0]
mul bl
mul bx

第11章

本章讲了关于标志寄存器的相关内容
标志寄存器位置
|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00| | | | | |of|df|if|tf|sf|zf| |af| |pf| |cf| debug中标志位的值 |flag| =1 | =0 | | of | OV | NV | | of | NG | PL | | of | ZR | NZ | | of | PE | PO | | of | CY | NC | | of | DN | UP |

本章总结

  • 影响标志寄存器的大多是运算指令,如add,sub,div

ZF

zf是零标志位,当操作的结果为0时,zf位的值为1 在8086CPU中,有的指令会影响标志寄存器,如add,sub,mul,div,inc,or,and
有的指令对标志寄存器没有影响,如mov,push,pop

PF

pf是奇偶标志位,它记录了相关指令执行后,如果其而2进制值中的1的个数为偶数,则pf=1

SF

sf是符号标志位,它记录了相关指令执行后,如果其结果为负数,则sf=1

CF

cf是进位标志位,当进行无符号运算过程中产生了进位或者借位,则cf=1

OF

在进行有符号运算产生溢出,of=1

DF

df是方向标志位,可在串处理指令中操作di,si的增减

; 相关指令参考
cld			; df=0,正向传送
std			; df=1,逆向传送
rep movsb	;每次传送byte, di,si步长=1, 用汇编描述就是 => 1.mov ds:[si], byte ptr es:[di] 2. inc si    3. inc di   
; rep movsb等价于 (描述用语)
s:
	mov [si], byte ptr [di]
	inc di
	inc si
loop s

rep movsw	;每次传送word, di,si步长=2, 用汇编描述就是 => 1.mov ds:[si], byte ptr es:[di] 2. add si,2  3. add di,2   
; rep movsw等价于 (描述用语)
s:
	mov [si], word ptr [di]
	add si, 2
	add di, 2
loop s

adc指令

加法指令,和add不同的是会加上cf的值, adc ax, bx => ax = ax + bx + cf

sbb指令

减法指令,和sub不同的是会减去cf的值, sbb ax, bx => ax = ax - bx - cf

cmp

cmp指令通过减法运算(不保存结果),影响标志寄存器的值,通过标志寄存器的值可得出相关对象的关系
如cmp ax, bx (无符号比较)

  • if ax=bx, zf=1
  • if ax!=bx, zf=0
  • if ax<bx, cf=1
  • if ax<=bx, cf=1 || zf=1
  • if ax>=bx, cf=0
  • if ax>bx, cf=0 && zf=0

如cmp ax, bx (有符号比较)

  • if ax=bx, zf=1
  • if ax>bx, sf=1 && of=1
  • if ax<bx, (sf=1 && of=0) || (sf=0 && of=1)
  • if ax>=bx, sf=0 && of=0 关于上面的详细证明查看书本的p222~p225

pushf和popf

pushf和popf是针对标志寄存器的入栈和出栈操作

; pushf和popf让访问标志寄存器中的数据提供了可能
pushf
pop ax		; 将标志寄存器的值送入ax

12章

本章总结

  • 什么是中断
  • 什么是中断向量表
  • 中断过程和iret指令
  • 中断的4种情况
  • 单步中断
  • 响应中断的特殊情况

什么是中断

CPU在执行程序的过程中,接收到的一种特殊请求,会暂停当前的操作,转而处理该特殊请求,完成后再继续执行,我们称之为中断
CPU接收的中断信息必须包含一个字节的中断类型码,

中断的4种情况

  • 除法错误(0)
  • 单步执行(1)
  • 执行into指令(4)
  • 执行int指令 (int n)

什么是中断向量表

中断向量表即是记录了中断处理程序地址的列表,其位于内存地址0:0~0:3FF中
一个表项的大小是4个字节,高位存储短地址,低位存储偏移地址

中断过程和iret指令

CPU通过中断类型码找到中断向量,并设置CS和IP,整个过程便是中断过程,由CPU自动完成
中断过程:

  1. 从中断信息获取中断类型码
  2. 标志寄存器入栈
  3. 设置标志寄存器TF,IF = 0
  4. CS入栈
  5. IP入栈
  6. CS:IP指向中断处理程序, IP=中断类型码4, CS=中断类型码4+2

用汇编描述就是

  1. 获取类型码
  2. pushf
  3. TF=0, IF=0
  4. push cs
  5. push ip
  6. ip = 中断类型码4, cs=中断类型码4+2

从某种角度上看, 中断和子程序的整个过程都是类似的

iret和ret比较像,仅仅多了个popf,用汇编描述:

pop ip
pop cs
popf

单步中断

当CPU执行完一条指令后发现TF=1,则产生单步中断(类型码=1)
也因为如此,所以中断过程需要将设置TF=0, 防止CPU无限执行1号中断操作

不响应中断的特殊情况

在一般情况下,CPU在执行完一条指令,如果发生中断,会马上执行中断
但在有些情况下,即便是发生了中断,CPU也不响应,我们可以测试下面的代码

	mov ax, 1
	mov ss, ax
	int 21h		; cpu不响应
	int 21h		; cpu不响应
	mov ax, 4c00h ; 当执行mov ss, ax后这条指令就被接着执行
	int 21h

修改ss不响应中断的原因是,当修改ss的时候,如果处理中断,那么需要将标志寄存器,cs,ip入栈,这样造成了指向了错误的栈顶位置,导致错误
因此我们需要将对ss和sp的修改放在一起连续执行

13章

int指令

int指令就是一个用来触发任意中断号的一个指令, 如int 21h就是触发21h号中断

利用中断实现jmp,loop等

要点:注意使用bx保留偏移位移,如何理解转移位移
add_1(需要跳转到的地址) - add_2(下一指令的地址) = add_3 (转移位移)
而在中断过程中已经将下一指令地址压栈(位置在sp+2)
因此我们只要将 (add_3+add_2)即可得到add_1的地址,将此地址修改到ss:[sp+2] (栈中保存IP的位置)
然后在中断例程执行iret后,程序便跳转到了add_1的地址处

14章

cpu可以直接读取3个地方的数据

  • cpu内部的寄存器
  • 内存单元
  • 端口(此端口非操作系统上的端口) 8086CPU可定位端口的范围为0~65535
    针对端口的指令有2个, in和out

访问内存单元和访问端口的过程

; 访问内存
mov ax, ds:[8]
; 1. cpu通过地址线将地址信息8发出
; 2. cpu通过控制线发出内存读命令,选中存储器,并通知要从中读取数据
; 3. 存储器将8号单元的数据通过数据线送入cpu

; 访问端口
in al, 60h
; 1. cpu通过地址线将地址信息60h发出
; 2. cpu通过控制线发出端口读命令,选中端口所在的芯片,并通知要从中读取数据
; 3. 端口所在的芯片将数据送入cpu

in和out

in和out只能使用ax和al进行对端口的读写, 8位时用al,16位时用ax

; 对0~255端口进行读写时
in al, 20h	; 从20h端口读一个字节
out 20h, al  ; 往20h端口写入一个字节

; 对256~65535端口进行读写时, 端口号放在dx
mov dx, 300h ; 读取300h端口
in al, dx	 ; 从300h端口读一个字节数据
out dx, al	 ; 从300h端口吸入一个字节

shl和shr

shl和shr是逻辑移位指令,它们的功能是将数据左移或右移,用0填充空位,最后移出的移位存入CF

; shl 寄存器, 移动位数
mov al, 0ffh	; al= 1111 1111
shl al, 1		; 左移移位, al = 1111 1110
mov cl, 2		; 当移位大于1时,需要通过cl保存移动位数
shl al, cl		; 左移2位, al = 1111 1000

; shr 寄存器, 移动位数
mov al, 0ffh	; al= 1111 1111
shr al, 1		; 右移移位, al = 0111 1111
mov cl, 2		; 当移位大于1时,需要通过cl保存移动位数
shr al, cl		; 右移2位, al = 0001 1111

CMOS RAM

目前我们只要知道CMOS RAM存储着系统的时间及系统信息即可
我们可以通过对地址端口(70h)和数据端口(71h)的访问获取到系统时间
而获取到的数据是BCD码, BCD码就是十进制数,那么我们将BCD码+30h即可得到改数字的ASCII码
CMOS RAM中的数据存放地址
0:秒 2:分 4:时 7:日 8:月 9:年

15章

外中断源的类型

  • 可屏蔽中断
  • 不可屏蔽中断

可屏蔽中断和不可屏蔽中断都属于外部中断,是由外部中断源引起的;但它们也有区别:可屏蔽中断是通过CPU的INTR引脚引入,当
中断标志IF=1时允许中断,当IF=0时禁止中断,不可屏蔽中断是由NMI引脚引入,不受IF标志的影响。
不可屏蔽中断源一旦提出请求,CPU必须无条件响应,而对可屏蔽中断源的请求,CPU可以响应,也可以不响应。
CPU一般设置两根中断请求输入线:可屏蔽中断请求INTR(Interrupt Require)和不可屏蔽中断请求NMI(NonMaskable Interrupt)。
对于可屏蔽中断,除了受本身的屏蔽位控制外,还都要受一个总的控制,即CPU标志寄存器中的中断允许标志位IF(Iinterrupt Flag)的控制,
IF位为1,可以得到CPU的响应,否则,得不到响应。IF位可以由用户控制,指令STI或Turbo c的Enable()函数,将IF位置1(开中断),
指令CLI或Turbo_c 的Disable()函数,将IF位清0(关中断)。
典型的非屏蔽中断源的例子是电源掉电,一旦出现,必须立即无条件地响应,否则进行其他任何工作都是没有意义的。
典型的可屏蔽中断源的例子是打印机中断,CPU对打印机中断请求的响应可以快一些,也可以慢一些,因为让打印机等待儿是完全可以的。
对于软中断,它不受IF位的影响,所以属于非屏蔽中断范畴。

可屏蔽中断

处理过程:

  1. 如果IF=0,不响应可屏蔽中断
  2. 取中断类型码n
  3. 标志寄存器入栈,设置IF=0,TF=0
  4. CS,IP入栈
  5. IP=n4, CS=n4+2

不可屏蔽中断

对于8086CPU,不可屏蔽中断的类型码一定是2,因此在中断过程中不需要去中断类型码
处理过程:

  1. 标志寄存器入栈,IF=0, TF=0
  2. CS,IP入栈
  3. IP=8, CS=0ah

键盘相关

int9中断

BIOS提供了int9中断来进行键盘输入输出处理

BIOS键盘缓冲区

BIOS键盘缓冲区是BIOS用于存放int9中断例程所接收的键盘输入的内存区,该内存区可存储15个键盘输入,一个键盘输入用一个字单元存储,高位存放扫描码,低位存放字符码
0040:17单元存储键盘状态字节

  • 0: 右shift状态 1表示按下
  • 1: 左shift状态 1表示按下
  • 2: Ctrl状态 1表示按下
  • 3: Alt状态 1表示按下
  • 4: ScrollLock状态 1表示Scroll指示灯亮
  • 5: NumLock状态 1表示小键盘输入的是数字
  • 6: CapsLock状态 1表示输入的是大写
  • 7: Insert状态 1表示处于删除态

覆盖int9中断需要注意的地方

不要忘了调用原来的int9中断处理键盘输入,要不然在下一次按键的时候并不会触发到新int9中断程序,具体细节不明也不细究

16章

描述了单元长度的标号

我们可以使用标号[偏移地址]表示某个单元的数据,且大小已经确定了的,下面是代码示例

assume cs:code
code segment
	a db 1,2,3,4,5,6,7,8
	b dw 0
	c dw offset a, offset b
	d dw offset a, seg a, offset b, seg b	; 获取标号a, b的段地址和偏移地址
start:
	mov ax, b		; => mov ax, cs:[8]
	inc b			; => inc word ptr cs:[8]
	inc a			; => inc byte ptr cs:[0]
	mov al, a[si]	; => mov al, cs:[si]
	;mov ax, a[si]	; => 错误, a[si]是的大小是byte
	
code ends
end start

17章

键盘缓冲区

关于寄存器的大小问题

一个寄存器通常是16位,如: al(8),ah(8) => ax(16)
al, ah的存值范围 0-255 => 1个字节 => 8个二进制数 => 2个16进制数
ax的存值范围 0-65535 => 2个字节 => 16个二进制数 => 4个16进制数

用16进制表示则是
al的范围是0-ff
ax的范围是0-ffff

About

王爽汇编

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
0