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

切换至Long Mode后VGA文本输出异常的原因排查

x86_64模式下VGA文本写入异常问题分析

问题背景

我正在开发一个x86_64架构的启动Demo:

  • 实模式阶段通过int 10h设置视频模式3,向0xb8000地址写入内容正常
  • 32位保护模式(带分页)下的C代码第二阶段运行完全正常
  • 切换到启用PAE和LME的x86_64模式后,显示功能出现异常

正常工作的代码

以下代码在64位模式下能正常将屏幕填满'A'字符:

for (uint32_t i = 0xb8000; i < 0xb8000 + (25 * 80 * 2); i += 2) {
    *((volatile uint16_t*)i) = 0x0741;
}

对应的汇编代码:

000000000000843f <main>:
    843f:   f3 0f 1e fa             endbr64
    8443:   55                      push   %rbp
    8444:   48 89 e5                mov    %rsp,%rbp
    8447:   c7 45 fc 00 80 0b 00    movl   $0xb8000,-0x4(%rbp)
    844e:   eb 0c                   jmp    845c <main+0x1d>
    8450:   8b 45 fc                mov    -0x4(%rbp),%eax
    8453:   66 c7 00 41 07          movw   $0x741,(%rax)
    8458:   83 45 fc 02             addl   $0x2,-0x4(%rbp)
    845c:   81 7d fc 9f 8f 0b 00    cmpl   $0xb8f9f,-0x4(%rbp)
    8463:   76 eb                   jbe    8450 <main+0x11>
    8465:   90                      nop
    8466:   eb fd                   jmp    8465 <main+0x26>

功能失效的代码

修改为指针索引形式后,功能异常:屏幕显示粉色控制字符(说明0x07被当作字符、0x41被当作颜色代码),且右下角最后两个字符未填充:

volatile uint16_t *screen_base = (volatile uint16_t*)0xb8000;
for (uint32_t i = 0; i < 25 * 80; i++) {
    screen_base[i] = 0x0741;
}

对应的汇编代码:

000000000000843f <main>:
    843f:   f3 0f 1e fa             endbr64
    8443:   55                      push   %rbp
    8444:   48 89 e5                mov    %rsp,%rbp
    8447:   48 c7 45 f0 00 80 0b    movq   $0xb8000,-0x10(%rbp)
    844e:   00 
    844f:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
    8456:   eb 17                   jmp    846f <main+0x30>
    8458:   8b 45 fc                mov    -0x4(%rbp),%eax
    845b:   48 8d 14 00             lea    (%rax,%rax,1),%rdx
    845f:   48 8b 45 f0             mov    -0x10(%rbp),%rax
    8463:   48 01 d0                add    %rdx,%rax
    8466:   66 c7 00 41 07          movw   $0x741,(%rax)
    846b:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
    846f:   81 7d fc cf 07 00 00    cmpl   $0x7cf,-0x4(%rbp)
    8476:   76 e0                   jbe    8458 <main+0x19>
    8478:   90                      nop
    8479:   eb fd                   jmp    8478 <main+0x39>

尝试将指针改为0xb8003能掩盖问题,但这显然不是正确解法,求问题根源。


问题根源与解决方法

1. 核心问题:分页映射的地址对齐/字节序错误

VGA文本模式的内存布局为:每个字符占2字节,低地址字节是ASCII字符,高地址字节是颜色属性。你写入的0x0741按小端序存储应为0x41(字符'A')在低地址、0x07(灰底黑字)在高地址,这是正确的格式。

失效代码出现字符与属性反转的现象,说明实际写入内存的字节顺序被颠倒了——0x07在低地址(被当作字符)、0x41在高地址(被当作颜色)。结合两种写法的差异,问题出在64位模式下分页表对对齐的16位访问的映射错误:

  • 第一种写法中,循环后期会出现未对齐的16位访问(最后一次写入地址0xb8f9f,为奇数地址),x86 CPU会将其拆分为两次独立的字节访问,而字节访问的映射是正确的,因此最终能填满屏幕。
  • 第二种写法的所有访问都是对齐的16位操作,触发了分页映射中的字节反转错误,导致写入的字节顺序颠倒;同时,对齐访问无法覆盖VGA内存的最后一个字节,造成右下角两个字符未填充。

2. 次要问题:分页表的地址偏移

你提到将指针改为0xb8003能掩盖问题,这进一步验证了地址偏移的存在:0xb8003是奇数地址,写入时会触发未对齐访问,绕过分页映射中对齐访问的错误处理,从而得到正确的字节顺序。

解决步骤

  1. 检查分页表配置:

    • 确保虚拟地址0x00000000000b8000到0x00000000000b8fff与物理地址是1:1映射,没有人为添加偏移或字节反转规则。
    • VGA内存属于内存映射I/O,需将其分页属性设置为未缓存(UC),避免缓存机制干扰写入操作。
  2. 验证字节访问的正确性:
    尝试用字节级写入替代16位写入,确认是否能正常工作:

    volatile uint8_t *screen_base = (volatile uint8_t*)0xb8000;
    for (uint32_t i = 0; i < 25 * 80; i++) {
        screen_base[2*i] = 'A';          // 写入字符字节
        screen_base[2*i+1] = 0x07;       // 写入属性字节
    }
    

    如果这段代码正常,说明问题确实出在对齐16位访问的映射配置上。

  3. 修正64位模式的初始化流程:
    检查PAE和LME的启用代码,确保CR4寄存器的PAE位、EFER寄存器的LME位设置正确,且分页表的各级条目(PML4、PDPT、PD、PT)均正确指向VGA物理内存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:52:33