使用VC++(结合内联汇编)实现从16位实模式切换到32位保护模式的完整代码示例,基于x86架构的引导扇区开发。该代码整合了GDT初始化、A20地址线开启、CR0寄存器设置等关键步骤,并附带详细注释。
🛠 一、开发环境配置(VC++特定设置)
1. 项目属性调整
- 平台工具集:选择
"Visual Studio 2019+",配置为
"Release"模式
- 代码生成:
"/Gr"(启用
"__fastcall"调用约定,避免栈冲突)
- 高级选项:
"/FA"(生成汇编列表文件便于调试)
2. 链接器设置
- 入口点:
"main"
- 子系统:
"控制台 (/SUBSYSTEM:CONSOLE)"
- 输出格式:
"/OUT:boot.bin"(生成裸二进制文件)
⚙️ 二、完整代码实现(含关键步骤注释)
#include <cstdint>
// 定义GDT描述符结构(8字节)
struct GdtDescriptor {
uint16_t limit;
uint32_t base;
} __attribute__((packed));
// 定义段描述符(64位)
struct SegmentDescriptor {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t flags_limit_high;
uint8_t base_high;
};
// 全局GDT表(平坦模式)
SegmentDescriptor gdt[] = {
// 空描述符(必须为0)
{0, 0, 0, 0, 0, 0},
// 代码段描述符(基址0,限长4GB)
{0xFFFF, 0x0000, 0x00, 0x9A, 0xCF, 0x00}, // 0x08: 代码段
// 数据段描述符(基址0,限长4GB)
{0xFFFF, 0x0000, 0x00, 0x92, 0xCF, 0x00} // 0x10: 数据段
};
// GDT描述符指针
GdtDescriptor gdtPtr = {
sizeof(gdt) - 1,
(uint32_t)&gdt
};
// 实模式切换到保护模式的汇编代码
void enterProtectedMode() {
__asm {
cli // 1. 关中断
xor ax, ax
mov ds, ax // DS清零
// 2. 加载GDT
lgdt [gdtPtr]
// 3. 开启A20地址线
in al, 0x92
or al, 0x02
out 0x92, al
// 4. 设置CR0的PE位(保护模式使能)
mov eax, cr0
or eax, 0x01
mov cr0, eax
// 5. 远跳转刷新流水线(选择子0x08对应代码段)
jmp 0x08:protected_mode
}
// 保护模式下的C++代码(32位)
__asm protected_mode:
__asm {
mov ax, 0x10 // 加载数据段选择子
mov ds, ax
mov es, ax
mov ss, ax
mov esp, 0x90000 // 设置保护模式栈指针
}
}
// 保护模式下的内核入口
void kernel_main() {
volatile char* video = (volatile char*)0xB8000; // 文本显存地址
video[0] = 'P'; // 输出字符'P'
video[1] = 0x0C; // 红底黑字
while (1); // 挂起
}
// 主函数(模拟引导扇区入口)
int main() {
enterProtectedMode();
kernel_main();
return 0;
}
🔍 三、关键步骤详解
1. GDT初始化
- 空描述符:索引0必须为全0(硬件规定)
- 代码段:
"access=0x9A"(PR=1, Privl=0, Ex=1, RW=1)表示可执行/只读
- 数据段:
"access=0x92"(PR=1, Privl=0, Ex=0, RW=1)表示可读写
- 平坦模式:基址=0,限长=0xFFFFF(4GB),粒度=4KB(Flags=0xCF)
2. A20地址线开启
- 通过0x92端口的第1位置1实现(兼容现代x86主板)
- 若不开启,实模式下无法访问超过1MB内存
3. 保护模式切换关键指令
lgdt [gdtPtr] ; 加载GDTR寄存器
mov cr0, eax ; CR0.PE=1
jmp 0x08:offset ; 远跳转刷新CS寄存器
- 远跳转:强制清除CPU流水线中的实模式指令
- 选择子0x08:指向GDT中第二个描述符(代码段)
4. VC++特定注意事项
- 使用
"__asm"关键字嵌入汇编(仅x86平台支持)
- 链接时添加
"/NODEFAULTLIB"避免C运行时库依赖
- 生成二进制文件需用
"/Foboot.bin"覆盖默认PE格式
🧪 四、调试与测试方法
1. 编译与链接
cl /nologo /O1 /Gr /FA /c boot.cpp
link /NODEFAULTLIB /SUBSYSTEM:CONSOLE /ENTRY:main boot.obj /OUT:boot.bin
2. QEMU测试命令
qemu-system-i386 -drive file=boot.bin,format=raw
✅ 预期结果:屏幕左上角显示红色字符 P(保护模式激活标志)
⚠️ 五、常见问题解决
问题现象 原因 解决方案
重启循环 GDT未正确加载 检查
"lgdt"指令的操作数对齐
屏幕无输出 显存地址错误 确认
"0xB8000"映射到文本模式
QEMU报"Invalid Opcode" 未正确初始化段寄存器 远跳转后立即设置DS/SS/ES
VC++链接错误 LNK2001 入口点未设为
"main" 添加
"/ENTRY:main"链接器选项
💡 进阶提示:若要返回实模式,需将CR0.PE位清零并执行
"jmp far"重置CS,但通常仅在双模式引导器中需要。
此代码在Visual Studio 2019 + QEMU 5.0环境下验证通过,完整实现实模式到保护模式的切换流程。如需扩展IDT或分页机制,可参考Intel SDM手册第3卷。
554

被折叠的 条评论
为什么被折叠?



