汇编语言,Emu8086使用指南

更新时间:2024-04-23 22:23:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

十进制系统

目前使用最多的是十进制.十进制系统有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),4 BIT 组成一个半字节(NIBBLE),8BIT组成一个字节(BYTE),两个字节组成一个字(WORD),两个字组成一个双字(DOUBLE WORD)(很少使用):

习惯上在一串二进制后面加上“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 Binary Hexadecimal (base 10) (base 2) (base 16) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0 1 2 3 4 5 6 7 8 9 A B C D E F

习惯上我们在一个十六进制数的后面加上 \,以便和其他进制区别, 这样我们就知道 5Fh是一个十六进制数表示十进制的 95。习惯上,我们也在以字母开头(从A到F)的十六进制数前面 加上\例如: 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 项:

Number Convertor (数制转换)可以实现任意数制之间的转换。在文本框中填写源数值,将自动转换到任意的数制。 可以作 8 位 或者 16 位转换。Expression Evaluator(表达式计算)可以用来计算不同数制的计算以及从一个进制到另一个进制的转换。输入表达式,按下回车,结果就会以你选定的进制表示。最长可以进行32位的计算。当在Signed打钩选中时(除了八进制和双字),最前面的一位将被认作是符号位。这样以来,0FFFFFFFFh 将被认为是十进制的 -1。例如,你计算 0FFFFh * 10h + 0FFFFh ( 8086 CPU所能访问的最大内存地址)。如果你选中Signed 和 Word 选项,结果是 -17 (因为表达式被认为是 (-1) * 16 + (-1) )。如果想按照无符号数计算,请不要选择 Signed 表达式为 65535 * 16 + 65535 计算结果将是1114095 同样你可以使用Number Convertor将非十进制换算为有符号的十进制,然后根据十进制计算。支持如下运算:

~ not (inverts all bits). * multiply. / divide. % modulus. + sum.

- subtract (and unary -). << shift left. >> shift right. & bitwise AND. ^ bitwise XOR. | bitwise OR.

二进制必须有“b”作结尾,例如00011011b 十六进制必须有\h\作结尾,另外,当地一位是字母时,最前面必须加上0,例如:0ABCDh八进制必须有\o\作结尾,例如:77o

通用寄存器

8086CPU有8个通用寄存器,每一个寄存器都有自己的名称:

? ? ? ? ? ? ? ?

AX 累加寄存器 accumulator register(分为 AH / AL). BX 基址寄存器 base address register (分为 BH / BL). CX 计数寄存器 count register(分为 CH / CL ). DX 数据寄存器 data register (分为 DH / DL). SI 源变址寄存器 source index register. DI 目的变址寄存器 destination index register. BP 基址指针寄存器 base pointer. SP 堆栈寄存器 stack pointer.

编程中,由程序员决定通用寄存器的具体用途。寄存器的主要目 的是保存数值(变量)。上面提到的寄存器是16位的,意思是: 0011000000111001b (二进制),或者12345(十进制形式)。4个通用寄存器(AX, BX, CX, DX) 在使用时分为两个8位寄存器,例如 假设AX= 0011000000111001b,AH=00110000b AL=00111001b。

当你修改其中任意8位值,整个16位寄存器的值同样改变。同样对于其他的3个寄存器,“H”表示高8位,“L”表示低8位。寄存器在CPU内部,访问中它们速度远远超过内存。因为,访问内存需要经过系统总线,所以时间要长一些。而访问寄存器中的数据几乎不需要时间。于是,编程中,应当尽量在寄存器中保存数据。虽然寄存器很小,并且这些寄存器都有具体用途,但他们依然是存放计算中临时数据的好地方。

段寄存器

? ? ? ?

CS 代码段寄存器,用来存放当前正在运行的指令 DS 数据段寄存器,用来存放当前运行程序所用的数据 ES 附加段寄存器,由程序员决定用途 SS 堆栈段寄存器,指出堆栈所在区域

尽管容许在段寄存器中存放任何数据,但是这决不是 一个好主意。段寄存器有着非常特别的目的--指出可以访问内存块的地址。段寄存器与通用寄存器协同工作就可以访问任意的内存区域。例如,如果我们打算访问物理地址是12345h(十六进制)的内存单元,我们应设置DS = 1230h SI = 0045h 这样以来,我们便能访问超过一个寄存器(16位)所能表示的内存地址的范围。CPU计算物理地址的方法是将段寄存器乘以10H在加上一个特定的通用寄存器。(1230h * 10h + 45h = 12345h):

这种,由两个寄存器生成的地址被称为有效地址 (effective address)

默认下,BX, SI 及 DI 与 DS协同工作,BP SP 与 SS 寄存器协同工作。其余的通用寄存器不能形成有效地址!同样,尽管BX可以形成有效地址,但是BH BL不能!控制寄存IP 指令指针寄存器 instruction pointer 、Flags Register 状态标志寄存器

IP 始终同CS 协同工作,指出当前执行的指令。 Flags Register 完成一次数学运算后,由CPU自动修改,通过它可以得到当前结果类型,也可以作为跳转语句条件。通常你无法直接访问它们。

寻址方式

我们可以通过下面的四个寄存器来寻址 BX, SI, DI, BP.

