汇编学习笔记第18~20章:常用汇编指令汇总-课后练习-源代码

本文深入探讨了8086CPU汇编指令的应用,包括数据移动、算术运算、条件转移、中断处理等核心指令,并通过具体示例展示了如何在中断处理程序中模拟特定中断(如INT 9h),以及如何安装中断处理程序到中断向量表中。文章还详细解释了字符显示过程,以及在字符显示过程中如何响应中断并执行相应的中断处理程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第十八章 常用汇编指令汇总

8086CPU汇编指令:

1、mov ax,23 表示将23移动到ax寄存器中。
2、add ax,bx 表示将ax和bx的值相加,将结果存放到ax中。
3、jmp 2AE3:3 表示将CS段地址更改为2AE3,将IP指令指针地址更改为3.
4、jmp ax 表示仅仅修改IP的值,相当于"mov IP,ax",即用寄存器ax中的值修改IP的值。
5、mov ax,200 ; jmp ax 这两条指令表示将IP的值修改成200。
6、push ax:将寄存器ax中的数据送入栈中。
7、pop ax :从栈顶取出数据送入ax。
8、loop:循环指令。
9、inc:增加1指令。
10、伪指令dw即define word,定义字型数据。通过8086CPU执行dw定义后的数据,地位存放于低字节,高位存放于高字节。
11、伪指令db即define byte,定义字节型数据。通过8086CPU执行db定义后的数据,地位存放于低字节,高位存放于高字节。
12、and 指令:逻辑与指令,按位进行与运算。
13、int 指令:表示中断。int 21H 表示 ‘21H号中断’。
14、div 指令:除法指令。
15、伪指令dd即define double word,定义双字节(32)位数据。通过8086CPU执行dd定义后的数据,地位存放于低字节,高位存放于高字节。
16、伪指令dup是一个操作符,在汇编语言中同db、dw、dd 等一样,也是由编译器识别处理的符号。它是和db、dw、dd 等数据定义伪指令配合使用的,用来进行数据的重复。
17、xor,异或指令。
18、call和ret 指令都是转移指令,它们都修改IP,或同时修改CS和IP。
19、mul,乘法指令。


第十九章 课后练习

第二章:

检测点2.1


mov ax,62627, ax=F4A3H
mov ah,31H,   ax=31A3H
mov al,23H,   ax=3123H
add ax,ax,    ax=6246H
mov bx,8263H  bx=826CH
mov cx,ax     cx=6246H
mov ax,bx     ax=826CH
add ax,bx     ax=04D8H
mov al,bh     ax=0482H
mov ah,bl     ax=6C82H
add ah,ah     ax=D882H
add al,6      ax=D888H
add al,al     ax=D810H
mov ax,cx     ax=6246H

mov ax,0002H ax=0002H
add ax,ax    ax=0004H
add ax,ax    ax=0008H
add ax,ax    ax=0010H


检测点2.2

(8086CPU段地址和偏移地址都只能够是16位)
00100  100FF
1001   2000
SA小于1001即可

检测点2.3

修改三次,
mov后修改一次,sub后修改一t次,jmp修改一次,最后IP位0000H

练习3.3

ax = 1000H
ds = 1000H ax = 1000H
ax = 1123H
bx = 6622H
cx = 2211H
bx = 2211 + 6622 = 8833H
cx = 2211 + 6622 = 8833H

ax = 1000
ds = 1000
ax = 2c34
10000 34
10001 2c
bx = 2c34
bx = 2c34-1122 = 1b12
10002 12
10003 1b

练习3.5

mov ax,0000
mov ds,ax
mov ax,[0]
add ax,[2]
add ax,[4]

课后3.1

a = 10
b = 11
c = 12
d = 13
e = 14
f = 15

mov ax,2000
mov ss,ax
mov sp,0010

第六章

检测点 6.1

mov cs:[bx],ax

mov ax,


第九章:

课后练习1

assume cs:code

data segment
     dw 00FFH,0FF00H
data ends

code segment
    start:
        mov ax,data
        mov ds,ax
        mov bx,0
        jmp word ptr [bx+1]
code ends
end start

课后练习2

assume cs:code

data segment
     dd 12345678H
data ends

code segment
    start:
        mov ax,data
        mov ds,ax
        mov bx,0
        mov [bx],bx
        mov [bx+2],ax
        jmp dword ptr ds:[0]
code ends
end start

检测点9.2

assume cs:code

code segment
    start:
        mov ax,2000H
        mov ds,ax
        mov bx,0
        s:
        mov cx,0
        mov cl,[bx]
        jcxz ok
        inc bx
        jmp short s
        ok:
        mov dx,bx
        mov ax,4c00h
        int 21h
code ends
end start

检测点9.3

assume cs:code

code segment
    start:
        mov ax,2000H
        mov ds,ax
        mov bx,0
        s:
        mov cl,[bx]
        mov ch,0
        or cx,0001H
        inc bx
        loop s  ;loop指令时先将cx减去1,然后再判断cx的值
        ok:
        dec bx
        mov dx,bx
        mov ax,4c00h
        int 21h
code ends
end start


检测点10.1

assume cs:code, ss:stack

stack segment
    db 16 dup(0)
stack ends

code segment
    start:
            mov ax,stack
            mov ss,ax
            mov sp,10H
            mov ax,1000H
            push ax             ;将段地址压栈
            mov ax,0000H
            push ax             ;将0压栈
            retf                ;相当于pop IP,也即将IP修改为0,然后pop CS,也就修改CS的值挝1000H
code ends
end start

检测点10.2

ax=0006H

检测点10.3

ax = 1010H

检测点10.4

ax=0005H

ZF
SF
OF
DF
PF
CF

ax = 0fff0
     00010
     10000h
     
     zf = 0
     sf = 0
     of = 0
     df = 0
     pf = 0
     cf = 0
    

第二十章 源代码

;第四章:
assume cs:test
test segment
    mov ax,0002
    add ax,ax
    add ax,ax
    mov ax,4c00H
    int 21H
test ends
end

;--mystart,是一个标号,表示程序的入口,这个标号可以取名为其他单词。
assume cs:test
test segment
mystart:    mov ax,2
        add ax,ax
        add ax,ax
        mov ax,4c00H
        int 21H
test ends
end mystart

;第五章:
assume cs:segstart
segstart segment
mystart:    
        mov ax,2000H
        mov ds,ax
        mov al,[0]
        mov bl,[1]
        mov cl,[2]
        mov dl,[3]
        
        mov ax,4c00H
        int 21H
segstart ends
end mystart


assume cs:segstart
segstart segment
mystart:    
        mov ax,2000H
        mov ds,ax
        mov bx,1000H
        mov ax,[bx]
        inc bx
        inc bx
        mov [bx],ax
        inc bx
        inc bx
        mov [bx],ax
        inc bx
        mov [bx],al
        inc bx
        mov [bx],al
        
        mov ax,4c00H
        int 21H
segstart ends
end mystart

;--计算2^12的值
assume cs:segstart

segstart segment
mystart:    
        mov ax,2
        mov cx,11
    s:  add ax,ax
        loop s
        
        mov ax,4c00H
        int 21H
segstart ends
end mystart

;--将内存单元(8big)地址为ffff:0006的值乘以3,将结果放到bx中。
assume cs:segstart
segstart segment
mystart:    
        mov ax,0ffffh
        mov ds,ax
        mov bx,0006H
        mov al,[bx]
        mov ah,0
        mov dx,0
        mov cx,3
    s:  add dx,ax
        loop s
        
        mov ax,4c00H
        int 21H
segstart ends
end mystart

;--不用[bx],几种等价的写法。
assume cs:segstart
segstart segment
mystart:    
        mov ax,0ffffh
        mov ds,ax
        mov bx,0006H
        mov ax,[bx]         ;用[bx]表示内存地偏移地址
        mov ax,ds:[bx]      ;用ds:[bx]段前缀的方式表示内存单元的物理地址
        mov ax,ds:[0006H]   ;用ds:[0006H]段前缀的方式表示内存单元的物理地址
        mov ax,4c00H
        int 21H
        
segstart ends
end mystart

;--将ffff:0000 - ffff:000b中的每个byte内存单元中存储的数据进行累加,将结果存放到dx中。
assume cs:segstart
segstart segment
mystart:    
        mov ax,0ffffh
        mov ds,ax
        mov ax,0000h
        mov bx,0000h
        mov cx,000Ch
        mov dx,0000h
    s:  mov al,ds:[bx]
        add dx,ax
        inc bx
        loop s
        
        mov ax,4c00h
        int 21h
segstart ends
end mystart

;将内存ffff:0~ffff:b段元中的数据拷贝到 0:200~0:20b单元中.
assume cs:segstart
segstart segment
mystart:    
        mov ax,0ffffh
        mov ds,ax
        mov ax,0200h
        mov es,ax
        
        mov cx,0006H
        mov bx,0000H
    s:  mov ax,ds:[bx]
        mov es:[bx],ax
        inc bx
        inc bx
        loop s
        
        mov ax,4c00h
        int 21h
        
segstart ends
end mystart

;第六章

;将下面几个数字累加
assume cs:codesegment
;定义代码段,CS段地址从下面开始
codesegment segment
        dw 1,2,3,4,5,6,7,8  ;定义字型数据,每个字16bit,两个字节
