x86-64长模式切换至用户态触发通用保护错误的排查求助
x86-64长模式切换用户态报错排查
我正在编写一款x86-64架构的简易操作系统,现已进入长模式,需切换至用户态以运行用户程序。
现有GDT配置
GDTLM: .Null: equ $ - GDTLM dq 0 .Code: equ $ - GDTLM dw 0xffff dw 0 db 0 db 10011010b db 10101111b db 0 .Data: equ $ - GDTLM dw 0xffff dw 0 db 0 db 10010010b db 11001111b db 0 .UserCode: equ $ - GDTLM dw 0xffff dw 0 db 0 db 11111010b db 10101111b db 0 .UserData: equ $ - GDTLM dw 0xffff dw 0 db 0 db 11110010b db 11001111b db 0 .Pointer: dw $ - GDTLM - 1 dq GDTLM
切换用户态代码
cli mov ax, 0x23 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov rax, rsp push 0x23 push rax pushf push 0x1B push _user iretq
Bochs报错信息
check_cs(0x5054): conforming code seg descriptor dpl > cpl, dpl=3, cpl=0 fetch_raw_descriptor: GDT: index (5547) aa8 > limit (27)
错误分析与修复
1. 用户态数据段描述符类型错误
你的.UserData描述符属性字节(第5个字节)是11110010b,其中低4位Type字段为0010,代表只读数据段。用户态数据段需要支持读写,正确的Type字段应为0011。这个错误会导致CPU访问数据段时触发权限异常,进而破坏栈帧引发后续错误。
修复:将.UserData的属性字节改为11110011b:
.UserData: equ $ - GDTLM dw 0xffff dw 0 db 0 db 11110011b ; 修正为可读写数据段 db 11001111b db 0
2. 内核态提前加载用户态数据段违规
在CPL=0(内核态)时直接加载DPL=3(用户态)的DS/ES/FS/GS寄存器,会触发权限校验异常,破坏栈帧结构,导致IRETQ时读取到错误的CS选择符。
修复:移除内核态下加载用户态数据段的代码,进入用户态后再由用户程序自行设置:
cli ; 移除以下四行数据段加载代码 ; mov ax, 0x23 ; mov ds, ax ; mov es, ax ; mov fs, ax ; mov gs, ax mov rax, rsp push 0x23 push rax pushfq push 0x1B push _user iretq
用户程序入口处补充数据段设置:
_user: mov ax, 0x23 mov ds, ax mov es, ax mov fs, ax mov gs, ax ; 后续用户程序逻辑
3. 验证GDT加载正确性
报错显示GDT limit为27(0x1B),但你的GDT实际应有40字节(5个8字节描述符),limit应为39(0x27)。需确认进入长模式前已正确执行lgdt [GDTLM.Pointer],确保长模式GDT被加载,而非之前的32位GDT。
4. 栈对齐检查
64位模式下栈需保持16字节对齐,执行iretq前可通过sub rsp, 8调整栈指针(若当前栈未对齐),避免栈帧解析错误。
内容的提问来源于stack exchange,提问作者MindW1n
相关产品推荐
相关产品推荐

