汇编:改写中断例程-以int9为例、更改屏幕颜色

assume cs:code

stack segment
	db 128 dup(0)		; 定义栈段,大小128字节
stack ends

code segment
	start:
	;设置各段地址
	mov ax,stack
	mov ss,ax
	mov sp,128		 ; 栈顶指向栈段末尾(128字节栈,sp初始为128)
	push cs
	pop ds			; 让ds = cs(代码段和数据段同段,方便访问自定义中断例程
	mov ax,0
	mov es,ax		; es指向0段(中断向量表所在的段)
	
	;安装新程序(自定义int9程序安装在0:204位置处)
	mov si,offset int9		; si = 自定义int9例程的偏移地址
	mov di,204h				; di = 0段204H(新例程的存放地址)
	mov cx,offset int9end - offset int9			;cx = 新例程的字节长度
	cld						; 方向标志DF=0(串操作时si/di递增)
	rep movsb				; 循环复制:将cs:[si]的内容复制到es:[di],共cx字节
	
	;将原中断地址保存在0:200单元处
	push es:[9*4]
	pop es:[200H]
	push es:[9*4+2]
	pop es:[202H]
	
	;将自定义的int9程序所在地址CS:IP写入---向量表中,向量表触发时就转到0:204处的自定义int9处
	cli							; 关中断(禁止CPU响应外部中断,防止修改向量时被打断)
	mov word ptr es:[9*4],204H	; int9偏移地址 = 204H(新例程偏移)
	mov word ptr es:[9*4+2],0	; int9段地址 = 0(新例程段地址)
	sti							; 开中断(恢复中断响应)
	
	mov ax,4c00h
	int 21h
	
	;定义新中断例程
int9:
	push ax
	push bx
	push cx
	push es
	
	in al,60H
	pushf			;仅将标志位寄存器压入栈中
	
	;调用旧中断例程
	call dword ptr cs:[200h]		;调用原系统向量表保存的CS:IP地址,并执行;
	
	;处理F1键
	cmp al,3BH
	jne int9ret
	
	mov ax,0B800H
	mov es,ax
	mov bx,1
	mov cx,2000
s:
	inc byte ptr es:[bx]
	add bx,2
	loop s 
	
int9ret:
	pop es
	pop cx
	pop bx
	pop ax
	iret
int9end:nop
code ends
end start
	

关键知识点:

在 Intel 8086 CPU 中,IRET(Interrupt Return,中断返回)是一条用于从中断服务程序(ISR, Interrupt Service Routine)返回到被中断程序的指令。


一、功能说明

IRET 指令的主要作用是恢复被中断时的程序执行上下文。它会从堆栈中弹出以下内容(按顺序):

  1. IP(Instruction Pointer) —— 返回地址的偏移部分
  2. CS(Code Segment) —— 返回地址的段部分
  3. FLAGS(标志寄存器) —— 恢复中断前的标志状态(包括 IF、TF 等)

注意:8086 是实模式处理器,不支持保护模式下的特权级切换,因此 IRET 在 8086 中只处理上述三个寄存器。


二、堆栈操作(执行 IRET 时)

假设堆栈指针为 SP,堆栈向下增长(向低地址方向),执行 IRET 相当于以下操作:

POP IP
POP CS
POP FLAGS

即:

  • 从 [SP] 弹出 IP(2 字节)
  • 从 [SP+2] 弹出 CS(2 字节)
  • 从 [SP+4] 弹出 FLAGS(2 字节)
  • 最终 SP 增加 6(因为弹出了 3 个字,每个 2 字节)

三、使用场景

IRET 必须用在中断服务例程的末尾,用于:

  • 软件中断(如 INT 21h)
  • 硬件中断(如 IRQ0 对应的 INT 08h)
  • 异常处理(如除零异常 INT 0)

例如:

; 中断服务程序示例
my_isr:
    push ax
    push dx
    ; ... 中断处理代码 ...
    pop dx
    pop ax
    iret      ; 返回到被中断的程序

注意:在进入 ISR 时,CPU 自动将 FLAGS、CS、IP 压入堆栈;因此必须用 IRET 返回,不能用 RET,否则无法恢复 FLAGS,可能导致中断系统异常(如 IF 标志未恢复,导致后续中断被屏蔽)。


问题:接收用户的键盘输入,输入r,屏幕上的字符为红色;输入g,屏幕上的字符为绿色,输入b,屏幕上的字符为蓝色。

assume cs:code
code segment
start:
    ; ========== 1. 等待键盘输入 ==========
    mov ah, 0       ; int 16h功能号0:读取键盘输入(阻塞)
    int 16h         ; 返回:al=按键ASCII码,ah=扫描码

    ; ========== 2. 识别按键并设置颜色值 ==========
    mov ah, 1       ; 初始颜色值(先给蓝色位,后续通过移位调整)
    cmp al, 'r'     ; 判断是否按了r键(红色)
    je red          
    cmp al, 'g'     ; 判断是否按了g键(绿色)
    je green        
    cmp al, 'b'     ; 判断是否按了b键(蓝色)
    je blue         
    jmp short sret  ; 非r/g/b则直接退出

    ; ========== 3. 颜色值调整(通过移位定位RGB位) ==========
red:                ; 红色:需要将1移到第2位(二进制 010)
    shl ah, 1       ; ah=1 → 左移1次 → 2 (010)
green:              ; 绿色:需要将1移到第1位(二进制 001→左移2次→100)
    shl ah, 1       ; 若从red跳转:ah=2→左移1次→4 (100);若直接跳green:ah=1→左移1次→2 (010)
blue:               ; 蓝色:初始值1(001),无需移位

    ; ========== 4. 设置屏幕颜色(显存操作) ==========
    mov bx, 0B800H  ; 文本模式显存起始地址
    mov es, bx      ; es指向显存段
    mov bx, 1       ; 显存中每个字符占2字节:0位=ASCII,1位=属性(颜色)
    mov cx, 2000    ; 80*25=2000个字符(全屏)
s:
    ; 清空原有颜色(保留高5位,低3位清0)
    and byte ptr es:[bx], 11111000B  
    ; 或运算设置新颜色(低3位:001=蓝,010=绿,100=红)
    or es:[bx], ah                   
    add bx, 2       ; 指向下一个字符的属性位
    loop s          ; 循环填充全屏

    ; ========== 5. 程序退出 ==========
sret:               ; 注意:原代码此处是全角冒号,需改为半角
    mov ax, 4c00h   
    int 21h         ; DOS中断:程序退出

code ends
end start

执行 red: shl ah,1 后的完整流程解析

要明确这条指令后的执行流程,核心是理解汇编的 “顺序执行” 规则 + 代码中red/green/blue标签的 “串联逻辑”,以下分步骤拆解(结合你最终修正后的代码):

前提回顾

代码中ah初始值为1(二进制00000001),且通过cmp al,'r'跳转到red标签,说明用户按下了r键。


第一步:执行red: shl ah,1

red:
    shl ah,1  ; 核心指令:ah = 1(00000001)左移1次 → ah = 2(00000010)
  • 指令功能:将ah的所有位向左移动 1 位,最低位补 0,最高位移入 CF(进位标志)。
  • 执行后:ah = 2(二进制00000010)。
  • 关键规则:汇编中没有跳转 / 分支指令时,会按代码物理顺序向下执行 → 执行完shl ah,1后,会直接跳到下一行的green:标签处。(这一步才是整个代码理解的重点)

第二步:执行green: shl ah,1

green:
    shl ah,1  ; ah = 2(00000010)左移1次 → ah = 4(00000100)
  • 注意:这里不是通过je green跳转,而是 “顺序执行” 进入green标签(green本质只是代码地址标记,不是 “仅跳转才能执行”)。
  • 执行后:ah = 4(二进制00000100),对应文本模式中 “红色位(第 2 位)为 1”。
  • 继续顺序执行:跳到下一行的blue:标签处。

第三步:执行blue:及后续显存操作

blue:
    mov bx,0B800H  ; 显存段地址
    mov es,bx      
    mov bx,1       ; 指向第一个字符的属性位(显存偶地址存ASCII,奇地址存颜色)
    mov cx,2000    ; 80*25=2000个字符(全屏)
s:
    and byte ptr es:[bx],11111000B  ; 清空原有前景色(低3位)
    or es:[bx],ah                   ; 写入红色(ah=4=100B)
    add bx,2                        ; 下一个字符的属性位
    loop s                          ; 循环填充全屏
  • 核心逻辑:
    1. es指向显存段0B800H
    2. 循环 2000 次,每次清空显存属性位的低 3 位(原有颜色),再将ah=4(红色)写入;
    3. bx+2是因为每个字符占 2 字节,跳过 ASCII 位直接操作属性位。

效果如下:

posted @ 2025-12-11 21:31  chenlight  阅读(1)  评论(0)    收藏  举报  来源