;程序入口,定义IP地址
mystart:        
        mov ax,0
        mov bx,0
        mov cx,8
    s:  add ax,cs:[bx]
        add bx,2
        loop s
        
        mov ax,4c00H
        int 21h
codesegment ends
end mystart


;将数据段中的数据依次入栈
;分开定义数据段、栈段、代码段
assume cs:code,ds:data,ss:stack
data segment
    dw 0001H,0002H,0003H,0004H,0005H,0006H,0007H,0008H
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,0010H ;the same as mov sp,16
        mov ax,data
        mov ds,ax
        mov bx,0
        mov cx,8
        
    s:  
        push [bx]
        add bx,2
        loop s
        
        mov ax,4c00h
        int 21h
code ends
end start

;将一个段的数据加和另一个段的数据依次相加,将结果放入第三个段中
assume cs: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
        mov ax,c
        mov es,ax
        mov cx,4
        mov bx,0
    s0:
        mov ax,[bx]
        mov es:[bx],ax
        add bx,2
        loop s0
        
        mov ax,b
        mov ds,ax
        mov cx,4
        mov bx,0
        
    s1:
        mov ax,ds:[bx]
        add es:[bx],ax
        add bx,2
        loop s1
        
        mov ax,4c00h
        int 21h
code ends
end start


;通过push指令,将a段中的前8个字型数据逆序存放到b段中。
assume cs:code

a segment
    dw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh
a ends
b segment
    dw 0,0,0,0,0,0,0,0
b ends

code segment
start:
        mov ax,a
        mov ds,ax
        mov ax,b
        mov ss,ax
        mov sp,16
        mov bx,0
        mov cx,8
    s:
        push ds:[bx]
        add bx,2
        loop s
        
        mov ax,4c00h
        int 21h
code ends
end start

;第七章

;显示自定字符型
assume ds:data
data segment
    db 'unIX' ;the same as db 75H,6EH,49H,58H
    db 'foRK' ;the same as db 66H,6FH,52H,4BH   
data ends

assume cs:code
code segment
    start:
        mov al,'a' ;the same as mov al,61H
        mov bl,'b' ;the same as mov bl,62H
        mov ax,4c00h
        int 21h
code ends
end start

;字母大小写转换
assume ds:data
data segment
    db 'BaSic'          ;将其中的小写字母转换成大写
    db 'InfOrMaTiOn'    ;将其中的大写字母转换成小写
data ends

assume cs:code
code segment
    start:
        mov ax,data
        mov ds,ax
        mov bx,0
        mov cx,5
    s0:
        mov al,[bx]
        and al,11011111B
        mov [bx],al
        inc bx
        loop s0
        
        mov bx,5
        mov cx,11
    s1:
        mov al,[bx]
        or al,00100000B
        mov [bx],al
        inc bx
        loop s1
        
        mov ax,4c00h
        int 21h
code ends
end start

;mov ax,[bx+200] 的几种等价的写法
assume cs:code
code segment
    start:  
        mov bx,0
        mov ax,[bx + 200]
        mov ax,[200 + bx]
        mov ax,200[bx]
        mov ax,[bx].200
        mov ax,4c00h
        int 21h
code ends
end start


;通过[bx + idata]处理数组的方式进行字母大小写转换
assume ds:data
data segment
    db 'BaSic'  ;将其中的小写字母转换成大写
    db 'InfOr'  ;将其中的大写字母转换成小写
data ends

assume cs:code
code segment
    start:
        mov ax,data
        mov ds,ax
        mov bx,0
        mov cx,5
    s:
        mov al,[bx]
        and al,11011111B
        mov [bx],al
        
        mov al,[bx+5]
        or al,00100000B
        mov [bx+5],al
        inc bx
        loop s

        mov ax,4c00h
        int 21h
code ends
end start

;利用si和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 cx,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


;‘mov ax, [bx][si]’ 和 'mov ax,[bx + si]'
assume cs:code
code segment
    start:  
        mov bx,0
        mov si,1
        mov ax,[bx][si]
        mov ax,[bx + si]
        mov ax,[bx].[si]
        mov ax,[si].[bx]
        mov ax,4c00h
        int 21h
code ends
end start


;几种合法的寻址方法
assume cs:code
code segment
    start:  
        mov bx,0
        mov si,1
        mov di,2
        mov ax,-0
        mov ax,-2
        mov ax,[bx][si]
        mov ax,[bx + si]
        mov ax,12[bx].13[si+12 + 2]
        mov ax,[si].[bx].12
        mov ax,[si].[bx].-12
        mov ax,[-2+si].[bx-1].12
        mov ax,-2[-2+si].[bx-1].12
        ;mov ax,[bx][si][di]        error A2047: Multiple index registers
        ;mov ax,[bx+si+di]          error A2047: Multiple index registers
        ;mov ax,1[bx].12[si+di].23  error A2047: Multiple index registers
        mov ax,4c00h
        int 21h
code ends
end start

;两层循环的使用
;将其中的小写字母转换成大写
assume cs:code,ds:data
data segment
    db 'ibm             '
    db 'dex             '
    db 'dos             '
    db 'vax             '
data ends

code segment
    start:
            mov ax,data
            mov ds,ax
            mov bx,0
            mov cx,4
        s0:
            mov si,0
            mov dx,cx   ;用dx来暂存cx的值,这里也可以将cx压栈。
            mov cx,3
            s1:
                mov al,[bx+si]
                and al,11011111B
                mov [bx+si],al
                inc si
            loop s1
            add bx,16
            mov cx,dx   ;还原cx的值,这里也可以通过出栈的方式回复cx的值
        loop s0
            
            mov ax,4c00h
            int 21h
code ends
end start


;第八章



;在没有寄存器名存在的情况下,用操作符x ptr指明内存单元的长度,X在汇编指令中可以为word或byte
assume cs:code

code segment
    start:
            mov word ptr ds:[0],1       ;将数字1当成一个字型数据存放到ds:[0]中
            mov bx,0001H
            inc word ptr [bx]           ;将ds:[bx]中存储的字型数据自增1
            inc word ptr ds:[0]         ;将ds:[0]中存储的字型数据自增1
            add word ptr [bx],2         ;将ds:[bx]中存储的字型数据加2,并将结果以字的方式存放到ds:[bx]
            
            mov byte ptr ds:[0],1       ;将数字1当成字节型数据存放到ds:[0]的字节处
            inc byte ptr [bx]           ;将ds:[bx]地址处的中存储的字节数据自增1
            inc byte ptr ds:[0]         ;将ds:[0]地址处中的存储的字节数据自增1
            add byte ptr [bx],2         ;将ds:[bx]中存储的字节型数据加2,并将结果以字节的方式存储到ds:[bx]
            
            mov ax,4c00h
            int 21h
code ends
end start


;push/pop只能够对字进行操作
assume cs:code

code segment
    start:
            push [bx]   ;push只能够对字进行操作,因为每一次push,sp都会减去2
            pop ax      ;pop也只能够对字进行操作,因为每一次pop,sp都会加上2
            mov ax,4c00h
            int 21h
code ends
end start


;除法指令
;当除数为8位的时候,被除数默认放在AX,当除数为16位的时候,被除数默认放在DX和AX中。
;当除数为8位的时候,商存放于AL中,余数存放于AH中。当除数为16位的时候,商存放于AX中,余数存放于DX中。
assume cs:code

code segment
    start:
            div byte ptr ds:[0]  ;(al) = (ax)/((ds)*16 + 0)的商 ,(ah) = (ax)/((ds)*16 + 0)的余数
            div word ptr es:[0]  ;(ax) = ((dx)*10000H + (ax))/((es)*16 + 0)的商,(dx) = ((dx)*10000H + (ax))/((es)*16 + 0)的余数
            mov ax,4c00h
            int 21h
code ends
end start



;利用除法指令计算100001/100,其中,100001和100是十进制数。
assume cs:code

code segment
    start:
            mov dx,0001H        ;将十进制数100001转换为十六进制数为186A1H,因此DX中存放高16位,也即0001H
            mov ax,86A1H        ;AX中存放低16位,也即86A1H
            mov bx,100          ;虽然十进制除数100小于FFH,可以用8位存放,但是被除数位32位,因此除数只能够用16位存放
            div bx              ;除法运算,计算后,将商放到AX中(03E8H),,将余数放到DX中(0001H)。
            
            mov ax,4c00h
            int 21h
code ends
end start



;利用除法指令计算1001/100,其中,1001和100是十进制数。
assume cs:code

code segment
    start:
            mov ax,03E9H        ;十进制数1001小于FFFFH(65535),因此可以用16位存放
            mov bl,100          ;十进制数100小于FF,可以用8位存放
            div bl              ;除法运算,计算后,将商放到AL中(0AH),将余数放到AH中(01H)。即AX中存放010AH
            
            mov ax,4c00h
            int 21h
code ends
end start



;用div计算data段中第一个数据除以第二个数据后的结果,商存放在第3个数据字中单元中,余数存放到底4个数据字单元中。
assume cs:code
assume ds:data

