汇编:操作显存数据、描述内存单元的标号、offset、数据的直接定址表

8086CPU 的显存地址分配在0xA0000~0xBFFFF的 128KB 地址空间内,不同显示模式又将该区间细分,适配单色文本、彩色文本和图形模式等不同场景。

具体的显存地址细分如下:


 

基于 8086CPU 的 1MB 寻址空间(0x00000~0xFFFFF)划分的常规内存(0x00000~0x9FFFF)
BIOS ROM(0xC0000~0xFFFFF)0xA0000– 0xAFFFF(64KB)​
主要用于 EGA/VGA 图形模式​ 的像素数据。在标准 VGA 13h 模式(320×200,256 色)中,帧缓冲区就是从 0xA0000开始的 64KB 区域。
0xB0000– 0xB7FFF(32KB)​
对应 单色文本模式(Monochrome Display Adapter, MDA)的显存。MDA 只支持文本,字符属性为单色(加亮、闪烁、下划线等)。
0xB8000– 0xBFFFF(32KB)​
对应 彩色文本模式(Color Graphics Adapter, CGA 及后来的 VGA 文本模式)的显存。
在实模式下,0xB800:0x0000(物理地址 0xB8000)是最常见的文本模式内存起点。每个字符占用 2 字节(ASCII 码 + 属性)。
8086CPU 的显存地址分配在(0xA0000~0xBFFFF)的 128KB 地址空间内

一、显存 0xA0000~0xAFFFF(64KB)EGA/VGA 彩色图形模式显存

该地址段是 8086 系统中 EGA/VGA 彩色图形模式的核心显存区,最典型的应用是 320×200×256 色图形模式(Mode 13h) —— 这是 DOS 时代最常用的图形模式,直接对该地址段读写即可控制屏幕像素。

; 8086汇编 - 显存0xA0000~0xAFFFF操作示例(Mode 13h)
; 功能:1. 切换到320×200×256色图形模式  2. 全屏填充红色  3. 绘制一个蓝色像素点  4. 按键退出
; 显存映射:0xA0000 = 段地址0xA000 + 偏移地址0x0000,对应屏幕(0,0)像素;偏移=Y*320 + X

stack   segment stack
        dw 100h dup(0)  ; 定义栈空间
stack   ends

data    segment
    color_red     db 4     ; 256色模式中红色的调色板索引(可改:0=黑,1=蓝,2=绿,4=红,15=白)
    color_blue    db 1     ; 蓝色调色板索引
    pixel_x       dw 160   ; 像素X坐标(0~319)
    pixel_y       dw 100   ; 像素Y坐标(0~199)
data    ends

code    segment
assume cs:code, ds:data, ss:stack

; 主程序入口
start:
    mov ax, data
    mov ds, ax           ; 初始化数据段

    ; ========== 步骤1:切换到VGA 320×200×256色模式(Mode 13h) ==========
    mov ah, 00h          ; BIOS中断功能号:设置显示模式
    mov al, 13h          ; 13h = 320×200×256色图形模式
    int 10h              ; 调用BIOS显示中断

    ; ========== 步骤2:全屏填充红色(操作0xA0000~0xAFFFF) ==========
    mov ax, 0A000h       ; 显存段地址(0xA000 << 4 = 0xA0000)
    mov es, ax           ; 附加段指向显存
    xor di, di           ; 偏移地址从0开始(对应屏幕(0,0))
    mov cx, 320*200      ; 总像素数:64000(刚好占满0xA0000~0xAFFFF)
    mov al, color_red    ; 填充颜色:红色
    rep stosb            ; 批量写入(cx次,每次写1字节到es:di)

    ; ========== 步骤3:在屏幕(160,100)绘制蓝色像素点 ==========
    ; 计算像素偏移地址:Y*320 + X
    mov ax, pixel_y      ; ax = Y坐标(100)
    mov bx, 320          ; bx = 每行像素数
    mul bx               ; ax = 100*320 = 32000
    add ax, pixel_x      ; ax = 32000 + 160 = 32160
    mov di, ax           ; di = 偏移地址32160(对应显存0xA0000+32160)
    mov al, color_blue   ; al = 蓝色
    mov es:[di], al      ; 直接写入显存,屏幕立即显示蓝色像素

    ; ========== 步骤4:按键等待退出 ==========
    mov ah, 07h          ; BIOS中断功能号:无回显读键盘
    int 21h              ; 按任意键继续

    ; ========== 步骤5:恢复文本模式 ==========
    mov ah, 00h          ; BIOS中断功能号:设置显示模式
    mov al, 03h          ; 03h = 80×25彩色文本模式(默认)
    int 10h              ; 恢复显示模式

    ; ========== 程序退出 ==========
    mov ah, 4Ch          ; DOS中断功能号:程序退出
    int 21h

