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

Assembly 如何从实模式写入视频内存地址为0xb8000的屏幕?,assembly,x86,nasm,bare-metal,real-mode,Assembly,X86,Nasm,Bare Metal,Real Mode,我创建了一个简单的代码,从硬盘加载第二个扇区,然后用红色背景的空格写入整个屏幕。问题是我总是得到@符号而不是空格。代码如下: 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

我创建了一个简单的代码,从硬盘加载第二个扇区,然后用红色背景的空格写入整个屏幕。问题是我总是得到@符号而不是空格。代码如下:

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

根据该代码,屏幕上应该填充空格,但不是。

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

0xb800:0x0000 :  0x20         ; ASCII char for 0x20 is ' '
0xb800:0x0001 :  0x40         ; Red background, black foreground
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
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]
在您的代码中,您似乎试图使用以下代码来实现这一点:

mov al,0x40 ;colour
mov ah,' ' ;character
.red:
    cmp bx,0x0FA0
    je .end
    mov WORD [es:bx], ax
    inc bx
    jmp .red
不幸的是,因为x86体系结构是little endian,所以放入内存的值首先是最低有效字节,最后是最高有效字节(在处理16位字时)。您的AX包含0x2040,并使用
mov-WORD[es:bx],AX
将整个单词移动到视频内存中。例如,它会将这些字节写入第一个单元格:

0xb800:0x0000 :  0x40         ; ASCII char for 0x40 is `@'
0xb800:0x0001 :  0x20         ; Green background, black foreground
我相信这是一个绿色的
@
,但由于第二个bug,我会提到它可能出现了红色。要解决此问题,需要反转AX寄存器中字符和属性的位置(交换AH和AL中的值)。代码如下所示:

0xb800:0x0000 :  0x20         ; ASCII char for 0x20 is ' '
0xb800:0x0001 :  0x40         ; Red background, black foreground
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
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]
第二个bug与遍历视频区域有关。因为每个单元格占用2个字节,所以每次迭代都需要将BX计数器增加2。您的代码执行以下操作:

mov WORD [es:bx], ax
inc bx                 ; Only increments 1 byte where it should be 2 
jmp .red
修改代码以将2添加到BX:

您可以通过使用在AX中获取值并将其复制到ES:[DI]的指令来简化代码。您可以为该指令添加前缀,该指令将重复执行CX次(它将在每次迭代期间相应地更新DI)。代码可能如下所示:

0xb800:0x0000 :  0x20         ; ASCII char for 0x20 is ' '
0xb800:0x0001 :  0x40         ; Red background, black foreground
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
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]

您的代码已经在代码的开头用CLD清除了方向标志,因此REP将在每次迭代中增加DI。如果方向标志被设置为STD,DI将被减少。

@vacus:
rep stosw
将是用相同的2字节值填充内存块的完美选择。@PeterCordes同意,我看到的大多数代码不会优化,并且会重用人们发布的内容。这绝对不是有效的代码。主要的问题是
为什么
代码会以这种方式失败。代码完全可以重新编写,以利用x86的字符串操作指令。我要感谢OP。这是为数不多的几次设置段寄存器和堆栈的初始化代码实际上是正确的!我通常会指出如何优化代码。如果你不想彻底调整你的代码,那就使用一个编译器,而不是一开始就直接编写asm。即使您只是想了解编译器的输出,也有助于理解将条件分支放在循环末尾之类的内容。我经常有人在评论中感谢我帮助他们“获得它”,所以我认为它是有用的。引导加载程序的一个特点是,现在它基本上是在汇编中编写它的地方(除非你尝试使用OpenWatcom C)。通常引导加载程序(这显然是)必须适合512字节。如果OP被压在空间上,那么花时间优化它的空间(而不是速度)将是有益的。大多数编写引导加载程序的人一旦进入保护模式,就会引导到用更高级语言编写的代码。引导加载程序也是空间优化比速度优化更有价值的地方之一。是的,我同意在asm中编写引导加载程序更有意义
rep stosw
也是一种很好的空间优化方法。无论如何,这看起来OP只是想学习一些asm/低级机器的东西,并选择裸机作为目标,而不是16位DOS(糟糕),或64位Linux/Windows函数。显然是一个asm新手,给出了循环结构(顶部是有条件的,
jmp
底部是有条件的,即使循环至少运行一次是一个安全的假设)。无论如何,很好地解决了字节顺序问题。
mov ss,src
隐式地禁用中断,直到下一条指令之后,所以在设置
ss
时,只要使用下一个insn设置
sp
,就不需要
cli/sti
。这还可以防止代码无条件地启用中断(例如,如果在
cli
之前已禁用中断)。如果编写一个可以在中断启用或禁用的上下文中调用的函数,则该函数非常有用。@PeterCordes部分正确,但存在问题的8086处理器除外(除非您能够找到80年代的批处理程序,否则现在可能很少遇到这些处理器)其中设置
ss
直到下一条指令结束时才打开中断。这就是为什么引导加载程序最初尝试在所有环境中工作,您可能会看到cli/sti特别是围绕SS:SP更改的环境。这只虫子在80年代袭击了我,所以我对它了如指掌。注意:在8086/8088(与80386不同)上,对段寄存器的任何
mov
都应该关闭中断,而不仅仅是SS@MichaelPetch:嗯,这听起来很令人兴奋。我希望所有8086内核的微控制器和诸如此类的没有那个缺陷。不过,如果你想制作一个小内核,那么这正是你想省去的一种额外的特例逻辑。@PeterCordes咯咯笑着说,这似乎要感谢1987年的谷歌-个人电脑杂志讨论了这一点。对不起,是8088芯片(不是8086)。我的脑子记不起30年前的每一个细节;)。我认为80年代生产的有轨电车芯片不太可能(很少)被使用,但我猜possible@CiroSantilli六四事件法轮功包卓轩 打印到屏幕的链接不会有任何帮助。抽象awa的是C代码