data segment
    dd 100001   ;定义一个32位的十进制数100001,对应的十六进制数为186A1H,则8086CPU定以后,数据段的存放顺序为
                ;data:[0] A1 data:[2] 86 data:[4] 01 data:[6] 00
    dw 100      ;定义16位的字型十进制数据100,对应的十六进制数位64H,则8086CPU定以后,数据段的存放顺序为
                ;data:[8] 64 data:[10] 00
    dw 0        ;定义16位的字型十进制数据0,对应的十六进制数位0H,则8086CPU定以后,数据段的存放顺序为
                ;data:[12] 00 data:[14] 00
    dw 0        ;定义16位的字型十进制数据0,对应的十六进制数位0H,则8086CPU定以后,数据段的存放顺序为
                ;data:[16] 00 data:[18] 00
                ;定义后,data段的数据显示为:A1 86 01 00 64 00 00 00 00 00
data ends

code segment
    start:
            mov ax,data
            mov ds,ax
            mov ax,ds:[0]           ;将被除数的低 16位数据存放到AX中
            mov dx,ds:[2]           ;将被除数的高16位数据存放到DX中
            div word ptr ds:[4]     ;除法运算,将结果的商存放到AX中(03E8H),余数存放到DX中(0001H)
            mov word ptr ds:[6],ax  ;将AX中的商填入指定的内存中,由于这里ax位16位,因此word ptr可以不写
            mov ds:[8],dx           ;将AX中的余数填入指定的内存中
                                    ;此时的data数据段的内存分布为:A1 86 01 00 64 00 E8 03 01 00
            
            mov ax,4c00h
            int 21h
code ends
end start


;dup的用法
assume cs:code
assume ds:data

data segment
    db 3 dup(1)     ;equal to db 1,1,1
    db 3 dup(1,2,3) ;equal to db 1,2,3,1,2,3,1,2,3
    db 3 dup('ab','AB');equal to db 'abABabABabAB'
data ends

code segment
    start:

            mov ax,4c00h
            int 21h
code ends
end start


;实验7,寻址方式在结构化数据访问中的应用
;将data段中的年份,收入,人数放到table段中,并计算人均收入情况。
assume ds:data
assume es:table
assume cs:code

data segment

    ;定义21个年份的字符串,每个字符串占用4个字节,两个字,因此地址区间为0H到53H
    db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
    db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
    db '1993','1994','1995'
        
    ;定义21个年份中每年的收入情况,每个收入占用2个字,4个字节,因此地址区间为54H到0A7H
    dd 16,22,382,1456,2390,8000,16000,24486,50065
    dd 97479,140417,197514,345980,590827,803530
    dd 1183000,1843000,2759000,3753000,4649000,5937000
    
    ;定义21年公司每年的雇员人数,每个数字占用1个字,两个字节,因此地址区间为0A8H到0D1H
    dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258
    dw 2793,4037,5635,8226,11542,14430,15257,17800
    
data ends

table segment

    ;定义表格式,表有4列,21行,每列占用16个字节
    ;字节0到3为年份,字节5到8为收入,字节10到11为人数,13到14字节为平均每人的收入值
    db 21 dup ('year summ ne ?? ')

table ends

code segment
start:
        mov ax,data
        mov ds,ax                   ;将ds和data关联
        mov ax,table
        mov es,ax                   ;将es和table关联
        mov cx,21                   ;循环21次
        
        mov bx,0H                   ;bx表示data段的偏移地址
        mov si,0H                   ;si表示table段的偏移地址
        mov di,08AH                 ;di表示data段中,人数的偏移地址
    s:
        mov ax, ds:[bx].0H
        mov es:[si].0H, ax          ;将data段中的年份的低字节放到table段年份的低字节处
        mov ax, ds:[bx].2H
        mov es:[si].2H, ax          ;将data段中的年份的高字节放到table段年份的高字节处
        
                                    ;由于年份和收入均占用四个字节,因此相对地址可以共用bx
        mov ax, ds:[bx].54H
        mov es:[si].5H, ax          ;将data段中的收入的低字节放到table段收入的低字节处
        mov ax, ds:[bx].56H
        mov es:[si].7H, ax          ;将data段中的收入的高字节放到table段收入的高字节处
        
                                    ;和年份收入不同,人数只占用两个字节,因此无法共用bx,得用di
        mov ax, ds:[di]
        mov es:[si].0AH, ax         ;将data段中的人数放到table段中的人数域
        
        mov ax, es:[si].5H
        mov dx, es:[si].7H
        div word ptr es:[si].0AH    ;除法运算
        
        mov es:[si].0DH,ax          ;只保留商,余数舍弃
        
        add bx,04H                  ;bx增加4个字节,索引data段中的下一个年份和收入
        add si,10H                  ;si增加16个字节,索引table段中的下一行
        add di,02H                  ;di增加2个字节,索引data段中的下一个人数

        loop s
        
        mov ax,4C00H
        int 21H
        
code ends
end start


;第九章

;段内短转移jmp short
assume cs:code
code segment
start:
        mov ax,0
        jmp short s
        add ax,1
        add ax,ax
    s:
        inc ax
code ends
end start


;段内近转移jmp near ptr
assume cs:code
code segment
start:
        mov ax,0
        jmp near ptr s
        add ax,1
        add ax,ax
    s:
        inc ax
code ends
end start

;实验九,自己写的
assume ds:data,ss:stack,cs:code
data segment
    db 'welcome to masm!'           ;十六个字符
    db 02H,24H,71H                    ;定义颜色值,02H表示绿色字,24H表示绿底红色字,71H表示白底蓝色
data ends

stack segment
    dw 8 dup(0)
stack ends

code segment
    start:    
            mov ax,data
            mov ds,ax                ;ds代表data的段地址
            mov bx,0000H            ;bx代表data段字符串的偏移地址
            mov di,0010H            ;di代表data段中颜色的偏移地址
            
            mov ax,0B872H        
            mov es,ax                ;es代表屏幕缓冲区地址
            mov si,0000H            ;si代表屏幕缓冲区的偏移地址
            
            mov ax,stack
            mov ss,ax                ;ss代表堆栈段的段地址
            mov ax,0010H
            mov sp,ax                ;si代表当前堆栈的栈顶地址
            
            mov cx,0003H            ;因为要将字符串放置3次,所以循环3次,没次将字符串放一行
        s0:
            push cx                    ;因为内存循环要用到cx,所以将cx压栈
            mov cx,0010H            ;因为字符串共有16个字符,所以内层循环需要循环16次
            
            s1:
                mov al,ds:[bx]            ;将字符数据放入ax寄存器的低位
                mov ah,ds:[di]            ;将字符属性数据放入ax的高位
                
                mov es:[si],ax            ;将ax中的值写入屏幕缓冲区中
                
                add si,0002H            ;将si指向屏幕缓冲区的下一个字的偏移地址,准备写入下一个字符数据
                inc bx                  ;将bx指向字符数据的下一个字符的偏移地址
                loop s1
                
            pop cx                        ;内层循环结束后,记得将cx出栈,进行下一行的字符显示
            
            mov bx,0000H            ;将bx复位,表示重新读取字符串了
            inc di                    ;将di自增,表示读取下一个属性值了
            add si,00A0H            ;将si增加160,表示重新开始一行写入字符
            loop s0
        
        mov ax,4C00H
        int 21H
            
code ends
end start


;实验九,网上版本
assume ds:data,ss:stack,cs:code
data segment
    db 'welcome to masm!'           ;十六个字符
    db 02H,24H,71H                    ;定义颜色值,02H表示绿色字,24H表示绿底红色字,71H表示白底蓝色
data ends

stack segment
    dw 8 dup(0)
stack ends

code segment
    start:    
        mov ax,data
        mov ds,ax
        mov ax,stack
        mov ss,ax
        mov sp,10H
        xor bx,bx
        mov ax,0b872h
        mov cx,3
    s3:
        push cx
        push ax
        push bx
        mov es,ax
        mov si,0
        mov di,0
        mov cx,10h
    s1:                            ;存放字符
        mov al,ds:[si]
        mov es:[di],al
        inc si
        add di,2
        loop s1
        
        mov di,1
        pop bx
        mov al,ds:10h[bx]
        inc bx
        mov cx,10h
    s2:                            ;存放属性
        mov es:[di],al
        add di,2
        loop s2
        
        pop ax
        add ax,01h
        
        pop cx
        loop s3
        
        mov ax,4C00H
        int 21H
            
code ends
end start



;第10章


;ret示例
assume cs:code, ss:stack

stack segment
    db 16 dup(0)
stack ends

code segment
            mov ax,4C00H
            int 21H
    start:
            mov ax,stack
            mov ss,ax
            mov sp,10H
            mov ax,0
            push ax                ;将0压栈
            mov bx,0
            ret                    ;相当于pop IP,也即将IP修改为0
code ends
end start


;retf示例
assume cs:code, ss:stack

stack segment
    db 16 dup(0)
stack ends

code segment
            mov ax,4C00H
            int 21H
    start:
            mov ax,stack
            mov ss,ax
            mov sp,10H
            mov ax,0
            push cs                ;将段地址压栈
            push ax                ;将0压栈
            mov bx,0
            retf                ;相当于pop IP,也即将IP修改为0,然后pop CS,也就修改CS的值
code ends
end start


;计算2*2
assume cs:code
code segment
    start:
            mov ax,0302H        ;将乘数2放到ax的低8位
            mov bx,0002H        ;将乘数2放到bx的低8位
            mul bl                ;(ax) = (al) * (bl)
            mov ax,4c00h
            int 21h