code    ends
end start

核心知识点解析

  1. 地址映射逻辑

8086 是分段寻址,0xA0000的物理地址 = 段地址0xA000 × 16 + 偏移地址0x0000

  • 320×200 分辨率下,每个像素占 1 字节,64000 个像素刚好填满0xA0000~0xAFFFF(64KB)。

  • Mode 13h 的优势

  • 该模式下显存是线性连续的(无平面 / 位平面划分),直接通过es:di指向像素位置即可读写,是 8086 图形编程最简洁的模式。

  • 像素坐标计算

  • 屏幕左上角为(0,0),右下角为(319,199)
  • 任意像素(X,Y)的显存偏移 = Y×320 + X(每行 320 个像素,每个像素 1 字节)。

  • 调色板说明

  • 256 色模式的调色板需通过 BIOS 中断10h设置(示例中用默认调色板:0 = 黑,1 = 蓝,2 = 绿,4 = 红,15 = 白),若需自定义颜色,可调用10h10h子功能修改调色板寄存器。


这是运行的效果,满屏红色像素,中间是一个蓝色像素点。

注意:需要将DOSBOX-X中的配置文件中的machine更改为VGA模式才可以

machine = vga     ; 强制模拟VGA显卡(默认可能是svga,部分Mode 13h兼容差)

二、显存0xB0000– 0xB7FFF(32KB)MDA 单色字符显示适配器显存

​对应 单色文本模式(Monochrome Display Adapter, MDA)的显存。MDA 只支持文本,字符属性为单色(加亮、闪烁、下划线等)。

8086CPU 的显存地址 0xB0000–0xB7FFF(32KB)是专为MDA 单色字符显示适配器设计的显存区域,主要用于早期 IBM PC 的黑白文本显示,是 8086 实模式下内存地址空间分配的重要组成部分。

在单色文本模式下,0xB0000–0xB7FFF 的 32KB 空间可支持80 列 ×25 行的字符显示(早期还支持 80×50 的显示模式),每个字符占用 2 字节:1 字节存储 ASCII 码,1 字节存储显示属性(如亮度、闪烁等)。

1981 年首款 IBM PC 仅配备 MDA 单色显卡,显存仅占用 0xB0000–0xB0FFF(4KB);1983 年 CGA 彩色显卡出现后,为兼容原有 MDA,将 B000 段拆分为单色(0xB0000–0xB7FFF)和彩色(0xB8000–0xBFFFF)两部分,各自占用 32KB。


三、显卡0xB8000– 0xBFFFF(32KB)CGA 彩色字符显示适配器显存

​对应 彩色文本模式(Color Graphics Adapter, CGA 及后来的 VGA 文本模式)的显存。
在实模式下,0xB800:0x0000(物理地址 0xB8000)是最常见的文本模式内存起点。每个字符占用 2 字节(ASCII 码 + 属性)。

屏幕的文本显示遵循 80 列 × 25 行 的标准布局,总共需要 80×25=2000 个字符位置,每个位置占 2 字节,总占用空间为 2000×2=4000 B(远小于 32KB),剩余空间为显卡硬件预留。如下所示:

下面用实际的代码展示:

以上是在debug模式下直接修改显存

问题:汇编代码,在屏幕的中间,用白底蓝字,显示‘welcome to masm!’

assume cs:code ,ds:data

data segment
	db 'welcome to masm!'
data ends

code segment
start:
    ; 初始化寄存器
    mov ax,data
    mov ds,ax
    mov ax,0B800H
	mov es,ax
	mov si,0
	mov di,160*12+80-16
	
	mov cx,16
w:	mov al,[si]
	mov es:[di],al
	inc di
	mov al,71H
	mov es:[di],al
	inc si
	inc di
	loop w 
	
    ; 退出
    mov ah,4Ch
    int 21h
code ends
end start

错误:

最开始以为mov al,71H应该是mov ah,71H才是赋值给高8位,后来实践才发现错了,因为inc di已经自动+1了,就相当于地址已经指向显存的高8位,此时mov al,71H就是赋值到显存的高8位,下一轮循环又是显存低8位,再一轮显存高8位,如此反复,一直使用al就可以了。

下面这个才是代码 的正确显示结果:


描述内存单元的标号

一、数据标号的本质

数据标号本质是内存地址的别名,包含两个关键属性:

  1. 段基址:默认关联DS段(数据段),可通过段超越前缀修改(如ES:);
  2. 偏移地址:标号在段内的偏移量(16 位);
  3. 类型属性:隐含数据长度(字节 / 字 / 双字等),由定义方式决定。

二、数据标号的定义(伪指令)

8086 中通过数据定义伪指令DB/DW/DD/DQ/DT)定义数据并关联标号,格式:

标号名  数据定义伪指令  操作数 [,操作数,...]  ; 注释
伪指令含义数据长度(字节)示例
DB定义字节(Byte)1num DB 12H
DW定义字(Word)2(低字节在前)arr DW 1234H, 5678H
DD定义双字(Double)4addr DD 00100020H
DQ定义四字(Quad)8big_num DQ 12345678H
DT定义十字节(Ten)10bcd_data DT 1234567890H
关键说明:
  1. 标号指向首个数据单元:标号的偏移地址是第一个数据的偏移,后续数据按长度连续存储;
  2. 操作数支持多种形式
    • 立即数:count DB 5(字节单元存 5);
    • 表达式:max_val DW 100+20(字单元存 120);
    • 字符串:str DB 'ABC'(每个字符占 1 字节,按 ASCII 存储);
    • 预留空间:buf DB 10 DUP(0)(DUP:重复,预留 10 个字节并初始化为 0);
    • 无初始值:temp DW ?(预留 1 个字单元,值不确定)。

下面看下实例:


offset

在 8086CPU 中,OFFSET是汇编语言里的运算符(而非指令),核心作用是获取标号(Label)或变量 在其所在段内的偏移地址(有效地址 EA),返回值是一个 16 位的立即数(因为 8086 的偏移地址是 16 位)。

一、核心概念:段地址 + 偏移地址

8086 采用 “分段寻址”,物理地址 = 段地址 ×16 + 偏移地址:

  • 段地址:由段寄存器(CS/DS/SS/ES)存放;
  • 偏移地址:指令 / 数据 / 栈在段内的偏移量,OFFSET就是获取这个值。

二、OFFSET 的语法与用法

1. 基本语法
MOV 寄存器, OFFSET 标号/变量

作用:将标号 / 变量的段内偏移地址送入寄存器(仅支持 16 位通用寄存器:AX/BX/CX/DX/SI/DI/BP/SP)。

2. 示例 1:获取标号的偏移地址
assume cs:code

code segment
start:  ; 标号start,偏移地址为0(段起始处)
    mov ax, OFFSET start  ; ax = 0(start在code段内的偏移是0)
    mov bx, OFFSET next   ; bx = 3(next的偏移:start占3字节,MOV指令占2字节?需看指令长度)
    
