BASM精要
3.1 BASM概念简要
汇编语句由指令和零至三个表达式构成。表达式由常数(立即数)、寄存器和标识符构成。例如:
movsb // 单指令语句
jmp @Here // 一个表达式: 标识符
add eax,1 // 两个表达式: 寄存器和立即数
// 三个表达式: 寄存器, 标识符(内存地址), 立即数
imul edx, [ebx].RandSeed, 08088405H
一段BASM代码以ASM关键字开始,END关键字结束。中间有任意多个汇编语句。
BASM代码通常写在例程中。Delphi的BASM是内嵌于语言的,无法独立编译出可执行程序或中间代码(.Obj)。但是,可以使用BASM来完成一个完全汇编的
程序,并使用Delphi编译器编译。如下例:
program TestBASM;
asm
mov eax, 100
end.
3.2 表达式的类别与类型
在BASM的语句中,每一个表达式都必须能够在编译器中计算出准确的值或者寻址地址。如果不能满足这个条件,语句不会被编译通过。事实上,对于指令系统来说,每一个表达式都最终对应于一个确定的操作数。
因此,表达式的类别(Expression classes),按表达式的计算结果可分成三类:寄存器、立即数和内存引用(存储器)。
与内存引用相关的表达式,会涉及到存储器寻址模式的问题,请查阅相关资料。下一小节会简要讲述在BASM中访问Delphi所定义的变量与常量,但不涉及寻址模式。
在BASM中,表达式的类型(Expression types)是一个长度值,它是指表达式值占用空间的字节数,即值的大小。这与Delphi中SizeOf()函数含义是一样的。但BASM中用关键字TYPE来返回表达式的类型(大小)。
如下例:
type
TArr = array [0..10] of char; // SizeOf(TArr) = 11
var
Arr : TArr
asm
mov eax, TYPE Arr
mov eax, TYPE TArr
mov eax, TYPE Arr[2]
end;
上面的三行汇编语句都会向eax送入值11。第三行看起来是要取Arr数组元素的长度,但实际上只能取到数组的长度。
较为复杂的表达式,其类型由第一个操作数的类型来决定。因此下面这个语句送入eax的值仍然为Arr的类型值11:
mov eax, TYPE (Arr + 2)
这里的括号不能理解成函数,而是用来改变运算优先级的。
同样的道理,在BASM中,以下两条语句面对的命运是不同的:
mov eax, 2 + Arr
mov eax, Arr + 2
第一代码行会被BASM理解成Arr的地址值+2。而第二行代码右边表达式的长度为11,不能送入寄存器eax,因而根本不会被编译通过。
3.3 数据定义和数据类型强制转换(1)
BASM可以使用所有通过Delphi语法定义的变量、常量。BASM扩展了ASM的语法,用于访问记录、数组、对象等复杂的数据结构。
下例简单解释了如何进行数据定义和访问:
type
TRec = record
rI : Integer;
rS : String;
end;
var
I : Integer;
R : TRec;
S : String =
'1234567';
A : Array [0..10] of char
= 'abcdefghij'#0;
const
C = 3124;
Str = 'abcde';
asm
mov eax, I // I 的值送入 eax
mov eax, [I] // 同上
mov eax, OFFSET I // I 的地址送入eax, 相当于 eax = @I
mov eax, R.rI // 域rI的值送入eax
mov eax, [TRec.rI + R] // 同上
mov eax, [Offset R + TRec.rI] // 同上
mov ebx, S
dec ebx // 忽略s[0]
mov esi, 4
mov al, BYTE [ebx + esi] // 将s[4]的字符值送入al
mov al, BYTE [ebx + 4] // 同上
mov eax, [ebx+4] // 将s[4]..s[7]四字节以DWORD值送入eax, eax=$37363534
mov ebx, OFFSET A
mov eax, [ebx+4] // 将 A[4]..S[7]四字节以DWORD值送入eax, eax=$68676665
mov eax, C // eax = 3124
mov eax, [C] // eax = PInteger(3124)^, 非法的内存地址访问
end;
在上例中,常量C总是作为数值直接被编码。因此,“mov eax, C”中,它作为立即数3124被送入EAX。而在“mov eax, [C]”却表明要访问内存地址“3124”,因为“[C]”表明是内存引用。
由于常量总是被直接编码,上例中,无法访问常量Str——Str的长度大于4,所以无法送入EAX。同样的原因,在BASM中,对常量使用OFFSET是没有意义的——尽管在Delphi中,字符串常量可以具有内存地址