code ends
end start



;计算data段中第一组数据的立方,结果保存在后面一组dword中
assume cs:code
assume ds:data

data segment
    dw 1,2,3,4,5,6,7,8
    dd 8 dup(0)
data ends

code segment
    start:
            mov ax,data
            mov ds,ax
            mov di,0
            mov si,0010H
            mov cx,8
        s0:    
            call s1
            mov ds:[si],ax            ;乘法运算的结果,AX中保存低16位
            mov ds:[si].0002H,dx    ;乘法运算的结果,DX中保存高16位
            add di,2
            add si,4
            loop s0
            
            mov ax,4C00H
            int 21H
            
        s1:
            mov ax,ds:[di]
            mov bx,ax
            mul bx
            mul bx
            ret
            
code ends
end start



;通过子程序将小写字母转换成大写
assume cs:code
assume ds:data

data segment
    db 'conversation'
data ends
code segment
    start:
            mov ax,data
            mov ds,data
            mov si,0
            mov cx,12
            call capital
            
            mov ax, 4C00H
            int 21H
        
    capital:
            and byte ptr ds:[si],11011111B
            inc si
            loop capital
            ret
code ends
end start



;将以0为结尾的字符串中的字符转换为大写
assume cs:code
assume ds:data
assume ss:stack

data segment
    db 'word',0
    db 'unix',0
    db 'wind',0
    db 'goog',0
data ends

stack segment
    dw 8 dup(0)
stack ends

code segment
    start:
            mov ax,data
            mov ds,ax
            mov ax,stack
            mov ss,ax
            mov sp,0010H
            
            mov bx,0
            mov cx,4
            
        s0:
            mov si,bx
            push cx
            call capital
            pop cx
            add bx,5
            loop s0
            
            mov ax, 4C00H
            int 21H
        
    capital:
            mov cl,[si]
            mov ch,0
            jcxz ok
            and byte ptr ds:[si],11011111B
            inc si
            jmp short capital
    ok:
            ret
code ends
end start

;实验十

;在指定的行号,列号,用指定的颜色显示一个以0结尾的字符串

assume cs:code,ss:stack
data segment
    db 'Welcome to masm!',0
data ends

stack segment
    dw 8 dup(0)
stack ends

code segment
    start:    mov dh,8            ;行号
            mov dl,3            ;列号
            mov cl,2            ;颜色属性值
            mov ax,data    
            mov ds,ax
            mov si,0            ;ds:si指向字符串的首地址
            
            mov ax,stack
            mov ss,ax
            mov sp,16
            
            call show_str
            
            mov ax, 4c00h
            int 21h
    show_str:
            push cx
            push si
            
            mov al,0A0H            ;每行有160个字节 160 = 0A0H
            dec dh                ;行号在显存中下标从0开始
            mul dh                ;定位到指定行的第一个字节处
            mov bx,ax            ;定位好位置的偏移地址,存放到bx中
            
            mov al,2            ;每个字符占用2个字节
            mul dl                ;定位列
            add bx,ax            ;bx保存行与列的偏移地址
            
            mov ax,08B00H        ;显存开始的地址
            mov es,ax            ;es保存显存的段地址
            
            mov di,0            ;di指向显存的偏移地址
            mov al,cl            ;cl存放颜色的属性值
            mov ch,0            ;下面cx存放的是每次处理的字符
    s:        
            mov cl,ds:[si]        ;将处理的字符存放到cx的低字节处
            jcxz ok                ;如果字符位0,则表示处理结束
            
            mov es:[bx+di],cl    ;向显存中写入字符
            mov es:[bx+di+1],al    ;向显存中写入属性值
            
            inc si                ;处理下一个字符
            add di,2            ;定位下一个存放字符的显存字地址
            jmp short s            ;无条件跳转,处理下一个字符
    ok:
            pop si
            pop cx
            ret;                ;显示字符串的子程序结束
            
code ends
end start


;解决除法溢出的问题
;就算1000000/10(F4240H/0AH)
;X/N=int(H/N)<<16 + (rem(H/N)<<16+L)/N,int求商,rem求余数
assume cs:code,ss:stack
stack segment
    dw 8 dup(0)
stack ends

code segment
start:
        mov ax,stack
        mov ss,ax
        mov sp,10h
        
        mov ax,4240h    ;ax线保存被除数的低16位
        mov dx,0fh        ;dx将在用于保存被除数的高16位
        mov cx,0ah        ;cx保存除数
        call divdw
        
        mov ax,4c00h
        int 21h
divdw:
        push ax            ;保存被除数的低16位
        mov ax,dx        ;将高十六位放到ax中,作为被除数
        mov dx,0        ;dx置零,用于存放高16位的除法所得到的余数
        div cx            ;计算高16位的除法
        mov bx,ax        ;ax中存放的是高16位除法的商,因此bx中存放的是高16位商
        pop ax            ;取出低16位的值
        div cx            ;用dx中的高16位和ax中的低16位组成的被除数除以cx
        mov cx,dx        ;dx存放最终的余数,此时cx中存放的时候整个除法的余数
        mov dx,bx        ;将高16位的商放到指定的dx中,低16位的商在ax中,余数在cx中
        ret;
code ends
end start


;将data段中的数据以十进制的形式显示出来
assume ds:data
data segment
    db 10 dup(0)
data ends

assume ss:stack
stack segment
    dw 8 dup(0)
stack ends
assume cs:code
code segment
    start:
            mov ax,12666        ;需要处理的word型数字存放到ax中
            mov bx,data
            mov ds,bx            ;data用于存放有dtoc转换而成的字符串
            mov si,0            ;ds:si指向字符串的首地址
            
            mov bx,stack
            mov ss,bx
            mov sp,0010H
            
            call dtoc            ;word型整数转换为字符串存储
            
            mov dh,8            ;初始化打印的位置
            mov dl,3
            mov cl,0CAH
            
            call show_str        ;开始打印字符串
            
            mov ax,4c00h
            int 21h
        
    dtoc:                        ;数字转换成字符串子程序
            push dx
            push cx
            push ax
            push si
            
            mov bx,0            ;bx用于存放数据的位数,用栈存放转换后的字符
    s0:
            mov cx,10D            ;10D表示十进制数10
            mov dx,0
            div cx                ;除以十,低位已经存放到ax中了,高位的dx存放0
            mov cx,ax
            jcxz s1                ;当余数为0的时候,跳转到s2
            add dx,30H            ;将余数加上30H就得到相应的ASCII码
            push dx
            inc bx
            jmp short s0
            
    s1:
            add dx,30H            ;当商是0的时候,余数位个位数
            push dx
            inc bx                ;当商是0而余数不是0的时候,将bx加1
            mov cx,bx            ;总共有bx位进栈了,循环bx次
            mov si,0
            
    s2:                            ;将数据依次出栈放到指定的内存中
            pop ax            
            mov ds:[si],al        ;只取低位,表示只显示字符
            inc si
            loop s2
            mov byte ptr ds:[si],0        ;字符串要以0结尾
            
    okay:
            pop bx
            pop si
            pop ax
            pop cx
            pop dx
            ret;                ;数字显示子程序结束
            
    show_str:                    ;在指定的行列用指定的属性显示以0结尾的字符串
            push cx
            push si
            
            mov al,0A0H            ;每行有160个字节 160 = 0A0H
            dec dh                ;行号在显存中下标从0开始
            mul dh                ;定位到指定行的第一个字节处
            mov bx,ax            ;定位好位置的偏移地址,存放到bx中
            
            mov al,2            ;每个字符占用2个字节
            mul dl                ;定位列
            add bx,ax            ;bx保存行与列的偏移地址
            
            mov ax,08B00H        ;显存开始的地址
            mov es,ax            ;es保存显存的段地址
            
            mov di,0            ;di指向显存的偏移地址
            mov al,cl            ;cl存放颜色的属性值
            mov ch,0            ;下面cx存放的是每次处理的字符
    s:        
            mov cl,ds:[si]        ;将处理的字符存放到cx的低字节处
            jcxz ok                ;如果字符位0,则表示处理结束
            
            mov es:[bx+di],cl    ;向显存中写入字符
            mov es:[bx+di+1],al    ;向显存中写入属性值
            
            inc si                ;处理下一个字符
            add di,2            ;定位下一个存放字符的显存字地址
            jmp short s            ;无条件跳转,处理下一个字符
    ok:
            pop si
            pop cx
            ret;                ;显示字符串的子程序结束
        
code ends
end start

;第11章
;计算1EF000H + 201000H,高16位放到ax中,低16位放到bx中
assume cs:code
code segment
    start:
            mov bx,0F000H
            add bx,1000H        ;将低16位的值存放于bx中
            
            mov ax,001EH
            adc ax,0020H        ;由于低16位相加有进位产生,因此高16位相加应该用adc指令
            
            mov ax,4C00H
            int 21H
code ends
end start

;cmp无符号比较跳转
;if((ah) == (bh)) (ah) = (ah) + (ah) else (ah) = (ah) + (bh)
assume cs:code
code segment
    start:
            mov ax,1234H
            mov bx,2345H
            cmp ah,bh
            je s                ;相等则转移,实际上是ZF=1则跳转
            add ah,bh
            jmp short ok
        s:
            add ah,ah
        ok:    
            mov ax,4C00H
            int 21H
