吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 1193|回复: 4

[汇编] 8086汇编-子程序设计

[复制链接]
啥都不会 发表于 2022-9-25 16:28 | 显示全部楼层 |阅读模式

前言:
这篇笔记主要学习8086汇编子程序设计。参考自 "杨季文《80x86汇编语言程序设计》"

编程语言:
8086汇编

以下为主题内容:

子程序设计

当某个程序片段需要反复使用、或是具有通用性可以在多个程序中使用,我们就需要把这段代码设计为子程序。这样能用小缩短程序地长度,节约存储空间,减少程序设计地工作量。此外当某个程序片段地功能相对独立时,可以把它设计成子程序,这样便于模块化,也比那与程序地阅读、调试和修改。


过程调用和返回指令
  • 过程调用(call)指令和过程返回(ret)指令属于程序控制指令。通常,过程调用指令用于由主程序转子程序,过程返回指令用于由子程序返回主程序。
  • 过程调用指令,可以像无条件跳转指令一样进行跳转,且过程调用指令有段内调用和段间调用之分。与之相对应的。过程返回指令也有段内跳转和段间跳转。
  • 段内调用和段内返回称为近调用和近返回,段间调用和段间返回称为远调用和远返回
  • 在汇编语言中,过程也有远近之分。


1. 过程调用指令
  • 过程调用指令,首先把子程序的返回地址压入堆栈,以便执行完子程序后返回调用程序(主程序)继续往下执行。
  • 按照转移目标是否是同一段来分,地址用指令分为段内调用和段间调用。
  • 按照获得转移目标地址的方式来分,调用指令分为直接调用和间接调用。
  • 过程调用指令不影响标志位。


(1)段内直接调用
段内直接调用指令用于调用当前段内的子程序,格式如下:
call 过程名(标号)


例如:
[Asm] 纯文本查看 复制代码
call sub1
;这条指令相当于
;push "call sub1的下一条指令的偏移地址"
;jmp sub1


具体的操作分解如下:
sp <= sp-2  ;提高栈顶
[sp] <= IP  ;call xxx的下一条指令的偏移地址。
IP <= IP+disp

  • call指令所在的偏移地址是,子程序的起始地址;
  • call 下面一条指令的偏移地址是,子程序的结束地址;
  • disp=结束地址-起始地址。
  • 段内直接调用指令中,是以一个字表示disp,所以转移范围在 -32768~+32767 之间。



总结验证

起始地址:076B:0008
结束地址:076B:000B
disp=3

185186188-38f1f346-d227-4bd1-acae-609c3afceecc.png
185191116-6761fb8c-f7b9-47ea-8c2d-786ff8b1e779.png

从上图中可以看到,call 指令执行前 sp=0000,指令执行之后 sp=sp-2=FFFE。内存中存储偏移地址 000B=起始IP + disp=0008+3。还需要注意一下,这里 IP寄存器 修改成了 0016

(2)段间直接调用
段间直接调用,用于调用其他代码段中的子程序。格式如下:
call 过程名(标号)

例如:
[Asm] 纯文本查看 复制代码
call far ptr SUBRO
call SUBF


该指令把返回地址的段值压入堆栈,再把返回地址的偏移压入堆栈,达到保存返回地址的目的。

具体操作分解如下:
sp <= sp-2
[sp] <= cs
sp <= sp-2
[sp] <= IP
IP <= 子程序的偏移地址
CS <= 子程序的代码段地址

总结验证
185669832-98db5bd0-7e7e-4993-a483-7a1f94cd2675.png
可以看到,远跳转的 call 指令,后面的地址是 "CS:IP"。堆栈中存放的也是 "CS:IP"

(3)段间间接调用
段间间接调用指令也用于调用其他代码段中的子程序。格式如下:
[Asm] 纯文本查看 复制代码
call OPRD
;OPRD 是双字存储器操作数


具体操作分解如下:
sp <= sp-2
[sp] <= cs
sp <= sp-2
[sp] <= IP
IP <= OPRD 的低字
CS <= OPRD 的高字

例如:


[Asm] 纯文本查看 复制代码
call DWORD PTR [bx]
call VARD


总结验证

185675073-0a852913-257f-4792-9f1a-096527503f88.png
可以看到,变量类型只要是 dd 就行,当编译程序时会自动加上 far 属性。

2. 过程返回指令
过程返回指令把子程序的返回地址从堆栈弹出到 “IP” 或 “CS:IP”,从而返回到主程序继续我往下执行。过程返回指令不影响标志位。


(1)段内返回指令
指令格式如下:
[Asm] 纯文本查看 复制代码
ret


该指令完成的具体操作如下所示:

IP <= [SP]
SP <= SP+2

总结验证
185675073-0a852913-257f-4792-9f1a-096527503f88.png

(2)段间返回指令
指令格式如下:
[Asm] 纯文本查看 复制代码
ret



该指令完成的具体操作如下所示:
IP <= [SP]
SP <= SP+2
CS <= [SP]
SP <= SP+2

总结验证
185728900-68aa2cac-c703-4721-9b53-914129b5cbe4.png
如上图所示,编译器自动把 RET 编译成另一个段间返回指令 RETF 。

段间返回指令 RETF
185730402-94d30c58-d64b-4b24-a584-e6e96d839aca.png
185730489-4963f55f-02d5-4fc3-818d-23462fd285f1.png

如上图所示,段内 call 指令调用子程序,但使用的是 RETF 返回,导致程序返回出错。所以无论 RETF 出现在远过程还是近过程中,编译器总是会把它编译成段间返回指令。

(3)返回指令+立即数

指令格式如下:
[Asm] 纯文本查看 复制代码
ret 表达式

注意:汇编程序会把表达式的结果取整。


指令执行流程
该指令会先弹出 “一个字” 或是 “双字” 作为返回地址,再根据 data 修改堆栈指针。

185730918-37368f3d-b1d0-4b06-9507-2eb56ba9ac77.png
如上图所示
[Asm] 纯文本查看 复制代码
ret 4 
把 SP 寄存器改为了 4。

过程定义语句
过程定义语句(子程序定义语句),可把子程序起名,并且定义近类型或远类型。过程定义语句的格式如下:


[Asm] 纯文本查看 复制代码
过程名 PROC [NEAR | FAR]
    .
    .
    .
过程名  ENDP


过程定义语句中,过程名必须要一致。现阶段程序的代码比较少,基本都使用 NEAR 属性。后面程序的代码多的时候,使用 FAR 属性会比较方便。

范例
把一个十六进制数转换为对应 ASCII 码


程序逻辑结构图
185746886-6cc547a1-ef79-4ad7-8d29-aa358bfab30f.png

代码
[Asm] 纯文本查看 复制代码
;程序名:HTOASC.asm
;功能:实现把一个十六进制数转换为对应的 ASCII 码的程序。
;方法一:分开计算,数字+30h,字母+37h
assume ds:data,cs:code

data segment
number db 0Fh
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov al,number
    call HTOASC
    mov dl,al
    mov ah,2
    int 21h
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp return
num_add:
    add al,30h
return:
    popf
    ret
HTOASC endp
code ends
end start


子程序举例
例1
把用ASCII码表示的两位十进制数,转换为对应的十进制数的子程序。设X为十位数,Y为个位数

程序流程图
185761096-f8bf1c03-bffb-42c8-9b11-4a0e3d4c72d8.png

代码
[Asm] 纯文本查看 复制代码
;程序名:T4-1.asm
;作者:啥都不会
;创建日期:2022/3/14
;修改日期:                    修改者:
;子程序名:subr
;程序的描述:把用ASCII码表示的两位十进制数,转换为对应的十进制数的子程序。设X为十位数,Y为个位数
;算法:10X+y
;=============================================================================================

assume cs:code,ds:data

data segment
    num db 31h,30h
    val db ?
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov dh,num[0]
    mov dl,num[1]
    call subr
    mov val,al
stop:
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
subr PROC
;
; 功能:两位ASCII码组成的十进制数,转换成十进制数
; 传参方式:寄存器传参
; 入口参数:dh高位、dl低位
; 出口参数:al
;-----------------------------------------------------
    mov al,dh
    and al,0fh
    mov ah,10
    mul ah
    mov ah,dl
    and ah,0fh
    add al,ah
    ret
subr endp
code ends
end start


例2
程序流程图
1. 2-1
写一个把一个字大小的16进制数,转换成4个ASCII码的子程序
185780562-ea3e4e38-d01a-44bf-96f7-5be109327354.png

2. 2-2
利用子程序 HTASCS 按十六进制数形式显示地址为 F000:0000H 的字单元内容
185782043-520cab69-a4f3-40b0-935d-825e6b3ff03a.png

代码
1. T4-2-1.asm
[Asm] 纯文本查看 复制代码
;程序名:T4-2-1.asm
;作者:啥都不会
;创建日期:2022/3/14
;修改日期:                    修改者:
;子程序名:htascs
;程序的描述:写一个把一个字大小的16进制数,转换成4个ASCII码
;算法:把16进制数向左循环移位4次,使高四位变为低四位,
;析出低四位调用子程序 htoasc 转换 1 位十六进制 ASCII 码,循环四次
;优化:直接循环输出结果
;=============================================================================================
assume cs:code,ds:data

data segment
num dw 1234h
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov bx,num
    call htascs
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
htascs PROC
;
; 功能:把一个字大小的16进制数,转换成4个ASCII码
; 传参方式:寄存器传参
; 入口参数:BX数值
; 出口参数:无
; 说    明:调用子函数 htascs
;-----------------------------------------------------
    mov cx,4
htascs1:
    ROL BX,1
    ROL BX,1
    ROL BX,1
    ROL BX,1
    mov al,bl
    call HTOASC
    mov dl,al
    mov ah,2
    int 21h
    loop htascs1
    ret
htascs endp

;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp return
num_add:
    add al,30h
return:
    popf
    ret
HTOASC endp
code ends
end start



2. T4-2-2.asm
[Asm] 纯文本查看 复制代码
;程序名:T4-2-2.asm
;利用子程序 HTASCS 按十六进制数形式显示地址为 F000:0000H 的字单元内容
assume cs:code

