You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自研OS的VESA模式代码如何在VMware/VirtualBox正常运行

问题背景

正在从零构建自研操作系统,学习VESA教程后编写了可切换VESA模式并将屏幕填充为白色的程序,其中Stage1.asm是仅负责加载执行内核的简易引导程序,核心Stage2.asm汇编代码如下:

[BITS 16]
MOV AX, 800
MOV BX, 600
MOV CL, 32
CALL vbe_set_mode
JMP Fill

vbe_set_mode:
    mov [.width], ax
    mov [.height], bx
    mov [.bpp], cl
 
    sti
 
    push es
    mov ax, 0x4F00
    mov di, vbe_info
    int 0x10
    pop es
 
    cmp ax, 0x4F
    jne .error
 
    mov ax, word[vbe_info.video_modes]
    mov [.offset], ax
    mov ax, word[vbe_info.video_modes+2]
    mov [.segment], ax
 
    mov ax, [.segment]
    mov fs, ax
    mov si, [.offset]
 
.find_mode:
    mov dx, [fs:si]
    add si, 2
    mov [.offset], si
    mov [.mode], dx
    mov ax, 0
    mov fs, ax
    
    push dx
    mov dx, 0xFFFF
    cmp [.mode], dx
    pop dx
    je .error
 
    push es
    mov ax, 0x4F01
    mov cx, [.mode]
    mov di, vbe_mode_info
    int 0x10
    pop es
 
    cmp ax, 0x4F
    jne .error
 
    mov ax, [.width]
    cmp ax, [vbe_mode_info.width]
    jne .next_mode
 
    mov ax, [.height]
    cmp ax, [vbe_mode_info.height]
    jne .next_mode
 
    mov al, [.bpp]
    cmp al, [vbe_mode_info.bpp]
    jne .next_mode
 
    mov ax, [.width]
    mov word[vbe_screen.width], ax
    mov ax, [.height]
    mov word[vbe_screen.height], ax
    mov eax, [vbe_mode_info.framebuffer]
    mov dword[vbe_screen.physical_buffer], eax
    mov ax, [vbe_mode_info.pitch]
    mov word[vbe_screen.bytes_per_line], ax
    mov eax, 0
    mov al, [.bpp]
    mov byte[vbe_screen.bpp], al
    shr eax, 3
    mov dword[vbe_screen.bytes_per_pixel], eax
 
    mov ax, [.width]
    shr ax, 3
    dec ax
    mov word[vbe_screen.x_cur_max], ax
 
    mov ax, [.height]
    shr ax, 4
    dec ax
    mov word[vbe_screen.y_cur_max], ax
 
    ; Set the mode
    push es
    mov ax, 0x4F02
    mov bx, [.mode]
    or bx, 0x4000           ; enable LFB
    mov di, 0           ; not sure if some BIOSes need this... anyway it doesn't hurt
    int 0x10
    pop es
 
    cmp ax, 0x4F
    jne .error
 
    clc
    ret
 
.next_mode:
    mov ax, [.segment]
    mov fs, ax
    mov si, [.offset]
    jmp .find_mode
 
.error:
    LEA SI, MsgNoVESA
    CALL Print
    jmp $
 
.width              dw 0
.height             dw 0
.bpp                db 0
.segment            dw 0
.offset             dw 0
.mode               dw 0

vbe_screen:
.width dw 0
.height dw 0
.bpp dw 0
.physical_buffer db 0
.bytes_per_pixel dw 0
.bytes_per_line dw 0
.x_cur_max dw 0
.y_cur_max dw 0


vbe_info:
.signature db "VESA"
.version dw 0
.oem dd 0
.capabilities dd 0
.video_modes dd 0
.video_memory dw 0
.software_rev dw 0
.vendor dd 0
.product_name dd 0
.product_rev dd 0
.reserved db 222 dup 0
.oem_data db 256 dup 0

vbe_mode_info:
.attributes dw 0
.window_a db 0
.window_b db 0
.granularity dw 0
.window_size dw 0
.segment_a dw 0
.segment_b dw 0
.win_func_ptr dd 0
.pitch dw 0
.width dw 0
.height dw 0
.w_char db 0
.y_char db 0
.planes db 0
.bpp db 0
.banks db 0
.memory_model db 0
.bank_size db 0
.image_pages db 0
.reserved0 db 0
.red_mask db 0
.red_position db 0
.green_mask db 0
.green_position db 0
.blue_mask db 0
.blue_position db 0
.reserved_mask db 0
.reserved_position db 0
.direct_color_attributes db 0
.framebuffer dd 0
.off_screen_mem_off dd 0
.off_screen_mem_size dw 0
.reserved1 db 206 dup 0

