汇编基础

汇编基础

结构和宏

结构

结构(structure)是一组逻辑相关变量的模板或模式。结构中的变量被称为字段(fields),程序语句可以把结构作为整体进行访问,也可以访问其中的单个字段。
使用结构包含三个连续的步骤:
1)定义结构
2)声明结构类型的一个或多个变量,称为结构变量(structure variables)
3)编写运行时指令访问结构字段

对齐结构字段

为了获得最好的内存I/O性能,结构成员应按其数据类型进行地址对齐。否则,CPU将会花更多时间访问成员。
使用ALIGN伪指令会试其后的字段或者变量按地址对齐

1
2
3
4
5
6
7
8
Employee STRUCT 
IdNum
LastName
ALGIN WORD
Years WORD 0
ALGIN DWORD
SalaryHistory DWORD 0,0,0,0
Employee ENDS

TYPE EmployeeSIZEOF Employee均为60
需要注意对齐:9+30+1(对齐2)+2+2(对齐4)+16

Q-A

Q:根据下面代码回答问题

1
2
3
4
MyStruct STRUCT
field1 WORD ?
field2 DWORD 20 DUP(?)
MyStruct ENDS

Q1:使用默认值创建变量
Q2:声明变量,将其第一个字段初始化为0
Q3:声明变量,将第二个字段初始化为全零数组

K:使用<>来在声明变量的同时初始化结构
A1:struct1 <>
A2:struct1 <0>
A3:struct1 <,20 DUP(0)>

Q4:一数组包含20个MyStruct,将该数组声明为变量
A4:array MyStruct 20 DUP(<>)

Q5:对上一题的数组,把第一个数组元素的field1送入AX
Q6:对上一题的数组,用ESI索引第三个数组元素,并将AX送入field1

K:使用变量加点来引用成员
A5: mov ax, array[0].field1
K:同样也可以用OFFSET运算符获取结构变量中一个字段的地址,间接操作数用寄存器对结构成员寻址(变址操作),但需要注意引用间接操作数时需要PTR运算符
A6:

1
2
3
4
5
6
;写法1
mov esi, 2*TYPE MyStruct
mov array[esi].field1, ax
;写法2
mov esi, OFFSET array[2]
mov (MyStruct PTR [esi]).field1, ax

例子

COORD结构:Windows API中定义的COORD结构确定了屏幕的X和Y坐标。相对于结构起始地址,字段X的偏移量为0,Y的偏移量为2

1
2
3
4
COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS

用程序模拟一个不太清醒的教授从计算机科学假期聚会回家的路线,利用随机数生成器,选择该教授每一步行走的方向。使用COORD结构追踪这个人行走路径上的每一步。

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
INCLUDE Irvine32.inc

WalkMax = 50 ;走的总步数
StartX = 25
StartY = 25

DrunkardWalk STRUCT
path COORD WalkMax DUP (<0, 0>)
pathsUsed WORD 0
DrunkardWalk ENDS

DisplayPosition PROTO currX:WORD, currY:WORD

.data
aWalk DrunkardWalk <>

.code
main PROC
mov esi, OFFSET aWalk
call TakeDrunkenWalk
call Crlf
call WaitMsg
exit
main ENDP

TakeDrunkenWalk PROC
LOCAL currX:WORD, currY:WORD
;向随机方向行走
;参数:ESI为DrunkardWalk结构的指针
;返回:结构初始化为随机数
;-----------------------------------
pushad
mov edi, esi
add edi, OFFSET DrunkardWalk.path
mov ecx, WalkMax
mov currX, StartX
mov currY, StartY

Again:
;把当前位置插入数组
mov ax, currX
mov (COORD PTR [edi]).X, ax
mov ax, currY
mov (COORD PTR [edi]).Y, ax

INVOKE DisplayPosition, currX, currY

mov eax, 4 ;选择一个方向
call RandomRange
.IF eax == 0 ;北
dec currY
.ELSEIF eax == 1 ;南
inc currY
.ELSEIF eax == 2 ;西
dec currX
.ELSE ;东
inc currX
.ENDIF
add edi, TYPE COORD ;指向下一个COORD
loop Again

Finish:
mov (DrunkardWalk PTR [esi]).pathsUsed, WalkMax
popad
ret
TakeDrunkenWalk ENDP

DisplayPosition PROC currX:WORD, currY:WORD
;-----------------------------
;显示当前的X和Y的位置
;参数:无
;返回:无
;-----------------------------
.data
commaStr BYTE ",", 0
.code
pushad
movzx eax, currX
call WriteDec
mov edx, OFFSET commaStr
call WriteString
movzx eax, currY
call WriteDec
call Crlf
popad
ret
DisplayPosition ENDP
END main

概述

