从 Int 10h/AX=4F00h 检索 VESA 视频模式列表
Retrieve list of VESA video modes from Int 10h/AX=4F00h
我正在尝试开发概念验证 os。不管怎样,在这个过程中我遇到的问题之一是 vesa 视频模式。在 vesa 告诉我们从 vbe bios 信息中获取它们并找到适合我们需要的那个之后,似乎缺少硬编码的视频模式编号。但是我无法接收视频模式,因为我不知道如何在 32 位
中使用来自 C 内核的 vbeFarPtr
这是我的内核代码:
在收到带有 int 0x10 ax=0x4f00
的信息后,我将 VbeInfoBlock
作为参数从我的第二阶段引导加载程序传递给内核
int kmain(struct VbeInfoBlock *vbeinfo)
{
init_idt();
SetPITSpeed(100);
init_DTCursor();
printf(vbeinfo->signature); // I can print VESA here means I have the vbeinfoblock
char* str = "";
itoa(vbeinfo->video_modes,str,16); // I want a hex dump so I convert it to hex
printf(str); // I get "VESA" for the signature followed by a string "1053" and nothing else while the list should be like this
// If for example video mode 0x0103, 0x0118 and 0x0115 are supported
// The list should be as 03 01 15 01 18 01 FF FF
// So I should atleast get some FF FF
// My output is "VESA 1053"
while(1);
}
如果您不知道,VbeInfoBlock 定义如下
struct VbeInfoBlock
{
char signature[4]; // must be "VESA" to indicate valid VBE support
uint16_t version; // VBE version; high byte is major version, low byte is minor version
uint32_t oem; // segment:offset pointer to OEM
uint32_t capabilities; // bitfield that describes card capabilities
uint32_t video_modes; // segment:offset pointer to list of supported video modes
uint16_t video_memory; // amount of video memory in 64KB blocks
uint16_t software_rev; // software revision
uint32_t vendor; // segment:offset to card vendor string
uint32_t product_name; // segment:offset to card model name
uint32_t product_rev; // segment:offset pointer to product revision
char reserved[222]; // reserved for future expansion
char oem_data[256]; // OEM BIOSes store their strings in this area
} __attribute__ ((packed));
我无法理解这个问题。还有其他方法吗?
还是我的方法正确但我的代码不正确?
我认为问题在于 VbeInfoBlock
中的 video_modes 部分被定义为 segment:offset 对。我不知道如何在 32 位 C 代码中使用它。
(你可以索取我的第二阶段bootloader或者我原来的bootloader但是这个问题我觉得没必要)
编辑:
我在 Brendan 的回答后尝试的代码
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr = '[=12=]';
while(*videoListPointer != 0xffff) {
itoa(*videoListPointer,chr,16);
printf(chr);
videoListPointer++;
}
和我的 gdt
gdt_start :
gdt_null : ; the mandatory null descriptor
dd 0x0 ; 'dd ' means define double word ( i.e. 4 bytes )
dd 0x0
gdt_code :
dw 0xffff ; Limit ( bits 0 -15)
dw 0x0 ; Base ( bits 0 -15)
db 0x0 ; Base ( bits 16 -23)
db 10011010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit ( bits 16 -19)
db 0x0 ; Base ( bits 24 -31)
gdt_data :
dw 0xffff ; Limit ( bits 0 -15)
dw 0x0 ; Base ( bits 0 -15)
db 0x0 ; Base ( bits 16 -23)
db 10010010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit ( bits 16 -19)
db 0x0 ; Base ( bits 24 -31)
gdt_end :
gdt_descriptor :
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
编辑 2:
图片
Screenshot of myy output
编辑 3:
我使用的代码:
int kmain(struct VbeInfoBlock *vbeinfo)
{
init_idt();
SetPITSpeed(100);
init_DTCursor();
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr[9];
while(*videoListPointer != 0xffff) {
//itoa(*videoListPointer, chr,16);
printf(*videoListPointer);
videoListPointer++;
}
while(1);
}
and screenshot of my output without itoa
编辑4:
gcc -v
C:\Users\Asus>gcc -v Using built-in specs. COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=D:/MinGW/mingw32/bin/../libexec/gcc/i686-w64-mingw32/8.1.0/lto-wrapper.exe
Target: i686-w64-mingw32 Configured with:
../../../src/gcc-8.1.0/configure --host=i686-w64-mingw32
--build=i686-w64-mingw32 --target=i686-w64-mingw32 --prefix=/mingw32 --with-sysroot=/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32 --enable-shared --enable-static --disable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdcxx-time=yes --enable-threads=win32 --enable-libgomp --enable-libatomic --enable-lto --enable-graphite --enable-checking=release --enable-fully-dynamic-string --enable-version-specific-runtime-libs --disable-sjlj-exceptions --with-dwarf2 --disable-libstdcxx-pch --disable-libstdcxx-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=i686 --with-tune=generic --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-mpc=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-isl=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-pkgversion='i686-win32-dwarf-rev0, Built by MinGW-W64 project' --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' CXXFLAGS='-O2 -pipe -fno-ident
-I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' CPPFLAGS='
-I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' LDFLAGS='-pipe -fno-ident
-L/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/lib -L/c/mingw810/prerequisites/i686-zlib-static/lib -L/c/mingw810/prerequisites/i686-w64-mingw32-static/lib -Wl,--large-address-aware' Thread model: win32 gcc version 8.1.0 (i686-win32-dwarf-rev0, Built by MinGW-W64 project)
编辑5:
Screen shot of output without *
首先,稍微更改一下结构,将 video_modes
分成 2 个字段,如下所示:
struct VbeInfoBlock {
char signature[4]; // must be "VESA" to indicate valid VBE support
uint16_t version; // VBE version; high byte is major version, low byte is minor version
uint32_t oem; // segment:offset pointer to OEM
uint32_t capabilities; // bitfield that describes card capabilities
uint16_t video_modes_offset;
uint16_t video_modes_segment;
uint16_t video_memory; // amount of video memory in 64KB blocks
uint16_t software_rev; // software revision
uint32_t vendor; // segment:offset to card vendor string
uint32_t product_name; // segment:offset to card model name
uint32_t product_rev; // segment:offset pointer to product revision
char reserved[222]; // reserved for future expansion
char oem_data[256]; // OEM BIOSes store their strings in this area
} __attribute__ ((packed));
接下来计算视频模式列表的物理地址,像这样:
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;
接下来,尽一切努力将物理地址转换为可用作指针的虚拟地址。如果您不使用分页并且段寄存器基地址为零,那么这将是微不足道的,例如 uint16_t *videoListPointer = (uint16_t *)physical_address;
。如果段寄存器基数不为零,那么您需要从物理地址中减去它们(并确保您使用“32 位无符号”减法,这样如果结果为负,它会环绕到有效的正结果) .如果使用分页,那么它将取决于分页的使用方式(例如,也许您将包含视频模式列表的物理 page/s 映射到您喜欢的任何虚拟地址)。
无论如何,一旦有了可用的指针,您就可以执行以下操作:
while(*videoListPointer != 0xFFFF) {
printf("0x%04X\n", *videoListPointer);
videoListPointer++;
}
但是;如果可行,您将得到一个无意义数字的列表(旧的 "fixed mode numbers" 已被弃用,现在任何模式编号都可以表示任何内容)。您必须使用 "int 0x10, ax = 0x4F01, Get VBE mode information" 来找出实际的模式(分辨率、色深……);并且您不能在保护模式下执行此操作,因此必须切换回实模式。
鉴于您必须切换回实模式才能理解模式编号,切换回实模式然后迭代模式编号列表(使用实模式 "segment and offset" VBE 给你的,没有任何转换)。
这是对 Brendan 回答的补充。在您的第一次编辑中,您合并了 Brendan 建议的更改并执行了以下操作:
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + \
vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr = '[=10=]';
while(*videoListPointer != 0xffff) {
itoa(*videoListPointer,chr,16);
printf(chr);
videoListPointer++;
}
首先,char chr = '[=18=]'
仅保证分配初始化为 0 的单个字节。您确实需要一个足够大的字符缓冲区,以容纳 itoa
可能返回的最长字符串。对于十六进制,即 9 个字符,其中包括 8 个十六进制数字和 NUL(\0) 终止符。对于基数 2(二进制)的最坏情况,它是 33 个字符,包括 NUL(\0) 终止符。你可以这样声明一个缓冲区:
char buf[9];
您可以将该缓冲区传递给 itoa
。如果在每个之间放置 space 字符,则更容易阅读视频模式编号。修改后的代码可能如下所示:
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + \
vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char buf[9];
while(*videoListPointer != 0xffff) {
itoa(*videoListPointer, buf, 16);
printf(buf);
printf(" ");
videoListPointer++;
}
最重要:直到我最终在 GitHub 上检查了您的所有代码,我才发现这个错误。 Brendan 建议通过替换
来拆分 VBEInfoBlock
结构的 video_modes
成员的正确更改:
uint32_t video_modes; // segment:offset pointer to list of supported video mode
有:
uint16_t video_modes_offset;
uint16_t video_modes_segment;
实模式 segment:offset 对存储在内存中,偏移量后跟段。问题出在 GitHub 您通过这样做反转了偏移量和分段:
uint16_t video_modes_segment; // segment:offset pointer to list of supported video modes
uint16_t video_modes_offset;
什么时候应该是:
uint16_t video_modes_offset; // segment:offset pointer to list of supported video modes
uint16_t video_modes_segment;
由于这个错误,您为视频模式列表计算的地址是错误的,这会导致生成不正确的列表。
如果进行了这些更改,输出应类似于:
这看起来像是一个合适的列表,特别是因为列表的末尾包括 EGA/VGA 视频模式:
0 1 2 3 4 5 6 7 D E F 10 11 12 13 6A
视频模式 8 9 A B C
通常是保留的,或者不是 QEMU 支持的标准 EGA/VGA 视频模式的一部分。模式 6A
脱颖而出,因为它恰好是标准的 VESA 800x600 16 位颜色模式。基于此,我假设我正在查看适合 QEMU 的列表。
我正在尝试开发概念验证 os。不管怎样,在这个过程中我遇到的问题之一是 vesa 视频模式。在 vesa 告诉我们从 vbe bios 信息中获取它们并找到适合我们需要的那个之后,似乎缺少硬编码的视频模式编号。但是我无法接收视频模式,因为我不知道如何在 32 位
中使用来自 C 内核的vbeFarPtr
这是我的内核代码:
在收到带有 int 0x10 ax=0x4f00
的信息后,我将VbeInfoBlock
作为参数从我的第二阶段引导加载程序传递给内核
int kmain(struct VbeInfoBlock *vbeinfo)
{
init_idt();
SetPITSpeed(100);
init_DTCursor();
printf(vbeinfo->signature); // I can print VESA here means I have the vbeinfoblock
char* str = "";
itoa(vbeinfo->video_modes,str,16); // I want a hex dump so I convert it to hex
printf(str); // I get "VESA" for the signature followed by a string "1053" and nothing else while the list should be like this
// If for example video mode 0x0103, 0x0118 and 0x0115 are supported
// The list should be as 03 01 15 01 18 01 FF FF
// So I should atleast get some FF FF
// My output is "VESA 1053"
while(1);
}
如果您不知道,VbeInfoBlock 定义如下
struct VbeInfoBlock
{
char signature[4]; // must be "VESA" to indicate valid VBE support
uint16_t version; // VBE version; high byte is major version, low byte is minor version
uint32_t oem; // segment:offset pointer to OEM
uint32_t capabilities; // bitfield that describes card capabilities
uint32_t video_modes; // segment:offset pointer to list of supported video modes
uint16_t video_memory; // amount of video memory in 64KB blocks
uint16_t software_rev; // software revision
uint32_t vendor; // segment:offset to card vendor string
uint32_t product_name; // segment:offset to card model name
uint32_t product_rev; // segment:offset pointer to product revision
char reserved[222]; // reserved for future expansion
char oem_data[256]; // OEM BIOSes store their strings in this area
} __attribute__ ((packed));
我无法理解这个问题。还有其他方法吗? 还是我的方法正确但我的代码不正确?
我认为问题在于 VbeInfoBlock
中的 video_modes 部分被定义为 segment:offset 对。我不知道如何在 32 位 C 代码中使用它。
(你可以索取我的第二阶段bootloader或者我原来的bootloader但是这个问题我觉得没必要)
编辑:
我在 Brendan 的回答后尝试的代码
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr = '[=12=]';
while(*videoListPointer != 0xffff) {
itoa(*videoListPointer,chr,16);
printf(chr);
videoListPointer++;
}
和我的 gdt
gdt_start :
gdt_null : ; the mandatory null descriptor
dd 0x0 ; 'dd ' means define double word ( i.e. 4 bytes )
dd 0x0
gdt_code :
dw 0xffff ; Limit ( bits 0 -15)
dw 0x0 ; Base ( bits 0 -15)
db 0x0 ; Base ( bits 16 -23)
db 10011010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit ( bits 16 -19)
db 0x0 ; Base ( bits 24 -31)
gdt_data :
dw 0xffff ; Limit ( bits 0 -15)
dw 0x0 ; Base ( bits 0 -15)
db 0x0 ; Base ( bits 16 -23)
db 10010010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit ( bits 16 -19)
db 0x0 ; Base ( bits 24 -31)
gdt_end :
gdt_descriptor :
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
编辑 2:
图片
Screenshot of myy output
编辑 3:
我使用的代码:
int kmain(struct VbeInfoBlock *vbeinfo)
{
init_idt();
SetPITSpeed(100);
init_DTCursor();
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr[9];
while(*videoListPointer != 0xffff) {
//itoa(*videoListPointer, chr,16);
printf(*videoListPointer);
videoListPointer++;
}
while(1);
}
and screenshot of my output without itoa
编辑4:
gcc -v
C:\Users\Asus>gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=D:/MinGW/mingw32/bin/../libexec/gcc/i686-w64-mingw32/8.1.0/lto-wrapper.exe Target: i686-w64-mingw32 Configured with: ../../../src/gcc-8.1.0/configure --host=i686-w64-mingw32 --build=i686-w64-mingw32 --target=i686-w64-mingw32 --prefix=/mingw32 --with-sysroot=/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32 --enable-shared --enable-static --disable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdcxx-time=yes --enable-threads=win32 --enable-libgomp --enable-libatomic --enable-lto --enable-graphite --enable-checking=release --enable-fully-dynamic-string --enable-version-specific-runtime-libs --disable-sjlj-exceptions --with-dwarf2 --disable-libstdcxx-pch --disable-libstdcxx-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=i686 --with-tune=generic --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-mpc=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-isl=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-pkgversion='i686-win32-dwarf-rev0, Built by MinGW-W64 project' --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' CXXFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' CPPFLAGS=' -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' LDFLAGS='-pipe -fno-ident -L/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/lib -L/c/mingw810/prerequisites/i686-zlib-static/lib -L/c/mingw810/prerequisites/i686-w64-mingw32-static/lib -Wl,--large-address-aware' Thread model: win32 gcc version 8.1.0 (i686-win32-dwarf-rev0, Built by MinGW-W64 project)
编辑5:
Screen shot of output without *
首先,稍微更改一下结构,将 video_modes
分成 2 个字段,如下所示:
struct VbeInfoBlock {
char signature[4]; // must be "VESA" to indicate valid VBE support
uint16_t version; // VBE version; high byte is major version, low byte is minor version
uint32_t oem; // segment:offset pointer to OEM
uint32_t capabilities; // bitfield that describes card capabilities
uint16_t video_modes_offset;
uint16_t video_modes_segment;
uint16_t video_memory; // amount of video memory in 64KB blocks
uint16_t software_rev; // software revision
uint32_t vendor; // segment:offset to card vendor string
uint32_t product_name; // segment:offset to card model name
uint32_t product_rev; // segment:offset pointer to product revision
char reserved[222]; // reserved for future expansion
char oem_data[256]; // OEM BIOSes store their strings in this area
} __attribute__ ((packed));
接下来计算视频模式列表的物理地址,像这样:
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;
接下来,尽一切努力将物理地址转换为可用作指针的虚拟地址。如果您不使用分页并且段寄存器基地址为零,那么这将是微不足道的,例如 uint16_t *videoListPointer = (uint16_t *)physical_address;
。如果段寄存器基数不为零,那么您需要从物理地址中减去它们(并确保您使用“32 位无符号”减法,这样如果结果为负,它会环绕到有效的正结果) .如果使用分页,那么它将取决于分页的使用方式(例如,也许您将包含视频模式列表的物理 page/s 映射到您喜欢的任何虚拟地址)。
无论如何,一旦有了可用的指针,您就可以执行以下操作:
while(*videoListPointer != 0xFFFF) {
printf("0x%04X\n", *videoListPointer);
videoListPointer++;
}
但是;如果可行,您将得到一个无意义数字的列表(旧的 "fixed mode numbers" 已被弃用,现在任何模式编号都可以表示任何内容)。您必须使用 "int 0x10, ax = 0x4F01, Get VBE mode information" 来找出实际的模式(分辨率、色深……);并且您不能在保护模式下执行此操作,因此必须切换回实模式。
鉴于您必须切换回实模式才能理解模式编号,切换回实模式然后迭代模式编号列表(使用实模式 "segment and offset" VBE 给你的,没有任何转换)。
这是对 Brendan 回答的补充。在您的第一次编辑中,您合并了 Brendan 建议的更改并执行了以下操作:
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + \
vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr = '[=10=]';
while(*videoListPointer != 0xffff) {
itoa(*videoListPointer,chr,16);
printf(chr);
videoListPointer++;
}
首先,char chr = '[=18=]'
仅保证分配初始化为 0 的单个字节。您确实需要一个足够大的字符缓冲区,以容纳 itoa
可能返回的最长字符串。对于十六进制,即 9 个字符,其中包括 8 个十六进制数字和 NUL(\0) 终止符。对于基数 2(二进制)的最坏情况,它是 33 个字符,包括 NUL(\0) 终止符。你可以这样声明一个缓冲区:
char buf[9];
您可以将该缓冲区传递给 itoa
。如果在每个之间放置 space 字符,则更容易阅读视频模式编号。修改后的代码可能如下所示:
uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + \
vbeinfo->video_modes_offset;
uint16_t *videoListPointer = (uint16_t *)physical_address;
char buf[9];
while(*videoListPointer != 0xffff) {
itoa(*videoListPointer, buf, 16);
printf(buf);
printf(" ");
videoListPointer++;
}
最重要:直到我最终在 GitHub 上检查了您的所有代码,我才发现这个错误。 Brendan 建议通过替换
来拆分VBEInfoBlock
结构的 video_modes
成员的正确更改:
uint32_t video_modes; // segment:offset pointer to list of supported video mode
有:
uint16_t video_modes_offset;
uint16_t video_modes_segment;
实模式 segment:offset 对存储在内存中,偏移量后跟段。问题出在 GitHub 您通过这样做反转了偏移量和分段:
uint16_t video_modes_segment; // segment:offset pointer to list of supported video modes
uint16_t video_modes_offset;
什么时候应该是:
uint16_t video_modes_offset; // segment:offset pointer to list of supported video modes
uint16_t video_modes_segment;
由于这个错误,您为视频模式列表计算的地址是错误的,这会导致生成不正确的列表。
如果进行了这些更改,输出应类似于:
这看起来像是一个合适的列表,特别是因为列表的末尾包括 EGA/VGA 视频模式:
0 1 2 3 4 5 6 7 D E F 10 11 12 13 6A
视频模式 8 9 A B C
通常是保留的,或者不是 QEMU 支持的标准 EGA/VGA 视频模式的一部分。模式 6A
脱颖而出,因为它恰好是标准的 VESA 800x600 16 位颜色模式。基于此,我假设我正在查看适合 QEMU 的列表。