如何在x86 PC分区引导扇区中检测CPU保护模式及子模式?
可靠检测CPU模式的硬件级方法(适配你的PBR调试场景)
由于你的BootManager会拦截BIOS中断,咱们完全不能依赖BIOS提供的服务来检测模式,必须用纯x86硬件寄存器和指令来实现——这些操作直接和CPU交互,不受BootManager的拦截影响。下面是分步骤的检测逻辑和对应的16位汇编实现:
核心检测思路
CPU的运行模式完全由控制寄存器(CR0、CR4)、扩展功能寄存器(IA32_EFER)和标志寄存器(EFLAGS)的特定位决定,咱们按从粗到细的顺序排查:
1. 区分实模式 vs 保护模式类(含虚拟8086、长模式)
首先检查CR0寄存器的PE位(第0位):
- PE=0:CPU处于实模式(Real Mode),这是最基础的16位模式
- PE=1:CPU处于保护模式类,需要进一步细分具体子模式
; 读取CR0寄存器,检测PE位 mov eax, cr0 test eax, 1 ; 掩码PE位 jz .detect_real_mode ; PE=0,跳转到实模式处理逻辑
2. 检测是否处于长模式(64位)
长模式是x86-64的核心模式,需要先确认CPU支持该特性,再检查相关寄存器位:
- 先通过CPUID指令确认CPU支持扩展功能(能读取IA32_EFER寄存器)
- 检查IA32_EFER的LME位(第8位)、CR0的PG位(第31位)、CR4的PAE位(第5位)是否都为1
- 最后检查CS段描述符的L位(第21位),确认是64位子模式还是32位兼容模式
; 检测是否支持CPUID(排除老款386/486) pushfd pop eax mov ecx, eax xor eax, 1 << 21 ; 翻转EFLAGS的ID位 push eax popfd pushfd pop eax cmp eax, ecx jz .skip_long_mode_detect ; 不支持CPUID,无法进入长模式 ; 检测是否支持长模式相关CPUID扩展 mov eax, 0x80000000 cpuid cmp eax, 0x80000001 jb .skip_long_mode_detect ; 不支持长模式扩展 ; 读取IA32_EFER的LM标志 mov eax, 0x80000001 cpuid test edx, 1 << 29 ; 检查EDX第29位(LM位) jz .skip_long_mode_detect ; 检查CR0.PG和CR4.PAE是否启用 mov eax, cr0 test eax, 1 << 31 ; PG位(分页启用) jz .skip_long_mode_detect mov eax, cr4 test eax, 1 << 5 ; PAE位(物理地址扩展) jz .skip_long_mode_detect ; 检查CS段的L位,区分64位模式和32位兼容模式 mov ax, cs call .get_cs_descriptor_attr test eax, 1 << 21 ; L位(64位模式标志) jz .detect_long_mode_compat ; 32位兼容模式 jmp .detect_long_mode_64 ; 64位模式 .skip_long_mode_detect: ; 进入非长模式的检测分支
3. 区分虚拟8086模式 vs 32位保护模式
如果不是长模式,就检查EFLAGS的VM位(第17位):
- VM=1:CPU处于虚拟8086模式(Virtual 8086 Mode),这是保护模式下模拟实模式的子模式
- VM=0:CPU处于32位保护模式(Protected Mode)
pushfd pop eax test eax, 1 << 17 ; 掩码VM位 jz .detect_protected_32 ; VM=0,32位保护模式 jmp .detect_virtual_8086 ; VM=1,虚拟8086模式
辅助函数:获取CS段描述符属性
要检测长模式的子模式,需要读取CS段描述符的属性位,这里用SGDT指令获取GDT基址,再计算描述符地址:
.get_cs_descriptor_attr: ; 输入:AX = CS段选择子 ; 输出:EAX = 描述符高32位(含L位等属性) push ebx esi mov ebx, eax and ebx, 0xFFF8 ; 提取描述符索引(乘以8) sgdt [.gdt_temp_ptr] ; 读取GDT指针到临时内存 mov esi, [.gdt_temp_ptr + 2] ; GDT基址 add esi, ebx ; 定位到CS对应的描述符 mov eax, [esi + 4] ; 读取描述符高32位 pop esi ebx ret .gdt_temp_ptr: dw 0xFFFF ; GDT限长(临时值) dq 0 ; GDT基址(实模式下低32位有效)
整合到你的PBR中
把这些检测逻辑和你现有的寄存器显示代码结合,比如检测完成后,用int 10h显示对应的模式字符串(注意如果处于保护模式类,int 10h可能被BootManager拦截,但你说调试时显示寄存器正常,应该BootManager会正确模拟该中断)。
另外要注意:所有这些指令都需要386及以上CPU支持,而现代x86 PC都满足这个要求;如果要兼容极老的386,可以加个简单的CPU检测(比如尝试执行mov eax, cr0,如果触发无效指令异常就说明是286及以下,但这种场景下你的BootManager也不可能运行)。
内容的提问来源于stack exchange,提问作者George Robinson
相关产品推荐
相关产品推荐