next:   ; 标号next
    mov cx, OFFSET next   ; cx = 3(假设start到next之间的指令占3字节)
    mov ax, 4c00h
    int 21h
code ends
end start

3. 示例 2:获取变量的偏移地址

asm

assume ds:data, cs:code

data segment
    db 10 dup(0)  ; 变量1:偏移0
var1 dw 1234h     ; 变量2:偏移10(前10个字节是db)
var2 db 'abc'     ; 变量3:偏移12(var1占2字节)
data ends

code segment
start:
    mov ax, data
    mov ds, ax     ; 初始化数据段寄存器
    
    mov si, OFFSET var1  ; si = 10(var1在data段内的偏移)
    mov di, OFFSET var2  ; di = 12(var2的偏移)
    
    mov ax, 4c00h
    int 21h
code ends
end start


将标号当做数据来定义

data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends

数据的直接定址表

在汇编语言和底层编程中,直接定址表是一种通过固定内存地址索引来快速访问数据或代码的存储结构,核心是利用基地址 + 偏移量的寻址方式,实现高效的数据查询、分支跳转等操作。

它的本质是一块连续的内存区域,每个表项占用固定字节数,通过直接计算偏移量就能定位到目标表项,无需遍历,广泛用于数据查表、函数跳转表等场景。

问题:以十六进制的形式在屏幕中间显示给定的byte型数据

assume cs:code  ; 声明代码段寄存器cs关联code段
code segment    ; 定义代码段
start:          ; 程序入口标签
    mov al,2BH  ; 把要显示的十六进制数2BH(二进制101011)送入al寄存器
    call showbyte ; 调用显示单字节十六进制的子程序
    
    mov ax, 4c00h ; DOS中断退出程序的功能号(4C)
    int 21h       ; 触发21h中断,程序退出
showbyte:
    jmp short show  ; 跳转到show标签(跳过字符表)
    table db '0123456789ABCDEF'  ; 十六进制字符对照表(索引0-15对应字符0-F)
show:
    ; 保护现场:将bx/es/cx压栈(子程序返回后恢复原值)
    push bx
    push es
    push cx
    
    ; 第一步:拆分高4位(十六进制的第一位)
    mov ah,al       ; al=2BH(101011) → ah=2BH
    mov cl,4        ; 移位次数=4
    shr ah,cl       ; ah右移4位 → ah=02H(高4位单独取出)
    and al,00001111B; al与0Fh相与 → al=0BH(低4位单独取出)
    
    ; 第二步:查字符表,获取高4位对应的字符('2')
    mov bl,ah       ; bl=02H(字符表索引)
    mov bh,0        ; bx=0002H(确保bh为0,仅用bl索引)
    mov ah,table[bx]; 查表:table[2]='2' → ah='2'
    
    ; 第三步:将高4位字符写入显存(屏幕第12行、第40列)
    mov bx,0b800H   ; 文本模式显存起始地址(B8000H)
    mov es,bx       ; es指向显存段
    mov es:[160*12+40*2],ah ; 显存偏移=12行×160(每行字节数)+40列×2(每个字符占2字节)→ 写入字符'2'
    
    ; 第四步:查字符表,获取低4位对应的字符('B')
    mov bl,al       ; bl=0BH(字符表索引)
    mov bh,0        ; bx=000BH
    mov al,table[bx]; 查表:table[11]='B' → al='B'
    
    ; 第五步:将低4位字符写入显存(屏幕第12行、第10列+2)
    mov es:[160*12+10*2+2],al
    
    ; 恢复现场:弹出cx/es/bx(与压栈顺序相反)
    pop cx
    pop es
    pop bx
    ret ; 子程序返回
code ends
end start ; 程序入口指向start标签

代码运行后,显示结果如下:

关键知识点:

table[bx] 本质:寄存器相对寻址

1. 寻址公式

