【问题标题】:Use IRET into CPL=3 in long mode在长模式下使用 IRET 进入 CPL=3
【发布时间】: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


【解决方案1】:

原来是我的 gdt 出了问题。感谢您的帮助。

【讨论】:

    猜你喜欢
    • 2011-10-17
    • 2014-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-05
    • 1970-01-01
    • 2016-10-12
    • 2016-01-18
    相关资源
    最近更新 更多