code segment
start:
    mov ax,0f000h
    mov ds,ax
    ;
    mov bx,[si]
    call HTASCS
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
HTASCS PROC
;
; 功能:把一个字大小的16进制数,转换成4个ASCII码
; 传参方式:寄存器传参
; 入口参数:dx
; 出口参数:DS:BX存储所得ASCII码串的缓冲区首地址
;-----------------------------------------------------
    mov cx,4
HTASCS1:
    ROL BX,1
    ROL BX,1
    ROL BX,1
    ROL BX,1
    mov al,dl
    call HTOASC
    mov dl,al
    mov ah,2
    int 21h
    loop HTASCS1
    ret
HTASCS endp
;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp pop_stack
num_add:
    add al,30h
pop_stack:
    popf
    ret
HTOASC endp
code ends
end start


例3
程序流程图


1. T4-3-1
把T3-11.asm改写成子程序,写一个把16位二进制数转换为5位十进制数ASCII码的子程序,为了简单,设二进制数为无符号数。
例3的逻辑结构和 T3-11-2 是一样的,这里就不再画了

2. T4-3-2
把8位二进制数转换为2位十六进制数的 ASCII 码
![T4-3-2]( 185801085-a7b55c88-f30c-4ab0-a400-0d8884c65f6c.png )

代码
1. T4-3-1
[Asm] 纯文本查看 复制代码
;程序名:T4-3-1.asm
;功能:写一个把16位二进制数转换为5位十进制数ASCII码的子程序,为了简单二进制数为无符号数。把T3-11.asm改写成子程序。

assume cs:code,ds:data

data segment
number dw 0AECFh
result db 0,0,0,0,0,'$'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,number
    lea si,result
    call BTOASC
    lea dx,[si]
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
BTOASC PROC
;
; 功能:把16位二进制数转换为5位十进制数
; 传参方式:寄存器传参
; 入口参数:AX=要转换的值、SI=存储结果的缓冲区首地址
; 出口参数:DS:SI存储所得ASCII码串的缓冲区首地址
;-----------------------------------------------------
    mov di,5
    mov cx,10
L1:
    xor dx,dx
    div cx
    add dl,30h
    mov result[di-1],dl     ;用di判断循环,但是存储结果di需要减1
    dec di
    jnz L1
    ;函数返回
    ret
BTOASC endp
code ends
end start



2. T4-3-2

[Asm] 纯文本查看 复制代码
;程序名:T4-3-2.asm
;功能:把8位二进制数转换为2位十六进制数的 ASCII 码
assume cs:code,ds:data
data segment
char db 0abh
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov al,char
    call WHTOASC
    ;
    mov cx,2
    mov dx,ax
L1:
    xchg dh,dl
    mov ah,2
    int 21h
    loop L1
    ;
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
WHTOASC PROC
;
; 功能:把8位二进制数转换为2位 ASCII 码
; 传参方式:寄存器传参
; 入口参数:AL=要转换的值
; 出口参数:AH = 十六进制数高位的 ASCII 码
;           AL = 十六进制数低位的 ASCII 码
;其他说明:(1)近过程
;          (2)除 AX 寄存器外,不影响其他寄存器
;          (3)调用HTOASC 实现十六进制数到ASCII码的转换
;-----------------------------------------------------
    mov ah,al
    shr al,1
    shr al,1
    shr al,1
    shr al,1
    call HTOASC
    xchg ah,al
    call HTOASC
    ret
WHTOASC endp
;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一位十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp pop_stack
num_add:
    add al,30h
pop_stack:
    popf
    ret
HTOASC endp

code ends
end start


寄存器的保护与恢复
子程序为了完成其功能,通常需要使用一些寄存器或存储单元存放内容。也就是说,子程序运行时通常会破坏寄存器的数据。所以在调用子程序时,我们还需要将,可能破坏的寄存器数据进行保护与恢复。

寄存器保护与恢复的方法:
在子程序一开始就把子程序中要改变的寄存器内容压入堆栈,在返回之前再恢复这些寄存器的内容。如下图所示:
192127943-a0efabbb-2b22-4513-8fb9-46a9e6db433b.png

同时还要注意:子程序运行时,会影响到标志寄存器。因此有时我们还需要保护标志寄存器。指令如下:
pushf(保护标志寄存器)
popf (恢复标志寄存器)。



主程序与子程序之间的参数传递
主程序在调用子程序时,通常需要向子程序传递参数(类似函数的参数);同样,子程序运行后也经常要把一些结果参数传给主程序(类似函数的返回值)。主程序与子程序之间的这种信息传递称为参数传递。
1. 由主程序传递给子程序的参数称为子程序的入口参数
2. 由子程序传给主程序的参数称为子程序的出口参数
3. 子程序可以有入口参数,有出口参数;有入口参数,无出口参数;无入口参数,有出口参数

参数传递的方法分别有:寄存器传递法、内存单元传递法、堆栈传递法和call 后续区传递法等。
说到底,只要子程序能定位到参数在哪,就可以用这种方法传递参数。以上这些方法的区别在于,使用寄存器传递法的子程序,比使用其他参数方法的速度要快。


寄存器参数传递法
利用寄存器传递参数就是把参数放在约定的寄存器中。
优点:实现简单、调用方便。
缺点:因为寄存器个数有限,且寄存器还要存放其他数据,所以只适用于传递参数较少的情况

例1


程序流程图
1. T4-4
写一个 把大写字母改为小写字母的子程序。
186200621-5dcdb8cd-085c-423d-9f69-e1f99a3e6d3f.png

2. T4-4-1
写一个把大写字母的字符串更改为小写字母的子程序。
186224985-1309de5b-f606-4c1d-a986-d17ebc60e100.png

代码
1. T4-4.asm

[Asm] 纯文本查看 复制代码
;程序名:T4-5.asm
;功能:写一个把一个大写字母更改为小写字母的子程序。
;子程序名:UPTOLW
assume cs:code,ds:data

data segment
char db 'H'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov al,char
    call UPTOLW
    ;
    mov dl,al
    mov ah,2
    int 21h
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
UPTOLW PROC
;
; 功能:把一个大写字母更改为小写字母,其他字符不变
; 传参方式:寄存器传参
; 入口参数:AL=字符ASCII码
; 出口参数:AL=字符ASCII码
;-----------------------------------------------------
    pushf
    cmp al,'A'
    jb UPTOLW1
    cmp al,'Z'
    JA UPTOLW1
    add al,'a'-'A'
    ;
UPTOLW1:
    popf
    ret
UPTOLW endp
code ends
end start



2. T4-4-1.asm

[Asm] 纯文本查看 复制代码
;程序名:T4-4-1.asm
;功能:写一个把大写字母的字符串更改为小写字母的子程序。
;子程序名:UPTOLWS
assume cs:code,ds:data

data segment
char db 'HELLO',0,0dh,0ah,'$'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    lea si,char
    call UPTOLWS
    ;
    mov dx,si
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
UPTOLWS PROC
;
; 功能:把一串大写字母更改为小写字母,其他字符不变
; 传参方式:寄存器传参
; 入口参数:SI=要转换字符的首地址
; 出口参数:DS:SI存储所得ASCII码串的缓冲区首地址
;-----------------------------------------------------
    pushf
    push ax
    push cx
    push si
    ;
    xor cx,cx
L1:
    inc si
    inc cx
    mov al,byte ptr [si-1]
    cmp al,0
    jz UPTOLW_POP
    cmp al,'A'
    jb UPTOLW_POP
    cmp al,'Z'
    ja UPTOLW_POP
    ;
    add al,20h
    mov byte ptr [si-1],al
    jmp L1

UPTOLW_POP:  
    pop si
    pop cx
    pop ax
    popf
    ;
    ret
UPTOLWS endp
code ends
end start



例2
写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。

程序流程图
186377272-581b5fdc-eb04-478a-abcf-2c6ff1193cc4.png

代码
1. T4-5

[Asm] 纯文本查看 复制代码
;程序名:T4-5.asm
;功能:写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
;子程序名:ISDECM
;处理方法:循环处理字符并复制到缓冲区中。缺点:多了复制字符的步骤。优点:准确性高
assume cs:code,ds:data
data segment
string db 'H1E2L3L4L5O',0
count dw $ - 1                  ;当前的偏移地址($)-1:表示字符串 string 的长度。
result db 10 dup(0),0dh,0ah,'$'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov cx,count
L1:
    mov al,string[si]
    call ISDECM
    inc si              ;inc 指令不影响 CF 位,所以不影响程序运行结果
    jc L2
    loop L1
    jmp stop
L2:
    mov result[di],al   ;di 寄存器的默认值是0,且默认使用 ds 段寄存器
    inc di
    cmp cx,0
    jz stop
    ;dec cx
    ;jnz L1
    loop L1
stop:
    lea dx,result
    mov ah,9
    int 21h             ;输出结果
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
ISDECM PROC
;
; 功能:判别一个字符是否为数字
; 传参方式:寄存器传参
; 入口参数:AL=字符
; 出口参数:CF为0表示是数字符,否则字符是非数字符
;-----------------------------------------------------
    cmp al,'0'
    jb ISDECM1
    cmp al,'9'
    ja ISDECM1
    CLC
    ret
ISDECM1:
    STC
    ret
ISDECM endp
code ends
end start



2. 优化1
[Asm] 纯文本查看 复制代码
;程序名:T4-5-1.asm
;功能:写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
;子程序名:ISDECM
;处理方法:循环处理字符,并替换原有字符。缺点:删除数字后,原有字符串与现有字符串宽度不一样,容易出错。优点:代码简洁了一点点。
assume cs:code,ds:data

data segment
string db 'AB=C950=asd',0,0dh,0ah,'$'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov si,offset string
    mov di,si
next:
    mov al,[si]
    inc si
    or al,al
    jz ok
    call ISDECM
    jnc NEXT
    mov [di],al
    inc di
    jmp next
ok:
    mov [di],al
    ;
    lea dx,string
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h


;-----------------------------------------------------
ISDECM PROC
;
; 功能:判别一个字符是否为数字
; 传参方式:寄存器传参
; 入口参数:AL=字符
; 出口参数:CF为0表示是数字符,否则字符是非数字符
;-----------------------------------------------------
    cmp al,'0'
    jb ISDECM1
    cmp al,'9'
    ja ISDECM1
    CLC
    ret