code ends
end start


;统计data中小于8的数字,将统计结果放到ax中
assume ds:data
data segment
    db 6,8,12,3,4,8,1
data ends

assume cs:code
code segment
    start:
            mov ax,data
            mov ds,ax
            mov si,0
            mov cx,0
        s0:
            cmp byte ptr [si],8
            jnb s
            inc ax
        s1:
            inc si
            loop s0
            
            mov ax,4C00H
            int 21H
code ends
end start


;用串传送指令进行字符串的传送,解法一
assume ds:data
data segment
    db 'Welcome to masm!'
    db 16 dup(0)
data ends

assume cs:code
code segment
    start:
            mov ax,data
            mov ds,ax            ;ds位字符串的段地址
            mov si,0
            
            add ax,0001H        ;ax + 1,得到目的段的段地址
                                ;计算方式为:(es) = ((ds)*16 + 字符串所占用的字节数刚好是16)/16 = (ds) + 1 = (ax) + 1
            mov es,ax            ;es位目的地的段地址
            mov di,0

            mov cx,16
            cld
            rep movsb
            
            mov ax,4C00H
            int 21H
code ends
end start


;用串传送指令进行字符串的传送,解法二
assume ds:data
data segment
    db 'Welcome to masm!'
    db 16 dup(0)
data ends

assume cs:code
code segment
    start:
            mov ax,data
            mov ds,ax                ;ds位字符串的段地址    
            mov si,0
    
            mov es,ax                ;es位字符串的段地址,只不过es的偏移地址从16开始算起
            mov di,16

            mov cx,16
            cld
            rep movsb
            
            mov ax,4C00H
            int 21H
code ends
end start


;用串传送指将F000H段的最后16个字符复制到data段中
assume ds:data
data segment
    db 16 dup(0)
data ends

assume cs:code
code segment
    start:
            mov ax,data
            mov es,ax            ;es:[di]目前指向目标段的最后一个字节处
            mov di,15
            
            mov ax,0F000H
            mov ds,ax
            mov si,0FFFFH        ;ds:[si]指向F000H段最后一个字节的地址
            
            mov cx,16
            std
            rep movsb
            
            mov ax,4C00H
            int 21H
code ends
end start


;第12章
;修改0号中断的中断处理子程序,也即更改0号中断向量表处所存放的CS:IP的值
assume cs:code
code segment
    start:
            mov ax,cs
            mov ds,ax
            mov si,offset do0                    ;ds:[si] 指向需要传递的中断处理子程序的地址
            
            mov ax,0
            mov es,ax
            mov di,200h                            ;es:[di] 指向需要存放中断处理子程序的地址
            
            mov cx,offset do0end - offset do0    ;通过偏移的方式计算代码传输的字节数
            cld                                    ;正向传递
            rep movsb                            ;将中断处理子程序的代码传递到0000:0200H处
            
            mov ax,0                            ;修改0号中断对应的中断向量表中所存储的CS:IP
            mov es,ax
            mov word ptr es:[0*4],200h            ;偏移地址修改为0200H
            mov word ptr es:[0*4+2],0            ;段地址修改位0000H
                                                ;表示0号中断的中断处理子程序被存放在0000:0200H处
            
            mov ax,4c00h
            int 21h
    do0:
            jmp short do0start                    ;jmp占用2个字节
            db "Welcome to masm!"                ;将字符串数据放到此处,是为了确保字符串数据在每次中断发生的时候都能够从固定的安全位置访问到
    do0start:
            mov ax,cs
            mov ds,ax
            mov si,202h                            ;ds:[si]指向字符串
            
            mov ax,0b800h
            mov es,ax
            mov di,12*160+36*2                    ;es:[di]指向显存位置
            
            mov cx,16                            ;cx为字符串字节数
    s:
            mov ah,02h                            ;ah中存放字符颜色属性
            mov al,[si]                            ;al中存放字符
            mov es:[di],ax
            inc si                                ;指向下一个字符
            add di,2                            ;指向下一个显存字
            loop s                                ;处理下一个字符
            
            mov ax,4c00h
            int 21
    do0end:
            nop
            
code ends
end start



;实现中断7ch,代替loop功能
;loop需要cx和位移,因此中断7ch也需要cx和位移
;加上cx用cx不变,位移存放到bx中
;由于是触发int中断,因此栈里面应该有,标识寄存器、CS、IP
assume cs:code
code segment

    start:
            mov ax,cs
            mov ds,ax
            mov si,offset lps                    ;ds:[si] 指向需要传递的中断处理子程序的地址
            
            mov ax,0
            mov es,ax
            mov di,200h                            ;es:[di] 指向需要存放中断处理子程序的地址
            
            ;传递中断例程
            mov cx,offset lpe - offset lps        ;通过偏移的方式计算代码传输的字节数
            cld                                    ;正向传递
            rep movsb                            ;将中断处理子程序的代码传递到0000:0200H处
            
            ;安装中断例程
            mov ax,0                            ;修7ch号中断对应的中断向量表中所存储的CS:IP
            mov es,ax
            mov word ptr es:[7ch*4],200h        ;7ch中断例偏移地址修改为0200H
            mov word ptr es:[7ch*4+2],0            ;7ch中断例程段地址修改位0000H
                                                ;表示7ch号中断的中断处理子程序被存放在0000:0200H处
            
            mov ax,4c00h
            int 21h
    lps:
            ;开始编写中断例程
                                ;由于是触发int中断,因此栈里面应该有,标识寄存器、CS、IP
            push bp                ;将bp入栈
            mov bp,sp            ;用bp指向栈顶,修后就可以在不影响SP的情况下,对栈进行操作了
            dec cx                ;cx中存放的是循环的次数,每次7ch中断,将其减去1
            jcxz lpr            ;如果cx位0,则结束循环
            add [bp+2],bx        ;否则将栈中存放IP的位置加上位移BX
    lpr:
            pop bp                ;还原bp的值
            iret                ;iret会将栈中以及被修改的IP出栈,然后CS、标志寄存器出栈
    lpe:
            nop
            
code ends
end start


;利用7ch在屏幕上显示80个惊叹号
assume cs:code
code segment
    start:
            mov ax,0b800h
            mov es,ax
            mov di,160*12
            mov bx,offset s - offset se            ;bx,设置位移
            mov cx,80
    s:
            mov byte ptr es:[di],'!'
            add di,2
            int 7ch                                ;触发7ch中断例程,相当于loop
                                                ;执行int之前,执行pushf、push CS、push IP
                                                ;然后修改CS、IP的值,跳转到7ch中断对应的中断例程中去执行
    se:
            nop
            
            mov ax,4c00h
            int 21h
code ends
end start


;在屏幕的5行12列显示3个红低高亮闪烁绿色的'a'
assume cs:code
code segment
    start:
            ;利用中断10h例程的2号子程序,将光标移动到指定的位置
            mov ah,2
            mov bh,0
            mov dh,5
            mov dl,12
            int 10h
            
            ;利用10h中断例程,显示3个字符
            mov ah,9
            mov al,'a'
            mov bl,11001010b
            mov bh,0
            mov cx,3
            int 10h
            mov ax,4c00h
            int 21h
code ends
end start


;在屏幕中间显示当前的月份
;月份被存放于CMOS RAM中的一个端口中,端口地址为08H
assume cs:code
code segment
    start:
            mov al,8            ;CMOS RAM的08H号地址存储月份
            out 70h,al            ;将08H写入地址端口70h中,让COMS RAM将该地址中的数据读取出来,放到71H端口中
            in al,71h            ;从71h端口中读取数据,读取出的数据是以BCD码的形式表示
                                ;al中的高4位表示的是十进制数月份的十位
                                ;al中的低4位表示的是十进制数月份的个位
                                
            mov ah,al
            mov cl,4
            shr ah,cl            ;将al中的数据存放到ah中,然后通过移位的方式仅仅保留al中8个bit位的高4位
                                ;此时ah中存放的是十进制数月份的十位
            
            and al,00001111b    ;由于al中的高4位以及被保存到al中的低4位上面,因此al中仅仅保留低4位即可
                                ;此时al中存放的是十进制月份数个位
                                ;ax寄存器的高字节的8个bit位仅仅使用低4位存储十进制月份的十位
                                ;ax寄存器的低字节的8个bit位仅仅使用低4位存储十进制月份的个位
            
            add ah,30h            ;将十进制数加上48(十六进制的30h),即可得到十进制数对应的ASCII码
            add al,30h            ;将十进制数加上48(十六进制的30h),即可得到十进制数对应的ASCII码
            
            mov bx,0b800h        ;将月份写入显存中
            mov es,bx
            mov byte ptr es:[160*12+40*2],ah
            mov byte ptr es:[160*12+40*2 + 2],al
            
            mov ax,4c00h
            int 21h
code ends
end start


;第15章
;在屏幕中间依次显示“a”~“z”,并可以让人看清。
;在显示的过程中,按下Esc键后,改变显示的颜色。
;实现int 9中断例程子程序
;1、从60h 端口读出键盘的输入;
;2、调用BIOS 的int 9 中断例程,处理其他硬件细节;
;3、判断是否为Esc的扫描码,如果是,改变显示的颜色后返回;如果不是则直接返回。
assume cs:code

