如何从实模式写入带有视频内存地址0xb8000的屏幕?

vak*_*kus 8 x86 assembly real-mode nasm bare-metal

我创建了简单的代码来从硬盘驱动器加载第二个扇区,然后写入整个屏幕,空格为红色背景.问题是我总是用@符号代替空格.这是代码:

org 0x7C00
bits 16

xor ax,ax
mov ds,ax
mov es,ax

mov bx,0x8000
cli
mov ss,bx
mov sp,ax
sti

cld
clc

xor ah,ah
int 0x13
mov bx,0x07E0
mov es,bx
xor bx,bx
mov ah,0x2 ;function
mov al,0x5 ;sectors to read
mov ch,0x0 ;track
mov cl,0x2 ;sector
mov dh,0x0 ;head
int 0x13
;jc error
;mov ah, [0x7E00]
;cmp ah,0x0
;je error
jmp error
cli
hlt
jmp 0x07E0:0x0000

error:
    xor bx,bx
    mov ax,0xb800
    mov es,ax
    mov al,0x40 ;colour
    mov ah,' ' ;character
    .red:
        cmp bx,0x0FA0
        je .end
        mov WORD [es:bx], ax
        inc bx
        jmp .red
    .end:
        cli
        hlt

times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA
Run Code Online (Sandbox Code Playgroud)

根据此代码屏幕应填充空格,但事实并非如此.

Mic*_*tch 11

写入视频内存(从@ 0xb8000开始)时,屏幕上的每个单元格都有2个字节.要显示的字符位于第一个字节中,而属性位于第二个字节中.要将红色(颜色代码0x40)空格(0x20)字符打印到屏幕上的第一个单元格,需要将字节放在内存中,如下所示:

0xb800:0x0000 :  0x20         ; ASCII char for 0x20 is ' '
0xb800:0x0001 :  0x40         ; Red background, black foreground
Run Code Online (Sandbox Code Playgroud)

在您的代码中,您似乎尝试使用以下代码执行此操作:

mov al,0x40 ;colour
mov ah,' ' ;character
.red:
    cmp bx,0x0FA0
    je .end
    mov WORD [es:bx], ax
    inc bx
    jmp .red
Run Code Online (Sandbox Code Playgroud)

不幸的是,因为x86体系结构是little-endian,所以放入内存的值首先是最低有效字节,最后是最高有效字节(当处理16位WORD时).你有AX0x2040和移动整个WORDmov WORD [es:bx], ax显存.例如,它会将这些字节写入第一个单元格:

0xb800:0x0000 :  0x40         ; ASCII char for 0x40 is `@'
0xb800:0x0001 :  0x20         ; Green background, black foreground
Run Code Online (Sandbox Code Playgroud)

我相信这是一个绿色,@但由于第二个错误我会提到它可能会出现红色.要解决此问题,您需要反转AX寄存器中字符和属性的位置(交换AHAL中的值).代码如下所示:

mov ah,0x40 ;colour is now in AH, not AL 
mov al,' '  ;character is now in AL, not AH
.red:
    cmp bx,0x0FA0
    je .end
    mov WORD [es:bx], ax
    inc bx
    jmp .red
Run Code Online (Sandbox Code Playgroud)

第二个错误与遍历视频区域有关.因为每个单元需要2个字节,所以每次迭代需要将BX计数器递增2.你的代码做了:

mov WORD [es:bx], ax
inc bx                 ; Only increments 1 byte where it should be 2 
jmp .red
Run Code Online (Sandbox Code Playgroud)

修改代码以将2添加到BX:

mov WORD [es:bx], ax
add bx,2               ; Increment 2 since each cell is char/attribute pair 
jmp .red
Run Code Online (Sandbox Code Playgroud)

您可以使用STOSW指令简化代码,该指令获取AX中的值并将其复制到ES:[DI].您可以在此指令前加上REP,它将重复CX次(它将在每次迭代期间相应地更新DI).代码可能看起来像这样:

error:
    mov ax,0xb800 
    mov es,ax     ;Set video segment to 0xb800
    mov ax,0x4020 ;colour + space character(0x20)
    mov cx,2000   ;Number of cells to update 80*25=2000
    xor di,di     ;Video offset starts at 0 (upper left of screen)
    rep stosw     ;Store AX to CX # of words starting at ES:[DI]
Run Code Online (Sandbox Code Playgroud)

您的代码已在代码开头用CLD清除方向标志,因此REP将在每次迭代期间增加DI.如果方向标志已使用STD设置,DI将会减少.