>>shiftright.
&bitwiseAND.
^bitwiseXOR.
|bitwiseOR.
二进制必须有“b”作结尾,例如00011011b十六进制必须有"h"作结尾,另外,当地一位是字母时,最前面必须加上0,例如:
0ABCDh八进制必须有"o"作结尾,例如:
77o
什么是汇编语言
汇编语言是底层编程语言。
为了学习这门语言,你需要对于计算机结构有所了解。
计算机系统模型如下:
系统总线systembus(图中黄色部分)是将计算机各个部分连接到一起的部件。
CPU是计算机的心脏,大部分的运算都是在CPU中完成的。
RAM是读取并且存放将要执行的程序的地方。
CPU内部
通用寄存器
8086CPU有8个通用寄存器,每一个寄存器都有自己的名称:
AX累加寄存器accumulatorregister(分为AH/AL).
BX基址寄存器baseaddressregister(分为BH/BL).
CX计数寄存器countregister(分为CH/CL).
DX数据寄存器dataregister(分为DH/DL).
SI源变址寄存器sourceindexregister.
DI目的变址寄存器destinationindexregister.
BP基址指针寄存器basepointer.
SP堆栈寄存器stackpointer.
编程中,由程序员决定通用寄存器的具体用途。
寄存器的主要目的是保存数值(变量)。
上面提到的寄存器是16位的,意思是:
001b(二进制),或者12345(十进制形式)。
4个通用寄存器(AX,BX,CX,DX)在使用时分为两个8位寄存器,例如假设AX=001b,AH=00110000bAL=00111001b。
当你修改其中任意8位值,整个16位寄存器的值同样改变。
同样对于其他的3个寄存器,“H”表示高8位,“L”表示低8位。
寄存器在CPU内部,访问中它们速度远远超过内存。
因为,访问内存需要经过系统总线,所以时间要长一些。
而访问寄存器中的数据几乎不需要时间。
于是,编程中,应当尽量在寄存器中保存数据。
虽然寄存器很小,并且这些寄存器都有具体用途,但他们依然是存放计算中临时数据的好地方。
段寄存器
CS代码段寄存器,用来存放当前正在运行的指令
DS数据段寄存器,用来存放当前运行程序所用的数据
ES附加段寄存器,由程序员决定用途
SS堆栈段寄存器,指出堆栈所在区域
尽管容许在段寄存器中存放任何数据,但是这决不是一个好主意。
段寄存器有着非常特别的目的--指出可以访问内存块的地址。
段寄存器与通用寄存器协同工作就可以访问任意的内存区域。
例如,如果我们打算访问物理地址是12345h(十六进制)的内存单元,我们应设置DS=1230hSI=0045h这样以来,我们便能访问超过一个寄存器(16位)所能表示的内存地址的范围。
CPU计算物理地址的方法是将段寄存器乘以10H在加上一个特定的通用寄存器。
(1230h*10h+45h=12345h):
这种,由两个寄存器生成的地址被称为有效地址(effectiveaddress)
默认下,BX,SI及DI与DS协同工作,BPSP与SS寄存器协同工作。
其余的通用寄存器不能形成有效地址!
同样,尽管BX可以形成有效地址,但是BHBL不能!
控制寄存IP指令指针寄存器instructionpointer、FlagsRegister状态标志寄存器
IP始终同CS协同工作,指出当前执行的指令。
FlagsRegister完成一次数学运算后,由CPU自动修改,通过它可以得到当前结果类型,也可以作为跳转语句条件。
通常你无法直接访问它们。
寻址方式
我们可以通过下面的四个寄存器来寻址BX,SI,DI,BP.
通过计算[]符号中的值,我们可以访问到不同内存单元的值。
具体组合请看下表:
[BX+SI][BX+DI][BP+SI][BP+DI]
[SI][DI]d16(variableoffsetonly)[BX]
[BX+SI]+d8[BX+DI]+d8[BP+SI]+d8[BP+DI]+d8
[SI]+d8[DI]+d8[BP]+d8[BX]+d8
[BX+SI]+d16[BX+DI]+d16[BP+SI]+d16[BP+DI]+d16
[SI]+d16[DI]+d16[BP]+d16[BX]+d16
d8-表示8位偏移量d16-表示16位偏移量
偏移量可以是一个立即数或者是一个变量的偏移,或者二者兼备。
这取决于编译器如何计算单独的立即数。
偏移量可以在[]符号里面或者外面,这不影响编译器生成相同的机器码。
偏移量是一个有符号数,可以是正数或者负数。
一般说来,8位或者16位,对于编译后的结果是有影响的。
例如,假定DS=100,BX=30,SI=70。
如下寻址方式[BX+SI]+25计算物理地址为100*16+30+70+25=1725默认下,DS寄存器应用在除了BP寄存器之外的所有物理地址计算中,寄存器是和SS寄存器一起工作的。
用过下面的表,你可以和轻松记住谁和谁是关联在一起使用的。
上表中,你可以从每一列中选择一个或者忽略任意一个列。
比如,可以看到,BX和BP始终不会选到一起。
SI和DI不会选到一起。
这是一个计算地址模式[BX+5]段寄存器(CS,DS,SS,ES)中数值被称作"段偏移"。
目的寄存器(BX,SI,DI,BP)中数值被称作"偏移量"
比如,ds中数值为1234h,si中数值为7890h,可以记作1234:
7890 物理地址为1234h*10h+7890h=19BD0h在编译过程中使用如下声明数据类型
BYTEPTR-表示字节;WORDPTR-表示字(2个字节)
例如:
BYTEPTR[BX];按字节访问;WORDPTR[BX];按字访问
Emu8086容许使用如下更简洁的前缀
b.-等价于上面的BYTEPTR;w.-等价于上面的WORDPTR
有时,编译器可以自动计算出数据类型,但是如果一个参与运算的数是立即数,这种方法就不可靠了。
MOV指令
将第二个操作数(源)拷贝到第一个操作数(目的)指定位值,源操作数可以是立即数,通用寄存器或者内存单元,目的寄存器可以是通用寄存器或者内存单元,源和目的必须是同样大小,要么都是字节要么都是字
操作类型如下:
MOVREG,memoryMOVmemory,REGMOVREG,REGMOVmemory,immediateMOVREG,immediate
REG:
AX,BX,CX,DX,AH,AL,BL,BH,CH,CL,DH,DL,DI,SI,BP,SP.
memory:
[BX],[BX+SI+7],变量,等等
immediate:
5,-24,3Fh,b,等等.
mov 指令只支持如下段寄存器:
MOVSREG,memoryMOVmemory,SREGMOVREG,SREGMOVSREG,REG
SREG:
DS,ES,SS,注意CS只能作操作源
REG:
AX,BX,CX,DX,AH,AL,BL,BH,CH,CL,DH,DL,DI,SI,BP,SP.memory:
[BX],[BX+SI+7],variable,等等
MOV指令不能用来设置CS和IP寄存器的值。
下面是一个使用MOV指令的例子:
#MAKE_COM# ;表示,这个是一个com程序ORG100h ;COM程序必须的MOVAX,0B800h ;将ax设置为B800h.MOVDS,AX ;将AX值拷贝到DS.MOVCL,'A' ;将ASCII码'A'的值传送到cl,这个值是41h.MOVCH,01011111b ;将ch设置为二进制的01011111bMOVBX,15Eh ;将BX设置成15Eh.MOV[BX],CX ;将CX放到bx指出的内存单元B800:
015ERET ;返回操作系统
你可以将上面的程序贴入Emu8086代码编辑器,接下来按下[complieandemulate] (或者按F5)模拟窗口将显示这个程序已经调入,点击[singlestep]观察寄存器数值变化,你可以猜到 ";" 表示注释,编译器忽略在";"后面的一切,程序结束后,你可以看到如下窗口
事实上,上面程序是将字符直接写入显示内存。
通过上面的例子,你可以发现MOV 指令是非常有用的。
变量
变量是一个内存地址。
对于编程者来说,使用诸如名称为“var1”这样的变量保存数据远远比使用5a73:
235b这样的地址容易的多。
特别是当你使用10个以上的变量的时侯。
编译器支持这两种变量BYTE和WORD.(字节和字)
声明变量的方法:
nameDBvalue名称DB值
nameDWvalue名称DW值
DB-staysforDefineByte.
DW-staysforDefineWord.
name-可以是任何字母与数字构成,但是必须由字母开头。
可以通过不命名来声明一个没有名称的的变量(这个变量只有地址,没有名称)
value-可以是任何数值支持三种进制(十六进制,二进制和十进制),你可以使用""符号表示初始值没有确定。
你可能从第二章了解到,MOV指令是将数值从源拷贝到目的。
让我们再看一个MOV指令的例子
#MAKE_COM#
ORG100h
MOVAL,var1
MOVBX,var2
RET;stopstheprogram.
VAR1DB7
var2DW1234h
将上面的代码拷贝到emu8086源程序编辑器中,按下F5键编译并在模拟器中执行。
你会看到如下画面
从画面可以看出,反编译后的代码同源程序很相似,不同的是变量被具体的内存地址取代。
当编译器生成机器代码它会自动将变量名称用该变量的便宜量代替。
默认情况下,DS寄存器存放段偏移(当执行com文件的时侯,DS寄存器的值同CS寄存器(代码段)的值一样)。
内存第一列是偏移(offset),第二列是一个十六进制值(hexadecimalvalue),第三列是十进制(decimalvalue),最后一列是ASCII字符。
编译器是非大小写敏感的,所以“VAR1”同“var1”都是同一个变量。
VAR1变量的偏移是0108h,物理地址是0b56:
0108
var2变量的偏移是0109h,物理地址是0b56:
0109
这个变量是字,它占用2字节。
这里假定低字节存放在低地址,所以34h位于12h前面。
你可以看到,在RET指令后面还有一些指令,这样是因为反编译工具无法判断数据从什么地方开始。
同样,你可以写出直接使用DB的程序.
#MAKE_COM#
ORG100h
DB0A0h
DB08h
DB01h
DB8Bh
DB1Eh
DB09h
DB01h
DB0C3h
DB7
DB34h
DB12h
将上面的代码拷贝到emu8086原代码编辑器,按下F5键编译,并在模拟器中运行,你可以看到同样的反汇编结果,得到同样的功能。
根据上面,你可以猜测,编译器将源程序转化为一些字节的集合,这个集合被称作机器代码(machinecode),处理器懂得他们,并且执行它们。
ORG100是一个编译指令(它告诉编译器如何处理源代码)当你使用变量的时侯,这条指令特别重要。
它通知编译器可执行程序将被调入偏移量是100h(256字节)的位置,有了它,编译器就可以计算出所有变量的正确地址,然后用这些地址(偏移量)来代替变量名称。
上面的这些指令不会真正的编译为任何机器代码。
为何可执行程序总是被装入偏移量100h操作系统在CS寄存器(代码段)存储着程序信息,比如命令行方式下的参数等等。
尽管上面只是一个COM文件的例子,EXE文件调入在偏移量0000的位置,他使用特定的段保存变量。
我们在下面会学习到关于EXE文件的知识。
数组
数组可以看作是变量链。
一个字符串是一个字节数组的例子,其中每一个字符都当作一个ASCII码的值(0....255)下面是一些定义数组的例子
aDB48h,65h,6Ch,6Ch,6Fh,00h
bDB'Hello',0
b是一个数组,当编译器发现引用了字符串值后,会自动将这些字符转化为对应的字节。
下面图表表示的就是声明数组后在内存中的分布:
你可以使用方括号做下标直接访问到数组中的值,例如:
MOVAL,a[3]同样,你还可以使用任意一个内存索引寄存器BX,SI,DI,BP,例如:
MOVSI,3
MOVAL,a[SI]
如果你想声明比较复杂的数组,你可以使用DUP指令形式如下numberDUP(value(s))number-重复的数量(任意常数)
value-将要复制的表达式
例如:
cDB5DUP(9)就相当于如下定义:
cDB9,9,9,9,9另外一个例子:
dDB5DUP(1,2)等同于dDB1,2,1,2,1,2,1,2,1,2当然,如果需要存放超过255或者小于-128的数值,你还可以使用DW来代替DB。
但是DW不能用于声明字符串。
DUP命令展开后不能超过1020个字符(上一个例子中展开之后是13个字符),如果需要声明请将它们分成两行(这样,内存中得到的仍然是一个大数组)。
取得变量地址LEA指令(LoadEffectiveAddress读取有效地址)或者OFFSET指令。
OFFSET和LEA二者都能够获得变量的偏移量。
LEA在使用中更有效,这是因为它能返回索引变量的地址。
取得变量地址在很多情况下是非常有用的,例如你打算向一个过程传递参数。
注意:
在编译过程中使用如下声明数据类型BYTEPTR-表示字节;WORDPTR-表示字(2个字节)
例如:
BYTEPTR[BX];按字节访问;WORDPTR[BX];按字访问
Emu8086容许使用如下更简洁的前缀
b.-等价于上面的BYTEPTR;w.-等价于上面的WORDPTR
有时,编译器可以自动计算出数据类型,但是如果一个参与运算的数是立即数,这种方法就不可靠了。
第一个例子:
ORG100h
MOVAL,VAR1;将变量var1的数值放入al以便检查
LEABX,VAR1;将var1的地址存入BX.
MOVBYTEPTR[BX],44h;修改变量var1的内容
MOVAL,VAR1;将变量VAR1的数值放入AL以便检查
RET
VAR1DB22h
END
下面是另外一个例子,用OFFSET指令代替LEA:
ORG100h
MOVAL,VAR1;将变量VAR1的值放入AL以便检查.
MOVBX,OFFSETVAR1;将变量VAR1的地址放入BX.
MOVBYTEPTR[BX],44h;修改变量VAR1内容
MOVAL,VAR1;将变量VAR1的值放入AL以便检查.
RET
VAR1DB22h
END
上面例子的功能相同。
这些语句:
LEABX,VAR1
MOVBX,OFFSETVAR1
都将生成同样的机器代码:
MOVBX,num,num是16位变量偏移
请注意,只有这些寄存器可以放入方括号中(作为内存指针)BX,SI,DI,BP(请参考本教程前述章节)
常量
常量同变量很相似,但是它一直存在。
定义一个变量之后,它的值不会改变。
使用EQU定义常量:
nameequ<任意表达式>例如:
kEQU5MOVAX,k
上面的例子等同于如下代码:
MOVAX,5
在程序执行过程中你可以选择模拟器"View"菜单下的"Variables"
你可以点一个变量然后设置Elements属性为数组大小来查看数组。
汇编语言对于数据类型并不严格,这样以来所有的变量都可以被看作是数组。
变量可以显示为下列进制
HEX-十六进制hexadecimal(基底16).
BIN-二进制(基底2).
OCT-八进制(基底8).
SIGNED-有符号十进制(基底10).
UNSIGNED-无符号十进制(基底10).
CHAR-ASCII码(一共有256个符号,其中一些符号是不可见的).
程序运行的时侯,你可以通过双击它来编辑变量值,或者选中之后点Edit按钮。
十六进制数值以"h"结尾,二进制以"b"结尾,八进制以"o"结尾,十进制没有结尾。
字符串用这样的方式表示:
'helloworld',0
(结尾以0表示)
数组按照如下输入:
1,2,3,4,5
(数组可以是一组字节或者字,这取决于你想以字节还是字的方式编辑)
表达式会自