《汇编语言》(王爽)补充笔记,第 10 ~ 13 章

第十章 CALL 和 RET 指令

● call 与 ret 指令均为转移指令,用于子程序实现

● ret 用栈中数据修改 IP 实现段内转移,执行本质:pop IP,或表示成 IP = ((ss) * 16 + (sp)),sp = (sp) + 2(括号表求值),以下代码展示一个从腰开始、到头结束的程序

 1 assume cs:codesg
 2 
 3 stack segment
 4     db 16 dup (0)
 5 stack ends
 6 
 7 codesg segment
 8     mov     ax, 4c00h
 9     int     21h
10 start:
11     mov     ax, stack   ; 手动维护一个栈
12     mov     ss, ax      
13     mov     sp, 16
14     mov     ax, 0       ; 把 0 入栈
15     push    ax
16     mov     bx, 0       ; 做一些其他事
17     ret                 ; IP = 0,CS:IP 指向第一条指令
18 codesg ends
19 
20 end start

● retf 用栈中数据修改 CS 与 IP 实现段间转移,执行本质:pop IP    pop CS,以下代码类似

 1 assume cs:codesg
 2 
 3 stack segment
 4     db 16 dup (0)
 5 stack ends
 6 
 7 codesg segment
 8     mov     ax, 4c00h
 9     int     21h
10 start:
11     mov     ax, stack
12     mov     ss, ax
13     mov     sp, 16
14     mov     ax, 0
15     push    cs          ; 多一个 cs 入栈
16     push    ax
17     mov     bx, 0
18     retf
19 codesg ends
20 
21 end start

● call mark 实现 IP 压栈后段内跳转,执行本质:push IP   jmp near PTR mark

● call far mark 实现 CS、IP 压栈后段间跳转,执行本质:push CS    push IP   jmp far PTR mark

● call reg16call word PTR addr ,call dword PTR addr 类似实现压栈和跳转

● 非溢出的除法,XH 与 XL 表 X 的高 16 位和低 16 位:X / N = XH / N * 65536 + ((X - XH / N * N) * 65536 + XL) / N

第十一章 标志寄存器(这部分的例子写的都挺好)

● 8086 CPU标志寄存器(16 位)中存储的信息称为程序状态字(PSW)。从高位(15)到低位(10)依次为:无,无,无,无,OF(11),DF,IF,TF,SF,ZF(6),无,AF(4),无,PF(2),无,CF(0)

● ZF 零标志位,PF 奇偶标志位,SF 符号标志位,CF 进位标志位,OF 溢出标志位,DF 方向标志位,

● SF 理解:将数据当有符号数运算时,可通过 SF 得知结果正负,将数据当无符号数运算时,SF 无意义,虽然指令可能影响了其值

● CF 理解:无符号运算最高有效位(依寄存器,不依实际数值)向假想的更高位的进位和借位的情况

● OF 理解:有符号运算是否溢出(正 / 负)的情况

● 好例,说明 CF 和 OF 之间并没有关系:

  mov al, 62h    add al, 63h  ; 执行后 CF = 0(无符号加法 98 + 99 = 197 < 255),OF = 1(有符号加法 98 + 99 = 197 > 127)

  mov al, F0h    add al, 88h  ; 执行后 CF = 1(无符号加法 240 + 136 = 376 > 255),OF = 1(有符号加法 -16 - 120 = -136 < -128)

  mov al, F0h    add al, 78h  ; 执行后 CF = 1(无符号加法 240 + 120 = 360 > 255),OF = 0(有符号加法 -16 + 120 = 104)

● 指令 cmp 理解:两个操作数作差,但不输出结果,仅根据结果调整标志寄存器的数值。

● 无符号数比较,执行 cmp ax, bx 后,根据 ZF 和 CF 来判断两个操作数的情况

  ZF = 1,说明 ax == bx

  ZF = 0,说明 ax != bx

  CF = 1 或 ZF = 1,说明 ax ≤ bx

  CF = 1,说明 ax < bx

  CF = 0,说明 ax ≥ bx

  CF = 0 且 ZF = 0,说明 ax > bx

