【发布时间】:2021-06-17 19:24:13
【问题描述】:
我正在创建一个手写操作系统,但在处理中断时遇到了一些问题。 问题是当我尝试从中断返回 CPL=3 时,我得到了一般保护错误。 我查了intel手册,我的结论是,在长模式下,不能用iret返回到不同的代码段。 所以我想出了一个解决方案,它使用 sysret 返回到 CPL=3 代码,然后跳转到用户代码。它似乎工作,但有一个小问题。有时在 CPL=3 代码中间会出现中断。 我无法在 CPL=3 代码末尾启用中断,因为它只能在 CPL=0 中使用。
关于 iret 指令,我有什么遗漏的吗? 还是我使用一些黑魔法,比如远距离回归?
这段代码可以工作,但过了一会儿就中断了,因为在 CPL3 部分时会发生中断。
我的代码如下所示:(NASM 语法)
; Registers are restored and neccessary values are pushed onto the stack
mov rax, cpu_cs ; check if return is to cpl0
mov rax, [rax]
cmp rax, 0x8 ; 0x8 is the kernel segment
jne iretq_cpl3
; return to cpl0
mov rax, cpu_rax ; restore rax
mov rax, [rax]
iretq
iretq_cpl3: ;return to cpl3
mov rax, cpu_rax ; restore rax from current_state
push qword [rax]
mov rax, cpl3_rax ; save rax to cpl_rax
pop qword [rax]
mov rax, cpl3_rcx ; save rcx
mov [rax], rcx
add rax, 8 ; save r11
mov [rax], r11
add rax, 8 ; pop and save rip
pop qword [rax]
add rsp, 8 ; dispose cs
pop r11 ; rflags into r11 for sysret; save it for cpl3 code
pop rsp ; restore rsp
mov rcx, cpl3_returner ; go to cpl3_returner
o64 sysret
还有 CPL=3 代码:
; cpl3
align(4096)
global cpl3_kernel_part
global cpl3_reg_save
global cpl3_returner
cpl3_kernel_part:
cpl3_reg_save:
cpl3_rax: dq 0
cpl3_rcx: dq 0
cpl3_r11: dq 0
cpl3_rip: dq 0
cpl3_returner:
mov rax, cpl3_r11 ; restore r11
mov r11, [rax]
mov rax, cpl3_rcx ; restore rcx
mov rcx, [rax]
mov rax, cpl3_rip ; push rip on the stack for the return
push qword [rax]
mov rax, cpl3_rax ; restore rax
mov rax, [rax]
ret
编辑:
这是我尝试使用 IRETQ 时的错误代码,所以基本上当我将 cpl3 代码也用于 cpl3 时。
当我尝试使用 IRETQ 返回 CPL3 时遇到的异常:
!!! Exception: 0xD //#GP
Error code: 0x28 //User code segment
SS: 0x10 //Kernel stack segment
CS: 0x8 //Kernel code segment
RSP: 0x20000024F98
RAX: 0x0
RIP: 0x200000031DA
instr: 0x48 0xCF 0x48 0xB8 0x60 0x37 0x0 0x0 0x0 0x2 0x0 0x0 0xC6 0x0 0x0 //IRETQ
!!!
IRETQ 之前的堆栈:
(gdb) x/5gx 0x20000024f98 //rsp
0x20000024f98: 0x0000000000000000 // User RIP 0x000000000000002b // User CS is 0x28
0x20000024fa8: 0x0000000000000202 // RFLAGS 0x0000000000006fec // User RSP
0x20000024fb8: 0x0000000000000023 // User SS is 0x20
RIP 故意为 0x0。用户码就是jmp $,映射到第一页。
GDT 如下所示:
__attribute__((aligned(4096)))
struct {
struct gdt_entry null;
struct gdt_entry kernel_code;
struct gdt_entry kernel_data;
struct gdt_entry null2;
struct gdt_entry user_data;
struct gdt_entry user_code;
struct gdt_entry ovmf_data;
struct gdt_entry ovmf_code;
struct gdt_entry tss_low;
struct gdt_entry tss_high;
} gdt_table = {
{0, 0, 0, 0x00, 0x00, 0}, /* 0x00 null */
{0, 0, 0, 0x9a, 0xa0, 0}, /* 0x08 kernel code (kernel base selector) */
{0, 0, 0, 0x92, 0xa0, 0}, /* 0x10 kernel data */
{0, 0, 0, 0x00, 0x00, 0}, /* 0x18 null (user base selector) */
{0, 0, 0, 0x92, 0xa0, 0}, /* 0x20 user data */
{0, 0, 0, 0x9a, 0xa0, 0}, /* 0x28 user code */
{0, 0, 0, 0x92, 0xa0, 0}, /* 0x30 ovmf data */
{0, 0, 0, 0x9a, 0xa0, 0}, /* 0x38 ovmf code */
{0, 0, 0, 0x89, 0xa0, 0}, /* 0x40 tss low */
{0, 0, 0, 0x00, 0x00, 0}, /* 0x48 tss high */
};
来自this 网站。
我让它更稳定一点,通过添加一个标志,如果它没有设置,中断会立即返回。 cpl3代码在return之前设置了它,但仍然不是一个完美的解决方案。
我注意到一件有趣的事情。如果我只是在 qemu 中运行它,它会在几秒钟后崩溃,但如果我附加 GDB,它会运行几分钟。
这是我的GitHub repository。
【问题讨论】:
-
当然您可以使用 IRETQ 返回到用户模式代码。您只需要确保寄存器和堆栈内容与中断进入内核时相同。你能指出手册中让你产生不同想法的部分吗?
-
你能展示你的 GDT 吗?用户代码之前是否运行过,或者这是用户代码的第一个入口?为什么用户 rsp 没有对齐? (不过,这不会导致#GP。)
-
GDT 中的问题: 1) 对于数据段,L 是保留的。 2) 用户模式段的 DPL = 3。
-
3) 在 64 位数据段中,我一直将 D/B 设置为 1;我不知道这是否重要。
-
非常感谢您的帮助,我修复了我的 GDT,它运行顺畅。我不知道它如何与 sysret 一起使用,但它现在也可以与 iret 一起使用。非常感谢。
标签: assembly x86-64 interrupt osdev interrupt-handling