ISDECM1:
    STC
    ret
ISDECM endp
code ends
end start



3. 优化2


[Asm] 纯文本查看 复制代码
;程序名:T4-5-2.asm
;功能:写一个删除字符串中所有非字母字符,除了空格。
;子程序名:ISDECMS
assume cs:code,ds:data

data segment
string db 'H!@EL36L0O asm',0
result db 14 dup(0),0dh,0ah,'$'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    lea si,string
    lea di,result
    call ONLYHCARS
    ;
    lea dx,result
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
ONLYHCARS PROC
;
; 功能:删除字符串中非字母的字符
; 传参方式:寄存器传参
; 入口参数:si=字符串首地址,di-存放处理好的字符串首地址
; 出口参数:di-处理好的字符串首地址
;-----------------------------------------------------
    pushf
    push bx
    push ax
    push si

    xor bx,bx
ONLYHCARS0:
    mov al,byte ptr [si]
    inc si
    cmp al,20h
    jz charmove
    ;
    cmp al,0
    jz LYHC_POP
    ;
    cmp al,'A'
    ja ONLYHCARS1
    cmp al,'a'
    ja ONLYHCARS2
    jmp ONLYHCARS0
ONLYHCARS1:
    cmp al,'Z'
    jb charmove
ONLYHCARS2:
    cmp al,'z'
    jb charmove
    jmp ONLYHCARS0
charmove:
    mov [di],al
    inc di
    inc bx
    jmp ONLYHCARS0
    sub di,bx
LYHC_POP:
    pop si
    pop ax
    pop bx
    popf
    ret
ONLYHCARS endp
code ends
end start



利用存储单元传递参数
在参数较多的情况下,可利用内存变量来传递参数。
优点:子程序要处理的数据或送出的结果都是独立的存储单元,编写子程序时不容易出错。
缺点:占用了一定的存储单元,通用性较差。
解决方法:把参数组织成一张参数表,存放在某个存储区,然后再把存储区首地址传送给子程序。


在当时内存空间是非常少的,因此当时的开发人员都非常注重内存空间的使用,尽量少使用内存。

例1
写一个实现32位数相加的子程序

程序流程图
1. T4-6
186618748-efe4db60-e7b3-4c0e-97ce-0a3f44bb1b7a.png

2. T4-6-1
186612755-d5850068-e8da-4836-841d-39283341b835.png

代码

1. T4-6

[Asm] 纯文本查看 复制代码
;程序名:T4-6.asm
;功能:实现32位数相加
;子程序名:MADD
;算法:使用顺序结构

assume cs:code,ds:data

data segment
DATA1 dd 0ABCDABCDh
DATA2 dd 0CDEFCDEFh
DATA3 dw  0,0,0,0
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    call MADD
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
MADD PROC
; 功能:32位数相加
; 传参方式:存储单元传参
; 入口参数:DATA1和DATA2分别存放要相加的32位数
; 出口参数:DATA3缓冲区存放结果
;说明: (1)32位数据的存放次序采用 “高高低低”的原则
;       (2)可能产生的进位存放在 DATA3 开始的第5个字节中。
;-----------------------------------------------------
    pushf
    push ax
    push si
    push dx
    push bx
    ;
    xor si,si
    xor dx,dx
    ;算出低16位,进位值存在bx
    mov ax,word ptr DATA1[si]
    add ax,word ptr DATA2[si]
    mov DATA3[si],ax
    ;算出高16位,进位值存在dx
    mov ax,word ptr DATA1[si+2]
    adc ax,0    ;将上一次计算的进位值加进来
    add ax,word ptr DATA2[si+2]
    adc dx,0
    mov word ptr DATA3[si+2],ax
    mov word ptr DATA3[si+4],dx
    ;
    pop bx
    pop dx
    pop si
    pop ax
    popf
    ret
MADD endp
code ends
end start


2. T4-6-1
[Asm] 纯文本查看 复制代码
;程序名:T4-6-1.asm
;功能:写一个实现32位数相加的子程序
;子程序名:MADD
;算法:使用循环结构

assume cs:code,ds:data

data segment
DATA1 dd 0ABCDABCDh
DATA2 dd 0CDEFCDEFh
DATA3 dw  0,0,0,0
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    call MADD
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
MADD PROC
; 功能:32位数相加
; 传参方式:存储单元传参
; 入口参数:DATA1和DATA2分别存放要相加的32位数
; 出口参数:DATA3缓冲区存放结果
;说明: (1)32位数据的存放次序采用 “高高低低”的原则
;       (2)可能产生的进位存放在 DATA3 开始的第5个字节中。
;-----------------------------------------------------
    pushf
    push ax
    push si
    push dx
    push bx
    ;初始化
    mov cx,2
    xor si,si
MADD1:
    ;算出低16位,进位值存在bx
    mov ax,word ptr DATA1[si]
    adc ax,word ptr DATA2[si]
    mov DATA3[si],ax
    inc si
    inc si
    loop MADD1
    ;处理进位
    mov al,0
    adc al,0
    mov byte ptr DATA3+4,AL
    ;
    pop bx
    pop dx
    pop si
    pop ax
    popf
    ret
MADD endp
code ends
end start


顺序结构和循环结构的代码行数都差不多,但顺序结构写的代码更加好一下,因为循环结构执行的代码比顺序结构执行的代码要多一些,其次顺序结构更容易理解。


例2
设计一个以ASCII码表示的十进制数字符串转换为二进制数的子程序。

程序流程图
186708338-d12ccd06-aa36-46f6-a54c-50008d350971.png

代码
[Asm] 纯文本查看 复制代码
;程序名:T4-7.asm
;功能:设计一个以ASCII码表示的十进制数字符串转换为二进制数的子程序。
;设表示十进制数不大于65535
;这个子程序有两点不足:(1)没有检查数字串中是否有非十进制数字符存在 (2)不适用于数字串表示的十进制数超过值65535的情况。
assume cs:code,ds:data

data segment
number db 5,'4','3','9','8','1'     ;数组的第一个值为要处理元素的个数
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    lea bx,number
    call DTOBIN
    mov ax,4c00h
    int 21h
    
;-----------------------------------------------------
DTOBIN PROC
; 功能:把用 ASCII 表示的十进制数字串转换为二进制。
; 传参方式:寄存器传参,利用约定存储单元传参
; 入口参数:DS:BX=缓冲区首地址
; 出口参数:AX=转换得到的二进制数
; 说明:    (1) 数字串的首地址存储的是要处理数字的个数
;-----------------------------------------------------
    push ax         ;设置初始值
    push cx
    push dx
    xor ax,ax
    mov cl,[bx]     ;将数字串的首地址,作为循环次数传入cl
    inc bx
    xor ch,ch       ;初始化ch
    jcxz DTOBIN2    ;jcxz如果cx为0就不做操作,主要是判断值是否为空
DTOBIN1:
    mov dx,10
    mul dx          ;ax*dx=ax*10
    mov dl,[bx]     ;
    inc bx
    and dl,0fh      ;取各位数的值
    ;这条指令可以去掉,因为乘数是16位,结果保存在 dx:ax 中
    ;而每次计算结果小于 FFFF 没有溢出的情况,所以每次 mul 执行后 DX 必然是 0 ,没必要再去初始化。
    ;xor dh,dh      
    add ax,dx       ;ax=ax*10+dl
    loop DTOBIN1
DTOBIN2:
    pop dx
    pop cx
    pop bx
    ret
DTOBIN endp
code ends
end start



利用堆栈传递参数
如果使用堆栈传递参数,那么主程序在调用子程序之前,把需要传递的参数依次压入堆栈,子程序从堆栈中取入口参数;如果使用堆栈传递出口参数,那么子程序在返回前,把需要返回的参数存入堆栈,主程序在堆栈中取出口参数。
优点:不占用寄存器,也无需使用额外的存储单元。
缺点:需要考虑保护寄存器

通常利用堆栈传递入口参数,利用寄存器传递出口参数

例1
写一个测量字符串长度的子程序,设字符串以 0 为结束标志。

程序流程图
187656884-ff93429e-3b46-4189-8280-792eb98dd5d2.png

代码

[Asm] 纯文本查看 复制代码
;程序名:T4-8.asm
;功能:写一个测量字符串长度的子程序,设字符串以 0 为结束标志。
;子程序名:STRLEN
assume cs:code,ds:data

data segment
string db 'Hello',0
data ends

code segment
start:
    mov ax,data
    mov ds,ax

    lea dx,string
    push ds
    push dx
    call STRLEN
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
STRLEN1:
    mov bl,es:[di]
    cmp bl,0
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
STRLEN endp
code ends
end start


利用 call 后续区传递参数
CALL 后续区是指位于CALL指令后的存储区域。主程序在调用子程序之前,把入口参数存入CALL指令后的存储单元中,子程序根据保存在堆栈中的返回地址找到入口参数。

例1

写一个把字符串中所有大写字母转换为小写字母子程序

程序结构图

187657164-07f41119-d043-499f-935a-c60b1c7b2da4.png

代码
[Asm] 纯文本查看 复制代码
;程序名:T4-9.asm
;功能:把字符串中所有大写字母转换为小写字母
;子程序名:STRLWR
assume cs:code,ds:data

data segment
string db 'HELLO','$'
data ends


code segment
start:
    mov ax,data
    mov ds,ax
    call STRLWR
    dw offset string        ;偏移地址和段地址参数都存放在代码区 call 指令后
    dw seg data
    mov dx,ax
    mov ah,9
    int 21h
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
STRLWR PROC
; 功能:把字符串中的所有大写字母改为小写字母
; 传参方式:call 后续区传递参数
; 入口参数:字符串起始地址的段值和偏移在call后续区
; 出口参数:无
;-----------------------------------------------------
    push bp
    mov bp,sp
    push si
    push ds
    mov si,[bp+2]       ;call 指令后的偏移地址
    mov ds,cs:[si+2]    ;取参数:段值
    mov si,cs:[si]      ;取参数:偏移