● 有符号数比较(有溢出的存在,不能仅从 ZF 和 SF 判断,要考虑溢出标志位),执行 cmp ax, bx 后,

  ZF = 1,说明 ax == bx

  ZF = 0,说明 ax != bx

  OF = 0 且 SF = 1,说明 ax < bx

  OF = 1 且 SF = 1,说明 ax > bx。溢出后结果为负,说明是正溢出,正数减负数导致

  OF = 0 且 SF = 0,说明 ax ≥ bx,配合 ZF判断是否取到等号

  OF = 1 且 SF = 0,说明 ax < bx。溢出后结果为正,说明是负溢出,负数减正数导致

● 无符号条件转移及其等价伪代码

  je      相等转移        if( ZF ==1 ) jmp

  jne    不等转移        if( ZF ==0 ) jmp

  jb      低于转移        if( CF == 1) jmp

  jnb    不低于转移    if( CF == 0 ) jmp

  ja      高于转移        if( CF == 0 && ZF == 0 ) jmp

  jna    不高于转移    if( CF == 1 || ZF == 1 ) jmp

● DF 作用:每次串处理操作后 si,di 按 DF 值进行递增或递减

● pushf 与 popf 指令,将标志寄存器压栈或出栈。可用于访问和修改标志寄存器:

1     pushf
2     pop ax  ; 标志寄存器的值出栈到 ax 中
3 
4     ...     ; 修改 ax 的值
5 
6     push ax
7     popf    ; ax 的值出栈到标志寄存器中

● 在 debug 中,标志寄存器直接按各个标志的值来显示,其对照关系如下:

 寄存器    == 1   == 0 

  OF  OV  NV

  DF  DN  UP

  SF  NG  PL

  ZF  ZR  NZ

  PF  PE  PO

  CF  CY  NC

第十二章 内中断

● 8086 CPU 内中断发生条件及中断类型码:除法错误(0),单步执行(1),into 执行(4),int 执行(n,字节型立即数)

● 中断向量表时中断处理程序入口地址的列表,在 8086 CPU 机器上占据内存首段 0000:0000 ~ 0000:03FF(1024 Byte),每个入口的段地址和偏移地址各占 1 Word(每入口 4 Byte),最多 256 入口。但是一般情况下 0000:0200 ~ 0000:02FF 都是空着的,操作协同和其他程序也不占用,debug 中查看如下

● 8086 CPU 中断过程:

  ① 从中断信息取得中断类型码 N

  ② 保存标志寄存器 pushf

  ③ 置标志寄存器 TF = 0,IF = 0

  ④ 保存当前指令位置 push CS,push IP

  ⑤ 计算中断程序入口 IP = (N * 4),CS = (N * 4 + 2)

  ⑥ 中断程序开始执行,保存要用到的寄存器,处理中断,回复用到的寄存器,用 iret 指令返回(iret 常与硬件自动完成的中断过程配合使用)

● 代码,自行改写 0 号中断,在发生除法溢出时执行自定义的程序(输出字符串 “overflow!”)

 1 assume cs:code
 2 
 3 code segment
 4 start:
 5     call div0               ; 向内存中注入 div0 代码(只需要运行一次,再次调用主程序时不用)
 6        
 7     mov     ax, 1000h
 8     mov     bh, 1h
 9     div     bh              ; 计算 1000h / 1h,使得 al 中放不下结果,进入 0 号中断
10       
11     mov     ax, 4c00h
12     int     21h
13 
14 div0:
15     mov     ax, cs                          ; 设置 ds:si 指向中断程序源代码地址
16     mov     ds, ax
17     mov     si, offset do0                  
18     mov     ax, 0                           ; 设置 es:di 指向目标地址 0000:0200
19     mov     es, ax
20     mov     di, 200h                        
21     mov     cx, offset do0end - offset do0  ; 设置 cx 为传输长度,用两个 offset 来计算
22     cld                                     ; 设置传输方向为正
23     rep     movsb                           ; 代码注入指定地址
24                                             
25     mov     ax, 0                           ; 设置 es:0000 指向中断向量表
26     mov     es, ax
27     mov     word PTR es:[0*4], 200h         ; 将第 0 个中断源指向注入的程序地址
28     mov     word PTR es:[0*4+2], 0h
29           
30     mov     ax, 4c00h
31     int     21h
32 
33 do0:    
34     jmp     short do0start      ; 跳转到真正可执行的代码地址
35     db      "overflow!"         ; 将输出字符串放到 .code 域而不是 .data 域,否则代码注入时找不到
36 
37 do0start:
38     mov     ax, cs              ; 设置 ds:si 指向字符串
39     mov     ds, ax
40     mov     si, 202h      
41     mov     ax, 0b800h          ; 设置 es:di 指向显存空间的中间位置
42     mov     es, ax
43     mov     di, 12*160+36*2
44     mov     cx, 9               ; 设置 cx 为字符串长度    
45 
46 s:                              ; 显示字符串
47     mov     al, [si]
48     mov     es:[di], al
49     inc     si
50     add     di, 2
51     loop s
52 
53     mov ax, 4c00h
54     int 21h
55 
56 do0end:                         ; 标记程序结束的地址,用于计算代码长度
57     nop
58 
59 code ends
60 
61 end start