table[bx] 等价于 [table + bx],最终物理地址的计算规则是:

plaintext

物理地址 = (DS × 16) + table(偏移量) + bx(寄存器值)
  • table:是字符表的起始偏移地址(汇编器会把 table 翻译成一个数值,代表它在段内的偏移);
  • bx:基址寄存器,存储「字符表的索引值」;
  • 结合后,table[bx] 指向「字符表起始位置 + bx 偏移量」的内存单元。

再进一步完善代码,在显示的时候让2B有颜色

assume cs:code

code segment
start:
    mov al,2BH
    call showbyte
    
    mov ax, 4c00h
    int 21h
	
showbyte:
    jmp short show 
    table db '0123456789ABCDEF'
show:
    push bx
    push es
    
    ; 拆分高低4位
    mov ah,al
    mov cl,4
    shr ah,cl
    and al,0FH
    
    ; 查表获取字符
    mov bx,0
    mov bl,ah
    mov ah,table[bx]  ; 高4位字符
    mov bl,al
    mov al,table[bx]  ; 低4位字符
    
    ; 写入显存(第12行第40列,相邻显示)
    mov bx,0b800H
    mov es,bx
    mov es:[160*12+40*2],ah   ; 高4位字符
    mov byte ptr es:[160*12+40*2+1],11001010B ; 第2字节=属性(11=红背景,001010=黄前景)
    mov es:[160*12+40*2+2],al ; 低4位字符
    mov byte ptr es:[160*12+40*2+3],11001010B ; 第2字节=属性(11=红背景,001010=黄前景)
    
    pop es
    pop bx
    ret
	
code ends
end start

在DOXBOX-X中使用masm命令编译后,显示错误:operand must have size

原因分析如下:

在 DOSBox-X 中编译时出现operand must have size(操作数必须指定长度),核心原因是汇编器无法自动推断内存操作数的访问长度—— 虽然你写的二进制数是 8 位,但es:[偏移地址]这种内存寻址方式本身没有明确的字节 / 字长度标识,MASM/TASM 等汇编器会要求显式指定操作数尺寸。

问题根源

8086 汇编中,当内存操作数仅用[]寻址(无寄存器 / 立即数暗示长度)时,汇编器无法判断你要访问1 字节(byte) 还是2 字节(word),因此必须用byte ptr/word ptr显式声明。

修正后的指令

将原指令补充字节长度标识即可解决,最终指令:

mov byte ptr es:[160*12+40*2+1], 11001010B
  • byte ptr:明确告诉汇编器,本次内存操作是字节级(8 位);
  • 若误写word ptr,会报错(因为源操作数是 8 位,长度不匹配)。

最终完善的代码如下:

assume cs:code

code segment
start:
    mov al,2BH
    call showbyte
    
    mov ax, 4c00h
    int 21h
	
showbyte:
    jmp short show 
    table db '0123456789ABCDEF'
show:
    push bx
    push es
    
    ; 拆分高低4位
    mov ah,al
    mov cl,4
    shr ah,cl
    and al,0FH
    
    ; 查表获取字符
    mov bx,0
    mov bl,ah
    mov ah,table[bx]  ; 高4位字符
    mov bl,al
    mov al,table[bx]  ; 低4位字符
    
    ; 写入显存(第12行第40列,相邻显示)
    mov bx,0b800H
    mov es,bx
    mov es:[160*12+40*2],ah   ; 高4位字符
    mov byte ptr es:[160*12+40*2+1],11001010B ; 第2字节=属性(11=红背景,001010=黄前景)
    mov es:[160*12+40*2+2],al ; 低4位字符
    mov byte ptr es:[160*12+40*2+3],11001010B ; 第2字节=属性(11=红背景,001010=黄前景)
    
    pop es
    pop bx
    ret
	
code ends
end start

2B高亮显示,而且文字不停闪烁着

posted @ 2025-12-08 22:06  chenlight  阅读(8)  评论(0)    收藏  举报  来源