STRLWR1:
    mov al,[si]
    cmp al,'$'
    jz STRLWR3
    cmp al,'A'
    JB STRLWR2
    cmp AL,'Z'
    JA STRLWR2
    add AL,'a'-'A'
    mov [si],al
STRLWR2:
    inc si
    jmp STRLWR1
STRLWR3:
    add word ptr [bp+2],4   ;修改返回地址
    mov si,[bp+2]           
    mov ax,si               ;返回值
    pop ds
    pop si
    mov sp,bp
    pop bp
    ret
STRLWR endp
code ends
end start



>一般情况下不会用这种方法调用参数,容易出错。

DOS 功能调用及应用
MS-DOS 内包含了涉及设备驱动和文件管理等方面的子程序,DOS的各种命令就是通过适当地调用这些子程序实现的。DOS功能调用主要包括三方面的子恒徐:设备驱动(基本I/O)、文件管理和其他(包括内存管理、置取时间、置取中断向量、终止程序等)。

调用方法
1. 根据情况准备 DOS 功能调用所需的参数。有部分功能是不需要参数的,但大部分调用需要入口参数,在调用前应按照要求准备好入口参数
2. 把功能调用号送入 AH 寄存器
3. 发送软中断指令 "int 21h"

例1
调用 "int21" 2号功能,使喇叭发出 "嘟" 的声音。

[Asm] 纯文本查看 复制代码
;程序名:T4-10.asm
;功能:调用 "int21" 2号功能,使喇叭发出 "嘟" 的声音。
assume cs:code

code segment
start:
    mov dl,7
    mov ah,2
    int 21h
    mov ax,4c00h
    int 21h
code ends
end start


大部分功能调用都有出口参数,在调用后,可根据有关功能调用的说明取得出口参数,如2号功能。

还有个别功能很特殊,调用后就不再返回。例如 4CH 号功能就是结束程序的运行返回DOS。4CH 号功能调用有一个存放在 AL 寄存器中的入口参数,该入口参数是程序的结束码,其值大小不影响程序的结束。

例2
修改 4CH 号功能入口参数 AL 的值,查看是否会影响程序。

[Asm] 纯文本查看 复制代码
;程序名:T4-11.asm
;功能:修改 4CH 号功能入口参数 AL 的值,查看是否会影响程序。
assume cs:code

code segment
start:
    mov dl,7
    mov ah,2
    int 21h
    mov ax,4c20h
    int 21h
code ends
end start



基本的 I/O 功能调用


AH 功能 调用参数 返回参数
00 程序终止(同INT 20H)  CS=程序段前缀
01 键盘输入并回显 AL=输入字符
02 显示输出 DL=输出字符
03 异步通迅输入 AL=输入数据
04 异步通迅输出 DL=输出数据
05 打印机输出 DL=输出字符
06 直接控制台I/O DL=FF(输入)</br>DL=字符(输出) AL=输入字符
07 键盘输入(无回显) AL=输入字符
08 键盘输入(无回显)
检测Ctrl-Break
AL=输入字符
09显示字符串 DS:DX=串地址</br>'$'结束字符串
0A键盘输入到缓冲区 DS:DX=缓冲区首地址
(DS:DX)=缓冲区最大字符数
(DS:DX+1)=实际输入的字符数

"int 21h"中断还有很多功能,我们在需要用到某个功能的时候再去查,不需要去记

应用举例

例1
编写程序,从键盘接收输入字符,如果数字是 N,则响铃N次;如果不是数字,则不响铃;如果是 CTRL+C 结束程序。

程序流程图
187860263-49ea55ea-6f87-48fd-b938-5a519ff9ed11.png

代码
[Asm] 纯文本查看 复制代码
;程序名:Ring.asm
;功能:编写程序,从键盘接收输入字符,如果数字是 N,则响铃N次;如果不是数字,则不响铃;如果是 CTRL+C 结束程序。
assume cs:code,ds:data

data segment
CTRL_C equ 3
Return db 0dh,0ah,'$'
DATA1 db 'please input number 1-9,quit with ctrl-c',0dh,0ah,'$'
ERROR db 'wrong number,please input again or with ctrl-c',0dh,0ah,'$'
data ends


code segment
start:
    mov ax,data
    mov ds,ax
Loop0:
    lea dx,DATA1
    mov ah,9
    int 21h
Ring0:
    mov ah,1
    int 21h
    lea dx,Return
    mov ah,9
    int 21h
    ;如果上 ctrl+c 中之程序
    cmp al,CTRL_C
    jz stop
    ;在1-9之间就响铃
    cmp al,31h
    jb ERROR1
    cmp al,39h
    ja ERROR1
    ;
    and al,0fh
    mov cl,al
Loop1:
    mov dl,7
    mov ah,2
    int 21h
    nop
    nop
    nop
    loop Loop1
    jmp Loop0
ERROR1:
    lea dx,ERROR
    mov ah,9
    int 21h
   jmp Ring0
stop:
    mov ax,4c00h
    int 21h
code ends
end start



例2
写一个程序,用二进制数形式显示按键的ASCII码。

程序流程图
187876828-055a541b-5ba6-438a-ab7b-c196027d4af9.png

代码

[Asm] 纯文本查看 复制代码
;程序名:T4-12.asm
;写一个程序,用二进制数形式显示按键的ASCII码。
assume cs:code

code segment
start:
    mov ah,1
    int 21h
    call NEWLINE
    mov bl,al
    mov cx,8
next:
    shl bl,1
    mov dl,30h
    adc dl,0
    mov ah,2
    int 21h
    loop next
    ;
    mov dl,'B'
    mov ah,2
    int 21h
    ;
    mov ax,4c00h
    int 21h

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp
code ends
end start



例3
写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度.

程序流程图
1. 十六进制转十进制(除法运算)
189184107-05168f14-ff97-47ea-a9a7-9edcf170fe9b.png

2. 十六进制转十进制(加法运算)
189182065-f8d747b4-a172-4b4b-bfa4-6518f183cecf.png

代码

1. 十六进制转十进制(除法运算)
[Asm] 纯文本查看 复制代码
;程序名:T4-13.asm
;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
;算法:HEXTODEC 使用除法运算
assume cs:code,ds:data,ss:stack

data segment
buffer db 128 dup(0)    ;未初始化数据要放在最前面
string1 db 'The number of letters is: %u',0,0
string2 db 'The number of numbers is: %u',0,0
string3 db 'String length is: %u',0,0
data ends

stack segment
print_buffer db 128 dup(0)
stack ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov ax,stack
    mov ss,ax
    ;
    lea bx,buffer
    call INPUT
    ;
    push ds
    push bx
    call CHARSTAT
    add sp,4
    ;
    lea dx,string1
    push dx
    xor dx,dx
    mov dl,ah
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,string2
    push dx
    xor dx,dx
    mov dl,al
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,buffer
    push ds
    push dx
    call STRLEN
    add sp,4
    ;
    lea dx,string3
    push dx
    push ax
    call far ptr print
    ;
    mov ax,4c00h
    int 21h

;--------------------------------------------------------
INPUT PROC
; 功能:接收输入,并存储到 128字节大小的 bufer 缓冲区中
; 入口参数:bx=buffer缓冲区首地址
; 出口参数:bx=buffer缓冲区首地址
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    pushf
    push ax
    push bx
INPUT1:
    mov ah,1
    int 21h
    cmp al,0dh
    jz INPUT2
    mov [bx],al
    inc bx
    jmp INPUT1
INPUT2:
    pop bx
    pop ax
    popf
    ;
    ret
INPUT endp

;-----------------------------------------------------
CHARSTAT PROC
; 功能:统计字符串中的字母、数字、其他字符的个数
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AH=字母个数、AL=数字的个数
;------------------------------------------------------
    push bp
    mov bp,sp
    pushf
    push es
    push di
    push bx
    ;
    mov di,[bp+4]
    mov es,[bp+6]
    xor ax,ax
CHARSTAT1:
    mov bl,es:[di]
    ;判断是否读取结束
    cmp bl,0
    jz CHARSTAT_POP
    ;判断小写字母
    cmp bl,'a'
    jae CHARSTAT3
    ;判断大写字母
    cmp bl,'A'
    jae CHARSTAT2
    ;判断数字
    cmp bl,'0'
    jae CHARSTAT0
    inc di
    jmp CHARSTAT1
CHARSTAT0:
    cmp bl,'9'
    jbe number
    inc di
    jmp CHARSTAT1
CHARSTAT2:
    ;判断字母
    cmp bl,'Z'
    jb letter
    inc di
    jmp CHARSTAT1
CHARSTAT3:
    cmp bl,'z'
    jb letter
    inc di
    jmp CHARSTAT1
letter:
    inc ah
    inc di
    jmp CHARSTAT1
number:
    inc al
    inc di
    jmp CHARSTAT1
    ;
CHARSTAT_POP:
    pop bx
    pop di
    pop es
    popf
    mov sp,bp
    pop bp
    ;
    ret
CHARSTAT endp

;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
STRLEN1:
    mov bl,es:[di]
    cmp bl,0
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
STRLEN endp

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp

print proc far  
    push bp
    mov bp,sp
    push ds
    push es
    push ss
    push si
    push di
    push ax
    push bx
    push cx
    push dx

    ;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
    ;初始化寄存器
    xor si,si
    xor di,di
    mov si,[bp+8]
    lea di,print_buffer
print1:
    mov ax,[si]
    cmp ax,0
    jz print_b
    ;
    cmp ax,'u%'
    jz decs
    mov ss:[di],al
    inc si
    inc di
    jmp print1
    ;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
decs:
    call HEXTODEC
    ;无:输出结果
print_b:

    mov al,'$'
    mov ss:[di],al
    mov ax,ss
    mov ds,ax
    lea dx,print_buffer
    mov ah,9
    int 21h
;
    pop dx
    pop cx
    pop bx
    pop ax
    pop di
    pop si
    pop ss
    pop es
    pop ds
    mov sp,bp
    pop bp
    ret
print endp

CalOffset proc
;如果 ax <9;di+2
    cmp ax,9
    jb CalOffset1
;如果 ax <99;di+3
    cmp ax,99
    jb CalOffset2
;如果 ax <999;di+4
    cmp ax,999
    jb CalOffset3
