汇编语言基础(二)

HelloWorld

来看一下汇编语言的helloWorld程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DATAS SEGMENT
;此处输入数据段代码
msg db 'helloworld!$' ;定义名称为msg的字符串,最后加上$,表示字符串结束

DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
db 128 dup(?)
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
;此处输入代码段代码
lea dx,msg ;dx寄存器中存储的是msg这一字符串的首地址
MOV AH,09H ;调用中断,该中断会用到dx寄存器
INT 21H
MOV AH,4CH ;调用中断,安全退出
INT 21H
CODES ENDS
END START

以上程序的作用就是输出:helloworld!

其中:db 128 dup(?)

dup即英文duplicate的缩写,重复的意思,用来定义重复的字节、字、双字、结构等内存缓冲区专。其中:db x dup(),x是重复的次数,()里是要重复的数

  • buf1 db 100 dup(?)---------开辟100个字节的内存区
  • buf2 dw 200 dup(?)--------开辟200个字的内存区
  • buf3 dd 300 dup(?)--------开辟300个双字的内存区

db 128 dup(?),其初始值实际为0,和db 128 dup(0)效果差不多。

一些注释已经写在代码中了,但是关于int 21H还有一点要说。

INT 21H

一般要输出的时候,就会用到int 21H。常用用法有如下:

AH 功能 调用参数 返回参数
01H 键盘输入并回显 AL=输入字符
02H 显示输出 DL=输出字符
09H 显示字符串 DS:DX=串地址,’$'结束字符串
4CH 带返回码结束 AL=返回码

比如说,要在键盘上输入一个数字,然后再输出它:

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
DATAS SEGMENT
;此处输入数据段代码
CRLF DB 0AH,0DH,'$' ;0AH换行符 0DH回车符 $字符串的结束符(不占内存)
DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
;此处输入代码段代码
mov ah,01H
int 21H;等待键入,键入后把字符的ASCII码送AL

lea dx,CRLF
mov ah,09H
int 21H ;换行

mov dl,al;待显示字符的ASCII码要放到DL里
mov ah,02H
int 21H;

MOV AH,4CH
INT 21H
CODES ENDS
END START

相关说明看注释。

关于int 21H更多的用法,有相关博文

汇编语言整数加减法示例

两个一位数相加,示例如下:

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
DATA SEGMENT
NUM1 DB 0 ;定义一个字节的NUM1并赋值为0
NUM2 DB 0 ;定义一个字节的NUM2并赋值为0
SUM DB 0 ;定义他们的和
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA
START:
MOV AX,DATA
MOV DS,AX

;输入第一个值把它保存在NUM1中
MOV AH,01H
INT 21H
SUB AL,30H ;将键盘输入的ASCII码转换成16进制的0到9
MOV NUM1,AL ;将值赋给NUM1

;输入一个值后自动回车加换行并等待输入第二个数
MOV DL,0AH ;回车
MOV AH,2
INT 21H
MOV DL ,0DH ;换行
MOV AH,2
INT 21H

;输入第二个值把它保存在NUM2中
MOV AH,01H
INT 21H
SUB AL,30H ;将键盘输入的ASCII码转换成16进制的0到9
MOV NUM2,AL ;将值赋给NUM2

;把两个值相加并保存在SUM中
ADD BL,NUM1
ADD BL,NUM2 ;把两个数相加存放在BL中
MOV SUM,BL ;把和赋给SUM

;为了输出在新的一行
MOV DL ,0AH ;回车
MOV AH,02H
INT 21H
MOV DL ,0DH ;换行
MOV AH,02H
INT 21H

;以下为输出程序
CMP SUM,9 ;判断是否不在0-9以内
JA E ;超过9则跳转
MOV DL,SUM ;若没有则将SUM的值给DL
ADD DL,30H ;需要在输出前转换到相应的ASCII码
JMP SHOW ;跳转到显示代码