■ 程序输出,注意 0000 : 0000 处入口地址发生了改变,0000 : 0200 处注入了程序,从右边可见 “overflow!” 的字符串

● 单步中断:CPU 检测到标志寄存器 TF = 1,则产生单步中断,其处理过程同一般中断过程,只是中断类型码为 1

● 有的情况下,即使 CPU 检测到中断信息,也不会发生响应。例如刚执行完 ss 寄存器的传送指令后,CPU 忽略中断,因为 ss : sp 指向栈顶,更改 ss 后可能指向敏感地址,不能调用中断等其他程序。此时应该将调整 sp 的指令紧接着调整 ss 指令存放。

第十三章 int 指令

● int 指令引发内中断,处理过程同一般中断过程

● iret 指令,放在需要返回原程序运行地址的中断例程的末端,等价于指令:pop IP,pop CS,popf,ret

● BIOS 主要包括几方面的内容:① 硬件系统检测室初始化程序;② 外部中断和内部中断的中断例程;③ 对硬件设备进行 I/O 操作的中断例程;④ 其他硬件系统相关的中断历程

● BIOS 安装到内存的过程:

  ① 开机 CPU 加电,初始化 CS = 0FFFFh,IP = 0,从 CS : IP 开始执行。该处有一条跳转指令,跳转执行 BIOS 硬件系统检测和初始化程序

  ② 初始化程序将 BIOS 支持的中断例程入口地址登记到内存的中断向量表中,中断例程本身是固化到 ROM 中的程序,一直在内存中存在

  ③ 硬件系统检测和初始化完成后,调用 int 19h 进行操作系统引导

  ④ DOS 操作系统启动,将其支持的中断例程填入内存

● 中断例程可以包含多个子程序,BIOS 和 DOS 的中断例程都使用寄存器 ah 来传递内部子程序编号

● 内存地址空间中,B80000h ~ BFFFFh 共 32 kB 空间用于 80 * 25 彩色自负模式的显示缓冲区,默认情况下显示第0页 B0000h ~ B8F9Fh 的内容

● 代码,在屏幕上显示文字,以及之前一直使用的程序退出(在这之前有屏幕输出的返利代码都不能正确输出,从这里开始可以用了)

 1 assume cs:code
 2 data segment 
 3  db 'Hello World!','$'  ; 需要输出的字符串用 $ 标记结尾
 4 data ends
 5 
 6 code segment
 7 start:        
 8     mov bh, 0           ; 第 0 页
 9     mov dh, 3           ; 行号
10     mov dl, 9           ; 列号
11     mov ah, 2           ; 调用 BIOS 10 号中断例程 2 号子程序指定光标位置
12     int 10h  
13         
14     mov al, 3           ; 字符 ASCII
15     mov bl, 11001010b   ; 颜色属性,闪烁(BL),背景(RGB),高亮(I),前景(RGB)
16     mov bh, 0           ; 第 0 页
17     mov cx, 8           ; 字符重复数
18     mov ah, 9           ; 调用 BIOS 10 号中断例程 9 号子程序打印字符
19     int 10h
20 
21     mov ah, 2           
22     mov bh, 0            
23     mov dh, 5    
24     mov dl, 9    
25     int 10h
26     
27     mov ax, data        ; ds : dx 指向 data 地址
28     mov ds, ax
29     mov dx, 0           
30     mov ah, 9           ; 调用 BIOS 21 号中断例程 9 号子程序输出字符串
31     int 21h
32 
33     mov ax,4c00h        ; 调用 BIOS 21h 号中断例程 4Ch 号子程序结束程序,指定返回值 00h 
34     int 21h 
35 
36 code ends
37 
38 end start               ; 有数据段的时必须指定入口,否则暴死,无数据段时可以没有入口

■ 输出结果

原文地址:https://www.cnblogs.com/cuancuancuanhao/p/9656436.html