Bochs 中 VESA/VBE DDC 检测失败、无法读取 EDID,如何排查?
先为 DDC 查询初始化 CX,并检查完整的 AX 返回值。这两处问题可以解释检测失败和部分“卡住”现象;目前的材料不足以认定 Bochs 缺少 DDC 支持。EDID 结构定义和引导镜像填充表达式也需要修正。
为什么配置了 DDC,查询仍然失败?
第一次调用只有:
mov ax, 0x4F15
xor bl, bl
int 0x10
这里没有设置 CX。上一阶段读软盘时使用过 mov cx, 0x0002,而跳转到第二阶段不会自动清空寄存器,因此不能假定此时 CX 为零。
在所查阅的 VGABIOS DDC 实现中,BL=0 的能力查询会检查 CX:如果 CX 非零,就进入未实现分支并返回 AX=0x0100;CX 为零才返回成功。这与当前检测失败的现象吻合,但实际调用时的 CX 值仍需通过调试器确认。
查询时应明确传入控制器编号:
xor ax, ax
mov es, ax
xor di, di
xor bx, bx
xor cx, cx
xor dx, dx
mov ax, 0x4F15
int 0x10
cmp ax, 0x004F
jne ddc_query_failed
AX=0x4F15, BL=0 查询的是 DDC 能力,不能仅凭它失败就输出“VBE 不存在”。如果还要单独判断 VBE 是否存在,可以另行调用 AX=0x4F00 获取控制器信息,并为该调用准备相应缓冲区。
为什么跳过检测后像是卡住了?
原代码读取 EDID 后使用:
cmp al, 0x4F
je parse_vbe_edid
AX=0x004F 才表示成功。所查实现读取失败时会返回 AX=0x014F,其中 AL 同样是 0x4F。只检查 AL,会把这类失败也送入成功分支。返回约定可见同一份 VGABIOS 源码。
而解析分支只有:
parse_vbe_edid:
cli
hlt
这段代码会关闭可屏蔽中断并执行停机指令,本来就不会继续正常运行。因此,屏幕没有变化不能证明 BIOS 读取操作没有返回。
应在两次 int 0x10 返回后立即保存完整的 AX,并让成功、查询失败和读取失败显示不同信息,以便区分程序是停在 BIOS 内部,还是已经执行到自己的停机位置。
一个便于调试的第二阶段示例
下面的 NASM 示例沿用 0x0000:0x7E00 入口,先查询能力,再读取基础 EDID 块。它保留完整状态供调试器查看,并在停机前输出结果。示例未在所提供的镜像中运行验证。
org 0x7E00
bits 16
start:
cli
xor ax, ax
mov ss, ax
mov sp, 0x7C00
mov ds, ax
mov es, ax
cld
sti
xor bx, bx
xor cx, cx
xor dx, dx
xor di, di
mov ax, 0x4F15
int 0x10
mov [ddc_status], ax
mov [ddc_caps], bx
cmp ax, 0x004F
jne ddc_failed
test bl, 0x02
jz ddc2_missing
xor ax, ax
mov es, ax
mov di, edid_buffer
mov bx, 0x0001
xor cx, cx
xor dx, dx
mov ax, 0x4F15
int 0x10
mov [edid_status], ax
cmp ax, 0x004F
jne edid_failed
mov si, msg_ok
jmp report
ddc_failed:
mov si, msg_ddc_failed
jmp report
ddc2_missing:
mov si, msg_ddc2_missing
jmp report
edid_failed:
mov si, msg_edid_failed
report:
call puts
cli
.stop:
hlt
jmp .stop
puts:
cld
.next:
lodsb
test al, al
jz .done
mov ah, 0x0E
mov bx, 0x0007
int 0x10
jmp .next
.done:
ret
ddc_status: dw 0
edid_status: dw 0
edid_buffer: times 128 db 0
ddc_caps: dw 0
msg_ok: db 'EDID read OK', 0
msg_ddc_failed: db 'DDC query failed', 0
msg_ddc2_missing: db 'DDC2 unavailable', 0
msg_edid_failed: db 'EDID read failed', 0
times 4096-($-$$) db 0
test bl, 0x02 检查能力结果中的 DDC2 支持位。读取时,CX 为控制器编号,DX 为块编号,ES
调试时检查 ddc_status、ddc_caps 和 edid_status。如果屏幕出现 EDID read OK,随后停住是示例安排的结束行为;校验和解析逻辑还需要继续编写。
EDID 缓冲区定义有哪些问题?
可以先使用连续的 128 字节原始缓冲区,读取成功后再按偏移解析,减少结构布局错误的干扰。EDID 的基础结构可对照 Linux DRM 的 EDID 定义。
原结构至少有以下问题:
- 所有详细时序字段都写成
resb 0,不会增加结构长度,这些字段的偏移会重合。resb后的数字是保留数量,可参照 NASM 结构定义说明。 - 标准时序区域应为 8 项、每项 2 字节,共 16 字节;
dq 0只提供 8 字节。 - 每个详细描述符占 18 字节,共有 4 个。描述符的前两个字节共同组成 pixel clock,不能分别解释为水平频率和垂直频率。
- 基础块偏移
0x7E是扩展块数量,0x7F才是 checksum,不能把前者简单当成无用字节。
原代码中的 horizontal_pixels_2 和 vertical_pixels_2 对应组合高位字段。以水平值为例,该字节的高 4 位补充 active pixels,低 4 位补充 blanking pixels,需要与前面的低字节拼接。
末尾的大段零填充可能暂时容纳 BIOS 写入的 128 字节,因此不能仅凭字段定义错误就断言发生了越界破坏或导致停机。不过,按这些错误的偏移读取数据,解析结果一定不可靠。
代码修正后仍然失败,接下来查什么?
先将配置临时改为内置 EDID:
vga: extension=vbe, ddc=builtin
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest.bin
Bochs 文档支持 builtin 和 file:路径 两种 EDID 来源,且 extension=vbe 与上述 VGA BIOS 文件搭配使用。参见 bochsrc 的 VGA 配置说明。
如果内置数据读取成功,再恢复 ddc=file:monitor.bin,检查文件路径、读取权限和日志。可以临时使用绝对路径,排除启动目录造成的路径歧义。同时确认 $BXSHARE 实际指向的 ROM;重新编译 Bochs 可执行文件,并不意味着配置加载的 VGA ROM 也已更新。
如果单步确认程序确实停在 BIOS 内部,再定位具体循环。所查 VGABIOS 的 DDC 延时例程会轮询端口 0x61 的位变化。确认停在这里后,可以继续调查计时设备行为;仅凭窗口静止还无法作出判断。
引导镜像填充也需要核对
两处填充表达式都把 $-$$ 写成了 $-$:
; stub.asm
times 510-($-$$) db 0
dw 0xAA55
; boot.asm
times 4096-($-$$) db 0
$-$ 恒为零,因此原表达式会直接追加 510 或 4096 字节,无法把文件补齐到指定长度。第一阶段的签名也就不会按这段源码的意图落在偏移 510。
这与“stub.asm 能正常工作”的描述存在出入,应核对实际参与构建的源码、镜像生成过程和最终扇区内容。第二阶段由引导程序读取 8 个扇区,需要加载的代码和数据也必须落在这 4096 字节内。
备注:内容仅供参考。