;如果 ax <9999;di+5
    cmp ax,9999
    jb CalOffset4
;如果 ax > 9999;di+5
    jmp CalOffset5
CalOffset1:
    ;di 为了符合循环,di 多加了1
    add di,1
    jmp CalOffset6
CalOffset2:
    add di,2
    jmp CalOffset6
CalOffset3:
    add di,3
    jmp CalOffset6
CalOffset4:
    add di,4
CalOffset5:
    add di,5
CalOffset6:
    ;将当前偏移备份到bx中
    mov bx,di
    ret
CalOffset endp

HEXTODEC PROC
    mov cx,10
    ;mov si,[bp+4]
    mov ax,[bp+6]
    call CalOffset
L1:
    xor dx,dx
    div cx
    add dl,30h
    dec di
    mov ss:print_buffer[di],dl     ;用di判断循环,但是存储结果di需要减1
    cmp ax,0
    jnz L1
    mov di,bx   ;恢复偏移地址
    ;
    ret
HEXTODEC endp
code ends
end start



2. 十六进制转十进制(加法运算)
[Asm] 纯文本查看 复制代码
;程序名:T4-13-1.asm
;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
;算法:HEXTODEC 使用加法运算
assume cs:code,ds:data,ss:stack

data segment
buffer db 128 dup(0)    ;未初始化数据要放在最前面
string1 db 'The number of letters is: %u',0,0
string2 db 'The number of numbers is: %u',0,0
string3 db 'String length is: %u',0,0
data ends

stack segment
print_buffer db 128 dup(0)
stack ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov ax,stack
    mov ss,ax
    ;
    lea bx,buffer
    call INPUT
    ;
    push ds
    push bx
    call CHARSTAT
    add sp,4
    ;
    lea dx,string1
    push dx
    xor dx,dx
    mov dl,ah
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,string2
    push dx
    xor dx,dx
    mov dl,al
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,buffer
    push ds
    push dx
    call STRLEN
    add sp,8
    ;
    lea dx,string3
    push dx
    push ax
    call far ptr print
    ;
    mov ax,4c00h
    int 21h

;--------------------------------------------------------
INPUT PROC
; 功能:接收输入,并存储到 128字节大小的 bufer 缓冲区中
; 入口参数:bx=buffer缓冲区首地址
; 出口参数:bx=buffer缓冲区首地址
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    pushf
    push ax
    push bx
INPUT1:
    mov ah,1
    int 21h
    cmp al,0dh
    jz INPUT2
    mov [bx],al
    inc bx
    jmp INPUT1
INPUT2:
    pop bx
    pop ax
    popf
    ;
    ret
INPUT endp

;-----------------------------------------------------
CHARSTAT PROC
; 功能:统计字符串中的字母、数字、其他字符的个数
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AH=字母个数、AL=数字的个数
;------------------------------------------------------
    push bp
    mov bp,sp
    pushf
    push es
    push di
    push bx
    ;
    mov di,[bp+4]
    mov es,[bp+6]
    xor ax,ax
CHARSTAT1:
    mov bl,es:[di]
    ;判断是否读取结束
    cmp bl,0
    jz CHARSTAT_POP
    ;判断小写字母
    cmp bl,'a'
    jae CHARSTAT3
    ;判断大写字母
    cmp bl,'A'
    jae CHARSTAT2
    ;判断数字
    cmp bl,'0'
    jae CHARSTAT0
    inc di
    jmp CHARSTAT1
CHARSTAT0:
    cmp bl,'9'
    jbe number
    inc di
    jmp CHARSTAT1
CHARSTAT2:
    ;判断字母
    cmp bl,'Z'
    jb letter
    inc di
    jmp CHARSTAT1
CHARSTAT3:
    cmp bl,'z'
    jb letter
    inc di
    jmp CHARSTAT1
letter:
    inc ah
    inc di
    jmp CHARSTAT1
number:
    inc al
    inc di
    jmp CHARSTAT1
    ;
CHARSTAT_POP:
    pop bx
    pop di
    pop es
    popf
    mov sp,bp
    pop bp
    ;
    ret
CHARSTAT endp

;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
STRLEN1:
    mov bl,es:[di]
    cmp bl,0
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
STRLEN endp

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp

print proc far  
    push bp
    mov bp,sp
    push ds
    push es
    push ss
    push si
    push di
    push ax
    push bx
    push cx
    push dx

    ;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
    ;初始化寄存器
    xor si,si
    xor di,di
    mov si,[bp+8]
    lea di,print_buffer
print1:
    mov ax,[si]
    cmp ax,0
    jz print_b
    ;
    cmp ax,'u%'
    jz unsigned_int
    ;
    ;cmp ax,'%s'
    ;jz %s
    ;
    ;cmp ax,'%x'
    ;jz %x
    mov ss:[di],al
    inc si
    ;inc si
    ;inc di
    inc di
    jmp print1
    ;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
unsigned_int:
    call HEXTODEC
    ;无:输出结果
print_b:

    mov al,'$'
    mov ss:[di],al
    mov ax,ss
    mov ds,ax
    lea dx,print_buffer
    mov ah,9
    int 21h
;
    pop dx
    pop cx
    pop bx
    pop ax
    pop di
    pop si
    pop ss
    pop es
    pop ds
    mov sp,bp
    pop bp
    ret
print endp

HEXTODEC PROC
    xor dx,dx
    mov ax,[bp+6]
    mov bx,ax
    ;结果小于9,就直接+30h,存储到ss:di中
    cmp ax,9
    jbe HEXTODEC4
HEXTODEC1:
    add ax,6
    adc dl,dl
    sub bx,0ah
    cmp bx,9
    jbe HEXTODEC2
    jmp HEXTODEC1
    ;运算结束后,判断是否有进位
HEXTODEC2:
    cmp dl,0
    jz HEXTODEC3
    ;有进位,先存储dl,再存储ax
    add dl,30h
    mov ss:[di],dl
    inc di
HEXTODEC3:
    ;无进位,处理ax,并存储到 ss:di 中
    call HTASCS
    jmp HEXTODEC5
HEXTODEC4:         
    add al,30h
    mov ss:[di],al
    inc di
HEXTODEC5:
    ;
    ret
HEXTODEC endp


;-----------------------------------------------------
HTASCS PROC
; 功能:把一个字大小的16进制数(每个位必须<=9),转换成4个ASCII码,存储内存中。
; 传参方式:寄存器传参
; 入口参数:AX
; 出口参数:ss:di(位置可自定义)
;-----------------------------------------------------
    mov cx,4
    mov bh,0        ;bh 作为计数器,判断最高位是否为0。默认最高位是0
HTASCS1:
    ROL AX,1
    ROL AX,1
    ROL AX,1
    ROL AX,1
    mov bl,al
    and bl,0fh
    cmp bl,0
    jnz HTASCS2      ;判断最高位是否为0,不为0,bh置1
    cmp bh,1
    jz HTASCS3       ;如果最高位大于等于1,保存结果
    jmp HTASCS4      ;如果最高位是0,就不保存结果
HTASCS2:
    mov bh,1
HTASCS3:
    add bl,30h
    mov ss:[di],bl
    inc di
HTASCS4:
    loop HTASCS1
    ret
HTASCS endp
code ends
end start


T4-12-1 的子程序 "HTASCS",将一个字的十进制数转换为ASCII,并保存到 ss:di。同时会判断最高的位置,并存储相应值。

3. 使用 int21 接收字符串输入

[Asm] 纯文本查看 复制代码
;程序名:T4-12-2.asm
;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
;使用 int21 中断,10号功能接收输入
assume cs:code,ds:data,ss:stack

data segment
MLENGTH = 128
buffer db MLENGTH
       db 0
       db MLENGTH dup (0)
string1 db 'The number of letters is: %u',0,0
string2 db 'The number of numbers is: %u',0,0
string3 db 'String length is: %u',0,0
data ends

stack segment
print_buffer db 128 dup(0)
stack ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov ax,stack
    mov ss,ax
    ;
    ;lea bx,buffer
    ;call INPUT
    mov dx,offset buffer
    mov ah,10
    int 21h
    ;
    push ds
    mov bx,offset buffer+2
    push bx
    call CHARSTAT
    add sp,4
    ;
    lea dx,string1
    push dx
    xor dx,dx
    mov dl,ah
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,string2
    push dx
    xor dx,dx
    mov dl,al
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    mov dx,offset buffer+2  ;使用 int21 中断,10号功能接收字符串,字符串首地址偏移需要+2
    push ds
    push dx
    call STRLEN
    add sp,8
    ;
    lea dx,string3
    push dx
    push ax
    call far ptr print
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
CHARSTAT PROC
; 功能:统计字符串中的字母、数字、其他字符的个数
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AH=字母个数、AL=数字的个数
;------------------------------------------------------
    push bp
    mov bp,sp
    pushf
    push es
    push di
    push bx
    ;
    mov di,[bp+4]
    mov es,[bp+6]
    xor ax,ax
CHARSTAT1:
    mov bl,es:[di]
    ;判断是否读取结束,这里用0dh
    cmp bl,0dh
    jz CHARSTAT_POP
    ;判断小写字母
    cmp bl,'a'
    jae CHARSTAT3
    ;判断大写字母
    cmp bl,'A'
    jae CHARSTAT2
    ;判断数字
    cmp bl,'0'
    jae CHARSTAT0
    inc di
    jmp CHARSTAT1
CHARSTAT0:
    cmp bl,'9'
    jbe number
    inc di
    jmp CHARSTAT1
CHARSTAT2:
    ;判断字母
    cmp bl,'Z'
    jb letter
    inc di
    jmp CHARSTAT1
CHARSTAT3:
    cmp bl,'z'
    jb letter
    inc di
    jmp CHARSTAT1
letter:
    inc ah
    inc di
    jmp CHARSTAT1
number:
    inc al
    inc di
    jmp CHARSTAT1
    ;
CHARSTAT_POP:
    pop bx
    pop di
    pop es
    popf
    mov sp,bp
    pop bp
    ;
    ret
CHARSTAT endp

;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
STRLEN1:
    mov bl,es:[di]
    cmp bl,0dh      ;int 21号功能,字符串最后存储的是0dh
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
STRLEN endp

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp

print proc far  
    push bp
    mov bp,sp
    push ds
    push es
    push ss
    push si
    push di
    push ax
    push bx
    push cx
    push dx

    ;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
    ;初始化寄存器
    xor si,si
    xor di,di
    mov si,[bp+8]
    lea di,print_buffer
print1:
    mov ax,[si]
    cmp ax,0
    jz print_b
    ;
    cmp ax,'u%'
    jz unsigned_int
    ;
    ;cmp ax,'%s'
    ;jz %s
    ;
    ;cmp ax,'%x'
    ;jz %x
    mov ss:[di],al
    inc si
    ;inc si
    ;inc di
    inc di
    jmp print1
    ;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
unsigned_int:
    call HEXTODEC
    ;无:输出结果
print_b:

    mov al,'$'
    mov ss:[di],al
    mov ax,ss
    mov ds,ax
    lea dx,print_buffer
    mov ah,9
    int 21h
;
    pop dx
    pop cx
    pop bx
    pop ax
    pop di
    pop si
    pop ss
    pop es
    pop ds
    mov sp,bp
    pop bp
    ret
print endp

HEXTODEC PROC
    xor dx,dx
    mov ax,[bp+6]
    mov bx,ax
    ;结果小于9,就直接+30h,存储到ss:di中
    cmp ax,9
    jbe HEXTODEC4
HEXTODEC1:
    add ax,6
    adc dl,dl
    sub bx,0ah
    cmp bx,9
    jbe HEXTODEC2
    jmp HEXTODEC1
    ;运算结束后,判断是否有进位
HEXTODEC2:
    cmp dl,0
    jz HEXTODEC3
    ;有进位,先存储dl,再存储ax
    add dl,30h
    mov ss:[di],dl
    inc di
HEXTODEC3:
    ;无进位,处理ax,并存储到 ss:di 中
    call HTASCS
    jmp HEXTODEC5
HEXTODEC4:         
    add al,30h
    mov ss:[di],al
    inc di
HEXTODEC5:
    ;
    ret
HEXTODEC endp


;-----------------------------------------------------
HTASCS PROC
; 功能:把一个字大小的16进制数(每个位必须<=9),转换成4个ASCII码,存储内存中。
; 传参方式:寄存器传参
; 入口参数:AX,
; 出口参数:ss:di(位置可自定义)
;-----------------------------------------------------
    mov cx,4
    mov bh,0        ;bh 作为计数器,判断最高位是否为0。默认最高位是0
HTASCS1:
    ROL AX,1
    ROL AX,1
    ROL AX,1
    ROL AX,1
    mov bl,al
    and bl,0fh
    cmp bl,0
    jnz HTASCS2      ;判断最高位是否为0,不为0,bh置1
    cmp bh,1
    jz HTASCS3       ;如果最高位大于等于1,保存结果
    jmp HTASCS4      ;如果最高位是0,就不保存结果
HTASCS2:
    mov bh,1
HTASCS3:
    add bl,30h
    mov ss:[di],bl
    inc di
HTASCS4:
    loop HTASCS1
    ret
HTASCS endp
code ends
end start


上面的程序执行结束后会出现问题,第一行的输出结果会错误,但是在调试过程中是正确的。

189300226-5aadb27a-39d9-4bd2-87b4-51d694b754eb.png

我认为是 int21 的10号功能,接收字符串输入之后,屏幕光标位置会重新定位还原到之前的输入点。后面输出结果会覆盖之前的输入的字符串,问题在于当前输出的结果,没有输入字符串长,那么多出来的这几个字符并不会被覆盖,导致第一行显示结果错误。


例4

写一个显示指定内存单元内容的程序。具体要求是:用户按十六进制数的形式输入指定内存单元的段值和偏移,然后用十六进制数形式显示指定字节单元的内容。

程序流程图
189802089-e98f37b1-0822-4127-8fe5-a5ab713d92b6.png

代码

[Asm] 纯文本查看 复制代码
;程序名:T4-13.asm
;写一个显示指定内存单元内容的程序。
;具体要求是:用户按十六进制数的形式输入指定内存单元的段值和偏移,然后用十六进制数形式显示指定字节单元的内容。
;子程序名:GETADR
assume cs:code,ds:data

data segment
addr dw 0
string db '1'
segr db 0,0,0,0
error db 'Please enter the correct memory address','$'
data ends

code segment
start:
    mov ax,data     ;这里的data是随机变动的,要输出指定位置的内存是需要指定段地址
    mov ds,ax
    ;
    lea si,segr
    lea di,addr
    call GETADR
    ;
    mov dl,20h
    mov ah,2
    int 21h
    ;
    mov al,byte ptr es:[di]
    call AHTOASC
    mov cx,2
    mov dx,ax
L1:
    xchg dh,dl
    mov ah,2
    int 21h
    loop L1
    ;
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
GETADR PROC
; 功能:用户输入的十六进制转换成二进制 "段值:偏移"
; 传参方式:ds:si=需要处理数据的缓冲区首地址,ds:di=存入结果的缓冲区首地址
; 入口参数:无
; 出口参数:ES:DI
;------------------------------------------------------
    pushf
    push ax
    push dx
    push bx
    ;
    ;获取段地址
    call GETSTR
    mov bx,[di]
    mov es,bx
    ;获取分隔符
    mov ah,1
    int 21h
    ;获取偏移
    call GETSTR
    mov bx,[di]
    mov di,bx
    ;判断分隔符是否为冒号
    cmp al,3ah
    jnz GETADR_error
    ;
    mov ax,es
    cmp ax,-1
    jz GETADR_error
    cmp di,-1
    jz GETADR_error
    ;
GETADR1:
    pop bx
    pop dx
    pop ax
    popf
    ret
GETADR_error:
    call NEWLINE
    mov dx,offset error
    mov ah,9
    int 21h
    mov ax,4c00h
    int 21h
;    pop bx
;    pop dx
;    pop ax
;    popf
GETADR endp

;--------------------------------------------------------
GETSTR PROC
; 功能:接收4个键盘输入0~9、A~F、a~f。
; 入口参数:si=buffer缓冲区首地址
; 出口参数:di=存储结果的缓冲区首地址,如果输入错误bx=-1
;---------------------------------------------------------
    pushf
    push ax
    push cx
    push si
    xor bx,bx
    xor ax,ax
    mov cx,4
GETSTR0:
    ;接收输入
    mov ah,1
    int 21h
   
    ;处理数字
    sub al,30h
    jb GETSTR_error
    cmp al,9
    ja GETSTR1
    jmp GETSTR_loop
    ;
GETSTR1:
    ;处理大写字母
    sub al,7
    cmp al,0ah
    jb GETSTR_error
    cmp al,0fh
    ja GETSTR2
    jmp GETSTR_loop
GETSTR2:
    ;处理小写字母
    sub al,20h
    cmp al,0ah
    jb GETSTR_error
    cmp al,0fh
    ja GETSTR_error
    jmp GETSTR_loop
GETSTR_error:
    mov bx,-1
    jmp GETSTR_pop
    ;
GETSTR_loop:
    ;将每次循环处理好的字符存到buffer中
    mov byte ptr [si],al
    inc si
    loop GETSTR0
    ;
GETSTR_pop:
    sub si,4
    call INOADR
    ;
    pop si
    pop cx
    pop ax
    popf
    ret
GETSTR endp

;--------------------------------------------------------
INOADR PROC
; 功能:将处理好的输入,转换为地址
; 入口参数:si=需要处理数据的缓冲区首地址,di=存入结果的buffer缓冲区首地址
; 出口参数:di=存入结果的buffer缓冲区首地址
;---------------------------------------------------------
    pushf
    push ax
    push cx
    push si
    ;
    xor ax,ax
    xor bx,bx
    mov dx,4
    mov cx,4
INOADR1:
    ;处理数据
    mov al,byte ptr [si]
    inc si
    or bl,al
    dec dx
    jz INOADR2
    shl bx,cl
    jmp INOADR1
INOADR2:
    mov word ptr [di],bx
    pop si
    pop cx
    pop ax
    popf
    ret
INOADR endp




;-----------------------------------------------------
AHTOASC PROC
;
; 功能:把8位二进制数转换为2位 ASCII 码
; 传参方式:寄存器传参
; 入口参数:AL=要转换的值
; 出口参数:AH = 十六进制数高位的 ASCII 码
;           AL = 十六进制数低位的 ASCII 码
;其他说明:(1)近过程
;          (2)除 AX 寄存器外,不影响其他寄存器
;          (3)调用HTOASC 实现十六进制数到ASCII码的转换
;-----------------------------------------------------
    mov ah,al
    shr al,1
    shr al,1
    shr al,1
    shr al,1
    call HTOASC
    xchg ah,al
    call HTOASC
    ret
AHTOASC endp

;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp pop_stack
num_add:
    add al,30h
pop_stack:
    popf
    ret
HTOASC endp

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp
code ends
    end start


磁盘文件管理及应用
DOS磁盘文件管理功能调用是 DOS 功能的重要组成部分,不仅有助于汇编语言程序设计练习,也有助于文件管理系统的理解。

DOS 磁盘文件管理功能调用
1. DOS 磁盘文件管理功能调用中,用于表示文件名的ASCII字符串必须以 ASCII 码值 0 结尾,这样的字符串通常称为 ASCIIZ 串。
2. 文件名可以是包含盘符和路径的文件标识。
3. 如果没有盘符,那么默认是当盘,如果路径不是从根目录开始,那么就会从当前目录开始。

这些功能调用均利用标志 CF 表示调用是否从成功,如果不成功,那么AX含有错误代码。常见错误代码如下:

错误代码 描述
01 无效的功能号
02 文件未找到
03 路径未找到
04 同时打开文件太多
05 拒绝存取
06 无效的文件号(柄)


创建文件(3CH号功能调用)
功能:创建一个新的或老的文件
入口参数:
        DS:DX=文件名字符串首地址
        CX=文件属性
出口参数:
        CF=0 表示成功,AX=文件号(柄)
        CF=1 表示失败,AX=错误代码

说明:
1. 可指定的文件属性

代码号 描述
00H 普通
01H 只读
02H 隐含
04H 系统


