Emu8086教程.docx

上传人:b****6 文档编号:7758819 上传时间:2023-01-26 格式:DOCX 页数:35 大小:559.69KB
下载 相关 举报
Emu8086教程.docx_第1页
第1页 / 共35页
Emu8086教程.docx_第2页
第2页 / 共35页
Emu8086教程.docx_第3页
第3页 / 共35页
Emu8086教程.docx_第4页
第4页 / 共35页
Emu8086教程.docx_第5页
第5页 / 共35页
点击查看更多>>
下载资源
资源描述

Emu8086教程.docx

《Emu8086教程.docx》由会员分享,可在线阅读,更多相关《Emu8086教程.docx(35页珍藏版)》请在冰豆网上搜索。

Emu8086教程.docx

Emu8086教程

%----------最详细的emu教程--------%

Emu8086集源代码编辑器,汇编/反汇编工具以及可以运行debug的模拟器(虚拟机器)于一身,它优于一般编译器的地方在于提供了一个虚拟的80x86环境,拥有自己一套独立的“硬件”,可以完成一些纯软件编译器无法完成的功能例如Led显示,交通灯,步进电机等等,而且动态调试(DEBUG)时非常方便。

简单的例子:

安装完成后选择菜单栏中的文件examplessteppermotor在编辑框出现了相应的源码。

点击compile编译选择一个文件保存

保存完之后会弹出一个对话框

点击run按钮则程序开始运行

调试时主控界面如下:

再次点击run可以停止运行

单击reload可以从头开始执行程序

单击singlestep可以单步调试。

单击stepback可以返回到上一条指令(这个功能也是一般调试器没有的)。

界面左边是寄存器栏,这里可以动态的观察每一步的执行结果

点击主控界面下面的screen可以显示模拟输出窗口

单击source可以查看源码窗口

Reset相当于上面的reload键。

单击aux会出现一个菜单选择第一项memory可以观察程序内存区数值的变化。

选择stoponcondition可以设置条件断点:

上面的设定当ax的值是0x0006是断下来

单击run按钮可以看到断下来的时候ax值正好等于6

单击vars可以查看运行过程中变量的变化。

Debug可以更详细的显示每一步的调试结果

通过Stack(堆栈)可以观察函数调用的过程

Flags显示标志寄存器的值。

如果刚刚执行的那一条指令修改了哪个标志位的值则以红色显示。

可以到这个网址去下载:

目前网上很多人找这个软件的破解版。

其实根本不用破解。

只要在用户名一栏输入任意的字符

注册码的前三位输入“112”就可以绕过注册认证了。

至于为什么我就不多说了有兴趣的可以看一下我在看雪论坛发表的一篇文章:

下面是在XX文库下载的一篇基础教程希望能对大家有所帮助。

如何运行?

1.在开始菜单选在它的图标,或者直接运行Emu8086.EXE

2.在"FILE"菜单中选择"SAMPLE"

3.点击"CompileandEmulate"按纽(或者按快捷键F5)

4.点击"SingleStep"按纽(或者按快捷键F8),可以查看代码如何运行.

十进制系统

目前使用最多的是十进制.十进制系统有10个数字0,1,2,3,4,5,6,7,8,9利用这些数字能表示任何数值,例如754这些数字是由每一位数字乘以“基数”的幂累加而成的(上一个例子中基数是10因为十进制中有十个数字)。

位置对于每一个数字是很重要的。

例如,你将上一个例子中的“7”放到结尾:

547

数值就成为:

特别提醒:

任何数字的0次幂都是1,0的0次幂也是1

二进制

计算机没有人类聪明(至少现在是这样),制造一个只有开关或者称为0,1两种状态的电子机器很容易。

计算机使用二进制系统,只有两个数字0,1基地为2每一位二进制数称作一位(BIT),4BIT组成一个半字节(NIBBLE),8BIT组成一个字节(BYTE),两个字节组成一个字(WORD),两个字组成一个双字(DOUBLEWORD)(很少使用):

习惯上在一串二进制后面加上“b”,这样,我们可以知道101b是二进制表示十进制的5。

二进制10100101b表示十进制的165,计算方法如下:

十六进制系统