;和为两位数
E:
MOV DL,1 ;直接先输出十位上的值
ADD DL,30H ;需要在输出前转换到相应的ASCII码
MOV AH,02H
INT 21H
MOV DL,SUM ;先将SUM的值赋给DL
SUB DL,10 ;把DL中的值减十进置的10,如果减十六进制的10会有误
ADD DL,30H ;需要在输出前转换到相应的ASCII码
JMP SHOW ;跳转到显示代码

;显示程序
SHOW:
MOV AH,02H
INT 21H

;程序结束
MOV AH,4CH
INT 21H

CODE ENDS
END START

对于这个程序有几个点需要写一下。

在程序输入输出中,分别进行了-30H和+30H操作

1
2
3
4
5
6
7
8
9
10
11
;输入第一个值把它保存在NUM1中
MOV AH,01H
INT 21H
SUB AL,30H ;将键盘输入的ASCII码转换成16进制的0到9
MOV NUM1,AL ;将值赋给NUM1

;输出sum
MOV DL,SUM ;若没有则将SUM的值给DL
ADD DL,30H ;需要在输出前转换到相应的ASCII码
MOV AH,02H
INT 21H

换行和回车

1
2
3
4
5
6
7
;为了输出在新的一行
MOV DL ,0AH ;回车
MOV AH,02H
INT 21H
MOV DL ,0DH ;换行
MOV AH,02H
INT 21H

汇编语言数据类型以及数据定义详解

在上述代码中,num1,num2和sum的定义是这样的:

1
2
3
4
5
DATA SEGMENT
NUM1 DB 0 ;定义一个字节的NUM1并赋值为0
NUM2 DB 0 ;定义一个字节的NUM2并赋值为0
SUM DB 0 ;定义他们的和
DATA ENDS

汇编器识别一组基本的内部数据类型(intrinsic data type),按照数据大小(字节、字、双字等等)、是否有符号、是整数还是实数来描述其类型。这些类型有相当程度的重叠,例如,DWORD 类型(32 位,无符号整数)就可以和 SDWORD 类型(32 位,有符号整数)相互交换。

下表给出了全部内部数据类型的列表,有些表项中的 IEEE 符号指的是 IEEE 计算机学会出版的标准实数格式。

类型 用法
BYTE 8 位无符号整数,B 代表字节
SBYTE 8 位有符号整数,S 代表有符号
WORD 16 位无符号整数
SWORD 16 位有符号整数
DWORD 32 位无符号整数,D 代表双(字)
SDWORD 32 位有符号整数,SD 代表有符号双(字)
FWORD 48 位整数(保护模式中的远指针)
QWORD 64 位整数,Q 代表四(字)
TBYTE 80 位(10 字节)整数,T 代表 10 字节
REAL4 32 位(4 字节)IEEE 短实数
REAL8 64 位(8 字节)IEEE 长实数
REAL10 80 位(10 字节)IEEE 扩展实数

数据定义语句

数据定义语句(data definition statement)在内存中为变量留岀存储空间,并赋予一个可选的名字。数据定义语句根据内部数据类型(上表)定义变量。如:

1
count DWORD 12345;

伪指令:数据定义语句中的伪指令可以是 BYTE、WORD、DWORD、SBTYE、SWORD 或其他在上表中列出的类型。此外,它还可以是传统数据定义伪指令,如下表所示:

伪指令 用法 伪指令 用法
DB 8位整数 DQ 64 位整数或实数
DW 16 位整数 DT 定义 80 位(10 字节)整数
DD 32 位整数或实数

数据定义中至少要有一个初始值,即使该值为 0。其他初始值,如果有的话,用逗号分隔。

如果希望不对变量进行初始化(随机分配数值),可以用符号 ? 作为初始值。所有初始值,不论其格式,都由汇编器转换为二进制数据。 初始值 0011 0010b、32h 和 50d 都具有相同的二进制数值。

定义 BYTE 和 SBYTE 数据(DB)

BYTE(定义字节)SBYTE(定义有符号字节)一个或多个无符号或有符号数值分配存储空间。每个初始值在存储时,都必须是 8 位的。例如:

