汇编语言有3类指令:
存储指令和数据, 内存.
指令即数据.
总线从逻辑上分为 地址总线, 控制总线, 数据总线.
CPU位宽与地址总线并不一定对齐. 比如 8086.
寻址不会拆分, 因此地址总线宽度直接决定了可用内存容量, 当然也有 RDIMM, LPRDIMM 这样的技术.
这二者中间涉及了 逻辑地址 到物理地址的转换.
8088 CPU 数据总线宽度为 8, 8086 为 16, 长的数据会拆分.
宽度决定控制能力.
这里指出了每个指令均由专用线路来控制.
即可访问的地址单元构成的空间
Adapter 啦, 名如其功能.
从读写属性上分为:
CPU 将系统中各类存储器看作一个逻辑存储器, 即内存地址空间.
一个典型的 CPU 由 运算器, 控制器, 寄存器 等器件组成, 这些由 CPU 内部总线链接.
AX, BX, CX, DX 为16位通用寄存器.
为了向上兼容8位, AX寄存器可分为 AH 和 AL.
字, word, 一个 word 由 两个字节组成.
A byte is eight bits, a word is 2 bytes (16 bits), a doubleword is 4 bytes (32 bits), and a quadword is 8 bytes (64 bits).
Ref:
Intel Data Types and Addressing Modes
介绍了 MOV, ADD
8086 是 16 位机器.
8086 有 20 位地址总线. 但其为16位
架构, 故使用两个16位地址合成一个20位地址. 其内部有个地址加法器用于合成.
两个16位地址一个称为段地址, 一个称为偏移地址. 段地址 x 16 + 偏移地址. 即左移4位
0000 0000 0000 0000
0000 0001 0010 0011 // 0123
0001 0010 0011 0000 // 1230
0000 0000 1100 1000 // 00C8
1 0010 0011 1100 1000 // 123C8
段地址是向高位对齐的.
CPU可以用不同的段地址和偏移地址形成同一个物理地址.
8086 有 CS, DS, SS, ES 四个段寄存器.
CS 为代码段寄存器, IP 为指令指针寄存器.
JMP 2AE3:3 CPU 从 CS=2AE3, IP=0003 处读取指令.
也可以只控制 IP:
JMP AX,
before: AX=1000, CS=2000, IP=0003
after: AX=1000, CS=2000, IP=1000 <-这里要注意
问题 2.3
CS=2000, IP=0000, AX=0000, BX=0000, CX=0000
MOV AX, 6622
CS=2000, IP=0003, AX=6622, BX=0000, CX=0000
JMP 1000:3
CS=1000, IP=0003, AX=6622, BX=0000, CX=0000
MOV AX, 0000
CS=1000, IP=0006, AX=0000, BX=0000, CX=0000
MOV BX, AX
CS=1000, IP=0008, AX=0000, BX=0000, CX=0000
JMP BX
CS=1000, IP=000A, AX=0000, BX=0000, CX=0000
MOV AX, 0123
CS=1000, IP=0000, AX=0123, BX=0000, CX=0000
即 IP 自动指向执行完毕后的偏移位置, 因为 CPU 自动运行 CS:IP 指向的数据, 而且是顺序执行的.
存放代码. 其得到执行需要定义 CS 及 IP.
8086 有 四个段寄存器, 其中 CS 用来存放指令的段地址. IP 存放指令的偏移地址.
8086 中, 任意时刻, CPU 将 CS:IP 指向的内容当作指令执行.
其工作过程:
debug 命令, 使用文档见: DOS-Debug
-r 打印寄存器状态/修改寄存器状态
-d {segment address}:{ address} dump, 查看指定内存地址中的内容
-e {segment address}:{ address} 修改指定内存地址中的内容
-u 打印本机的CPU机器码
-t 运行当前cs:ip指向的命令
-a 直接使用汇编编写命令
字单元, 即 16bit, 存储一个字.
[...]
表示内存单元, [...]
中的0表示内存单元的偏移地址, DS 段寄存器中的数据为段地址. 这里需要先将段地址送入通用寄存器, 然后再传递给DS.
问题 3.3 注意字的大小, 是图中的两个单元.
可以将小于64KB的连续内存地址定义为段.
用内存当栈, 以字为单位进行操作. CPU 提供PUSH
和POP
指令. CS, IP 寄存器负责指向栈的基地址, 段寄存器SS存放栈顶地址, 寄存器SP存放偏移地址. 即, SS, SP 指向栈顶元素. PUSH 和 POP 指令执行时, CPU从SS和IP中的得到栈顶地址.
入栈时, 数据从高地址向低地址增长.
即 POP 和 PUSH 都可能越界, 需要手动控制.
PUSH, POP 指令可以操作寄存器和内存单元.
即简单的入栈过程:
MOV AX, 1000H
MOV SS, AX
MOV SP, 0010H
PUSH AX
PUSH BX
PUSH DS
设置SS, SP寄存器指定当前栈顶地址, 然后PUSH即可. SS 寄存器不能直接设置立即数, 所以要用AX寄存器.
强调了入栈与出栈顺序相反, 另外寄存器清零可以用 SUB AX, AX, 或 MOV AX, 0.
即利用栈的特性交换数据.
MOV AX, 1000H
MOV SS, AX
MOV SP, 0002H
MOV AX, 2266H
PUSH AX
即指定固定长度的空间当栈空间使用.
问题 3.11
问题 3.12
段的综述
即表述的是, 在内存中, 数据与代码并无区别, 段也是人为划分的. 但CPU为了让程序符合架构模型, 故存在段的概念, 并提供了操作段的寄存器:
数据段, 地址存放在DS
代码段, CS
栈段, SS
检测点 3.2
MOV AX, 1000H
MOV DS, AX
即, D命令本身也依靠段寄存器的地址来显示内容. 同样, E, A, U 命令也可以.
由于中断的原因, T 命令并不能完全单步. 修改SS寄存器时, 吓一条指令也会被执行.
讲了编译, 链接, 执行的过程
assume cs:codesg
codesg segment
mov ax, 0123h
mov bx, 0456h
add ax, bx
add ax, ax
mov ax, 4c00h
int 21h
codesg ends
end
{} segment ... {} ends
伪指令, 用来定义一个段.
end
伪指令, 标记程序结束.
assume
伪指令, 定义段寄存器与段代码的关联.
mov ax 4c00H
int 21H
masm 1.exe
link 1.exe
command.com 是DOS系统的shell, 如果执行其他程序, command 根据文件名找到可执行文件, 然后将可执行文件载入内存, 设置 CS:IP 指向程序入口, 然后运行程序.
编程->1.asm->编译->1.obj->连接->1.exe->加载->内存中的程序->运行
(edit) (masm) (link) (command) (CPU)
SA:0 空闲内存
SA:0 PSP 区(用于DOS系统与被加载程序通信)
SA+10H:0 程序区
assume cs:codesg
codesg segment
MOV AX, 2000H
MOV SS, AX
mov sp, 0
add sp, 10
pop ax
pop bx
push ax
push bx
pop ax
pop bx
mov ax, 4c00h
int 21h
codesg ends
end
ax 2000H
ss 2000H
SS 2000H, SP 0
SS 2000H, SP 0A
AX 0000, BX 0000
AX 0000, BX 0000
AX 0000, BX 0000
049A:0040 CD20 INT 20
049A:0042 FF9F00EA CALL FAR [BX+EA00]
049A:0046 FFFF ??? DI
049A:0048 AD LODSW
049A:0049 DE4202 FIADD WORD PTR [BP+SI+02]
049A:004C 92 XCHG DX, AX
049A:004D 017002 ADD [BX+SI+02], SI
...
即用BX作为偏移地址. MOV 时会自动判断是word还是byte.
loop
我们定义的描述性的符号"()"
(ax)=((ds)*16+2) 等同于 mov ax,[2]
(sp)=(sp)-2
((ss)*16+(sp))=(ax) 等同于 push ax
mov ax,[idata] 代表 mov ax,[1] 等.
DS 2000H
AX 00BE
BX 1002
assume cs:code
code segment
mov ax, 2
mov cx, 11
s: add ax, ax
loop s
mov ax, 4c00h
int 21h
code ends
end
即 loop 使用 cx 作为计数器.
assume cs:codesg
codesg segment
mov ax, 123
mov cx, 236
p1: add ax, 123
loop s
mov ax, 4c00h
int 21h
codesg ends
end
有点无厘头...
assume cs:code
code segment
mov ax, 0ffffh ; 因为是数据, 前缀必须加0
mov ds, ax
mov bx, 6
mov al, [bx] ; 这里应该是把ROM的内容搞进去了, 我看这部分内存貌似是BIOS时间? 我的DOSBOX值是31, 不是32
mov ah, 0
mov dx, 0
mov cx, 3
s:add dx, ax
loop s
mov ax, 4c00h
int 21h
code ends
end
介绍了 debug 的 g命令, 可以让IP寄存器到达指定位置停止.
p 命令, 运行结束循环. 用 g也可以代替. 很奇怪我的debug不支持p命令.
这里指出了 debug 与 asm 程序在立即数上的不同处理.
如果直接写[idata] 会被当成立即数而不是内存偏移地址. 所以要么送入 BX 并设置 DS 然后写成 MOV AL, [BX]
, 要么就写成 MOV AL, DS:[0]
.
即, 需要显示声明用哪个段寄存器.
assume cs:code
code segment
mov ax, 0fffh
mov ds, ax
mov dx, 0
mov al, ds:[0]
mov ah, 0
add ax, ax
mov al, ds:[1]
mov ah, 0
add dx, ax
mov al, ds:[2]
...
这里al, ah 分别赋值防止溢出.
改进为用loop.
assume cs:code
code segment
mov ax, 0ffffh
mov ds, ax
mov bx, 0
mov dx, 0
mov cx, 12
s:mov al, [bx]
mov ah, 0
add dx, ax
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
mov ax, ss:[0]
mov ax, cs:[0]
这些均合法, ds, cs, ss, es 这些出现在访问内存单元指令中, 用于显式指明内存单元的段地址, 被称为段前缀.
讲了随意操作内存带来的问题. 0:200-0:2FF
在DOS中存有数据.
assume cs:code
code segment
mov bx, 0
mov cx, 12
s:mov ax, 0ffffh
mov ds, ax
mov dl, [bx]
mov ax, 0020h
mov ds, ax
mov [bx], dl
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
优化, 使用两个寄存器
assume cs:code
code segment
mov ax, 0ffffh
mov ds, ax
mov ax, 0020h
mov es, ax
mov bx, 0
mov cx, 12
s:mov dl, [bx]
mov es:[bx], dl
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
assume cs:code
code segment
mov ax, _
mov ds, ax
mov ax, 0020h ; 复制到 0:0200
mov es, ax
mov bx, 0
mov cx, _
s:mov al, [bx]
mov es:[bx], al
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
本章主要讨论:
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h; define word, 定义字型数据
mov bx, 0
mov ax, 0
mov cx, 8
s:add ax, cs:[bx]
add bx, 2
loop s
mov ax, 4c00h
int 21h
code ends
end
dw 定义的数据位于, cs:0 - cs:e. 即如同代码所写, 在代码段的开始.
04ae:0000 23 01 56 04 89 07 BC 0A-EF 0D ED 0F BA 0C 87 09
这种情况 debug 加载程序后, 需要手动指定 IP 寄存器位置. padding 过去 dw 的位置. 同样, 程序本身也需要修改才能运行:
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h; define word, 定义字型数据
start:mov bx, 0
mov ax, 0
mov cx, 8
s:add ax, cs:[bx]
add bx, 2
loop s
mov ax, 4c00h
int 21h
code ends
end start
即预先分配出一段内存在代码段中当作栈用.
assume cs:codesg
codesg segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start: mov ax, cs
mov ss, ax
mov sp, 30h
mov bx, 0
mov cx, 8
s: push cs:[bx]
add bx, 2
loop s
mov bx, 0
mov cx, 8
s0: pop cs:[bx]
add bx, 2
loop s0
mov ax, 4c00h
int 21h
codesg ends
end start
assume cs:code, ds:data, ss:stack
data segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 20h ; set ss:sp to stack:20
mov ax, data
mov ds, ax ; set ds to data segment
mov bx, 0 ; set ds:bx to data segment first element
mov cx, 8
s: push [bx]
add bx, 2
loop s
mov bx, 0
mov cx, 8
s0: pop [bx]
add bx, 2
loop s0
mov ax, 4c00h
int 21h
code ends
end start
其中, 数据段和栈段的地址可以用定义的标识符来直接获取地址. 例如 mov ax, data
. 获取data段中的 0abch数据的地址即 data:6
.
注意不能直接让段寄存器使用, 所以还是要这么操作:
mov ax, data
mov ds, ax
mov bx, ds:[6]
CPU 并没有具体区分段的区别, 这种区分是代码定义的.
assume cs:code, ds:data, ss:stack
data segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, data
mov ds, ax
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax. 4c00h
int 21h
code ends
end start
and
指令, 按位与运算
or
指令, 按位或运算
没啥新鲜的
assume cs:code, ds:data
data segment
db 'unIX'
db 'foRK'
data ends
code segment
start: mov al, 'a'
mov bl, 'b'
mov ax, 4c00h
int 21h
code ends
end start
小写字幕-20h即可得到大写字母. 或判断第5位是否为0.
assume cs:codesg, ds:datasg
datasg segment
db 'BaSic'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start: mov ax, datasg
mov ds, ax
mov bx, 0
mov cx, 5
s:mov al[bx]
and al 11011111B
mov [bx], al
inc bx
loop s
mov bx, 5
mov cx, 11
s0:mov al,[bx]
or al, 00100000B
mov [bx], al
inc bx
loop s0
mov ax, 4c00h
int 21h
codesg edns
end start
程序利用AND, 并使用 11011111B
来进行按位与, 达到变为小写的目的.
mov ax, [bx+200]
即将 ds:bx+200 位置的数据放入ax.
简化上面的大小写转换的例子:
mov ax, datasg
mov ds, ax
mov bx, 0
mov cx, 5
s:mov al, [bx]
and al,11011111b
mov [bx],al
mov al, [5+bx]
or al, 00100000b
mov [5+bx], al
inc bx
loop s
SI 和 DI 是8086中类似 BX 的寄存器. 不过不能够分成2个8位寄存器使用.
assume cs:codesg, ds:datasg
datasg segment
db 'welcode to masm!'
db '................'
data sg ends
codesg segment
start: mov ax, datasg
mov ds, ax
mov si, 0
mov di, 16
mov cx, 8
s: mov ax, [si]
mov [di], ax
add si, 2
add di, 2
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
codesg segment
start: mov ax, datasg
mov ds, ax
mov si, 0
mov cx, 8
s: mov ax, 0[si]
mov 16[si], ax
add si, 2
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
即 mov ax, [bx+si]
或写作 mov ax, [bx][si]
可以写作:
mov ax, [bx+200+si]
mov ax, [200+bx+si]
mov ax, 200[bx][si]
mov ax, [bx].200[si]
mov ax, [bx][si].200
assume cs:codesg, ds:datasg
datasg segment
db '1. file '
db '2. edit '
datasg ends
这个问题要将数据段的英文首字母大写.
即演示寻址方式的灵活性.
assume cs:codesg, ds:datasg,ss:stacksg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
codesg segments
start: mov ax, stacksg
mov ss, ax
mov sp, 16
mov ax, datasg
mov ds, ax
mov bx, 0
mov cx, 4
s0: push cx
mov si, 0
mov cx, 3
s: mov al, [bx+si]
and al, 11011111b
mov [bx+si], al
inc si
loop s
add bx, 16
pop cx
loop s0
mov ax, 4c00h
int 21h
codesg ends
end start
描述性表示:
reg: ax, bx, cx, dx, ah, al, bh, bl, ch, cl, dh, dl, sp, bp, si, di
sreg: ds, ss, cs, es
[]
里面.机器码 | 汇编指令 | 指令执行前数据的位置 |
---|---|---|
8E1E0000 | mov bx, [0] | 内存, ds:0 |
89C3 | mov bx, ax | CPU 内部, ax 寄存器 |
BB0100 | mov bx, 1 | CPU 内部, 指令缓冲器 |
立即数 idata
即直接包含在机器指令中的数据.
寄存器
指令要处理的数据在寄存器中.
段地址 SA 和 偏移地址 EA
数据在内存中, 用段地址和偏移地址来取出数据.
给各种寻址方式命了名.
mov word ptr ds:[0], 1
mov byte ptr ds:[0], 1
PUSH
针对字进行操作mov ax, seg
mov ds, ax
mov bx 60h
mov word ptr [bx+0ch], 38
add word ptr [bx+0eh], 70
mov si, 0
mov byte ptr [bx+10h+si], 'V'
inc si
mov byte ptr [bx+10h+si], 'A'
inc si
mov byte ptr [bx+10h+si], 'X'
使用DIV指令需要注意以下问题
db, byte
dw, word
dd, double word
db 3 dup (0) 定义3个字节, 都是0, 等价于 0, 0, 0. 通常用来初始化大量的数据.
.......
可以修改IP, 或同时修改CS和IP的指令统称为转移指令
按类分
按修改范围
直接分类
start: mov ax, offset start; 相当于 mov ax, 0
s: mov ax, offset s; 相当于 mov ax, 3
assume cs:codesg
codesg segment
s: mov ax, bx
mov si, offset s
mov di, offset s0
mov ax, cs:[si]
mov cs:[di], ax
s0: nop ; 两个nop 正好一个word, 这算冯诺依曼体系的优点了, 哈佛体系就不能边运行边修改code segment.
nop
codesg ends
end s
jmp short
是段内短转移, 即最多向前128byte, 向后 127byte.
start: mov ax, 0
jmp short s
add ax, 1
s: inc ax
编译到机器码后, 即为 EB{x}
其中x部分代表了向前或向后的字节数. 是编译器根据汇编指令中的标号计算出来的.
jmp near ptr
实现段内近转移, 可以达到16bit.
jmp far ptr
实现段间转移, cs标记段地址, IP标记偏移地址.
即 jmp bx
等.
条件转移, 所有的条件转移指令都是短转移. 它会检测如果CX=0, 则转移.
所有循环指令都是短转移.
即不硬编码地址, 方便程序随时载入到任何位置, 与平台无关.
call 和 ret都是转移指令, 修改IP. 或者同时修改CS, IP.
ret修改io实现近转移, retf 修改 cs, ip 实现远转移.
直接执行ret, IP=0, cs:ip 指向代码段第一行.
执行CALL时,
执行 CALL 标号, 相当于:
PUSH IP
JMP NEAR PTR 标号
即跳转到指定位置.
; Program that demonstrates CALL and RET instructions
.386
.MODEL FLAT
ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD
.CODE ; start of main program code
_start:
mov eax, 10 ; move data into registers
mov ebx, 20
call add2nums ; push address of next instruction
; onto the stack, and pass control
; to the address of add2nums
INVOKE ExitProcess, 0 ; exit with return code 0
PUBLIC _start ; make entry point public
add2nums:
add eax, ebx ; Add two numbers
ret ; Return result in EAX
END ; end of source code
嘛当然也要有far的call指令: call far ptr 标号
call 16bit reg
call word ptr 内存单元地址
例如:
mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
mov word ptr ds:[2], 0
call dword ptr ds:[0]
8 位结果放在 AX, 16 位 结果放在 DX, AX.
mov al, 100
mov bl, 10
mul bl
ax=1000(03E8H)
mov ax, 100
mov bx, 10000
mul bx
ax=4240h, dx=000fh
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov di 16
mov cx, 8
s: mov bx, [si]
call cube
mov [di], ax
mov [di].2, dx
add si, 2
add di, 4
loop s
mov ax, 4c00h
int 21h
cube: mov ax, bx
mul bx
mul bx
ret
code ends
end start
assume cs:code
data segment
db 'conversation'
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, 12
call capital
mov ax, 4c00h
int 21h
capital: and byte ptr [si], 11011111b
inc si
loop capital
ret
code ends
end start
assume cs:code
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
code segment
start: mov ax, data
mov ds, ax
mov bx, 0
mov cx, 4
s: mov si, bx
call captial
add bx, 5
loop s
mov ax. 4c00h
int 21h
capital: push cx
push si
change: mov cl, [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
code ends
end start
add128: push ax
push cs
push si
push di
sub ax, ax
mov cx, 8
s: mov ax, [si]
adc ax, [di]
mov [si], ax
inc si
inc si
inc di
inc di
loop s
pop di
pop si
pop cx
pop ax
ret
sbb 带借位减法指令. 同样利用CF位上的借位值.
cmp是比较指令, cmp 的功能相当于减法指令, 只是不保存结果.
cmp ax, bx
如果ax==bx, 则 ax-bx=0, zf = 1
ax != bx, zf = 0,
ax < bx, cf = 1,
ax >= bx cf = 0
ax > bx cf = 0, zf = 0
ax <= bx, cf = 1, 或 zf = 1
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | zf = 1 |
jne | 不等于则转移 | zf = 0 |
jb | 小于则转移 | cf = 1 |
jnb | 不小于则转移 | cf = 0 |
ja | 大于则转移 | cf = 0, zf = 0 |
jna | 不大于则转移 | cf=1或 zf = 1 |
编程实现如果 ah=bh, 则 ah=ah+ah, 否则 ah=ah+bh
cmp ah, bh
je s
add ah, bh
jmp short ok
s: add ah, ah
ok: ....
所以鉴于比较命令检测的都是FLAG寄存器, 因此一定要注意其中的值. 不要使用旧值.
data segment
db 8, 11, 8, 1, 8, 5, 63, 38
data ends
code segment
mov ax, data
mov ds, ax
mov bx, 0
mov ax, 0
mov cx, 0
s: cmp byte ptr [bx], 8
je ok
jmp short next
ok: inc ax
next: inc bx
loop s
code ends
mov ax, data
mov ds, ax
mov ax, 0
mov bx, 0
mov cx, 8
s: cmp byte ptr [bx], 8
jna next
inc ax
next: inc bx
loop s
DF位是方向标志位, 在串处理中, 控制si, di的增减.
df=0, si, di 递增, 反之递减.
串传送 movsb 将 ds:si 指向的内存单元中的字节送入 es:di, 然后根据df, 将si,di递增或递减.
movsw, 移动word, 并si,di +-2. 这两个串传送指令通常搭配rep使用.
因此df决定向哪个地址方向复制数据.
cld 指令可以将 df设置为0, std 则置为1.
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
code segment
mov ax, data
mov ds, ax
mov si, 0
mov es, ax
mov di, 16
mov cx, 16
cld
rep movsb
code ends
即DS:SI事项原始数据起始位置, ES:DI 指向目标地址起始位置, 然后设置cx为需要复制的大小, cld 设置df指向, rep movsb 开始复制.
pushf 将标志寄存器值压栈, popf 则从栈弹出数据到标志寄存器
mov ax, 0
push ax
popf
mov ax, 0FFF0H ; 0000 1111 1111 1111 0000
add ax, 0010H ; 0001 0000 0000 0000 0000
pushf
pop ax
and al, 11000101B
and ah, 00001000B
执行完毕后AX = , 即求出FLAG寄存器变动的bit. 然后按bit 与计算.
即 CPU内部产生的中断
如何产生内中断
编写的用来中断处理的程序角中断处理程序. 因此需要映射中断与程序.
在内存中存放 256 个中断源死哦对应的中断处理程序的入口地址.
在8086中, 中断向量表默认存放在 0000:0000 - 0000:03FF 中, 高地址存放段地址, 低地址存放偏移地址.
中断过程是CPU自动完成的.
中断过程还要记录下之前执行的位置, 以便中断结束后继续运行.
具体流程为:
简化即:
编写中断处理程序通常是:
iret相当于:
pop IP
pop CS
popf
默认直接显示信息并退出
讨论了如何改写中断处理程序, 以及需要放到安全的内存地址
利用movsb将中断处理程序放到需要的位置.
assume cs:code
code segment
start: mov ax, cx
mov ds, ax
mov si, offset do0
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset do0end-offset do0; "-" 是编译期运算指令, 可以做减法.
cld ; 传输方向为正
rep movsb
; 设置中断向量表
mov ax, 4c00h
int 21h
do0: 显示字符串 'Overflow!'
mov ax, 4c00h
int 21h
do0end: nop
code ends
end start
assume cs:code
code segment
start: mov ax, cs
mov ds, ax
mov si, offset do0 ; 设置 ds:si 指向源地址
mov ax, 0
mov es, ax
mov di, 200h ; 设置 es:di 指向目的地址
mov cs, offset do0end-offset do0 ; 设置cs为传输长度
cld ; 设置方向为正
rep movsb
; 设置中断向量表
mov ax, 0
mov es, ax
mov word ptr es:[0*4], 200h ; 中断向量地址为0000:0200h 固定地址.
mov word ptr es:[0*4+2], 0
mov ax, 4c00h
int 21h
do0: jmp short do0start
db "Overflow!" ; 这里强调了如果数据放到数据段, 运行完毕数据段内存就释放了. 所以硬编码到程序中.
do0start: mov ax, cs
mov ds, ax
mov si, 202h ; 设置 ds:si 指向字符串
mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2 ; 设置 es:di 指向显存空间的中间位置
mov cx, 9 ; 设置 cx为字符串长度.
s: mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s
mov ax, 4c00h
int 21h
do0end: nop
code ends
end start
见上文代码
CPU提供单步中断为实现单步执行提供了机制.
不响应中断:
mov ax, 1000h
mov ss, ax
mov sp, 0
int为手动触发中断.
assume cs:code
code segment
start: mov ax, 0b800h
mov es, ax
mov byte ptr es:[12*160+40*2], '!'
int 0
code ends
end start
assume cs:code
code segment
start: mov ax, cs
mov ds, ax
mov si, offset sqr
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset sqrend-offset sqr
cld
rep movsb
mov ax, 0
mov es, ax
mov word ptr es:[7ch*4], 200h
mov word ptr es:[7ch*4+2], 0
mov ax, 4c00h
int 21h
sqr: mul ax
iret
sqrend: nop
code ends
end start
安装程序:
assume cs:code
code segment
start: mov ax, cs
mov ds, ax
mov si, offset capital
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset capitalend-offset capital
cld
rep movsb
mov ax, 0
mov es, ax
mov word ptr es:[7ch*4], 200h
mov word ptr es:[7ch*4+2], 0
mov ax, 4c00h
int 21h
capital: push cx
push si
change: mov cl, [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok: pop si
pop cx
iret
capitalend: nop
code ends
end start
BIOS主要包含以下几部分内容:
和硬件设备相关的DOS中断例程中, 一般都调用了BIOS的中断例程
即先加载BIOS的中断向量, BIOS中的中断程序由于是ROM的, 已经在内存了. 然后执行 int19h, 引导操作系统. 系统启动后, 也将需要的历程装入内存并建立中断向量.
解释了如何显示字符
mov ah, 4ch
mov al, 0
int 21h
即调用 21h 中断例程的 4ch 号子程序, 功能为程序返回.
即
mov ax, 4c00h
int 21h
CPU 可以直接读写以下3个地方的数据
端口读写用 in, out
访问端口:
in al, 60h ; 从60h端口读入一个字节.
mov ax, 3f8h
in al, dx
out dx, al
shl, 逻辑左移指令
将一个寄存器或内存单元的数据向左位移, 将在以后移出的一位写入CF中, 最低位用0补充.
mov al, 01001000b
shl al, 1 ; al左移一位.
CMOS中时间使用BCD码存放的.
CPU 通过端口 和外部设备通信
外中断源分为两类:
sti 设置 if=1, cli, if = 0
几乎所有外设引发的外部中断, 都是可屏蔽中断.
扫描码长度为一个字节, 通码的第7位为0, 断码的第7位为1.
断码=通码+80h
键盘输入到达60h端口, 芯片会向CPU发出中断类型为9的可屏蔽中断信息. CPU 检测IF=1, 响应中断, 引发中断例程, 执行 int9中断例程.
执行int9 中断例程
读取60h端口中的扫描码.
字符键将对应的字符码送入内存的BIOS键盘缓冲区, 控制键和切换键则转变为状态字节, 存入内存中存储状态字节的单元
对键盘系进行相关控制, 比如向相关芯片发出应答信息.
BIOS 的键盘缓冲区是系统启动后, BIOS存放键盘输入的内存区域. 可以存储15个键盘输入.