寄存器:

以8086为例:

寄存器都是16位

*通用寄存器:*AX, BX, CX, DX
每个寄存器又可以分为高8位和低8位:如AX -> AH, AL 每个寄存器都可以独立使用

处理数据的尺寸
字节 byte 8位 字 word 16位

简单的汇编指令
mov ax bx ax <- bx
add ax bx ax <- ax + bx
jmp 3:0b16 CS <- 3h IP <- 0b16h
jmp ax ip <- ax

16位cpu:
1.运算器一次最多处理16位数据
2.寄存器最大宽度位16
3.reg和alu之间的数据通路为16位

地址:(内存中没有段的划分)
20位物理地址 16位段地址 16位偏移地址 -> 一个段的最大长度为 2 << 16 = 64kb
20位地址只在内存中使用
物理地址 = 段地址 << 4 + 偏移地址

段寄存器
CS DS SS ES (段:segment)

CS和IP:
CS 代码段寄存器 IP 指令指针寄存器
读取一个指令后IP自动增加一个指令的长度
启动,复位后 CS = FFFFH IP= 0000H

代码段
最大64kb, 起始地址16倍数

模块化程序设计的相关问题

例子:简单的模块实例,设计一个子程序,根据提供的N,计算N的三次方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
assmume 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, ax
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

这是一个很简单的子程序,没有任何的寄存器冲突

寄存器冲突存在下的模块化设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 capital
add bx, 5
loop s

mov ax, 4c00h
int 21h

capital
push 1

汇编代码の使用教程

retretf

ret 相当于,
pop ip
适用于近迁移

retf 相当于
pop ip
pop cs
适用于远迁移

call