十六进制系统使用16个数字0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F基底是16.十六进制非常紧凑,便于阅读。

将二进制转换为十六进制很容易,半字节(4bits)对应一位十六进制如下表

Decimal

(base10)

Binary

(base2)

Hexadecimal

(base16)

0

0000

0

1

0001

1

2

0010

2

3

0011

3

4

0100

4

5

0101

5

6

0110

6

7

0111

7

8

1000

8

9

1001

9

10

1010

A

11

1011

B

12

1100

C

13

1101

D

14

1110

E

15

1111

F

习惯上我们在一个十六进制数的后面加上"H",以便和其他进制区别,这样我们就知道5Fh是一个十六进制数表示十进制的 95。

习惯上,我们也在以字母开头(从A到F)的十六进制数前面加上"0" 例如:

0E120h.十六进制1234h 等于 4660:

十进制到另外进制的换算

在换算中,将十进制数不断除以目标进制的基底,每一次都要记录下商和余数,直到商0。

余数用来表示结果。

下面是一个十进制39(基底是10)到十六进制(基底是16)的换算:

结果为 27H

上例中所有的余数都小于10,不必使用字母。

再举一个更复杂的例子:

十进制43868换算为十六进制:

结果是0AB5Ch,使用上面提到的表将大于9的数字替换成字母。

运用同样的原理,我们可以换算为二进制(用2作除数),或者是先换算成十六进制,再用上面的表 换算成二进制:

于是,得到二进制:

1010101101011100b

有符号数

对于十六进制数 0FFh 无法确定它是正数还是负数,因为它可以表示十进制的"255"或者 "-1"。

8位可以表示256个状态,于是,我们可以假定前128个表示正数(从0到127),接下来的128个数(从128到256)表示负数。

如果想表示"-5",我们从256中减去5,即 256-5=251。

用这种复杂的方法表示一个负数有着数学依据的,数学上"-5"加上"5"等于0。

当我们将两个8位的数字 5和 251相加时,结果超过255,溢出处理为0!

128到256高位始终是1,这个可以作为数字符号的标记对于字(16位),16位有65536个状态,头32768个状态(从0到32767)用来表示正数,下面的32768个状态(从32767到65535)表示负数 

Emu8086 带有数制转换工具,也可以计算各种数值表达式。

选择菜单Math项:

NumberConvertor(数制转换)可以实现任意数制之间的转换。

在文本框中填写源

数值,将自动转换到任意的数制。

可以作8位或者16位转换。

ExpressionEvaluator(表达式计算)可以用来计算不同数制的计算以及从一个进制到另一个进制的转换。

输入表达式,按下回车,结果就会以你选定的进制表示。

最长可以进行32位的计算。

当在Signed打钩选中时(除了八进制和双字),最前面的一位将被认作是符号位。

这样以来,0FFFFFFFFh 将被认为是十进制的 -1。

例如,你计算 0FFFFh*10h+0FFFFh(8086CPU所能访问的最大内存地址)。

如果你选中Signed和Word 选项,结果是 -17 (因为表达式被认为是 (-1)*16+(-1))。

如果想按照无符号数计算,请不要选择 Signed 表达式为 65535*16+65535 计算结果将是1114095同样你可以使用NumberConvertor将非十进制换算为有符号的十进制,然后根据十进制计算。

支持如下运算:

~not (invertsallbits).

*multiply.

/divide.

%modulus.

+sum.

-subtract(andunary-).

<

>>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位的,意思是:

 0011000000111001b(二进制),或者12345(十进制形式)。

4个通用寄存器(AX,BX,CX,DX)在使用时分为两个8位寄存器,例如假设AX=0011000000111001b,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,memory

MOVmemory,REG

MOVREG,REG

MOVmemory,immediate

MOVREG,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,10001101b,等等.

mov 指令只支持如下段寄存器:

MOVSREG,memory

MOVmemory,SREG

MOVREG,SREG

MOVSREG,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设置为二进制的01011111b

MOVBX,15Eh       ; 将BX设置成15Eh.

MOV[BX],CX      ;将CX放到bx指出的内存单元B800:

015E

RET                   ;返回操作系统

你可以将上面的程序贴入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.-等价于上面的WORDPT

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1