Fill:
    MOV EBX, [vbe_mode_info.framebuffer]
    MOV EAX, [vbe_mode_info.framebuffer]
    ADD EAX, 800 * 600 * 4
    MOV ECX, 0x00FFFFFF
    MOV EDX, 0

FillLoop:
    MOV [EBX], ECX
    ADD EBX, 4
    CMP EBX, EAX
    JA EndFunc
    JMP FillLoop

EndFunc:
    CLI
    HLT

MsgNoVESA: db "This PC is not support VESA.", 0x00
问题现象

程序在QEMU环境可正常运行,在VMware、VirtualBox环境中无法生效。

故障原因
  • VBE中断调用段寄存器配置错误:代码调用int 0x10实现VBE功能(0x4F00读VBE信息、0x4F01读模式信息)时,只做了es寄存器的入栈出栈,根本没给es赋值指向信息结构体所在的内存段。QEMU的BIOS校验很松,默认es的值刚好和放结构体的段对上,能正常写数据;但VMware、VirtualBox的VBE实现完全按规范来,es:di不对就把VBE信息写到随机内存位置,后面读出来的分辨率、帧缓冲地址全是垃圾值,模式自然切不成。
  • 结构体定义不符合VBE规范:存在两个明显错误,一是vbe_screen里存帧缓冲地址的physical_buffer定义成了db 0,只留了1字节空间,但32位的帧缓冲地址要占4字节,写的时候直接把后面相邻的内存变量冲了;二是vbe_mode_info结构体末尾的预留区只分了206字节,算下来整个结构体比VBE要求的256字节少2字节,读framebuffer这类靠偏移取值的字段时,直接读错位置。
  • 模式匹配缺必要校验:遍历VBE模式列表时,只判断宽高和色深对不对,完全没检查模式属性。VMware、VirtualBox返回的模式里,同参数的可能是不支持线性帧缓冲的老式banked模式,甚至是没开放的保留模式,直接给这种模式加0x4000标志要开LFB,BIOS直接返回失败。
  • 填屏逻辑硬编码参数:填白色的时候直接写死按800*600*4算帧缓冲长度,根本没用VBE返回的pitch(每行实际占的字节数)。绝大多数虚拟机的VBE模式都会给行字节数做内存对齐,pitch比宽度*每像素字节数大,硬编码长度要么写越界碰了别的内存,要么没填完整个屏幕,甚至直接触发异常。
  • 中断状态不对:进vbe_set_mode就直接sti开中断,不少虚拟机的BIOS VBE服务要求调用时关中断,中途被中断打断的话,BIOS直接返回错误码。
修复方案
  • 补全VBE调用时的段寄存器赋值:每次调VBE中断前,明确把es设为0(实模式下这些结构体都在段基址0的平坦地址空间里),比如调0x4F00的时候要写成:
push es
mov ax, 0
mov es, ax
mov ax, 0x4F00
mov di, vbe_info
int 0x10
pop es

调0x4F01、0x4F02的时候也做一样的es赋值,别光push不赋值。

  • 修正结构体错误:把vbe_screen.physical_buffer改成dd 0,留够4字节存32位地址;把vbe_mode_info末尾的reserved1改成db 208 dup(0),凑够256字节的规范要求长度。
  • 加模式属性校验:找到宽高、色深匹配的模式后,先查属性位,不符合就直接跳过找下一个:
; 校验模式是否可用、是否支持线性帧缓冲
test word[vbe_mode_info.attributes], 0x90
jnz .next_mode

这里属性位bit0为1代表模式当前可用,bit7为1代表支持线性帧缓冲,两个位都满足才是需要的模式。

  • 改填屏逻辑,别硬编码参数:用VBE返回的pitch算帧缓冲总长度,不要写死分辨率和每像素字节数:
mov ebx, [vbe_screen.physical_buffer]
xor eax, eax
mov ax, [vbe_screen.bytes_per_line]
mul word[vbe_screen.height]
add eax, ebx  ; 算出来eax就是帧缓冲的结束地址
mov ecx, 0x00FFFFFF ; 32位色下的白色值
.fill_loop:
mov [ebx], ecx
add ebx, 4
cmp ebx, eax
jb .fill_loop
  • 调整中断开关:把vbe_set_mode开头的sti删掉,整个VBE设置过程关中断跑,等模式切完、填完屏再按需开中断就行。

内容的提问来源于stack exchange,提问作者Nine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:57:11