1
2
3
4
5
value1 BYTE  'A'    ;字符常量
value2 BYTE 0 ;最小无符号字节
value3 BYTE 255 ;最大无符号字节
value4 SBYTE -128 ;最小有符号字节
value5 SBYTE +127 ;最大有符号字节

DB 伪指令也可以定义有符号或无符号的 8 位变量

1
2
val1 DB 255    ;无符号字节
val2 DB -128 ;有符号字节

问号(?)初始值使得变量未初始化,这意味着在运行时分配数值到该变量:

1
value6 DB ?

可选名字是一个标号,标识从变量包含段的开始到该变量的偏移量。比如,如果 value1 在数据段偏移量为 0000 处,并在内存中占一个字节,则 value2 就自动处于偏移量为 0001 处:

1
2
value1 DB 10h
value2 DB 20h

1.多初始值

如果同一个数据定义中使用了多个初始值,那么它的标号只指出第一个初始值的偏移量。在下面的例子中,假设 arr的偏移量为 0000。那么,数值 10 的偏移量就为 0000, 20 的偏移量为 0001,30 的偏移量为 0002,40 的偏移量为 0003

1
arr DB 10,20,30,40

下图给出了字节序列 arr,显示了每个字节及其偏移量。

并不是所有的数据定义都要用标号。比如,在 arr后面继续添加字节数组,就可以在下一行定义它们:

1
2
3
arr DB 10,20,30,40
DB 50,60,70,80
DB 81,82,83,84

在单个数据定义中,其初始值可以使用不同的基数。字符和字符串常量也可以自由组合。在下面的例子中,arr1和 arr2有相同的内容

1
2
arr1 DB 10, 32, 41h, 00100010b
arr2 DB 0Ah, 20h, 'A', 22h

2.定义字符串

定义一个字符串,要用单引号或双引号将其括起来。最常见的字符串类型是用一个**$作为结束标记**:

1
2
3
msg DB 'helloworld!$'	;定义名称为msg的字符串,最后加上$,表示字符串结束
greeting1 DB "Good afternoon$"
greeting2 DB 'Good night$'

每个字符占一个字节的存储空间。一个字符串可以分为多行,并且不用为每一行都添加标号:

1
2
3
4
msg   DB "Welcome to the Encryption Demo program "
DB "created by Kip Irvine.",0dh,0ah
DB "If you wish to modify this program, please "
DB "send me a copy.$",0dh,0ah

其中0DH,0AH分别表示回车和换行,如:

1
CRLF DB 0AH,0DH,'$' ;0AH换行符 0DH回车符 $字符串的结束符(不占内存)

**行连续字符(\)**把两个源代码行连接成一条语句,它必须是一行的最后一个字符。下面的语句是等价的:

1
2
3
4
greeting1 DB "Welcome to the Encryption Demo program$"
;等效于:
greeting1 \
DB "Welcome to the Encryption Demo program$"

3.DUP 操作符

**DUP 操作符使用一个整数表达式作为计数器,为多个数据项分配存储空间。**在为字符串或数组分配存储空间时,这个操作符非常有用,它可以使用初始化或非初始化数据:

1
2
3
DB 20 DUP ( 0 )      ;20 个字节,值都为 0
DB 20 DUP ( ? ) ;20 个字节,非初始化
DB 4 DUP ( "STACK" ) ; 20 个字节:

定义 WORD 和 SWORD 数据(DW)

WORD(定义字)和 SWORD(定义有符号字)伪指令为一个或多个 16 位整数分配存储空间

1
2
3
word1 WORD 65535    ;最大无符号数
word2 SWORD -32768 ;最小有符号数
word3 WORD ? ;未初始化,无符号

也可以使用传统的**DW 伪指令**:

1
2
val1 DW 65535   ;无符号
val2 DW -32768 ;有符号

16 位字数组通过列举元素使用 DUP 操作符来创建字数组。下面的数组包含了一组数值:

1
myList DW 1,2,3,4,5

下图是一个数组在内存中的示意图,假设 myList 起始位置偏移量为0000。由于每个数值占两个字节,因此其地址递增量为 2