2. 文件创建成功后,文件长度定为0

打开文件(3DH号功能调用)
功能:打开文件
入口参数:
        DS:DX=代表文件名的字符串的首地址
        AL=存取方式
出口参数:
        CF=0 表示成功,AX=文件号(柄)
        CF=1 表示失败,AX=错误代码。

1. 存取方式规定如下:
代码号 描述
00H 只读方式
01H 只写方式
02H 独写方式


2. 文件打开成功后,文件指针定位于开始的第一个字节(偏移0)处。

读文件(3FH号功能调用)
功能:读文件
入口参数:
        BX=文件号(柄)
        CX=字节数
        DS:DX=准备存放所读数据的缓冲区首地址。
出口参数:
        CF=0 表示成功,AX=实际读到的字节数。
        CF=1 表示失败,AX=错误代码

说明:
1. 通常情况下,实际读到的字节数与需要读入的字节数相同,除非不够读。
2. 缓冲区应保证能容下所读到的数据
3. 文件应以读或读写方式打开
4. 读文件后,文件指针将定位到读出字节之后的第一个字节处

写文件(40H号功能调用)
功能:写文件
入口参数:
        BX=文件号(柄)
        CX=写入字节数
        DS:DX=存放写数据的缓冲区的首地址
出口参数:
        CF=0 表示成功,AX=实际写入的字节数
        CF=1 表示失败,AX=错误代码
说明:
1. 通常情况下,实际写入的字节数与需要写入的字节数相同,除非磁盘已满
2. 文件应以写或读写的方式打开
3. 写文件后,文件指针将定位到写入字节后的第一个字节数

关闭文件(3EH号功能)
功能:关闭文件
入口参数:
        CF=0 表示成功
        CF=1 表示失败

说明:
1. 文件号是打开文件时系统给定的文件号。

移动文件读写指针(42H号功能调用)
功能:移动文件(读写)指针
入口参数:
        CF=0 表示成功,此时 DX:AX=移动后的文件指针值。
        CF=1 表示失败,此时 AX=1 表示无效的移动方式,AX=6 表示无效的文件号。
说明:
1. 文件指针值(双字)是以问年间首字节为0计算的。
2. 移动方式和表示的意义如下:
代码号 描述
00H 移动文件后指针值=0(文件头)+移动位移量
01H 当前文件指针值+移动位移量
02H 文件长(文件尾)+移动位移量


3. 在第一种移动方式中,移动位移量总是正的
4. 在后两种移动方式中,移动位移量可正可负
5. 该子功能不考虑文件指针是否超出文件范围

删除文件(41H号功能调用)
功能:删除文件
入口参数:
        DS:DX=代表文件名的字符串首地址。
出口参数:
        CF=0 表示成功
        CF=1 表示失败,AX=错误代码
说明:只能删除一个普通文件

例1
显示文本内容。文本文件固定为当前目录下的test.txt
文件读取流程:打开文件、读取文件、输出内容、关闭文件

打开文件:以只读方式打开文件,并且根据返回值输出不同的信息。
读文件:传入相应参数,判断有效读取字节数(AX)和将要读取字节数(CX)是否一致
关闭文件:如不能关闭文件,提示错误信息。

[Asm] 纯文本查看 复制代码
;程序名:T4-14.asm
;功能:显示文本内容。文本文件固定为当前目录下的test.txt
;执行过程:打开文件,按顺序读取文件,每次读一个字符,把所有字符在屏幕上显示出来,关闭文件。
;txt文本文件结束符ASCII为1AH

assume cs:code,ds:data

data segment
fname db 'test.txt',0
handle dw 0
buffer db 0
flag db 0
error1 db 'ERROR: Invalid function',07h,24h
error2 db 'ERROR: file cannot be found',07h,24h
error3 db 'ERROR: path not found',07h,24h
error4 db 'ERROR: Too many files open at the same time',07h,24h
error5 db 'ERROR: Access Denied',07h,24h
error6 db 'ERROR: cannot close file',07h,24h
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    lea dx,fname
    call far ptr openfile
    ;
read:
    mov bx,handle
    lea dx,buffer
    mov cx,1
    call far ptr readfile
    mov ah,flag
    cmp ah,1
    jz closes
    ;
    mov dl,buffer
    cmp dl,0
    jz closes
    cmp dl,1Ah
    jz closes
    call far ptr output
    jmp read
    ;
closes:
    mov bx,handle
    call far ptr closefile
    ;
    mov ax,4c00h
    int 21h

;入口参数:DS:DX
;出口参数:缓冲区handle
openfile proc far
    push bp
    push si
    push dx
    ;
    xor si,si
    mov ax,3d00h    ;以只读方式打开文件
    int 21h
    jc next         
    mov handle[si],ax
    jmp stack
next:
    xor dl,dl
    adc dl,0
    mov flag,dl
    ;
    xor bh,bh
    mov bl,2
error:
    cmp al,bl
    jz error22
    jz error33
    jz error44
    inc bl
    jmp error
error22:
    mov dx,offset error2
    jmp outputop
error33:
    mov dx,offset error3
    jmp outputop
error44:
    mov dx,offset error3
outputop:
    mov ah,9
    int 21h
stack:
    pop si
    pop dx
    pop bp
    ret
openfile endp

;入口参数:bx(文件句柄)、dx 数据缓冲区地址、cx:读取多少字节
readfile proc far
    ;读取一个字节
    mov ah,3Fh
    int 21h
    ;
    cmp ax,cx
    jz stack1
    mov buffer,0
stack1:
    ret
readfile endp

output proc far
    push ax
    mov ah,02
    int 21h
    pop ax
    ret
output endp

closefile proc far
    mov ah,3EH
    int 21h
    jnc stack2
    ;
    mov dl,0ah
    mov ah,2
    int 21h
    mov dx,offset error6
    mov ah,9
    int 21h
stack2:
    ret
closefile endp
code ends
    end start
```
>这段代码中多写了一些不必要且重复的判断。这个程序当时写的急,没有把各个子程序的信息详细写明。


```
;程序名:T4-14-1.asm
;功能:写一个显示文本文件内容的程序。文本文件固定为当前目录下的 TEST.TXT 文件
;具体算法:(1) 打开文件
;          (2) 按顺序读取文件,每次读一个字符,把所读字符在屏幕上显示出来
;          (3) 关闭文件
;说    明:考虑到 TEST.TXT 是文本文件,所以认为 ASCII 码的值为 1AH 的字符就是文件结束符。
;子程序名:READCH
assume ds:data,cs:code

EOF = 1AH
data segment
fname db 'test.txt',0
error1 db 'File not found',07h,0
error2 db 'Reading error',07h,0
buffer db ?
data ends


code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov dx,offset fname
    mov ax,3d00h    ;只读模式打开文件
    int 21h
    jnc open_ok     ;打开成功
    ;
    mov si,offset error1
    call  DMESS     ;显示打开不成功的信息
    jmp over
    ;
open_ok:
    mov bx,ax
cont:
    call READCH     ;从文件中读一个字符
    jc READERR
    cmp al,EOF
    jz type_ok
    call putch
    jmp cont
    ;
READERR:
    mov si,offset error2
    call DMESS
    ;
TYPE_OK:
    mov ah,3Eh
    int 21h
over:
    mov ax,4c00h
    int 21h
;-----------------------------------------------------
READCH PROC
; 功能:每次从文件中按顺序读一个字符。
; 传参方式:寄存器传参
; 入口参数:AL=十六进制
; 出口参数:AX=高4位,低4位
; 说    明:子程序通过进位标志CF来反映是否正确读到字符,
;如果读时发生错误,则CF置位,否则CF清零。考虑到万一文本
;文件没有文件结束符的情况,所以该子程序还判断是否的却已
;读到文件尾(如果实际读到的字符为0就意味着文件结束),
;当这种情况发生时,就返回一个文件结束符。
;------------------------------------------------------
    mov cx,1
    mov dx,offset buffer
    mov ah,3FH
    int 21h
    jc READCH2
    cmp ax,cx
    mov al,EOF
    jb READCH1
    mov AL,BUFFER
READCH1:
    CLC
READCH2:
    ret
READCH endp

;-----------------------------------------------------
DMESS PROC
;
; 功能:显示一个以 0 结束符的字符串
; 入口参数:SI = 字符串首地址
; 出口参数:无
;-----------------------------------------------------
DMESS1:
    mov DL,[SI]
    inc SI
    or DL,DL
    jz DMESS2
    mov ah,2
    int 21h
    jmp DMESS1
DMESS2:
    ret
DMESS endp
;-----------------------------------------------------
PUTCH PROC
;
; 功能:使用 int 21h的 2 号功能输出字符。
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:无
;-----------------------------------------------------
    push dx
    mov dl,al
    mov ah,2
    int 21h
    pop dx
    ret
PUTCH endp
code ends
end start


例2
把键盘上输入的全部字符,存入某个文件的程序。文件固定为当前根目录下的test.txt。如果它已经存在,则更新它。

1. 书中的举例(重新建立 test.txt 覆盖原数据,写入输入数据)

[Asm] 纯文本查看 复制代码
;程序名:T4-15.asm
;功能:把键盘上输入的字符全部字符(直到CTRL+Z键,值1AH)存入文件 test.txt 中,
;如果 test.txt 文件已存在则更新它。
assume cs:code,ds:data

EOF = 1AH

data segment
FNAME db '\TEST.txt',0
ERRMESS1 db 'Can not create file',07h,'$'
ERRMESS2 db 'Writing error',07h,'$'
BUFFER db ?
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov dx,offset FNAME     ;建立文件
    mov cx,0
    mov ah,3CH
    int 21h
    jnc CREA_OK             ;建立成功,跳转
    ;
    mov dx,offset ERRMESS1  ;显示不能新建文件提示信息
    call DISPMESS
    jmp over
    ;
CREA_OK:
    mov bx,ax               ;保存文件句柄
CONT:
    call GETCHAR            ;接收一个键
    push ax
    call WRITECH            ;向文件写所读的字符
    pop ax
    jc WERROR               ;写出错,转
    cmp AL,EOF              ;是否已读到文件结束符
    jnz CONT                ;不是,继续
    jmp CLOSEF              ;遇文件结束符,转结束
    ;