宏过程(macro procedure)是一个命名的汇编句快。一但定义好,它就可以在程序中被多次调用。在调用宏的过程时,其代码的副本将被直接插入到程序中该宏被调用的位置。这种自动插入代码也被称作内联展开(inline expansion)

宏定义一般出现在程序源代码开始的位置,或者是放在独立的文件中,再用INCLUDE伪指令复制到程序里。宏在汇编器预处理(preprocessing)阶段进行扩展。在这个阶段中,预处理程序读取宏定义并扫描程序剩余的代码。每到宏被调用的位置,汇编器就将宏的源代码复制插入到程序中。

Q(判断):
1.当一个宏被调用时,CALL和RET指令将自动插入汇编程序中
2.宏展开由汇编器的预处理程序控制
3.只要宏定义在代码段中,它就能出现在宏调用语句之前,也能出现在宏调用语句之后
4.对一个长过程而言,若用包含这个过程代码的宏来代替它,则多次调用该宏通常就会增加程序的编译代码量
5.宏不能包含数据定义
A:F T F T F
K:

定义并调用宏

定义宏使用MACRO和ENDM伪指令

macroname MACRO param1, param2…
statement-list
ENDM

在宏名前使用前缀m,形成易识别的名称
宏形参(marco parameter)是需传递给调用者的文本实参的命名占位符,该形参不包含类型信息,如下:

1
2
3
4
5
6
mPutchar MACRO char
push eax
mov al, char
call WriteChar
pop eax
ENDM

调用时不需要call,仍以上例mPutchar 'A'就调用了宏
通常, 与过程相比,宏执行起来更快,其原因是过程的CALL和RET指令需要额外的开销
但是,宏也存在缺点:重复使用大型宏会增加程序的大小,因为每次调用都会插入代码

其他宏特性

规定形参

利用REQ限定符,可以指定必需的宏形参。如果被调用的宏没有实参与规定形参相匹配,那么汇编器将显示出错误消息

1
2
3
4
5
6
mPutchar MACRO char:REQ
push eax
mov al, char
call WriteChar
pop eax
ENDM

注释

如果想忽略宏展开时的注释,需要使用双分号(;😉

ECHO

程序汇编时,ECHO伪指令写一个字符串到标准输出

1
2
3
4
5
6
7
mPutchar MACRO char:REQ
ECHO Expanding the mPutchar marco
push eax
mov al, char
call WriteChar
pop eax
ENDM

在汇编时会显示消息“ECHO Expanding the mPutchar marco”

LOCAL

宏定义中经常含有标号,并会在代码中对这些标号进行自引用,

1
2
3
4
makeString MACRO text
.data
string BYTE text, 0
ENDM

但这存在一个问题,若调用两次宏,就生成了两个一样的标号,会出现错误
为了避免这种错误,对标号使用LOCAL指令,这样预处理程序就把标号名转换成唯一的标识符(生成??nnnn的形式,其中nnnn为具有唯一性的整数)

宏嵌套(nested macro)

当汇编器的预处理程序遇到对被嵌套宏的调用时,它就会展开该宏。传递给主调宏的形参也将直接传递给它的被嵌套宏

1
2
3
4
mWriteIn MACRO text
mWrite text
call Crlf
mWriteIn ENDM

特殊运算符

运算符 说明
& 替换运算符
<> 文字文本运算符
! 文字字符运算符
% 展开运算符
&
1
2
3
4
5
6
7
;X,regName被当做字符串
mShowRegister MACRO regName
.data
tempStr BYTE "regName=", 0

;√,使用替换运算符
tempStr BYTE "&regName=", 0
%

展开运算符展开文本宏并将常量表达式转换为文本形式

1
2
3
4
5
6
7
8
9
10
11
12
count = 10
sumVal TEXTEQU %(5+count) ;="15"

;X,屏幕输出没有什么用
.data
array DWORD 1,2,3,4,5,
.code
ECHO The array contains (SIZEOF array) bytes
ECHO The array contain %(SIZEOF array) bytes
;√,使用TEXTEQU编写文本宏
TempStr TEXTEQU %(SIZEOF array)
% ECHO The array contains TempStr bytes
<>

文字文本(literal-text)运算符把一个或多个字符和符号组合成一个文字文本,以防预处理程序把列表中的成员解释为独立的参数

1
2
3
4
5
;mWrite接收一个字符串作为唯一实参
;X,第一个括号后的文本会被丢弃
mWrite "Line Three", 0ah, 0dh
;√
mWrite <"Line Three", 0ah, 0dh>
!

构造文字字符(literal-character)运算符的目的与文字文本运算符几乎完全一样:强制预处理程序把预先定义的运算符当做普通的字符。

1
BadYValue TEXTEQU <Warning:Y-coodinate is !> 24>

上例使用!防止>被当做文本分隔符

写在后面

题目等等再更,一口气把基础过完

Author

Ctwo

Posted on

2019-08-07

Updated on

2020-10-25

Licensed under

Comments