CPU执行call操作的时候进行两步操作:
1.将当前的CS和IP压栈,或者只压栈当前的IP
2.转移

  • 第一种情况:call标号
    相当于:
    push IP
    jmp near ptr 标号

  • 第二种情况:call far ptr 标号
    相当于:
    push CS push IP jmp far ptr 标号`

  • 第三种情况:call 16位reg
    相当于:
    push IP
    jmp 16位reg

  • 第四种情况:call word ptr 内存单元地址

    相当于:
    push IP
    jmp word ptr 内存单元地址
    如果是dword的话
    call dword ptr 内存单元地址
    相当于:
    push CS
    push IP
    jmp dword ptr 内存单元地址

mul

格式如下:
mul reg
mul 内存单元
分两种情况:

如果是两个8位相乘,那么结果全部放在AX中。
如果是两个16位相乘,那么结果的高16位放在DX中,低16位放在AX中。

adc

adc 带进位加法指令
用法:adc ax, bx
相当于计算:
(ax) = (ax) + (bx) + CF

sbb

sbb 带借位的减法
用法:sbb ax, bx
相当于计算:
(ax) = (ax) - (bx) - CF

cmp

cmp 是比较指令, 本质上是不保存结果的减法指令
用法:cmp ax, bx
先进行计算ax - bx,计算后
![[Pasted image 20260426232144.png]]
对应的跳转指令如下:
![[Pasted image 20260426232305.png]]

pushf和popf

pushf:标志寄存器入栈
popf:标志寄存器出栈

标志寄存器Related

  1. ZF寄存器:判断相关指令结果是否为0,是0则ZF = 1
  2. PF寄存器:奇偶标准位,记录相关指令执行后结果的所以bit中1的个数是否为偶数,如果是偶数则PF = 1,16位寄存器进行操作时,只看低8位。
  3. SF寄存器,记录相关指令执行后结果是否位负,如果为负则SF = 1
  4. CF寄存器,在进行无符号数运算的时候,记录运算结果最高有效位向更高位的进位值
  5. OF寄存器,在进行有符号数的运算的时候,如果结果超过机器表示范围,称为溢出
  6. DF寄存器和串传送指令:
    df是方向标志位,在传处理指令中控制si, di的增减
    1. movsb

      相当于执行下面几步操作
      es:[di] = ds:[si]
      if df = 0:
      (si) = (si) + 1
      (di) = (di) + 1
      if df = 1:
      (si) = (si) - 1
      (di) = (di) - 1
      使用格式为:rep movsb
      相当于:
      s:movsb
      loop s
      所以使用这个指令的时候要设置cx, si, di, es, ds

    2. movsw

      具体操作与movsb相同,si, di的步长改为2

    3. df的设置指令:

      cld: df <- 0
      sld: df <- 1

内中断

cpu发生以下情况时,将产生中断信息

  1. 除法错误,如div的指令溢出 –中断码0
  2. 单步执行 –中断码1
  3. 执行into 指令 –中断码4
  4. 执行int指令 –中断码为指令参数

中断向量表:

储存在内存的0000:00000000:03FF 这1024个单元中

中断过程(8086CPU):

  • 从中断信息中获取中断类型码
  • 标志寄存器入栈(中断过程要改变标志寄存器的值)
  • 设置标志寄存器第八位的TF和第九位IF的值为0
  • CS入栈
  • IP入栈
  • 从内存地址为中断类型码 * 4 和中断类型码* 4 + 2 两个字单元中读取中断处理程序的入口地址,设置CSIP
    相当于:

    1. 获取终端类型码N
    2. pushf
    3. TF = 0, IF = 0
    4. push CS
    5. push IP
    6 (IP) = (N * 4), (CS) = (N * 4 + 2)

  • 中端处理程序和iret 指令

    iret 指令相当于:
    pop IP
    pop CS
    popf

编程处理0号中断

现在我们考虑,重新写一个0号中断处理程序,功能是在拼命中间显示”overflow!”,然后返回系统。

为了实现这个功能我们来进行分析:

  1. 当除法发生溢出的时候,产生0号中断信息,从而引发中断过程。
    • 此时CPU进行以下工作:
      (1). 获取中断类型码0;
      (2). 标志寄存器入栈,TF和IF设置为0
      (3). CS, IP入栈
      (4). (IP) = (0 * 4), (CS) = (0 * 4 + 2)
    • 可见中断0发生的时候,CPU将转去执行中断处理程序,只要按照以下步骤编写中断处理程序,当中断0发生的时候,即可显示“overflow!”
      1. 相关处理;
      2. 向显示缓冲区宋字符串”overflow!”;
      3. 返回DOS
        我们将这段程序成为do0
    • 现在问题是:do0应该放在内存中。因为除法溢出随时有可能发生,CPU随时可能将CS:IP指向do0的入口,执行程序。
    • 内存0000:0000-0000:03ff,大小为1kb的空间是存放中断程序入口地址的中断向量表。8086支持256个中断,但是实际上系统要处理的中断远没有256个。一般情况下,从0000:0200-0000:02ff的256个字节对于空间的中断向量表是空的,我们可以把do0放到0000:0200后
      程序的整体框架如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    assume cs:code

    code segment

    start:
    do0安装程序
    设置中断向量表
    mov ax, 4c00h
    int 21h

    do0:
    显示字符串"overflow!"
    mov ax, 4c00h
    int 21h

    code ends

    end start

do0的安装

可以用movsb指令,将do0的代码送入0:200处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
assume cs:code
code segment

start:
mov ax, cs
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

do0

按照我们一贯的思路:

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code

data segment
db "overflow!"
data ends

code segment

start:
mov ax, cs
mov ds, ax
mov si, ....

看似很合理,实际上大错特错。”overflow!”在程序的data段中,当程序执行完后返回,它所占用的空间会被系统是否,其中存放的字符串很肯可能被其他的信息覆盖。
正确的程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
asuume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0
mov ax, 0
mov es, cx
mov di, 200h
mov cx, offset do0end - offset do0

cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[0 * 4], 200h
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

mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2

mov cx, 9
s:
mov al, [si]
mov es:[di], al
int si
add di, 2
loop s

mov ax, 4c00h
int 21h
do0end:nop
code ends
end start