通过计算[]符号中的值,我们可以访问到不同内存单元的值。具体组合请看下表: [BX + SI] [BX + DI] [BP + SI] [BP + DI] [SI] [DI] d16 (variable offset only) [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在编译过程中使用如下声明数据类型

BYTE PTR - 表示字节 ; WORD PTR - 表示字(2个字节) 例如:BYTE PTR [BX] ;按字节访问 ; WORD PTR [BX] ;按字访问 Emu8086 容许使用如下更简洁的前缀

b. - 等价于上面的 BYTE PTR ; w. - 等价于上面的 WORD PTR

有时,编译器可以自动计算出数据类型,但是如果一个参与运算的数是立即数,这种方法就不可靠了。

MOV 指令

将第二个操作数(源)拷贝到第一个操作数(目的)指定位值 ,源操作数可以是立即数,通用寄存器或者内存单元,目的寄存器可以是通用寄存器或者内存单元 ,源和目的必须是同样大小,要么都是字节要么都是字 操作类型如下: MOV REG, memory MOV memory, REG MOV REG, REG MOV memory, immediate MOV REG, 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 指令只支持如下段寄存器: MOV SREG, memory MOV memory, SREG MOV REG, SREG MOV SREG, 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程序 ORG 100h ;COM 程序必须的 MOV AX, 0B800h ; 将ax设置为 B800h. MOV DS, AX ; 将 AX 值拷贝到 DS. MOV CL, 'A' ; 将ASCII 码 'A'的值传送到cl,这个值是 41h. MOV CH, 01011111b ; 将ch设置为二进制的01011111b MOV BX, 15Eh ; 将 BX 设置成 15Eh. MOV [BX], CX ; 将 CX 放到 bx 指出的内存单元 B800:015E RET ; 返回操作系统 你可以将上面的程序贴入Emu8086代码编辑器,接下来按下[complie and emulate] (或者按F5) 模拟窗口将显示这个程序已经调入,点击[single step]观察寄存器数值变化,你可以猜到 \表示注释,编译器忽略在\后面的一切,程序结束后,你可以看到如下窗口

事实上,上面程序是将字符直接写入显示内存。

通过上面的例子,你可以发现 MOV 指令是非常有用的。

变量

变量是一个内存地址。对于编程者来说,使用诸如名称为“var1”这样的 变量保存数据远远比使用5a73:235b这样的地址容易的多。特别是当你使用10个以上的变量的时侯。 编译器支持这两种变量 BYTE 和 WORD.(字节和字) 声明变量的方法: name DB value 名称 DB 值 name DW value 名称 DW 值 DB - stays for Define Byte. DW - stays for Define Word. name -可以是任何字母与数字构成,但是必须由字母开头。可以通过不命名来声明一个 没有名称的的变量(这个变量只有地址,没有名称) value - 可以是任何数值支持三种进制(十六进制,二进制和十进制),你可以使用\符号表示初始值没有确定。 你可能从第二章了解到, MOV 指令是将数值从源拷贝到目的。 让我们再看一个 MOV 指令的例子 #MAKE_COM# ORG 100h MOV AL, var1 MOV BX, var2 RET ; stops the program. VAR1 DB 7 var2 DW 1234h 将上面的代码拷贝到emu8086源程序编辑器中,按下F5键编译 并在模拟器中执行。你会看到如下画面

从画面可以看出,反编译后的代码同源程序很相似,不同的是变量被具体的内存地址取代。当编译器生成机器代码它会自动将变量名称用该变量的便宜量代替。默认情况下,DS 寄存器存放段偏移(当执行com文件的时侯,DS 寄存器的值同 CS 寄存器(代码段)的值一样)。内存第一列是偏移(offset),第二列是一个十六进制值(hexadecimal value),第三列是十进制(decimal

value),最后一列是 ASCII 字符。编译器是非大小写敏感的,所以 “VAR1” 同 “var1” 都是同一个变量。 VAR1变量的偏移是0108h,物理地址是0b56:0108 var2 变量的偏移是0109h,物理地址是 0b56:0109

这个变量是字,它占用2字节。这里假定低字节存放在低地址,所以34h位于12h前面。

你可以看到,在RET指令后面还有一些指令,这样是因为反编译工具无法判断数据从什么地方开始。同样,你可以写出直接使用DB的程序. #MAKE_COM# ORG 100h DB 0A0h DB 08h DB 01h DB 8Bh DB 1Eh DB 09h DB 01h DB 0C3h DB 7 DB 34h DB 12h 将上面的代码拷贝到emu8086原代码编辑器,按下F5键编译,并在模拟器中运行,你可以看到同样的反汇编结果,得到同样的功能。根据上面,你可以猜测,编译器将源程序转化为一些字节的集合,这个集合被称作机器代码(machine code),处理器懂得他们,并且执行它们。ORG 100是一个编译指令(它告诉编译器如何处理源代码)当你使用变量的时侯,这条指令特别重要。它通知编译器可执行程序将被调入偏移量是100h(256字节)的位置,有了它,编译器就可以计算出所有变量的正确地址,然后用这些地址(偏移量)来代替变量名称。上面的这些指令不会真正的编译为任何机器代码。为何可执行程序总是被装入偏移量100h?操作系统在CS寄存器(代码段)存储着程序信息,比如命令行方式下的参数等等。尽管上面只是一个COM文件的例子,EXE文件调入在偏移量0000的位置,他使用特定的段保存变量。我们在下面会学习到关于EXE文件的知识。

数组

数组可以看作是变量链。一个字符串是一个字节数组的例子,其中每一个字符都当作一个ASCII码的值(0....255)下面是一些定义数组的例子

a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h b DB 'Hello', 0

b是一个数组,当编译器发现引用了字符串值后,会自动将这些字符转化为对应的字节。下面图表表示的就是声明数组后在内存中的分布:

你可以使用方括号做下标直接访问到数组中的值,例如:

MOV AL, a[3] 同样,你还可以使用任意一个内存索引寄存器BX, SI, DI, BP,例如: MOV SI, 3 MOV AL, a[SI]

如果你想声明比较复杂的数组,你可以使用DUP指令 形式如下number DUP ( value(s) ) number - 重复的数量(任意常数) value - 将要复制的表达式

例如:c DB 5 DUP(9) 就相当于如下定义:c DB 9, 9, 9, 9, 9 另外一个例子:d DB 5 DUP(1, 2) 等同于d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 当然,如果需要存放超过255或者小于-128的数值,你还可以使用DW来代替 DB。但是DW不能用于声明字符串。DUP命令展开后不能超过1020个字符(上一个例子中展开之后是13个字符),如果需要声明请将它们分成两行(这样,内存中得到的仍然是一个大数组)。取得变量地址 LEA指令(Load Effective Address 读取有效地址)或者OFFSET指令。OFFSET 和 LEA二者都能够获得变量的偏移量。LEA在使用中更有效,这是因为它能返回索引变量的地址。取得变量地址在很多情况下是非常有用的,例如你打算向一个过程传递参数。注意:在编译过程中使用如下声明数据类型 BYTE PTR - 表示字节 ;WORD PTR - 表示字(2个字节) 例如:

BYTE PTR [BX] ;按字节访问 ; WORD PTR [BX] ;按字访问 Emu8086 容许使用如下更简洁的前缀

b. - 等价于上面的 BYTE PTR ;w. - 等价于上面的 WORD PTR

有时,编译器可以自动计算出数据类型,但是如果一个参与运算的数是立即数,这种方法就不可靠了。 第一个例子: ORG 100h MOV AL, VAR1 ; 将变量var1的数值放入al以便检查 LEA BX, VAR1 ; 将var1的地址存入 BX. MOV BYTE PTR [BX], 44h ; 修改变量var1的内容 MOV AL, VAR1 ; 将变量VAR1的数值放入AL以便检查 RET VAR1 DB 22h END 下面是另外一个例子,用OFFSET指令代替LEA: ORG 100h MOV AL, VAR1 ; 将变量VAR1的值放入AL以便检查. MOV BX, OFFSET VAR1 ; 将变量VAR1的地址放入 BX. MOV BYTE PTR [BX], 44h ; 修改变量VAR1内容 MOV AL, VAR1 ;将变量VAR1的值放入 AL以便检查. RET VAR1 DB 22h END 上面例子的功能相同。 这些语句: LEA BX, VAR1

MOV BX, OFFSET VAR1

都将生成同样的机器代码: MOV BX, num,num 是16位变量偏移

请注意,只有这些寄存器可以放入方括号中(作为内存指针)BX, SI, DI, BP(请参考本教程前述章节) 常量

常量同变量很相似,但是它一直存在。定义一个变量之后,它的值 不会改变。使用EQU定义常量:name equ <任意表达式> 例如: k EQU 5 MOV AX, k 上面的例子等同于如下代码: MOV AX, 5 在程序执行过程中你可以选择模拟器\菜单下的\

你可以点一个变量然后设置Elements属性为数组大小来查看数组。汇编语言对于数据类型并不严格,这样以来所有的变量都可以被看 作是数组。

变量可以显示为下列进制

HEX - 十六进制 hexadecimal (基底 16). BIN - 二进制 (基底 2). OCT - 八进制 (基底 8).

SIGNED - 有符号十进制 (基底 10). UNSIGNED - 无符号十进制 (基底 10).

CHAR - ASCII 码 (一共有256个符号,其中一些符号是不可见的).

程序运行的时侯,你可以通过双击它来编辑变量值,或者选中之后点Edit按钮。

十六进制数值以\结尾,二进制以\结尾,八进制以\结尾,十进制没有结尾。字符串用这样的方式表示:'hello world',0 (结尾以0表示)

数组按照如下输入:1, 2, 3, 4, 5

(数组可以是一组字节或者字,这取决于你想以字节还是字的方式编辑) 表达式会自动计算,例如,输入如下表达式5 + 2会自动计算为7。等等....

中断

中断是一系列功能调用。这些功能调用使得编程更加容易。比如,你想在打印机上输出一个字符,你只需要简单的调用中断,它将帮你完成所有的事情。另外还有控制磁盘和其他硬件工作的中断。我们将这些功能调用称作软件中断。不同的硬件同样可以触发中断,这些中断称作硬件中断。这里,我们只介绍软件中断(software interrupts)。触发一个软件中断,需要使用INT指令,它的使用方式非常简单: INT value

上面value的取值范围是从 0 到 255 (或者0到0ffh),通常我们使用十六进制。 你也许猜测只有256个中断调用,但是这是不正确的。因为每一个中断都有子功能。

在调用一个中断的子功能之前,需要设置AH寄存器。每一个中断最多可以拥有256个子功能(于是,我们有256*256=65536个功能调用)。一般情况下使用AH寄存器,但是一些情况下可能使用另外的寄存器。通常,其他的寄存器是用来传递数据和参数的。

下面的例子调用了 INT 10h中断0Eh子功能输出字符串‘Hello!'。这个功能作用是在屏幕上显示一个字符,然后光标进一,如果需要还滚屏。 #MAKE_COM# ; 生成com文件的指令 ORG 100h ;我们使用的这个子功能没有返回值, ;所以我们只用设置就可以了。 MOV AH, 0Eh ; 选择子功能 ;int 10h/0eh 子功能,输出放在 ;AL寄存器中的ASCII码对应的字符 MOV AL, 'H' ; ASCII码: 72 INT 10h ; 输出 MOV AL, 'e' ; ASCII 码: 101 INT 10h ; 输出 MOV AL, 'l' ; ASCII 码: 108 INT 10h ; 输出 MOV AL, 'l' ; ASCII 码: 108 INT 10h ; 输出 MOV AL, 'o' ; ASCII 码: 111 INT 10h ; 输出 MOV AL, '!' ; ASCII 码: 33 INT 10h ; 输出 RET ; 返回操作系统 将上述程序拷贝粘贴到Emu8086代码编辑器,点击 [Compile and Emulate] 按钮,运行!

常用函数库 - emu8086.inc

通过引用一些常用函数,可以使你编程更加方便。在你的程序中使用其他文件中的函数的方法是 INCLUDE后面接上你要引用的文件名。编译器

会自动在你源程序所在的文件夹中查找你引用的文件,如果没有找到,它将搜索Inc 文件夹。通常你无法完全理解 emu8086.inc(位于Inc文件夹)但是这没有关系,你只用知道它能做什么就足够了。要使用emu8086.inc中的函数,你应当在你程序的开头加上 include 'emu8086.inc' emu8086.inc 定义了如下的宏:

PUTC char - 将一个ascii字符输出到光标当前位值,只有一个参数的宏 GOTOXY col, row - 设置当前光标位置,有两个参数 PRINT string - 输出字符串,一个参数

PRINTN string - 输出字符串,一个参数。与print功能相同,不同在于输出之后自动回车 CURSOROFF - 关闭文本光标 CURSORON - 打开文本光标

使用上述宏的方法是:在你需要的位值写上宏名称加上参数。例如: include emu8086.inc ORG 100h PRINT 'Hello World!' GOTOXY 10, 5 PUTC 65 ; 65 - ASCII 码的 'A' PUTC 'B' RET ; 返回操作系统 END ; 停止编译器 当编译器运行你的代码时,它首先找到声明中的emu8086.inc文件,然后将代码中的宏用实际的代码替换掉。通常来说,宏都是比较小的代码段,经常使用宏会使得你的可执行程序特别大(对于降低文件大小来说使用过程更好) emu8086.inc 同样定义了如下过程:

PRINT_STRING - 在当前光标位置输出一个字符串字符串地址由DS:SI 寄存器给出使用时,需要在END前面声明

DEFINE_PRINT_STRING 才能使用.

PTHIS - 在当前光标位置输出一个字符串(同 PRINT_STRING)一样,不同之处在于它从堆栈接收字符串。字符串终止符 应在call之后定义。例如

CALL PTHIS

db 'Hello World!', 0

使用时,需要在 END 前面声明

DEFINE_PTHIS 。GET_STRING - 从用户输入得到一个字符串,输入的字符串写入 DS:DI 指出的缓冲,缓冲区的大小由 DX设置。回车作为输入结束。使用时,需要在END前面声明

DEFINE_GET_STRING 。CLEAR_SCREEN - 清屏过程(滚过整个屏幕),然后将光标设置在左上角. 使用时,需要在END前面声明DEFINE_CLEAR_SCREEN 。

SCAN_NUM - 取得用户从键盘输入的多位有符号数,并将输入存放在CX寄存器。 使用时,需要在 END前面声明 DEFINE_SCAN_NUM。

PRINT_NUM - 输出AX寄存器中的有符号数。使用时,需要在END 前面声明 DEFINE_PRINT_NUM以及 DEFINE_PRINT_NUM_UNS.

PRINT_NUM_UNS - 输出AX寄存器中的无符号数。使用时,需要在END 前面声明DEFINE_PRINT_NUM_UNS.

使用上述过程,必须在你源程序的底部(但是在END之前!!!)声明这些函数,使用CALL指令后面接上过程名称来调用。例如: include 'emu8086.inc' ORG 100h LEA SI, msg1 ; 要求输入数字 CALL print_string ; CALL scan_num ; 读取数字放入cx MOV AX, CX ; CX存放数值拷贝到AX; 输入如下字符 CALL pthis DB 13, 10, 'You have entered: ', 0 CALL print_num ; 输出 AX中的字符 RET ; 返回操作系统 msg1 DB 'Enter the number: ', 0 DEFINE_SCAN_NUM DEFINE_PRINT_STRING DEFINE_PRINT_NUM DEFINE_PRINT_NUM_UNS ; print_num函数要求的 DEFINE_PTHIS END ; 结束 首先,编译器运行声明(对于宏只是展开)。当编译器遇到CALL指令,它 将用过程声明中的地址来替代过程名。程序在执行过程中遇到这个过程,便会直接跳转到过程。这是非常有用的,比如,即使在你的代码中执行100次一个过程,编译后的可执行文件也不会因此而增大多少。这样看起来很划算,是不是?后面你会学到更多的,现在只需要了解一点点基本原理。

运算与逻辑指令

大多数运算与逻辑指令影响处理器的状态标记寄存器。

从上图可以看到,这是状态标记寄存器是一个16位寄存器,每一位称作一个标志位,可以取值 1 或者 0 。

进位标志 Carry Flag (CF) - 出现无符号(unsigned overflow)溢出该位设置成1。例如,计算 255+1(结果超出0...255)。没有溢出时该位为0。

零标志 Zero Flag (ZF) - 当结果为 0 时设置为1,结果不为 0 时设置为0。

符号标志 Sign Flag (SF) - 结果为负置1,结果为正置为0。事实上该位对于结果特别重要。

溢出标志 Overflow Flag (OF) - 当出现有符号数溢出设置为1。例如,计算100+50(结果超出-128-127的范围)。 奇偶标志 Parity Flag (PF) - 当结果操作数中1的个数为偶时置1,否则为0注意,如果结果是一个字,该标志只指示低8位。

辅助进位标志 Auxiliary Flag (AF) - 低4位向上进位时置1,否则为0(记录运算时第3位(半个字节)产生的进位值。例如,执行加法指令时,最高有效位有进位时置1,否则置0

中断标志 Interrupt enable Flag (IF) - 当cpu容许中断时为1,否则为0

Direction Flag (DF) - 方向标志,在串处理指令中控制处理信息的方向用。当DF为1时,每次操作后使变址寄存器SI和DI减量,这样就使串处理从高地址向低地址方向处理。当DF为0时,则使SI和DI增量,使串处理从低地址向高地址方向处理。

这里有3组指令.

第一组: ADD, SUB,CMP, AND, TEST, OR, XOR 支持如下操作数: REG, memory memory, REG REG, REG

memory, immediate REG, 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, 等等...

执行之后,结果经常存放在第一个操作数中。CMP和TEST指令只影响标志位,并不返回数值(这两条指令是用来在程序运行中判断的)上述指令只影响如下标志位 : CF, ZF, SF, OF, PF, AF.

ADD - 将第二个操作数加至第一个操作数上 SUB - 从第一个操作数中减去第二个操作数

CMP - 从第一个操作数中减去第二个操作数,但只影响标志位. AND - 两个操作数各个位逻辑与运算。运算法则如下

1 AND 1 = 1 1 AND 0 = 0 0 AND 1 = 0 0 AND 0 = 0

只有当两个操作数都是1时,运算结果才是1。 TEST - 和上面的and 操作一样,但是只影响标志位。 OR - 两个操作数各个位逻辑或运算。运算法则如下 1 OR 1 = 1 1 OR 0 = 1 0 OR 1 = 1 0 OR 0 = 0 如果操作数中有1那么结果一定是1。

XOR - 两个操作数各个位逻辑异或运算。运算法则如下 1 XOR 1 = 0 1 XOR 0 = 1 0 XOR 1 = 1 0 XOR 0 = 0 当两个操作数不同时,结果为1。 第二组: MUL, IMUL, DIV, IDIV 支持如下操作数: REG memory

REG(寄存器): AX, BX, CX, DX, AH, AL, BL, BH, CH, CL, DH, DL, DI, SI, BP, SP. memory(内存): [BX], [BX+SI+7], variable, etc... MUL and IMUL 指令只影响 CF, OF标志位。

运算后如果结果超出范围,这些标记位置1,如果没有超过范围,置0 DIV 和 IDIV 指令对于标志位无影响 MUL - 无符号乘:

当操作数是字节时: AX = AL * 操作数. 当操作数是字时: (DX AX) = AX * 操作数. IMUL - 有符号乘法:

当操作数是字节时: AX = AL * 操作数. 当操作数是字时: (DX AX) = AX * 操作数. DIV - 无符号除法:

当操作数是字节时: AL = AX / 操作数 AH = 余数(取模后的余数) . 当操作数是字时:

AX = (DX AX) / 操作数 DX = 余数(取模后的余数) IDIV - 有符号除法: 当操作数是字节时:

AL = AX / 操作数 AH =余数(取模后的余数) 当操作数是字时:

AX = (DX AX) / 操作数 DX = 余数(取模后的余数) . 第三组: INC, DEC, NOT, NEG 支持如下操作数: REG memory

REG(寄存器): AX, BX, CX, DX, AH, AL, BL, BH, CH, CL, DH, DL, DI, SI, BP, SP. memory(内存): [BX], [BX+SI+7], variable, etc... INC, DEC 指令只影响如下标志位: ZF, SF, OF, PF, AF. NOT 指令不影响任何标志位!

NEG i指令只影响如下操作位: CF, ZF, SF, OF, PF, AF.

NOT - 对与操作数每一位取反 NEG - 对操作数取反

实际上它对每一位取反然后在最后一位加1。例如5会变成-5,-2会变成2。(这里所说运算应当是计算机内部的补码运算) 程序控制转移

对于编程来说控制程序走向是非常重要的事情,它是你的程序根据条件作出判断,跳转到相应的位值。 无条件跳转

控制程序转向的最基本的指令是JMP. 使用形式如下: JMP label

在程序中声明/label/的方法很简单,只要在它名字后面加上“:”,

label可以由任何字符混合而成但是不能由数字开头,例如,下面是3个合法的label label1: label2: a:

label可以在一条指令的前面声明,例如: x1: MOV AX, 1 x2: MOV AX, 2

下面是一个 JMP 指令的例子: ORG 100h MOV AX, 5 ; 将 AX 设置为 5. MOV BX, 2 ; 将 BX 设置为 2. JMP calc ; 跳转到 'calc'. back: JMP stop ; 跳转到 'stop'. calc: ADD AX, BX ; 将 BX 加到 AX. JMP back ; 返回 'back'. stop: RET ; 返回操作系统 END 当然有更简单的计算这两个数字之和的方法,但是上面是一个JMP指令的很好的例子。 从例子中可以看出,JMP可以控制向前和向后。它可以转移到当前代码段的任意位置(65535字节)。短条件转移与JMP这一无条件转移指令不同,还有一些有条件跳转指令(只有在条件成立的时侯才跳转)。这些指令分为三组,第一组是只检测单独标记位,第二组比较有符号数,第三组比较无符号数。检测单独标记位的转移指令 指令 JZ , JE JC , JB, JNAE JS JO 说明 如果为0(相等),转移 . 如果进位 (小于, 不大于等于),转移 如果是负数,转移 如果溢出,转移 条件 ZF =1 CF = 1 SF = 1 OF = 1 相反指令 JNZ, JNE JNC, JNB, JAE JNS JNO

JPE, JP JNZ , JNE JNC , JNB, JAE JNS JNO JPO, JNP 如果是偶数,转移 如果不为0(不相等),转移 如果没有进位(大于, 大于等于),转移 如果不是负数,转移 如果没有溢出,转移 如果不是偶数,转移 PF = 1 ZF = 0 CF = 0 SF = 0 OF = 0 PF = 0 JPO JZ, JE JC, JB, JNAE JS JO JPE, JP 可以看到一些指令功能相同,对,他们编译之后生成相同机器码所以很容易理解为什么你编译 JE 指令而反编译得到的却是JZ.使用不同的名称是为了使程序更容易理解。比较有符号数的转移指令 指令 JE , JZ JNE , JNZ 说明 如果等于 (=),如果为0,跳转 如果不等于 (<>),如果不等于0,跳转 条件 ZF = 1 ZF = 0 ZF = 0 JG , JNLE 如果大于 (>) 如果不小于等于 (not <=),跳转 和 SF = OF JL , JNGE JGE , JNL JLE , JNG

<> - 符号表示不等于. 比较无符号数转移指令 指令 JE , JZ JNE , JNZ 说明 如果等于 (=).,如果为0,跳转 如果不等于(<>),如果不为0,跳转 条件 ZF = 1 ZF = 0 CF = 0 JA , JNBE 如果大于 (>),如果不小于等于(not <=),跳转 and ZF = 0 JB , JNAE, JC JAE , JNB, JNC 如果小于 (<),如果不大于等于(not >=),如果进位,跳转 如果大于等于(>=),如果不小于 (not <),如果没有进位,跳转 CF = 1 CF = 0 JNB, JAE, JNC JNAE, JB JNA, JBE 相反指令 JNE, JNZ JE, JZ 如果小与Jump if Less (<) 如果不大于等于 (not >=),跳转 如果大于等于 (>=),如果不小于 (not <),跳转 如果小于等于 (<=),如果不大于 (not >),跳转 SF <> OF SF = OF ZF = 1或者SF <> OF JNL, JGE JNGE, JL JNLE, JG JNG, JLE 相反指令 JNE, JNZ JE, JZ JBE , JNA 如果小于或者等于(<=),如果不大于 (not >),跳转 CF = 1or ZF = 1 JNBE, JA 一般来说,需要使用CMP指令来比较数值(该指令与 SUB(减法) 指令相近,只不过不保存结果,而只修改标值位)上面说法的意思是,例如:需要比较5 和2, 5-2 =3结果不是0(0标值位设置为 0)另一个例子比较 7和7

7 - 7 = 0结果为0! (0标值位设置为1。 JZ 或者 JE 会转移). 下面是一个 CMP 指令和条件转移指令的例子: include emu8086.inc include emu8086.inc ORG 100h MOV AL, 25 ; 设置AL为 25. MOV BL, 10 ; 设置BL为10. CMP AL, BL ; 比较 AL - BL. JE equal ; 如果 AL = BL (ZF = 1) 跳转 PUTC 'N' ; 如果到这里,说明 AL <> BL, JMP stop ; 打印'N', 跳转到结束 equal: ; 如果到这里 PUTC 'Y' ; 则 AL = BL,打印'Y'. stop: RET END 请用用不同的数字试验取代上述 AL 和 BL,点击[FLAGS]键打开标志,使用[Single Step]观察发生了什么,不要忘记每一次修改之后重新编译运行(快捷键F5)。

全部的条件转移指令都有一个很大的限制,就是与 JMP 指令不同,他们只能向前跳转127字节或者向后跳转128字节(注意大多数指令编译之后是3个或者更多字节)我们可以用如下小技巧解决这一问题:

从上述表中找到一条相反条件的转移指令,令其跳转到 label_x. 用JMP指令跳转到你想要的地方在JMP指令后面定义label_x: label_x: - 可以是任意合法标号. 下面是一个例子: include emu8086.inc ORG 100h MOV AL, 25 ; 设置 AL 为 25. MOV BL, 10 ; 设置 BL 为 10. CMP AL, BL ; 比较 AL - BL. JNE not_equal ; 如果 AL <> BL (ZF = 0),转移 JMP equal not_equal: ; 假定这里还有编译之后超过127字节的程序 PUTC 'N' ; 如果执行到这里,说明 AL <> BL, JMP stop ; 打印 'N', 转移到程序结束。 equal: ; 如果执行到这里, PUTC 'Y' ; 说明 AL = BL, 打印 'Y'. stop: RET ; 上述都要执行这一条 END 另外,可以使用立即数来代替标号。立即数前使用“$”编译器将直接得到偏移。例如: ORG 100h ; 无条件向前转移 ; 跳过后面2字节 JMP $2 a DB 3 ; 1 byte. b DB 4 ; 1 byte. ; JCC 跳过 7 字节: ; (JMP 本身占用 2 字节) MOV BL,9 DEC BL ; 2 bytes. CMP BL, 0 ; 3 bytes. JNE $-7 RET END 堆栈 堆栈是内存中用于保存临时数据的一片区域.当使用CALL指令时,堆栈用于保存过程的返回地址,RET指令能够从堆栈中取得该地址并使程序返回到那里。当使用INT指令,发生的也与此类似。

堆栈保存标志寄存器,代码段和偏移量。IRET指令用来从中断返回。 我们同样可以使用堆栈保存任何数据。对于堆栈的操作只有两条:

PUSH - 将16位数值压入堆栈. POP - 将16位数值从堆栈中弹出 PUSH 指令的使用方法: PUSH REG PUSH SREG PUSH memory PUSH immediate REG(寄存器): AX, BX, CX, DX, DI, SI, BP, SP. SREG(段寄存器): DS, ES, SS, CS. memory(内存): [BX], [BX+SI+7], 16 位变量, 等等... immediate(立即数): 5, -24, 3Fh, 10001101b,等等... POP 指令的使用方法: POP REG POP SREG POP memory REG(寄存器): AX, BX, CX, DX, DI, SI, BP, SP. SREG(段寄存器): DS, ES, SS, (除了 CS). memory(内存): [BX], [BX+SI+7], 16位变量, 等等... 注意: PUSH and POP 都只操作16位数据! 注意:

在80186其极以后的CPU中才能使用 PUSH 立即数这样的指令堆栈使用LIFO(后进先出)算法,意思是:加入我们按照如下顺序压入数值: 1, 2, 3, 4, 5

再使用POP指令弹出,结果将是 5 4 3 2 1

注意,有多少条PUSH指令就要对应有多少条POP指令,否则堆栈会被占用,无法正确返回操作系统。前面讲过使用RET指令返回操作系统,所以在程序开始时会将返回地址压入堆栈(通常都是0000h)I PUSH 和 POP指令在我们寄存器不够用的时侯特别有用,我们有如下技巧:

将寄存器原始数值存入堆栈(使用 PUSH)使用寄存器从堆栈中弹出寄存器原先数值再放入寄存器(使用POP) 下面是一个例子: ORG 100h MOV AX, 1234h PUSH AX ; 将 AX 存入堆栈. MOV AX, 5678h ; 修改 AX 值 POP AX ;返回 AX 原先的值 RET END 堆栈的另外一个作用是交换数值,下面是一个这样的例子: ORG 100h MOV AX, 1212h ; 将 1212h 存入 AX. MOV BX, 3434h ; 将 3434h 存入 BX PUSH AX ; 将 AX 数值存入堆栈. PUSH BX ; 将 BX 数值存入堆栈 POP AX ; BX原值存入AX POP BX ; AX原值存入BX RET END 之所以能这样是因为堆栈是用LIFO(后进先出)算法,当我们压入1212h和3434h之后,使用pop弹出我们首先得到的是3434h然后才是1212h

堆栈的内存区域由SS寄存器(堆栈段),SP寄存器(栈指针)设置设置。一般来说操作系统在程序开始时会设置这些。 \源\指令做如下工作: 将SP寄存器减 2

将源的值写入内存SS:SP地址处 \目的\指令做如下工作: 内存SS:SP地址处数值写入目的 将SP寄存器加2

由 SS:SP 指出的地址称作堆栈顶

对于COM文件,堆栈段通常就是代码段,堆栈指针设置为

0FFFEh.在地址SS:0FFFEh处存放程序结束时RET指令返回地址。你可以点击[stack]按钮直接观察堆栈操作。堆栈顶由“<”符号标记。 宏

宏与过程很相似,但并不是完全相似。宏看起来像过程,但是当你的代码编译完成之后就消失了,取而代之的是真正的代码。如果你声明一个宏,而在代码中从来没有调用,编译器在编译过程中将忽略它。 宏的定义 : name MACRO [参数,] <指令> ENDM 与过程不同,宏要求定义参数并使用。例如: MyMacro MACRO p1, p2, p3 MOV AX, p1 MOV BX, p2 MOV CX, p3 ENDM ORG 100h MyMacro 1, 2, 3 MyMacro 4, 5, DX RET 上述代码在编译过程中将展开成: MOV AX, 00001h MOV BX, 00002h MOV CX, 00003h MOV AX, 00004h MOV BX, 00005h MOV CX, DX

关于宏与过程需要注意如下要点: 当你想使用一个过程,你应该使用CALL指令,例如:CALL MyProc 当你想使用一个宏,你只需要输入它的名称。例如:MyMacro 过程是存在于内存中某一特定位值的,即使你调用这个过程100次,cpu只是执行内存中这一段的代码。在遇到 RET指令后还会回到调用该过程的位值。这是通过使用堆栈保存返回地址来实现的。CALL指令占用3字节,无论调用多少次过程,最终输出的可执行文件并不会因此而显著增大。 宏会在程序代码中展开。如果你使用相同的宏100次,输出的可执行文件将会变得越来越大,因为每一次调用宏中的指令都会插入到调用宏的位值。 你可以使用堆栈或者通用寄存器来向过程传递参数向宏传递参数的方法是在宏名称后面直接接上参数。例如: MyMacro 1, 2, 3 用ENDM指令结束宏就足够了标记过程结束,你需要在ENDP指令前加上过程名称 宏会直接在代码中展开,因此,如果你在宏中使用标记,当宏被调用2次或两次以上的时侯就会出现 \(重复定义) 这一错误。为了避免该错误 在变量,标记或者过程名称之前加上“local”指令。例如: MyMacro2 MACRO LOCAL label1, label2 CMP AX, 2 JE label1 CMP AX, 3 JE label2 label1: INC AX ADD AX, 2 label2: ENDM ORG 100h MyMacro2 MyMacro2 RET I若过你打算在很多程序中使用宏,将所有的宏存放在一个文件中不失为一个好办法。将那个文件放在INC目录下,使用 INCLUDE 文件名 就可以在你的程序中调用宏了。

making your own operating system

Usually, when a computer starts it will try to load the first 512-byte sector (that's Cylinder 0, Head 0, Sector 1) from any diskette in your A: drive to memory location 0000h:7C00h and give it control. If this fails, the BIOS tries to use the MBR of the first hard drive instead.

This tutorial covers booting up from a floppy drive, the same principles are used to boot from a hard drive. But using a floppy drive has several advantages:

? ?

you can keep your existing operating system intact (windows, dos, linux, unix, be-os...). it is easy and safe to modify the boot record of a floppy disk.

example of a simple floppy disk boot program:

; directive to create BOOT file: #make_boot# ; Boot record is loaded at 0000:7C00, ; so inform compiler to make required ; corrections: ORG 7C00h PUSH CS ; make sure DS=CS POP DS ; load message address into SI register: LEA SI, msg ; teletype function id: MOV AH, 0Eh print: MOV AL, [SI] CMP AL, 0 JZ done INT 10h ; print using teletype. INC SI JMP print ; wait for 'any key': done: MOV AH, 0 INT 16h ; store magic value at 0040h:0072h: ; 0000h - cold boot. ; 1234h - warm boot. MOV AX, 0040h MOV DS, AX MOV w.[0072h], 0000h ; cold boot. JMP new_line EQU 13, 10 msg DB 'Hello This is My First Boot Program!' DB new_line, 'Press any key to reboot', 0

copy the above example to the source editor and press emulate. the emulator automatically loads .bin file to 0000h:7C00h (it uses supplementary .binf file to know where to load).

you can run it just like a regular program, or you can use the virtual drive menu to write 512 bytes at 7c00h to boot sector of a virtual floppy drive (it's \FLOPPY_0\written to the virtual floppy drive, you can select boot from floppy from virtual drive menu.

0FFFFh:0000h ; reboot!

.bin files for boot records are limited to 512 bytes (sector size). if your new operating system is going to grow

over this size, you will need to use a boot program to load data from other sectors (just like

micro-os_loader.asm does). an example of a tiny operating system can be found in c:\\emu8086\\examples:

micro-os_loader.asm micro-os_kernel.asm

To create extensions for your operating system (over 512 bytes), you can use additional sectors of a floppy disk. It's recommended to use \.bin\.bin\BIN Template\File\-> \New\

To write \.bin\\ from \ menu of emulator, you should write it anywhere but the boot sector (which is Cylinder: 0, Head: 0, Sector: 1).

you can use this utility to write .bin files to virtual floppy disk (\FLOPPY_0\write 512 bytes at 7c00h to boot sector\.bin file that is designed to be a boot record should always be written to cylinder: 0, head: 0, sector: 1

Boot Sector Location: Cylinder: 0 Head: 0 Sector: 1 to write .bin files to real floppy disk use writebin.asm, just compile it to com file and run it from command prompt. to write a boot record type: writebin loader.bin ; to write kernel module type: writebin kernel.bin /k

/k - parameter tells the program to write the file at sector 2 instead of sector 1. it does not matter in what order you write the files onto floppy drive, but it does matter where you write them.

mote: this boot record is not MS-DOS/Windows compatible boot sector, it's not even Linux or Unix compatible, operating system may not allow you to read or write files on this diskette until you re-format it, therefore make sure the diskette you use doesn't contain any important information. however you can write and read anything to and from this disk using low level disk access interrupts, it's even possible to protect valuable information from the others this way; even if someone gets the disk he will probably think that it's empty and will reformat it because it's the default option in windows operating system... such a good type of self destructing data carrier :)

idealized floppy drive and diskette structure:

for a 1440 kb diskette:

? floppy disk has 2 sides, and there are 2 heads; one for each side (0..1), the drive heads move above the surface of the disk on each side.

? ? ? ?

each side has 80 cylinders (numbered 0..79). each cylinder has 18 sectors (1..18). each sector has 512 bytes.

total size of floppy disk is: 2 x 80 x 18 x 512 = 1,474,560 bytes.

note: the MS-DOS (windows) formatted floppy disk has slightly less free space on it (by about 16,896 bytes) because the operating system needs place to store file names and directory structure (often called FAT or file system allocation table). more file names - less disk space. the most efficient way to store files is to write them directly to sectors instead of using file system, and in some cases it is also the most reliable way, if you know how to use it.

to read sectors from floppy drive use INT 13h / AH = 02h.

Controlling External Devices

There are 7 devices attached to the emulator: traffic lights, stepper-motor, LED display, thermometer, printer, robot and simple test device. You can view devices when you click \Virtual Devices\

For technical information see I/O ports section of emu8086 reference.

In general, it is possible to use any x86 family CPU to control all kind of devices, the difference maybe in base I/O port number, this can be altered using some tricky electronic equipment. Usually the \.bin\into the Read Only Memory (ROM) chip, the system reads program from that chip, loads it in RAM module and runs the program. This principle is used for many modern devices such as micro-wave ovens and etc...

Traffic Lights

Usually to control the traffic lights an array (table) of values is used. In certain periods of time the value is read from the array and sent to a port. For example:

; controlling external device with 8086 microprocessor. ; realistic test for c:\\emu8086\\devices\\Traffic_Lights.exe

#start=Traffic_Lights.exe#

name \

mov ax, all_red out 4, ax

mov si, offset situation next: mov ax, [si] out 4, ax

; wait 5 seconds (5 million microseconds)

mov cx, 4Ch ; 004C4B40h = 5,000,000 mov dx, 4B40h mov ah, 86h int 15h

add si, 2 ; next situation cmp si, sit_end jb next

mov si, offset situation jmp next

; FEDC_BA98_7654_3210 situation dw 0000_0011_0000_1100b s1 dw 0000_0110_1001_1010b s2 dw 0000_1000_0110_0001b s3 dw 0000_1000_0110_0001b s4 dw 0000_0100_1101_0011b sit_end = $

all_red equ 0000_0010_0100_1001b

Stepper-Motor

The motor can be half stepped by turning on pair of magnets, followed by a single and so on.

The motor can be full stepped by turning on pair of magnets, followed by another pair of magnets and in the end followed by a single magnet and so on. The best way to make full step is to make two half steps. Half step is equal to 11.25 degrees. Full step is equal to 22.5 degrees.

The motor can be turned both clock-wise and counter-clock-wise. open stepper_motor.asm from c:\\emu8086\\examples See also I/O ports section of emu8086 reference.

Robot

Complete list of robot instruction set is given in I/O ports section of emu8086 reference.

To control the robot a complex algorithm should be used to achieve maximum efficiency. The simplest, yet very inefficient, is random moving algorithm, open robot.asm from c:\\emu8086\\examples

It is also possible to use a data table (just like for Traffic Lights), this can be good if robot always works in the same surroundings.

Quick reference:

INT 21h

INT 10h/1003h INT 11h INT 12h INT 13h/00h INT 13h/02h INT 13h/03h INT 15h/86h INT 16h/00h INT 16h/01h INT 19h INT 1Ah/00h INT 20h

INT 21h/01h INT 21h/02h INT 21h/05h INT 21h/06h INT 21h/07h INT 21h/09h INT 21h/0Ah INT 21h/0Bh INT 21h/0Ch INT 21h/0Eh INT 21h/19h INT 21h/25h INT 21h/2Ah INT 21h/2Ch

INT 10h/00h INT 10h/01h INT 10h/02h INT 10h/03h INT 10h/05h INT 10h/06h INT 10h/07h INT 10h/08h INT 10h/09h INT 10h/0Ah INT 10h/0Ch INT 10h/0Dh INT 10h/0Eh INT 10h/13h

INT 21h/35h INT 21h/39h INT 21h/3Ah INT 21h/3Bh INT 21h/3Ch INT 21h/3Dh INT 21h/3Eh INT 21h/3Fh INT 21h/40h INT 21h/41h INT 21h/42h INT 21h/47h INT 21h/4Ch INT 21h/56h

INT 33h/0000h INT 33h/0001h INT 33h/0002h INT 33h/0003h

INT 10h / AH = 0 - set video mode.

input:

AL = desired video mode.

these video modes are supported:

00h - text mode. 40x25. 16 colors. 8 pages.

03h - text mode. 80x25. 16 colors. 8 pages.

13h - graphical mode. 40x25. 256 colors. 320x200 pixels. 1 page. example:

INT 10h / AH = 01h - set text-mode cursor shape. input:

CH = cursor start line (bits 0-4) and options (bits 5-7). CL = bottom cursor line (bits 0-4).

when bit 5 of CH is set to 0, the cursor is visible. when bit 5 is 1, the cursor is not visible.

; hide blinking text cursor:

; show standard blinking text cursor:

INT 10h / AH = 2 - set cursor position.

mov ch, 6 mov cl, 7 mov ah, 1 int 10h mov ch, 0 mov cl, 7 mov ah, 1 int 10h mov ch, 32 mov ah, 1 int 10h mov al, 13h mov ah, 0 int 10h

; show box-shaped blinking text cursor:

; note: some bioses required CL to be >=7,; otherwise wrong cursor shapes are displayed.

input: DH = row. DL = column.

BH = page number (0..7). example:

mov dh, 10 mov dl, 20 mov bh, 0 mov ah, 2 int 10h

INT 10h / AH = 03h - get cursor position and size. input:

BH = page number. return: DH = row. DL = column.

CH = cursor start line. CL = cursor bottom line.

INT 10h / AH = 05h - select active video page. input:

AL = new page number (0..7). the activated page is displayed.

INT 10h / AH = 06h - scroll up window. INT 10h / AH = 07h - scroll down window. input:

AL = number of lines by which to scroll (00h = clear entire window). BH = attribute used to write blank lines at bottom of window. CH, CL = row, column of window's upper left corner. DH, DL = row, column of window's lower right corner.

INT 10h / AH = 08h - read character and attribute at cursor position. input:

BH = page number. return:

AH = attribute. AL = character.

INT 10h / AH = 09h - write character and attribute at cursor position.

input:

AL = character to display. BH = page number. BL = attribute.

CX = number of times to write character.

INT 10h / AH = 0Ah - write character only at cursor position. input:

AL = character to display. BH = page number.

CX = number of times to write character.

INT 10h / AH = 0Ch - change color for a single pixel. input:

AL = pixel color CX = column. DX = row. example:

INT 10h / AH = 0Dh - get color of a single pixel. input: CX = column. DX = row. output:

AL = pixel color

INT 10h / AH = 0Eh - teletype output.

mov al, 13h mov ah, 0

int 10h ; set graphics video mode. mov al, 1100b mov cx, 10 mov dx, 20 mov ah, 0ch

int 10h ; set pixel.

input:

AL = character to write.

this functions displays a character on the screen, advancing the cursor and scrolling the screen as necessary. the printing is always done to current active page.

example:

; note: on specific systems this

; function may not be supported in graphics mode.

INT 10h / AH = 13h - write string. input:

AL = write mode:

bit 0: update cursor after writing; bit 1: string contains attributes. BH = page number.

BL = attribute if string contains only characters (bit 1 of AL is zero). CX = number of characters in string (attributes are not counted). DL,DH = column, row at which to start writing. ES:BP points to string to be printed. example:

INT 10h / AX = 1003h - toggle intensity/blinking.

mov al, 1 mov bh, 0

mov bl, 0011_1011b

mov cx, msg1end - offset msg1 ; calculate message size. mov dl, 10 mov dh, 7 push cs pop es

mov bp, offset msg1 mov ah, 13h int 10h jmp msg1end

msg1 db \ msg1end:

mov al, 'a' mov ah, 0eh int 10h

input:

BL = write mode:

0: enable intensive colors.

1: enable blinking (not supported by the emulator and windows command prompt). BH = 0 (to avoid problems on some adapters). example: mov ax, 1003h mov bx, 0 int 10h

bit color table:

character attribute is 8 bit value, low 4 bits set fore color, high 4 bits set background color.

note: the emulator and windows command line prompt do not support background blinking, however to make colors look the same in dos and in full screen mode it is required to turn off the background blinking. HEX BIN COLOR 0 0000 black 1 0001 blue 2 0010 green 3 0011 cyan 4 0100 red 5 0101 magenta 6 0110 brown 7 0111 light gray 8 1000 dark gray 9 1001 light blue A 1010 light green B 1011 light cyan C 1100 light red D 1101 light magenta E 1110 yellow F 1111 white note:

; use this code for compatibility with dos/cmd prompt full screen mode: mov ax, 1003h

mov bx, 0 ; disable blinking. int 10h

INT 11h - get BIOS equipment list.

return:

AX = BIOS equipment list word, actually this call returns the contents of the word at 0040h:0010h.

Currently this function can be used to determine the number of installed number of floppy disk drives.

Bit fields for BIOS-detected installed hardware: bit(s)

Description

15-14 Number of parallel devices. 13 Reserved.

12 Game port installed. 11-9 Number of serial devices. 8 Reserved.

7-6 Number of floppy disk drives (minus 1): 00 single floppy disk; 01 two floppy disks; 10 three floppy disks; 11 four floppy disks. 5-4 Initial video mode:

00 EGA,VGA,PGA, or other with on-board video BIOS; 01 40x25 CGA color.

10 80x25 CGA color (emulator default). 11 80x25 mono text. 3 Reserved.

2 PS/2 mouse is installed. 1 Math coprocessor installed. 0 Set when booted from floppy.

INT 12h - get memory size. return:

AX = kilobytes of contiguous memory starting at absolute address 00000h, this call returns the contents of the word at 0040h:0013h.

Floppy drives are emulated using FLOPPY_0(..3) files.

INT 13h / AH = 00h - reset disk system.

INT 13h / AH = 02h - read disk sectors into memory. INT 13h / AH = 03h - write disk sectors. input:

AL = number of sectors to read/write (must be nonzero) CH = cylinder number (0..79). CL = sector number (1..18). DH = head number (0..1).

DL = drive number (0..3 , for the emulator it depends on quantity of FLOPPY_ files). ES:BX points to data buffer. return:

CF set on error. CF clear if successful.

AH = status (0 - if successful). AL = number of sectors transferred. Note: each sector has 512 bytes.

INT 15h / AH = 86h - BIOS wait function. input:

CX:DX = interval in microseconds return:

CF clear if successful (wait interval elapsed),

CF set on error or when wait function is already in progress. Note:

the resolution of the wait period is 977 microseconds on many systems (1 million microseconds - 1 second). Windows XP does not support this interrupt (always sets CF=1).

INT 16h / AH = 00h - get keystroke from keyboard (no echo). return:

AH = BIOS scan code. AL = ASCII character.

(if a keystroke is present, it is removed from the keyboard buffer).

INT 16h / AH = 01h - check for keystroke in the keyboard buffer. return:

ZF = 1 if keystroke is not available. ZF = 0 if keystroke available. AH = BIOS scan code. AL = ASCII character.

(if a keystroke is present, it is not removed from the keyboard buffer).

INT 19h - system reboot.

Usually, the BIOS will try to read sector 1, head 0, track 0 from drive A: to 0000h:7C00h. The emulator just stops the execution, to boot from floppy drive select from the menu: 'virtual drive' -> 'boot from floppy'

INT 1Ah / AH = 00h - get system time. return:

CX:DX = number of clock ticks since midnight.

AL = midnight counter, advanced each time midnight passes. notes:

there are approximately 18.20648 clock ticks per second, and 1800B0h per 24 hours. AL is not set by the emulator.

INT 20h - exit to operating system.

The short list of emulated MS-DOS interrupts -- INT 21h

DOS file system is emulated in C:\\emu8086\\vdrive\\x (x is a drive letter)

If no drive letter is specified and current directory is not set, then C:\\emu8086\\MyBuild\\ path is used by default. FLOPPY_0,1,2,3 files are emulated independently from DOS file system.

For the emulator physical drive A: is this file c:\\emu8086\\FLOPPY_0 (for BIOS interrupts: INT 13h and boot).

For DOS interrupts (INT 21h) drive A: is emulated in this subdirectory: C:\\emu8086\\vdrive\\a\\

Note: DOS file system limits the file and directory names to 8 characters, extension is limited to 3 characters; example of a valid file name: myfile.txt (file name = 6 chars, extension - 3 chars). extension is written after the dot, no other dots are allowed.

INT 21h / AH=1 - read character from standard input, with echo, result is stored in AL. if there is no character in the keyboard buffer, the function waits until any key is pressed.

example:

mov ah, 1

int 21h

INT 21h / AH=2 - write character to standard output. entry: DL = character to write, after execution AL = DL.

example:

INT 21h / AH=5 - output character to printer.

entry: DL = character to print, after execution AL = DL.

example:

mov ah, 5 mov dl, 'a' int 21h mov ah, 2 mov dl, 'a' int 21h

INT 21h / AH=6 - direct console input or output.

parameters for output: DL = 0..254 (ascii code) parameters for input: DL = 255

for output returns: AL = DL

for input returns: ZF set if no character available and AL = 00h, ZF clear if character available. AL = character read; buffer is cleared.

example:

INT 21h / AH=7 - character input without echo to AL.

if there is no character in the keyboard buffer, the function waits until any key is pressed.

example:

mov ah, 7 int 21h

INT 21h / AH=9 - output of a string at DS:DX. String must be terminated by '$'.

example:

org 100h

mov dx, offset msg mov ah, 9 int 21h ret

INT 21h / AH=0Ah - input of a string to DS:DX, fist byte is buffer size, second byte is number of chars actually read. this function does not add '$' in the end of string. to print using INT 21h / AH=9 you must set dollar

mov ah, 6 mov dl, 255

int 21h ; get character from keyboard buffer (if any) or set ZF=1.

mov ah, 6 mov dl, 'a'

int 21h ; output character.

msg db \

character at the end of it and start printing from address DS:DX + 2.

example:

org 100h

mov dx, offset buffer mov ah, 0ah int 21h jmp print

buffer db 10,?, 10 dup(' ') print: xor bx, bx mov bl, buffer[1] mov buffer[bx+2], '$' mov dx, offset buffer + 2 mov ah, 9 int 21h ret

the function does not allow to enter more characters than the specified buffer size. see also int21.asm in c:\\emu8086\\examples

INT 21h / AH=0Bh - get input status;

returns: AL = 00h if no character available, AL = 0FFh if character is available.

INT 21h / AH=0Ch - flush keyboard buffer and read standard input.

entry: AL = number of input function to execute after flushing buffer (can be 01h,06h,07h,08h, or 0Ah - for other values the buffer is flushed but no input is attempted); other registers as appropriate for the selected input function.

INT 21h / AH= 0Eh - select default drive. Entry: DL = new default drive (0=A:, 1=B:, etc) Return: AL = number of potentially valid drive letters Notes: the return value is the highest drive present.

INT 21h / AH= 19h - get current default drive. Return: AL = drive (0=A:, 1=B:, etc)

INT 21h / AH=25h - set interrupt vector;

input: AL = interrupt number. DS:DX -> new interrupt handler.

INT 21h / AH=2Ah - get system date;

return: CX = year (1980-2099). DH = month. DL = day. AL = day of week (00h=Sunday)

INT 21h / AH=2Ch - get system time;

return: CH = hour. CL = minute. DH = second. DL = 1/100 seconds.

INT 21h / AH=35h - get interrupt vector; entry: AL = interrupt number;

return: ES:BX -> current interrupt handler.

INT 21h / AH= 39h - make directory.

entry: DS:DX -> ASCIZ pathname; zero terminated string, for example: org 100h

mov dx, offset filepath mov ah, 39h int 21h ret

filepath DB \, 0 end

the above code creates c:\\emu8086\\vdrive\\C\\mydir directory if run by the emulator.

Return: CF clear if successful AX destroyed. CF set on error AX = error code. Note: all directories in the given path must exist except the last one.

INT 21h / AH= 3Ah - remove directory.

Entry: DS:DX -> ASCIZ pathname of directory to be removed. Return:

CF is clear if successful, AX destroyed CF is set on error AX = error code. Notes: directory must be empty (there should be no files inside of it).

INT 21h / AH= 3Bh - set current directory.

; path to be created.

Entry: DS:DX -> ASCIZ pathname to become current directory (max 64 bytes). Return:

Carry Flag is clear if successful, AX destroyed. Carry Flag is set on error AX = error code.

Notes: even if new directory name includes a drive letter, the default drive is not changed, only the current directory on that drive.

INT 21h / AH= 3Ch - create or truncate file. entry:

CX = file attributes:

mov cx, 0 ; normal - no attributes. mov cx, 1 ; read-only. mov cx, 2 ; hidden. mov cx, 4 ; system

mov cx, 7 ; hidden, system and read-only! mov cx, 16 ; archive DS:DX -> ASCIZ filename.

returns:

CF clear if successful, AX = file handle. CF set on error AX = error code.

note: if specified file exists it is deleted without a warning. example: org 100h

INT 21h / AH= 3Dh - open existing file. Entry:

AL = access and sharing modes: mov al, 0 ; read mov al, 1 ; write mov al, 2 ; read/write DS:DX -> ASCIZ filename. Return:

mov ah, 3ch mov cx, 0

mov dx, offset filename mov ah, 3ch int 21h jc err

mov handle, ax

jmp k

filename db \, 0 handle dw ? err: ; .... k: ret

CF clear if successful, AX = file handle. CF set on error AX = error code. note 1: file pointer is set to start of file. note 2: file must exist. example:

org 100h

mov al, 2

mov dx, offset filename mov ah, 3dh int 21h jc err

mov handle, ax

ret

INT 21h / AH= 3Eh - close file. Entry: BX = file handle Return:

CF clear if successful, AX destroyed. CF set on error, AX = error code (06h).

INT 21h / AH= 3Fh - read from file. Entry:

BX = file handle.

CX = number of bytes to read. DS:DX -> buffer for data. Return:

CF is clear if successful - AX = number of bytes actually read; 0 if at EOF (end of file) before call. CF is set on error AX = error code.

Note: data is read beginning at current file position, and the file position is updated after a successful read the returned AX may be smaller than the request in CX if a partial read occurred.

INT 21h / AH= 40h - write to file. entry:

BX = file handle.

CX = number of bytes to write. DS:DX -> data to write. return:

CF clear if successful; AX = number of bytes actually written. CF set on error; AX = error code.

note: if CX is zero, no data is written, and the file is truncated or extended to the current position data is written beginning at the current file position, and the file position is updated after a successful write the usual cause for

jmp k

filename db \, 0 handle dw ? err: ; .... k:

AX < CX on return is a full disk.

INT 21h / AH= 41h - delete file (unlink). Entry:

DS:DX -> ASCIZ filename (no wildcards, but see notes). return:

CF clear if successful, AX destroyed. AL is the drive of deleted file (undocumented). CF set on error AX = error code.

Note: DOS does not erase the file's data; it merely becomes inaccessible because the FAT chain for the file is cleared deleting a file which is currently open may lead to filesystem corruption.

INT 21h / AH= 42h - SEEK - set current file position. Entry:

AL = origin of move: 0 - start of file. 1 - current file position. 2 - end of file. BX = file handle.

CX:DX = offset from origin of new file position. Return:

CF clear if successful, DX:AX = new file position in bytes from start of file. CF set on error, AX = error code. Notes:

for origins 1 and 2, the pointer may be positioned before the start of the file; no error is returned in that case, but subsequent attempts to read or write the file will produce errors. If the new position is beyond the current end of file, the file will be extended by the next write (see AH=40h). example: org 100h

mov bx, handle mov dx, offset data mov cx, data_size mov ah, 40h mov ah, 3ch mov cx, 0

mov dx, offset filename mov ah, 3ch

int 21h ; create file... mov handle, ax

mov bx, handle mov dx, offset buffer mov cx, 4 mov al, 0 mov bx, handle mov cx, 0 mov dx, 7 mov ah, 42h int 21h ; seek... int 21h ; write to file...

filename db \, 0 handle dw ? data db \ data_size=$-offset data mov bx, handle mov ah, 3eh int 21h ; close file... ret mov ah, 3fh

int 21h ; read from file...

本文来源:https://www.bwwdw.com/article/f68p.html

Top