stack segment
    db 128 dup(0)                        ;程序中的堆栈
stack ends

data segment
    dw 0,0                                ;data用于保存中断向量表中记录9号中断例程的IP地址和CS段地址
                                        ;其中第0字存放IP,第1个字存放CS
data ends

code segment
    start:
            mov ax,stack                ;初始化栈,并让sp指向栈顶
            mov ss,ax
            mov sp,128
            
            mov ax,data
            mov ds,ax                    ;让ds执行数据段
            
            mov ax,0
            mov es,ax                    ;让es指向中断向量表的起始位置
            
            push es:[9*4]                ;es:[9*4],表示9号中断例程子程序的IP地址
            pop ds:[0]                    ;将9号中断例程子程序的IP地址放到ds:[0]处
            push es:[9*4+2]                ;es:[9*4+2],表示9号中断例程子程序的CS地址
            pop ds:[2]                    ;将9号中断例程子程序的CS段地址放到ds:[2]处
            
            ;将我们所实现的中断例程安装到9号中断的中断向量表中
            mov word ptr es:[9*4], offset int9    ;offset int9代表我们所实现的int 9中断子程序的偏移地址
                                                ;这里表示将该偏移地址放到9号中断向量表中存储IP的字中
            mov es:[9*4+2],cs                    ;cs代表本程序的段地址
                                                ;将本程序的段地址放到9号中断向量表中存储CS的字中
            
            ;9号中断例程安装好了后,我们就可以显示我们的字符了
            ;但在字符显示的过程中,如果我们按下按钮,就会触发9号中断,发出可屏蔽中断请求
            ;请求发出后,CPU在处理完当前指令后,就会处理我们所写的9号中断子程序了
            
            mov ax,0B800H                ;初始化显存地址
            mov es,ax
            mov ah,'a'                    ;ah中存放的是要显示的字符,初始化为'a'
            
    s:
            mov es:[160*12 + 40*2],ah    ;将要显示的字符送入显存中                    
            call delay                    ;为了方便让字符在屏幕中停留得比较久一点儿,先让CPU干一点儿其它事情
            inc ah                        ;让ah中存放即将要显示的下一个字符
            cmp ah,'z'                    ;看看ah中的字符是否为'z'的下一个字符
            jna s                        ;如果不是,说明还没有显示完成,继续显示下一个字符
            
            ;处理到这里,说明字符已经显示完成了,接下来程序该返回了
            ;在程序返回前,将中断向量表中的ini 9中断例程的入口地址恢复为原来的地址。
            ;否则程序返回后,别的程序将无法使用键盘。
            mov ax,0                    
            mov es,ax                    ;让es指向中断向量表的首地址
            
            push ds:[0]                    ;将原来9号中断真正的IP入栈
            pop es:[9*4]                ;将9号中断例程真正的IP放入9号中断所对应的中断向量表中记录IP的字中
            push ds:[2]                    ;将原来9号中断真正的CS入栈
            pop es:[9*4+2]                ;将9号中断例程真正的CS放入9号中断所对应的中断向量表中记录CS的字中
            
            mov ax,4c00h                ;程序触发4C号中断,返回值为0
            int 21h

;-------------模拟Sleep函数------------        
    delay:
            ;让CPU做很多次循环,以此模拟Sleep函数,这种做法很霸道,以C++语言的描述方式如下:
                                        ;for(int i = 0; i < (dx); i++)
                                        ;    for(int j = 0; j < FFFFH; j++);
                                        ;
                                        
            push ax                        ;ax和bx要用,先入栈保存
            push dx
            mov dx,2000h                ;初始化dx为2000H
            mov ax,0                    ;初始化ax为0
    s1:
            sub ax,1                    ;第一个执行后,ax为FFFFH,由于有借位操作,因此CF在第一次执行后为1
                                        ;在后面的执行,ax比1大,因此没有借位,因此后面直到ax位0的减法操作,CF均为0
            sbb dx,0                    ;第一次执行时,由于上面的第一次执行产生了借位,第一次执行时,(dx)=(dx)-0-CF=2000H-0H-1H=1FFF
                                        ;第二次执行时,由于dx中存储的值已经大于0了,而上面的CF又位0,所以dx的值不变
                                    
            cmp ax,0                    ;将ax和0进行比较
            jne s1                        ;如果ax不为0,则继续调到s1处,对ax减,直到ax等于后,才继续下面的处理
            cmp dx,0                    ;将bx和0进行比较
            jne s1                        ;如果dx不等于0,则调到s1处,继续处理,每次跑到这里时,ax肯定是0,只要dx不为0,都需要进行跳转
                                        ;此时,如果跳到s1处执行ax减1,则会产生借位,ax又恢复成了FFFFH,CF又恢复成了1
                                        ;那么再次执行"sbb dx,0"的时候,(dx)=(dx)-0-CF,此时dx的值又会减去1,然后继续执行
                                        ;直到新产生的ax值为0,然后又会执行到这里来。
                                        ;也就是说,dx的值初始化为2000H,dx的值每次减1的时候,程序已经进行了FFFFH次循环了
                                        ;那么程序总共就会执行2000H*FFFFH次循环,CPU也就会在这里停留很久了,这样就相当于Sleep函数了。
                                        
            pop dx                        ;循环完成后,将dx、ax的值复位
            pop ax
            ret                            ;pop IP,返回去,继续处理下一个字符,表示delay结束
            
;-------------9号中断例程子程------------
    int9:
            push ax                        ;先保存我们要用到的寄存器值
            push bx
            push es
            
            in al,60h                    ;从60H号端口中读入从键盘中输入的数据,数据占用一个字
                                        ;高位是扫描码,地位是ASCII字符,因此此处仅仅读取低位即可

            ;因为我们不能够让CPU仅仅执行我们所写的9号中断例程子程序
            ;在捕获到按键信息后,我们让CPU回到原有的9号中断例程中执行
            ;也即在新编写的9号中断子程序中调用原来的中断子程序
            ;但是原有9号中断例程的入口地址已经不再中断向量表中了
            ;它们被存放到了ds:[0]和ds:[2]处,因此我们不能够通过通过"int 9h"执行来调用9号中断程序
            ;那么此时,我们可以通过一些指令模拟"int 9h"的执行过程
            
            ;模拟第一步:取中断类型码n;
            ;取中断类型码是为了定位中断例程的入口地址,中断例程的入口地址已经知道,这个步骤不需
            
            ;模拟第二步:pushf:标志寄存器入栈
            pushf                        ;将标识寄存器入栈,保存标识寄存器
            
            ;模拟第三步:IF = 0,TF = 0,将标识寄存器的TF和IF位置0
            pushf                        ;再次入栈
            pop bx                        ;将标识寄存器出栈,放到bx中,此时bx中存放的时候标识寄存器中的值
            and bh,11111100B            ;bx中的低8位和第9位置0,表示将标识寄存器的IF和TF置0
            push bx                        ;然后再次将bx入栈
            popf                        ;将bx出栈并赋值给标识寄存器,这样我们通过栈结合bx的方式就修改了标识寄存器中的TF和IF位为0

            
            ;模拟第三步,push CS, push IP, CS、IP入栈;
            ;模拟第四步,修改CS/IP的值,(IP) = (n*4),(CS) = (n*4+2)。
            ;上面两步骤,我们可以通过call命令模拟,因为call命令在调用之前会将CS/IP入栈,然后修改CS/IP的值
            call dword ptr ds:[0]        ;因为此时ds:[0]处存放的是9号中断真正的中断处理程序
                                        ;因此在中断程序返回时,会将标识寄存器位进行还原
                                        ;此时第二步pushf的标识寄存器位就会在中断程序返回的时候进行popf
            
            ;通过上面四个步骤,我们就完成了"int 9h"的模拟,到这里,上面第二步的标识寄存器位出栈了
            ;等真正的9号中断例程子程序执行完后并返回后,继续处理我们所写的中断例程子程序。
            cmp al,1                    ;检测输入是否为Esc键,1位Esc键的键盘扫描码
            jne int9ret                    ;如果不是,则跳到int9ret,结束int 9号中断
            
            mov ax,0B800H                ;如果是Esc键,则改变当前所显示的字符的属性
            mov es,ax
            inc byte ptr es:[160*12 + 40*2 +1]    ;增加属性值,就会改变对应字符的显示方式
    int9ret:
            pop es                        ;字符属性显示完成后,应该还原寄存值值
            pop bx
            pop ax
            iret                        ;返回int 9号中断例程?
                                        ;iret指令会将标识寄存器还原(该标识寄存器是在int9标号的第一个pushf指令入栈)
code ends
end start


;第16章
;将data 段中a标号处的8 个数据累加,结果存储到b标号处的字中。
assume cs:code
assume ds:data                    ;如果想在代码段中,直接用数据标号访问数据,
                                ;则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。
                                ;否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。

                                ;我们要在代码段code中用data段中的数据标号a、b 访问数据,
                                ;则必须用assume将一个寄存器和data段相联。在程序中,如果我们用ds寄存器和data 段相联,
                                ;则编译器对相关指令的编译如下:
                                ;指令:mov al,a[si] 编译为:mov al,[si+0]
                                ;指令:add b,ax 编译为:add [8],ax
