目录

DOS 系统功能调用(INT 21H)

/images/dos_image.png

INT表示interrupt(中断), INT指令是X86汇编语言中最重要的指令之一,它的作用是引发中断,调用“中断例程”(interrupt routine)。
中断是由于软件或硬件的信号,使CPU暂停执行当前的任务,转而去执行另一段子程序。

  • 硬中断(外中断):由外部设备,如网卡、硬盘随机引发的,比如网卡收到数据包的时候,就会发出一个中断。
  • 软中断(内中断):由执行中的指令产生的,可以通过程序控制触发。

可以通过 “ INT 中断码 ” 实现中断,内存中有一张中断向量表,用来存放中断码处理中断程序的入口地址。CPU在接受到中断信号后,暂停当前正在执行的程序,跳转到中断码对应的向量表地址处去执行中断。

我们理解上可以当INT就是调用系统内置的一些功能。
常用的中断:

  • INT 21H:DOS系统功能调用
  • INT 10H:BIOS终端调用
  • INT 3H:断点中断,用于调试程序

这篇文章重点记录下DOS的系统功能调用,也就是INT 21H

本文环境:

  • 操作系统:虚拟机中的Windows 10
  • Visual Studio Code
  • VSCode 插件:MASM/TASM 这个插件还蛮方便的,省去了自己配环境的麻烦,右键单击文件选择Run ASM code就可以执行代码:

/images/vsc_run_code.png

DOS系统功能调用格式都是一致的,步骤如下:

  1. AH寄存器中设置系统功能调用号。
  2. 在指定的寄存器中设置入口参数。
  3. INT 21H指令执行功能调用。
  4. 根据出口参数分析功能调用执行情况。

常见功能

DOS 系统功能调用 INT 21H,有数百种功能供用户使用。下面介绍几个常用的 DOS 系统功能调用,简要描述如表所示。

/images/dos_api.png
4C肯定天天用,毕竟用来返回DOS的,这里写点其他功能的例子。

一些例子

汇编代码中,指令和寄存器名大写和小写没关系,所以这里ah和AH,dx和DX也没问题。但是4H和4还是有点不一样的,4H是16进制,不带h表示10进制。

显示一个字符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
assume cs:code, ds:data
  
; 数据段
data segment  
    message db 'hello'
data ends

; 代码段
code segment  
start: 
    mov ax, data
    mov ds, ax

    mov dl, ds:[1h]

    ;调用DOS系统功能,2表示输出DL寄存器的字符到显示器
    mov ah, 2h
    int 21h
    
    ;返回DOS
    mov ah, 4ch
    int 21h
     
code ends     
end start

这个程序会输出一个e的字符。

打印hello world

代码是用别人的,他的注释写的蛮好的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
; 注:这里的关联并没有任何实际操作,相当于给我们自己的注释而已
; 相当于即使不写这一行也没有关系
assume cs:code, ds:data
  
; 数据段
data segment  
    ; 创建字符串
    ; 汇编打印字符串要在尾部用 $ 标记字符串的结束位置
    ; 将字符串用hello做一个标记,方便后面使用它
    hello db 'Hello World!$'
data ends

; 代码段
code segment  
; 指令执行的起始,类似于C语言的main函数入口
start:  
    ; 汇编语言不会自动把数据段寄存器指向我们程序的数据段
    ; 将数据段寄存器指向我们自己程序的数据段
    mov ax, data
    mov ds, ax

    ; 打印字符串的参数
    ; DS:DX=串地址,将字符串的偏移地址传入dx寄存器
    ; 字符串是在数据段起始创建的,它的偏移地址是0H
    ; offset hello 即找到标记为hello的数据段字符串的编译地址
    ; 还可以写成 mov dx, 0H
    mov dx, offset hello  
    ; 打印字符串,ah=9H代表打印
    mov ah, 9h
    int 21h
    
    ; 正常退出程序,相当于高级语言的 return 0
    mov ah, 4ch
    int 21h
     
code ends     
end start

读取键盘输入两个数求和

这个例子复杂了点

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
assume cs:code, ds:data, ss:stack

stack segment
    dw 30h dup(0)
stack ends
  
; 数据段
data segment
    buf db 20h, 0, 20h dup (0)
    message db 'input a number:$'
    num dw ?
data ends

code segment
start:
    mov ax, data
    mov ds, ax
    call printMsg
    call readInput
    call atoi
    push num

    ; again
    call printMsg
    call readInput
    call atoi
    push num

    ; cal summ
    call sum
    call printAx

    mov ah, 4ch
    int 21h

printMsg:
    mov dx, offset message
    mov ah, 9h
    int 21h
    ret

readInput:
    ; first byte to tell dos maximum characters buffer can hold
    mov dx, 0h
    mov ah, 0Ah
    int 21h
    ; print \n
    mov dl, 0Ah
    mov ah, 02h
    int 21h
    ret

atoi proc
    mov dx,0
    mov bx,10
    mov si,2
    mov num,0
    mov ax,0
lop:
    mov al,buf[si]
    cmp al,0Dh
    je  final
    sub al,30h
    cmp num,0
    je  do_delta
    push ax
    mov ax,num
    mul bx
    mov num,ax  
    pop ax
do_delta:
    add num,ax
    mov ax,0
    inc si
    jmp lop
final:    
    ret
atoi endp
; 内平衡
sum:
    mov bp, sp
    mov ax, ss:[bp+2]
    add ax, ss:[bp+4]  
    
    ret 4


printAx proc
    ;initialize count
    mov cx,0
    mov dx,0
    label1:
        ; if ax is zero
        cmp ax,0
        je print1     
        mov bx,10
        div bx
         
        ;push it in the stack
        push dx             
         
        ;increment the count
        inc cx             
         
        ;set dx to 0
        xor dx,dx
        jmp label1
    print1:
        ;check if count
        ;is greater than zero
        cmp cx,0
        je exit
         
        ;pop the top of stack
        pop dx
         
        ;add 48 so that it
        ;represents the ASCII
        ;value of digits
        add dx,48
        ; print character
        mov ah,02h
        int 21h
         
        ;decrease the count
        dec cx
        jmp print1
exit:
        ret
printAx endp

code ends
end start

/images/dos_cal_result.png

调试工具DEBUG常用命令

R ——查看和修改寄存器
D ——查看内存单元

内存每16个字节单元为一小段,逻辑段必须从小段的首址开始。用D命令可以查看存储单元的地址和内容。
D命令格式为:

1
D  段地址:起始偏移地址 [结尾偏移地址] [L范围]

例如:

1
2
3
4
5
D DS:0      查看数据段,从0号单元开始  
D ES:0      查看附加段,从0号单元开始  
D DS:100   查看数据段,从100H号单元开始  
D 0200:5 15   查看0200H段的5号单元到15H号单元(在虚拟机上该命令不能执行)  
D 0200:5 L 11  用L选择范围。查看0200H段的5号单元到15H号单元共10个单元  
T /P——单步执行

P可以跳过子程序或系统调用,其他方面T和P是类型的。

参考: