汇编语言基础(二)
HelloWorld
来看一下汇编语言的helloWorld程序:
1 | DATAS SEGMENT |
以上程序的作用就是输出: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 | DATAS SEGMENT |
相关说明看注释。
关于int 21H更多的用法,有相关博文
汇编语言整数加减法示例
两个一位数相加,示例如下:
1 | DATA SEGMENT |
对于这个程序有几个点需要写一下。
在程序输入输出中,分别进行了-30H和+30H操作
1 | ;输入第一个值把它保存在NUM1中 |
换行和回车
1 | ;为了输出在新的一行 |
汇编语言数据类型以及数据定义详解
在上述代码中,num1,num2和sum的定义是这样的:
1 | DATA SEGMENT |
汇编器识别一组基本的内部数据类型(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 | value1 BYTE 'A' ;字符常量 |
DB 伪指令也可以定义有符号或无符号的 8 位变量:
1 | val1 DB 255 ;无符号字节 |
问号(?)初始值使得变量未初始化,这意味着在运行时分配数值到该变量:
1 | value6 DB ? |
可选名字是一个标号,标识从变量包含段的开始到该变量的偏移量。比如,如果 value1 在数据段偏移量为 0000 处,并在内存中占一个字节,则 value2 就自动处于偏移量为 0001 处:
1 | value1 DB 10h |
1.多初始值
如果同一个数据定义中使用了多个初始值,那么它的标号只指出第一个初始值的偏移量。在下面的例子中,假设 arr的偏移量为 0000。那么,数值 10 的偏移量就为 0000, 20 的偏移量为 0001,30 的偏移量为 0002,40 的偏移量为 0003:
1 | arr DB 10,20,30,40 |
下图给出了字节序列 arr,显示了每个字节及其偏移量。
并不是所有的数据定义都要用标号。比如,在 arr后面继续添加字节数组,就可以在下一行定义它们:
1 | arr DB 10,20,30,40 |
在单个数据定义中,其初始值可以使用不同的基数。字符和字符串常量也可以自由组合。在下面的例子中,arr1和 arr2有相同的内容:
1 | arr1 DB 10, 32, 41h, 00100010b |
2.定义字符串
定义一个字符串,要用单引号或双引号将其括起来。最常见的字符串类型是用一个**$作为结束标记**:
1 | msg DB 'helloworld!$' ;定义名称为msg的字符串,最后加上$,表示字符串结束 |
每个字符占一个字节的存储空间。一个字符串可以分为多行,并且不用为每一行都添加标号:
1 | msg DB "Welcome to the Encryption Demo program " |
其中0DH,0AH分别表示回车和换行,如:
1 | CRLF DB 0AH,0DH,'$' ;0AH换行符 0DH回车符 $字符串的结束符(不占内存) |
**行连续字符(\)**把两个源代码行连接成一条语句,它必须是一行的最后一个字符。下面的语句是等价的:
1 | greeting1 DB "Welcome to the Encryption Demo program$" |
3.DUP 操作符
**DUP 操作符使用一个整数表达式作为计数器,为多个数据项分配存储空间。**在为字符串或数组分配存储空间时,这个操作符非常有用,它可以使用初始化或非初始化数据:
1 | DB 20 DUP ( 0 ) ;20 个字节,值都为 0 |
定义 WORD 和 SWORD 数据(DW)
WORD(定义字)和 SWORD(定义有符号字)伪指令为一个或多个 16 位整数分配存储空间:
1 | word1 WORD 65535 ;最大无符号数 |
也可以使用传统的**DW 伪指令**:
1 | val1 DW 65535 ;无符号 |
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 | val1 DWORD 12345678h ;无符号 |
传统的**DD 伪指令**也可以用来定义双字数据:
1 | val1 DD 12345678h ;无符号 |
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 | intVal TBYTE 800000000000001234h ;有效 |
传统的**DT 伪指令**也可以用来定义压缩 BCD 变量:
1 | intVal DT 800000000000001234h ;有效 |
定义浮点类型(REAL4 ,REAL8 ,REAL10)
REAL4 定义 4 字节单精度浮点变量。REAL8 定义 8 字节双精度数值,REAL10 定义 10 字节扩展精度数值。每个伪指令都需要一个或多个实常数初始值:
1 | rVal1 REAL4 -1.2 |
下表描述了标准实类型的最少有效数字个数和近似范围:
数据类型 | 有效数字 | 近似范围 |
---|---|---|
短实数 | 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 | rVal1 DD -1.2 ;短实数 |
声明未初始化数据
DATAS SEGMENT伪指令声明未初始化数据。当定义大量未初始化数据时,DATAS SEGMENT伪指令减少了编译程序的大小。例如,下述代码是有效声明:
1 | DATAS SEGMENT |