data segment
    a    db 1,2,3,4,5,6,7,8        ;a为data段中的一个数据标号,数据标号后面没有冒号
                                ;数据标号既可以表示数据的地址,又可以表示数据的长度
    b    dw 0                    ;同a标号一样,b也是data段中的一个数据标号
data ends

code segment
    start:
            mov ax,data
            mov ds,ax
            mov si,0            ;ds:[si]指向a标号的数据
            mov cx,8
    s:
            mov al,a[si]        ;相当于 mov al,ds:[si]
            mov ah,0
            add b,ax            ;相当于 add word ptr ds:[8],ax
            inc si
            loop s
            
            mov ax,4c00h
            int 21h
code ends
end start



;可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。
data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw a,b                ;数据标号c处存储的两个字型数据为标号a、b 的偏移地址。
data ends

;相当于:
    data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw offset a, offset b
data ends

data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dd a,b                ;数据标号c处存储的两个双字型数据为标号a的偏移地址和段地址、标号b的偏移地址和段地址。
data ends

;相当于:
data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw offset a, seg a, offset b, seg b     ;seg操作符,功能为取得某一标号的段地址。
data ends


;通过查表法,以十六进制的形式在屏幕中间显示给定的byte型数据。
assume cs:code
code segment
    start:
            mov al,0eh                            ;需要显示的byte字节型数据,在屏幕上将其显示成:'0E'字符串
            call showbyte
            
            mov ax,4c00h
            int 21h
            
    showbyte:
            jmp short show
            table db '0123456789ABCDEF'            ;定义0~15的字符映射表
    show:
            push bx                                ;将后面需要用到的寄存器压栈
            push es
            
            mov ah,al
            shr ah,1                            ;如果移动位数大于1时,必须将移动位数放在cl中
            shr ah,1
            shr ah,1
            shr ah,1                            ;ah中仅仅保留原来字节数据的高四位
            and al,00001111b                    ;al中仅仅保留原来字节数据的低四位
            
            mov bl,ah
            mov bh,0
            mov ah,table[bx]                    ;通过ah中的数字,查表得到ah数字对应的ASCII字符
            
            mov bl,al
            mov bh,0
            mov al,table[bx]                    ;通过al中的数字,查表得到al数字对应的ASCII字符
            
            mov bx,0b800h
            mov es,bx
                                                ;由于现实一个字符需要两个字节
            mov es:[160*12+40*2],ah                ;将byte数据的高四位对应的ASCII字符显示在屏幕的前面
            mov es:[160*12+40*2+2],al            ;将byte数据的低四位对应的ASCII字符显示在屏幕的后面
            
            pop es
            pop bx
            ret
code ends
end start        


;编写一个子程序,计算sin(x),x∈{0°,30°,60°,90°,120°,150°,180°},并在屏幕中间显示计算结果。
;比如sin(30)的结果显示为"0.5"。

;用查表发进行计算,先将结果以字符串的方式存储起来
assume cs:code

code segment
    start:
            mov al,60                                    ;计算sin(60°)
            call showsin
            
            mov ax,4c00h
            int 21h
            
    showsin:
            jmp short show
            table dw ag0,ag30,ag60,ag90,ag120,ag180        ;offset ag0,offset ag30,...,offset ag180,每个偏移地址占用16bit
            ag0        db '0',0                            ;sin(0°)的结果字符串
            ag30    db '0,5',0                            ;sin(30°)的结果字符串
            ag60     db '0.866',0                        ;sin(60°)的结果字符串
            ag90    db '1',0                            ;sin(90°)的结果字符串
            ag120    db '0.866',0                        ;sin(120°)的结果字符串
            ag150    db '0.5',0                            ;sin(150°)的结果字符串
            ag180    db '0',0                            ;sin(180°)的结果字符串
    show:
            push bx
            push es
            push si
            
            mov bx,0b800h
            mov es,bx
            
            ;角度除以30作为索引table中的项值
            mov ah,0            ;al中存放角度值,将ah清零,ax用于存放角度值
            mov bl,30            ;角度值除以30就可以得到索引下标
            div bl                ;商,也即索引下标,放在al中,余数放到ah中
            mov bl,al
            mov bh,0            ;bx中存放索引下标
            add bx,bx            ;ag0、ag30...、ag180位字形数据,每个值占用一个word
            mov bx,table[bx]    ;通过bx在table中索引字符串的偏移地址
            
    shows:
            mov ah,cs:[bx]        ;显示一个以0位结尾的字符串
            cmp ah,0
            je showret
            mov es:[si],ah
            inc bx
            add si,2
            jmp shows
            
    showret:
            pop si
            pop es
            pop bx
            ret
code ends
end start


;实现一个子程序setscreen ,为显示输出提供如下功能:
;1、清屏。
;2、设置前景色。
;3、设置背景色。
;4、向上滚动一行

;入口参数说明:
;1、用ah 寄存器传递功能号:
;    0 表示清屏,
;    1 表示设置前景色,
;    2 表示设置背景色,
;    3 表示向上滚动一行;

;2、对于2、3号功能,用al传送颜色值,(al)∈{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }

;清屏:将显存中当前屏幕中的字符设为空格符;
;设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位;
;设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位;
;向上滚动一行:依次将第n+1行的内容复制到第n行处:最后一行为空。

assume cs:setscreen
assume ss:stack

stack segment
    dw 8 dup(0)
stack ends

setscreen segment
    start:
            mov ax,stack                    ;初始化堆栈,让sp指向栈顶
            mov ss,ax
            mov sp,16
        
            jmp short set
    table:
            dw sub1,sub2,sub3,sub4            ;定义程序入口地址的直接地址表
                                            ;每个地址占用一个字,表示程序入口的偏移地址
    set:
            push bx
            cmp ah,3
            ja sret                            ;比较ah是否大于3
            
            mov bl,ah
            mov bh,0
            add bx,bx                        ;获取程序入口偏移地址
            call word ptr table[bx]            ;调用子程序
    sret:
            pop bx
            ret
    
    ;将屏幕清屏
    sub1:
            push bx
            push cx
            push es
            mov bx,0b800h
            mov es,bx
            mov bx,0
            mov cx,2000                        ;清2000个字
    sub1s:
            mov byte ptr es:[bx],' '        ;将屏幕中存放ASCII的字节填充为空格字符
            add bx,2
            loop sub1s
            
            pop es
            pop cx
            pop bx
            ret
    
    ;设置屏幕前景色
    sub2:
            push bx
            push cx
            push es
            
            mov bx,0b800h
            mov es,bx
            mov bx,1
            mov cx,2000                        ;设置2000个字的颜色属性
    sub2s:
            and byte ptr es:[bx],11111000b    ;前景色处于奇地址的属性字节的第0、1、2位;
            or es:[bx],al                    ;al最大值位7,因此最多占用3个位,这几or即可写入前景色    
            add bx,2
            loop sub2s
            
            pop es
            pop cx
            pop bx
            ret
            
    ;设置屏幕背景色    
    sub3:
            push bx
            push cx
            push es
            
            mov cl,4
            shl al,cl                        ;al最大值位7,因此最多占用3个位,向左移懂4位
                                            ;表示用al中的4、5、6位存储前景色值
                                            
            
            mov bx,0b800h
            mov es,bx
            mov bx,1
            mov cx,2000                        ;设置2000个字的颜色属性
    sub3s:
            and byte ptr es:[bx],10001111b
            or es:[bx],al                    ;通过or操作将前景色属性写入屏幕缓存
            add bx,2
            loop sub3s
            
            pop es
            pop cx
            pop bx
            ret
    
    ;向上滚动一行
    sub4:
            push cx
            push si
            push di
            push es
            push ds
            
            mov si,0b800h
            mov es,si
            mov ds,si
            mov si,160
            mov di,0                            ;ds:[si]表示屏幕的n+1行,es:[di]表示屏幕的n行
            cld                                    ;将DF设置为0,表示每复制一个字节后,将si和di加1,继续复制下一个字节
            mov cx,24                            ;复制24行,因为一个屏幕占用24行
    sub4s:
            push cx
            mov cx,160                            ;一行占用160个字节
            rep movsb                            ;按照字节移动的方式移动160个字节,DF=0,表示向前复制
                                                ;将ds:[si]中存储的字以字节为单位传送到es:[di]中
            pop cx
            loop sub4s
            
            mov cx,80                            ;最后一行又80个字,每个字可以存放一个字符
            mov si,0
    sub4s1:
            mov byte ptr ds:[160*24 + si],' '   ;将最后一行数据所占用的80个字中存放ASCII字符的字节设置为空格字符
            add si,2
            loop sub4s1
            
            pop ds
            pop es
            pop di
            pop si
            pop cx
            ret
            
setscreen ends
end start

;第17章

;调用16h号中断的0号子程序,从键盘缓冲区中读取一个键盘输入,并且将其从缓冲区中删除:
mov ah 0
int 16h
;结果:
;(ah)=扫描码,
;(al)=ASCII码。


;编程,接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色
;输入“g”,将屏幕上的字符设置为绿色
;输入“b”,将屏幕上的字符设置为蓝色

;可以调用int 16h从键盘缓冲区中读取键盘的输入。

assume cs:code

