微机原理
本文用于记录一下微机原理8086汇编作业😁
8086/8088指令系统
一、内存寻址
直接寻址
物理地址=段地址×16+偏移地址
- 段地址由段寄存器(如
DS
)提供。 - 偏移地址是指令中的地址值(在
[1234H]
中的1234H
)。
在指令 MOV AL, [1234H]
中,如果假设 DS = 2000H
,那么:
物理地址=2000H×10H+1234H=21234H
乘以 10H
是因为 x86 架构中的段地址是以 16 字节为单位的,所以需要乘以 10H
(或者说 16
)来将段地址转换成字节为单位的地址。
简单地说,DS * 16
将段地址转换成字节地址,然后再加上偏移地址,就得到了完整的物理地址。
注:
凡是含有 BP
的寻址方式中,都是对堆栈区的存储单元寻址的,因此其段地址由段寄存器 SS
提供
二、移位指令
1 逻辑左移指令 SHL
SHL des, CL/1
-
移位数=1,直接写入指令;大于1时,应送入
CL
预先设定。 -
无符号数要增倍时,可使用
SHL
指令。
2 逻辑右移指令 SHR
SHR des, CL/1
-
移位数 =1,直接写入指令;大于1时,应送入
CL
预先设定。 -
无符号数要减半时,可使用
SHR
指令。
3 算术左移指令 SAL
与逻辑左移指令功能完全相同
SAL des, CL/1
-
移位数=1,直接写入指令;大于1时,应送入
CL
预先设定。 -
带符号数要增倍时,可使用
SAL
指令。
4 算术右移指令 SAR
SAR des, CL/1
-
移位数 =1,直接写入指令;大于1时,应送入
CL
预先设定。 -
带符号数要减半时,可使用
SAR
指令。
在 8086 汇编语言中,当处理带符号数时,通常使用 SAR
指令进行右移(带符号右移),而处理无符号数时,使用 SHR
指令进行右移(逻辑右移)
8086/8088 汇编
一、 伪指令
1 符号定义伪指令
EQU
伪指令
<symbol> EQU <expression>
指令功能: 给符号名赋值。
PORT EQU 88H; 定义符号 PORT 表示 88H
DATA EQU PORT+2; 定义符号 DATA 表示 PORT+2
注:
-
用
EQU
定义的符号,不能重新再定义,除非用PURGE
伪指令解除; -
如果在表达式中用了其他符号,必须事先定义。
解除符号定义伪指令
PURGE symbol1, symbol2, ...
指令功能: 撤消用 EQU
赋值的符号名, 以便于赋予新值。
PURGE PORT, DATA; 撤销 EQU 对 PORT, DATA 的赋值
等号(赋值)伪指令
<symbol> = <expression>
指令功能: 与 EQU
类似,唯一的区别是 “=
” 可以随时对符号名赋新值, 而不必使用 PURGE
伪指令。
PORT = 88H; 定义符号 PORT 表示 88H
DATA = PORT +2; 定义符号 DATA 表示 PORT+2
2 内存数据定义伪指令
数据定义语句
变量名 命令 参数1, 参数2, ...
功能: 分配存储单元,存入指定的数据。
数据定义语句的命令有:
-
DB
: 定义字节数据 -
DW
: 定义字数据 -
DD
: 定义双字数据 -
DQ
: 定义8字节数据 -
DT
: 定义10字节数据
复制操作符
<复制次数> DUP 数据1, 数据2, ...
功能:汇编程序把所定义的数据按先后次序连续分配存储空间,所起的名称只代表第一个单元的字符。
VAR1 DB 32H, 'ABC'
VAR2 DW 1234H, 40H, 'AB'
DD 12345678H
DB ?, 11000011B
ARRY1 DB 2 DUP (0, 1)
ARRY2 DW 2 DUP (?, 1)
补充- 补码、原码、反码
一. 机器数和机器数的真值
1、机器数
一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用机器数的最高位存放符号,正数为0,负数为1。
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是0000 0011。如果是 -3 ,就是 100 00011 。
那么,这里的 0000 0011 和 1000 0011 就是机器数。
2、机器数的真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。
例如上面的有符号数 1000 0011,其最高位1代表负,其真正数值是 -3,而不是形式值131(1000 0011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1
原码, 反码, 补码的基础概念和计算方法
在探求为何机器要使用补码之前,让我们先了解原码、反码和补码的概念。对于一个数,计算机要使用一定的编码方式进行存储,原码、反码、补码是机器存储一个具体数字的编码方式。
1、原码
**原码就是符号位加上真值的绝对值,**即用第一位表示符号,其余位表示值。比如:如果是8位二进制:
[+1]原= 0000 0001
[-1]原= 1000 0001
第一位是符号位,因为第一位是符号位,所以8位二进制数的取值范围就是:(即第一位不表示值,只表示正负。)
[1111 1111 , 0111 1111]
即 [ − 127 , 127 ] [-127 , 127] [−127,127],因为根据定义数字 0 的原码有两种不同形式
[+0]原 = 00000000B, [-0]原 = 10000000B
2、反码
反码的表示方法是:
正数的反码是其本身;
负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
[+1] = [0000 0001]原 = [0000 0001]反
[-1] = [1000 0001]原 = [1111 1110]反
可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值。通常要将其转换成原码再计算。
根据定义数字 0 的反码同样也有两种不同形式
[+0]反 = 00000000B, [-0]原反= 11111111B
3、补码
补码的表示方法是:
正数的补码就是其本身;
负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(也即在反码的基础上+1)
[+1] = [0000 0001]原= [0000 0001]反= [0000 0001]补
[-1] = [1000 0001]原= [1111 1110]反= [1111 1111]补
对于负数,补码表示方式也是人脑无法直观看出其数值的。通常也需要转换成原码再计算其数值。
将补码变为原码和将原码变为补码操作方法一致。
参考
微机作业
DosBox DeBug 指令记录
编译 .asm
文件,链接 .obj
文件生成 .exe
文件
masm prob1.asm
link prob1;
开始 DeBug
debug prob1.exe
# 常用 DeBug 指令
U - U (Unassemble) 反汇编一段内存, 将内存中的机器指令翻译成汇编指令
R - R (Registers) 查看和修改寄存器的值
D - D (Dump) 用于查看内存的内容
T - T (Trace) 按照程序的正常执行顺序逐条执行指令,但是不进入子程序
G - G (Go) 用于运行程序
Q - Q (Quit) 退出调试
补充
D DS:0000 - 查看数据段中偏移地址为0000的数据
D SS:0000 - 查看堆栈段中偏移地址为0000的数据
作业 问题1
a、b、c、s
为带符号的字数据,求
s
=
(
a
∗
b
+
c
)
/
a
s=(a*b+c)/a
s=(a∗b+c)/a,结果的商存放在 s
中,余数 不计。s
的偏移地址是自己学号的和
汇编代码如下:
DATA SEGMENT
ORG 0009H ; 设置偏移地址,定义 s 后 s 的偏移地址为09H
S DW ?
A DW 1234H ; 定义字数据A
B DW 5678H ; 定义字数据B
C DW 9ABCH ; 定义字数据C
A_B DD ? ; 定义双字数据用于储存 a*b 的结果
DATA ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START:
MOV AX, DATA ; 将数据段地址加载至AX
MOV DS, AX ; 段地址送至DS
MOV AX, [A] ; 将A的值加载到AX
IMUL WORD PTR [B] ; 带符号数B X AX -> DX, AX
MOV [A_B], AX ; 将 a*b 的结果的低16位 送至 A_B 的低16位
MOV [A_B+2], DX ; 将 a*b 的结果的高16位 送至 A_B 的高16位
MOV AX, [C] ; 将C的值送至AX
CWD ; 将C扩展为32位带符号数,DX储存其高16位,AX储存其低16位
ADD AX, [A_B] ; C的低16位和A_B的低16位相加
ADC DX, [A_B+2] ; 带进位加法,考虑低16位相加的进位
IDIV WORD PTR [A] ; DX, AX / A -> AX(商),DX(余数)
MOV [S], AX ; 将运算结果商储存到变量S中
; 程序结束
MOV AH, 4CH ; 设置DOS功能号,表示程序结束
INT 21H ; 调用DOS中断
CODE ENDS
END START
分析
首先利用 ORG
指令将程序起始地址设置为 09H
,即本人学号的学号的和
在数据段中首先定义 S
,则其偏移地址即为 09H
A
和 B
相乘后为32位双字数据,且为带符号数,所以不能直接和 C
相加,需要先将 C
扩展为双字数据然后相加,使用 CWD
指令
相加时先将两者的低16位相加,再考虑低16位的进位再将高16位相加得到结果,使用 ADC
指令
s, a, b, c
都为带符号数,乘法采用 IMUL
,除法采用 IDIV
DeBug
debug prob1.exe
U
反汇编一段内存,将内存中的机器指令翻译成汇编指令。这里我们可以看到 S
的偏移地址为 0009H
。
R
查看寄存器中的数据,D
查看内存中的数据
T
单步执行
D DS:0009
查看数据段中偏移地址为 0009H
的数据
G
运行程序,查看 S
的值
验证结果 s = ( a ∗ b + c ) / a s = (a*b+c)/a s=(a∗b+c)/a
a ∗ b = 1234 H × 5678 H = 06260060 H a*b = 1234H \times 5678H = 06260060H a∗b=1234H×5678H=06260060H 结果正确
c
=
9
A
B
C
H
c = 9ABCH
c=9ABCH 为带符号数且为复数其原码为
E
544
H
E544H
E544H,其数值绝对值为
6544
6544
6544
s
=
(
(
a
∗
b
+
c
)
/
a
=
06260060
H
−
6544
H
)
/
1234
H
=
5672
H
s = ((a*b+c)/a = 06260060H - 6544H)/1234H = 5672H
s=((a∗b+c)/a=06260060H−6544H)/1234H=5672H
验证结果正确
作业问题2_1
设 BX=84F0H,若 BX 为无符号数,求BX/2
汇编代码如下
DATA SEGMENT
B DW 84F0H ; 定义字数据B=84F0H
RESULT DW ? ; 用于储存结果的变量
DATA ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START:
MOV AX, DATA ; 将数据段地址加载到AX
MOV DS, AX ; 设置DS指向数据段
LEA DI, B ;
MOV AX, [DI] ; 将B的值加载到AX
SHR AX, 1 ; 将AX逻辑右移一位,无符号数除以2
MOV RESULT, AX ; 将结果储存到RESULT变量中
; 程序结束
MOV AH, 4CH ; 设置DOS功能号,表示程序结束
INT 21H ; 调用DOS中断
CODE ENDS
END START
验证结果正确,无符号数逻辑右移实现减半
作业问题2_2
设 BX=84F0H,若 BX 为带符号数,求BX/2
汇编代码如下:
DATA SEGMENT
B DW 84F0H ; 定义字数据B=84F0H
RESULT DW ? ; 用于储存结果的变量
DATA ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START:
MOV AX, DATA ; 将数据段地址加载到AX
MOV DS, AX ; 设置DS指向数据段
LEA DI, B ;
MOV AX, [DI] ; 将B的值加载到AX
SAR AX, 1 ; 将AX算术右移一位,带符号数除以2
MOV RESULT, AX ; 将结果储存到RESULT变量中
; 程序结束
MOV AH, 4CH ; 设置DOS功能号,表示程序结束
INT 21H ; 调用DOS中断
CODE ENDS
END START
验证结果正确,带符号数算术右移实现减半
作业问题2_3
设 BX=84F0H,把BX中的数按每4位压入堆栈,且高位先进栈,即第一次8进栈,第二次4进栈……
汇编代码如下:
DATA SEGMENT
BX_VAL DW 84F0H ; 定义字数据储存BX的值
TMP DW 0F000H ; 定义字数据用于和BX相与
SHR_NUM DB 0CH ; 定义字节数据表示逻辑右移的位数
COUNT DW 05H ; 定义字数据表示循环的次数
DATA ENDS
STACK_SEG SEGMENT STACK
DW 10 DUP(?) ; 定义堆栈段
STACK_SEG ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA, SS: STACK_SEG
START:
MOV AX, DATA ; 将数据段地址加载到AX
MOV DS, AX ; 设置DS指向数据段
MOV AX, STACK_SEG ; 将堆栈段地址加载至AX
MOV SS, AX ; 设置SS指向堆栈段
MOV SP, OFFSET STACK_SEG + 10 ; 设置堆栈指针,指向堆栈段的顶部
MOV CX, 04H ; 循环次数送至CX
PUSH_LOOP:
MOV BX, BX_VAL ; 将字数据加载到BX
MOV CL, SHR_NUM ; 将逻辑右移位数送至CL
; 第一轮循环F000H和BX相与得到BX的高4位,第二轮0F00H和BX相与得到BX的次高4位,以此类推...
AND BX, TMP ;
; 第一次右移12位后BX为0008H,第二次右移8位后BX为0004H,第三次为000FH,...
SHR BX, CL ;
MOV CL, 04H ;
SHR TMP, CL ; 和BX相与的值右移4位,以分别从高到低到得到BX的每个4位
SUB SHR_NUM, 04H ; BX右移的位数-4
PUSH BX ; 将BX压入堆栈
DEC COUNT ; 循环次数-1
MOV CX, COUNT ; 循环次数送至CX
LOOP PUSH_LOOP ;
; 程序结束
MOV AH, 4CH ; 设置DOS功能号,表示程序结束
INT 21H ; 调用DOS中断
CODE ENDS
END START
验证结果正确
作业问题3
A、B为带符号32位数,分别存放 于DX,AX和BX,CX中。编程求:当 A>B 时,转移到标志x处执行,否则转移到y处执行
汇编代码如下:
DATA SEGMENT
A DD 12345678H ; 定义双字数据A
B DD 9ABCDEF0H ; 定义双字数据B
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA ; 将数据段地址加载至AX
MOV DS, AX ; 段地址送至DS
MOV DX, DWORD PTR [A+2] ; 将A的高16位加载至DX
MOV AX, DWORD PTR [A] ; 将A的低16位加载至AX
MOV BX, DWORD PTR [B+2] ; 将B的高16位加载至BX
MOV CX, DWORD PTR [B] ; 将B的低16位加载至CX
CMP DX, BX ; 比较A和B的高16位
JG FLAGX ; 带符号数,若A的高16位大于B的高16位,则跳转至FLAGX
JL FLAGY ; 带符号数,若A的高16位小于B的高16位,则跳转至FLAGY
CMP AX, CX ; 若A的高16位等于B的高16位,比较低16位
JA FLAGX ; 无符号数,若A的低16位大于B的低16位,则跳转至FLAGX
JBE FLAGY ; 无符号数,若A的低16位小于B的低16位,则跳转至FLAGY
FLAGY:
; 在这里执行当A<=B时的操作
JMP END_PROGRAM
FLAGX:
; 在这里执行当A>B时的操作
END_PROGRAM:
; 程序结束
MOV AH, 4CH
INT 21H
CODE ENDS
END START