WERROR:
    mov dx,offset ERRMESS2  ;显示写出错误提示信息
    call DISPMESS
    ;
CLOSEF:
    MOV ah,3EH              ;关闭文件
    int 21h
over:
    mov ax,4c00h
    int 21h

;-----------------------------------------------------
WRITECH PROC
;
; 功能:把需要写入的一个字节,送入缓冲区
;-----------------------------------------------------
    mov BUFFER,AL
    mov dx,offset BUFFER
    mov cx,1
    mov ah,40h
    int 21h
    ret
WRITECH endp

;-----------------------------------------------------
GETCHAR PROC
; 功能:输入一个字符
;-----------------------------------------------------
    mov ah,1
    int 21h
    ret
GETCHAR endp

;--------------------------------------------------------
DISPMESS PROC
; 功能:显示由 DX 所指的提示信息
; 入口参数:DX=字符串首地址
; 出口参数:无
;---------------------------------------------------------
    mov ah,9
    int 21h
    ret
DISPMESS endp
code ends
end start


2. 读 test 文件,在写入输入缓冲区的内容,实现文件更新

[Asm] 纯文本查看 复制代码
;程序名:T4-15-1.asm
;功能:把键盘上输入的全部字符,存入某个文件的程序
;文件固定为当前根目录下的test.txt。如果它已经存在,则更新它。

;打开文件(判断是否已存在),如已存在:读取文件并存入缓冲区、读取键盘输入存入缓冲区
;如不存在:创建文件,将输入存入缓冲区

assume cs:code,ds:data
data segment
fname db 'test.txt',0
handle dw 0
flag db 0
read_buffer db 0
input_buffer db 30 dup(0)
len dw 0
message db 'Please enter the data you want to write: ',24h
input_error db 'Please enter char$'
close_error db 'ERROR: cannot close file',07h,24h
data ends

code segment
start:
    mov ax,data
    mov ds,ax
open:
    ;打开文件,并判断是否打开成功
    call far ptr openfile
    jnc create_ok
    ;创建文件
create:
    lea dx,fname
    mov cx,0
    mov ah,3ch
    int 21h
    jmp open    ;创建文件后,还需要打开文件才能写入数据
    ;文件创建成功,提示输入
create_ok:
    xor di,di
read_ch:
    ;读文件,并将内容复制到缓冲区中。
    mov bx,handle
    lea dx,read_buffer
    mov cx,1
    mov ax,3Fh
    int 21h
    cmp ax,0            ;如果没有正确读到字符,ax=0
    jz input_ch
    ;将读到的字符传入写字符缓冲区(只需要将文件内容读一遍,原文件字符就不会被覆盖)
    ;mov al,read_buffer
    ;mov input_buffer[di],al
    ;inc di
    jmp read_ch

input_ch:
    call far ptr input
    ;输入成功,将字符串写到文件中
write:
    lea dx,input_buffer
    mov bx,handle
    mov cx,len
    mov ah,40h
    int 21h
close:
    call far ptr closefile
    mov ax,4c00h
    int 21h

openfile proc far
    lea dx,fname
    mov ax,3d02h    ;以读写方式打开文件
    int 21h
    mov handle,ax
stack:
    ret
openfile endp

input proc far
    ;打印提示信息
    lea dx,message
    mov ah,9
    int 21h
loop_input:
    ;用户输入
    mov ah,1
    int 21h
    ;判断用户输入结束按键
    cmp al,0dh
    jz returnA
    ;根据情况设置用户输入规则
    cmp al,20h
    jb input_err
    ;
    mov input_buffer[di],al
    inc di
    jmp loop_input
input_err:
    ;换行
    mov dl,0ah
    mov ah,2
    int 21h
    ;
    lea dx,input_error
    mov ah,9
    int 21h
    jmp stop
stop:
    mov ax,4c00h
    int 21h
returnA:
    mov input_buffer[di],0  ;1AH
    mov len,di
    ret
input endp

closefile proc far
    mov bx,handle
    mov ah,3EH
    int 21h
    jnc stack2
    ;
    mov dl,0ah
    mov ah,2
    int 21h
    mov dx,offset close_error
    mov ah,9
    int 21h
stack2:
    ret
closefile endp
code ends
    end start


3. 使用42h号功能,修改文件读写指针,实现更新功能

[Asm] 纯文本查看 复制代码
;程序名:T4-15.asm
;功能:把键盘上输入的全部字符(直到Ctrl+Z键,值1AH)存入某个文件的程序
;文件固定为当前根目录下的test.txt。如果它已经存在,则更新它
;算法:使用 42H 号功能实现

assume cs:code,ds:data
data segment
fname db 'test.txt',0
handle dw 0
flag db 0
read_buffer db 0
input_buffer db 30 dup(0)
len dw 0
message db 'Please enter the data you want to write: ',24h
input_error db 'Please enter char$'
close_error db 'ERROR: cannot close file',07h,24h
data ends

code segment
start:
    mov ax,data
    mov ds,ax
open:
    ;打开文件,并判断是否打开成功
    call far ptr openfile
    jnc create_ok
    ;创建文件
create:
    lea dx,fname
    mov cx,0
    mov ah,3ch
    int 21h
    jmp open    ;创建文件后,还需要打开文件才能写入数据
    ;文件创建成功,提示输入
create_ok:
    ;修改读写指针
    mov bx,handle
    xor cx,cx
    xor dx,dx
    mov ax,4202h
    int 21h

input_ch:
    call far ptr input
    ;输入成功,将字符串写到文件中
write:
    lea dx,input_buffer
    mov bx,handle
    mov cx,len
    mov ah,40h
    int 21h
close:
    call far ptr closefile
    mov ax,4c00h
    int 21h

openfile proc far
    lea dx,fname
    mov ax,3d02h    ;以读写方式打开文件
    int 21h
    mov handle,ax
stack:
    ret
openfile endp

input proc far
    ;打印提示信息
    lea dx,message
    mov ah,9
    int 21h
loop_input:
    xor di,di
    ;用户输入
    mov ah,1
    int 21h
    ;判断用户输入结束按键
    cmp al,0dh
    jz returnA
    ;根据情况设置用户输入规则
    cmp al,20h
    jb input_err
    ;
    mov input_buffer[di],al
    inc di
    jmp loop_input
input_err:
    ;换行
    mov dl,0ah
    mov ah,2
    int 21h
    ;
    lea dx,input_error
    mov ah,9
    int 21h
    jmp stop
stop:
    mov ax,4c00h
    int 21h
returnA:
    mov input_buffer[di],0  ;1AH
    mov len,di
    ret
input endp

closefile proc far
    mov bx,handle
    mov ah,3EH
    int 21h
    jnc stack2
    ;
    mov dl,0ah
    mov ah,2
    int 21h
    mov dx,offset close_error
    mov ah,9
    int 21h
stack2:
    ret
closefile endp
code ends
    end start


例3
写一个程序把文件2拼接到文件1上。文件1固定为当前目录下的TEST1,文件2固定为当前目录下的TEST2
该示例是使用到 21中断的```42h号``` 功能,疑问文件读写指针实现的。
[Asm] 纯文本查看 复制代码
;程序名:T4-17.asm
;功能:写一个程序把文件2拼接到文件1上。文件1固定为当前目录下的TEST1,文件2固定为当前目录下的TEST2
assume cs:code,ds:data
;符号常量定义
BUFFLEN = 512
;数据段
data segment
fname1 db 'test1.txt',0
fname2 db 'test2.txt',0
handle1 dw 0
handle2 dw 0
ERRMESS1 db 'Can not open file',07h,'$'
ERRMESS2 db 'Reading eror',07h,'$'
ERRMESS3 db 'Writing error',07h,'$'
BUFFER db BUFFLEN dup(0)
data ends
;代码段
code segment
start:
    mov ax,data
    mov ds,ax
    mov dx,offset fname1
    mov ax,3d01h
    int 21h
    jnc OPENOK1
    ;
ERR1:
    mov dx,offset ERRMESS1
    call DISPMESS
    jmp OVER
OPENOK1:
    mov handle1,ax
    mov dx,offset fname2
    mov ax,3d00h
    int 21h
    jnc OPENOK2
    ;
    mov bx,handle1
    mov ah,3Eh
    int 21h
    jmp ERR1
OPENOK2:
    mov handle2,ax
    mov bx,handle1
    xor cx,cx
    xor dx,dx
    mov ax,4202h
    int 21h
    ;
CONT:
    mov dx,offset BUFFER
    mov cx,BUFFLEN
    mov bx,handle2
    mov ah,3fh
    int 21h
    jc RERR
    ;
    or ax,ax
    jz copyok
    mov cx,ax
    mov bx,handle1
    mov ah,40h
    int 21h
    jnc CONT
WERR:
    mov dx,offset ERRMESS3
    call DISPMESS
    jmp SHORT COPYOK
    ;
RERR:
    mov dx,offset ERRMESS2
    call DISPMESS
COPYOK:
    mov bx,handle1
    mov ah,3Eh
    int 21h
    mov BX,handle2
    mov AH,3Eh
    int 21h
    ;
OVER:
    mov ah,4ch
    int 21h
;--------------------------------------------------------
DISPMESS PROC
; 功能:显示由 DX 所指的提示信息
; 入口参数:DX=字符串首地址
; 出口参数:无
;---------------------------------------------------------
    mov ah,9
    int 21h
    ret
DISPMESS endp
code ends
end start

评分

参与人数 12HB +14 THX +7 收起 理由
消逝的过去 + 1
鼎香楼 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
今日下雨 + 1
459121520 + 1
后学真 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
娄胖胖 + 1
只为去踩缝纫机 + 1 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
zxjzzh + 2 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
sjtkxy + 1
水清流 + 2 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
欲心磨 + 1 + 1
192939 + 4 + 1

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
水清流 发表于 2022-9-27 12:13 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
头像被屏蔽
sjtkxy 发表于 2022-9-28 05:12 | 显示全部楼层

提示: 作者被禁止或删除 内容自动屏蔽
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
后学真 发表于 2022-10-7 16:01 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
吾爱稀饭 发表于 2022-10-11 23:15 | 显示全部楼层

感谢分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表