code segment
    start:
            mov ah,0
            int 16h
            
            mov ah,1
            cmp al,'r'
            je red
            cmp al,'g'
            je green
            cmp al,'b'
            je blue
            jmp short sret    
    red:
            shl ah,1
    green:
            shl ah,1
    blue:
            mov bx,0b800h
            mov es,ax
            mov bx,1
            mov cx,2000
    s:
            and byte ptr es:[bx],11111000b
            or es:[bx],ah
            add bx,2
            loop s
    sret:
            mov ax,4c00h
            int 21h            
code ends
end start



;编写一个接收字符串的输入子程序,实现如下功能:
;在输入的同时需要显示这个字符串;
;一般在输入回车符后,字符串输入结束;
;能够删除已经输入的字符。
;子程序的参数如下:
;(dh)、(dl)=字符串在屏幕上显示的行、列位置;
;ds:si 指向字符串的存储空间,字符串以0为结尾符。

assume cs:code
assume ss:stack
assume ds:data

stack segment
    dw 16 dup(0)
stack ends

data segment
    dd 64 dup(0)
data ends

code segment
    start:
        mov ax,stack
        mov ss,ax
        mov sp,32                    ;ss:[sp]指向栈顶
        
        mov ax,data
        mov ds,ax
        mov si,0                    ;将从键盘缓冲区中读出的字符存放到ds所在的段空间中
                                    ;段的操作子程序为charstack
        
        call getstr

    return:
        mov ax,4c00h
        int 21

    getstr:
        push ax
        
    getstrs:
        mov ah,0
        int 16h                        ;通过16h号中断的0号子程序,从键盘缓冲区中读取一个字单元数据到ax中
                                    ;其中ah中存放的是扫描码,al中存放的是字符的ASCII码
        
        cmp al,20h                    ;20h为空格符的ASCII码,如果小于20h的话,则输入的不是字符
        jb nochar
        mov ah,0                    ;ah设置为0,表示将输入的字符通过调用子程序入栈
        call charstack                ;调用charstack,将输入的字符入栈
        mov ah,2                    ;ah设置为2,表示将输入的字符显示到由dh、dl指定行列的屏幕位置
        call charstack                ;调用charstack,显示字符
        jmp getstrs                    ;字符显示完成,继续触发int 16h中断例程,获取字符输入

    nochar:
        cmp ah,0eh                    ;ax的高8位为0eh,代表输入的是退格键的扫描码
        je backspace                ;调用退格键,删除字符
        cmp ah,1ch                    ;ax的高8位为1ch,代表输入的是回车键的扫描码
        je enter                    ;调用enter子程序,结束字符的输入,并在字符串末尾添加'\0'字符
        jmp getstrs                    ;处理完回车和退格后,继续获取字符串
        
    backspace:
        mov ah,1                    ;ah设置为1,表示将字输入的字符通过调用子程序出栈
        call charstack                ;调用charstack,将输入的字符出栈
        mov ah,2                    ;ah设置为2,表示在出栈后,将栈中剩余的字符显示在屏幕上
        call charstack                ;调用charstack,将栈中剩余的字符显示在屏幕上
        jmp getstrs                    ;显示完成后,继续获取字符串
        
    enter:
        mov ax,0                    ;回车键,将0入栈,为字符串末尾添加'\0',表示字符串的结尾
        call charstack                ;将0入栈
        mov ah,2
        call charstack                ;显示栈中以0为结尾的字符
        
        pop ax                        ;回车结束后,表示字符串输入完成,返回
        ret

    ;字符栈的入栈、出栈和显示。
    ;参数说明:
    ;(ah)=功能号,0表示入栈,1表示出栈,2表示显示;
    ;ds : si 指向字符栈空间;
    ;对于0 号功能:(al)=入栈字符;
    ;对于1 号功能:(al)=返回的字符;
    ;对于2 号功能:(dh)、(dl) =字符串在屏幕上显示的行、列位置。
    charstack:
        jmp short charstart
        table dw charpush,charpop,charshow    ;通过直接地址定位表的方式,将子程序的入口偏移地址放入表中
        top dw 0                            ;top中存放一个字,该字中的内容表示栈中元素的个数
        
    charstart:
        push bx
        push dx
        push di
        push es
        
        cmp ah,2
        ja sret                                ;ah大于2,输入的功能号不合法,直接返回
        mov bl,ah
        mov bh,0
        add bx,bx                            ;通过bx定位到子程序的入口偏移地址
        jmp word ptr table[bx]                ;调用子程序,处理字符串
        
    charpush:
        mov bx,top
        mov ds:[si][bx],al                    ;将从键盘缓冲区中读取到的字符的ASCII码存放到ds:[si][bx]字的低地址上
        inc top                                ;存放完成后,表示字符入栈成功,将top所在的字加1,代表栈中多了一个字符
        jmp sret                            ;入栈成功后,子程序返回,继续接收下一个ah参数,处理字符串
        
    charpop:
        cmp top,0
        je sret                                ;pop的时候,如果栈中没有字符,则直接返回
        dec top                                ;将pop所在的字减1,表示从栈中pop一个字符
        mov bx,top
        mov al,ds:[si][bx]                    ;将从栈中ds:[si][bx]低8位pop出的字符存放到al中
        jmp sret                            ;pop完成后,子程序返回,继续接收下一个ah参数,处理字符串
        
    charshow:                                ;(dh)、(dl)=字符串在屏幕上显示的行、列位置;
        mov bx,0b800h
        mov es,bx
        mov al,160                            ;屏幕一行占用160个字节
        mov ah,0
        
        mul dh                                ;dh为行数,mul乘法运算,如果都是8位:一个默认放到AL中,
                                            ;另一个放在8位寄存器或者内存的字节单元中,此时的结果存放到AX中。
        
        mov di,ax                            ;经过乘法运算后,ax中存放屏幕第dh行的行首偏移地址
        add dl,dl                            ;屏幕一列占用2个字节,一个字,add dl dl,定位到屏幕列的偏移地址
        mov dh,0
        add di,dx                            ;经过将ax和dx相加就得到指定行列的偏移地址
        mov bx,0                            ;初始化bx为0,表示从ds:[si][0]处显示字符
    charshows:
        cmp bx,top                            ;当bx等于top的时候,说明ds段中存储的字符已经显示完成了
        jne noempty                            ;如果ds段中还有字符没有显示,则调用noempty子程序显示字符
        mov byte ptr es:[di],' '            ;如果ds段中所有的字符已经显示完成,在字符串末尾显示空格
        jmp sret                            ;ds段中所有字符显示完成,子程序返回
        
    noempty:
        mov al,ds:[si][bx]                    ;显示字符,将需要显示的字符存放到al中
        mov es:[di],al                        ;将字符写入显存中
        mov byte ptr es:[di+2],' '            ;继续向显存中写入空格
        inc bx                                ;显示下一个字符
        add di,2                            ;指定下一个即将要是字符的位置
        jmp charshows
        
    sret:
        pop es
        pop di
        pop dx
        pop bx
        ret                                    ;charstack返回
code ends
end start


;读取0面0道1扇区的内容到0:200
;返回参
;操作成功:(ah)=0,(al)=读入的扇区数
;操作失败:(ah)=出错代码
assume cs:code
code segment
    start:

        mov ax,0
        mov es,ax
        mov bx,200h        ;es:bx指向接收此扇区读入数据的内存区

        mov al,1        ;读取的扇区数
        mov ch,0        ;磁道号
        mov cl,1        ;扇区号
        mov dl,0        ;磁头号,对于软驱即面号,因为一个面用一个磁头来读写
        mov dh,0        ;驱动器号,软驱从0开始,0:软驱A,1:软驱B;硬盘从80h开始,80h:硬盘C,81h:硬盘D。
        mov ah,2        ;int 13h的功能号(2表示读扇区)
        int 13h

        mov ax,4c00h
        int 21h
code ends
end start

;将0:200中的内容写入0面0道1扇区
;返回参
;操作成功:(ah)=0,(al)=写入的扇区数
;操作失败:(ah)=
assume cs:code
code segment
    start:

        mov ax,0
        mov es,ax
        mov bx,200h        ;es:bx指向将写入磁盘的数据

        mov al,1        ;写入的扇区数
        mov ch,0        ;磁道号
        mov cl,1        ;扇区号
        mov dl,0        ;磁头号(面)
        mov dh,0        ;驱动器号,软驱从0开始,0:软驱A,1:软驱B;硬盘从80h开始,80h:硬盘C,81h:硬盘D。
        mov ah,3        ;int 13h的功能号(3表示写扇)
        int 13h

        mov ax,4c00h
        int 21h
code ends
end start


;将当前屏幕的内容保存在磁盘上
;1屏的内容占4000个字节,需要8个扇区,我们用0面0道的1~8扇区存储显存中的内容。
assume cs:code
code segment
    start:
        mov ax,08b00h
        mov es,ax
        mov bx,0        ;es:bx指向当前屏幕显存页面的其实地址
        
        mov al,8        ;写入的扇区数
        mov ch,0        ;磁道号
        mov cl,1        ;扇区号
        mov dl,0        ;磁头号(面)
        mov dh,0        ;驱动器号,软驱从0开始,0:软驱A,1:软驱B;硬盘从80h开始,80h:硬盘C,81h:硬盘D。
        mov ah,3        ;int 13h的功能号(3表示写扇)
        int 16h
        
        mov ax,4c00h
        int 21h
code ends
end start

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值