DUP 操作符提供了一种方便的方法来声明数组:

1
array DW 5 DUP (?)	; 5个数值,未初始化

定义 DWORD 和 SDWORD 数据(DD)

DWORD(定义双字)和 SDWORD(定义有符号双字)伪指令为一个或多个 32 位整数分配存储空间

1
2
3
val1 DWORD 12345678h    ;无符号
val2 SDWORD -2147483648 ;有符号
val3 DWORD 20 DUP (?) ;无符号数组

传统的**DD 伪指令**也可以用来定义双字数据:

1
2
val1 DD 12345678h ;无符号
val2 DD -2147483648 ;有符号

DWORD 还可以用于声明一种变量,这种变量包含的是另一个变量的 32 位偏移量。如下所示,pVal 包含的就是 val3 的偏移量:

1
pVal DD val3

现在定义一个32位双字数组,并显式初始化它的每一个值:

1
myList DD 1,2,3,4,5

下图给岀了这个数组在内存中的示意图,假设 myList 起始位置偏移量为 0000,偏移量增量为 4

定义 QWORD 数据(DQ)

QWORD(定义四字)伪指令为 64 位(8 字节)数值分配存储空间

1
quad1 QWORD 1234567812345678h

传统的**DQ 伪指令**也可以用来定义四字数据:

1
quad1 DQ 1234567812345678h

定义压缩 BCD(TBYTE)数据(DT)

Intel 把一个压缩的二进制编码的十进制(BCD, Binary Coded Decimal)整数存放在一个 10 字节的包中。每个字节(除了最高字节之外)包含两个十进制数字。在低 9 个存储字节中,每半个字节都存放了一个十进制数字。最高字节中,最高位表示该数的符号位。如果最高字节为 80h,该数就是负数;如果最高字节为 00h,该数就是正数。整数的范围是 -999 999 999 999 999 999 到 +999 999 999 999 999 999。
示例下表列出了正、负十进制数 1234 的十六进制存储字节,排列顺序从最低有效字节到最高有效字节:

十进制数值 存储字节
+1234 34 12 00 00 00 00 00 00 00 00
-1234 34 12 00 00 00 00 00 00 00 80

MASM 使用 TBYTE 伪指令来定义压缩 BCD 变量。常数初始值必须是十六进制的,因为,汇编器不会自动将十进制初始值转换为 BCD 码。下面的两个例子展示了十进制 数 -1234 有效和无效的表达方式:

1
2
intVal TBYTE 800000000000001234h ;有效
intVal TBYTE -1234 ;无效

传统的**DT 伪指令**也可以用来定义压缩 BCD 变量:

1
intVal DT 800000000000001234h ;有效

定义浮点类型(REAL4 ,REAL8 ,REAL10)

REAL4 定义 4 字节单精度浮点变量REAL8 定义 8 字节双精度数值REAL10 定义 10 字节扩展精度数值。每个伪指令都需要一个或多个实常数初始值

1
2
3
4
rVal1 REAL4 -1.2
rVal2 REAL8 3.2E-260
rVal3 REAL10 4.6E+4096
ShortArray REAL4 20 DUP(0.0)

下表描述了标准实类型的最少有效数字个数和近似范围:

数据类型 有效数字 近似范围
短实数 6 1.18x 10-38 to 3.40 x 1038
长实数 15 2.23 x 10-308 to 1.79 x 10308
扩展精度实数 19 3.37 x 10-4932 to 1.18 x 104932

DD、DQ 和 DT 伪指令也可以定义实数

1
2
3
rVal1 DD -1.2      ;短实数
rVal2 DQ 3.2E-260 ;长实数
rVal3 DT 4.6E+4096 ;扩展精度实数

声明未初始化数据

DATAS SEGMENT伪指令声明未初始化数据当定义大量未初始化数据时,DATAS SEGMENT伪指令减少了编译程序的大小。例如,下述代码是有效声明:

1
2
3
DATAS SEGMENT
;此处输入数据段代码
DATAS ENDS