当CPL = 3时硬件中断发生且只有写位被设置,导致x86-64页面错误的原因是什么?

3
我正在使用Rust编写自己的内核(基于phil-opp的博客,https://os.phil-opp.com/),到目前为止,我已经成功复制了第4级页表,为用户模式应用程序代码和数据创建了新表,将裸函数映射到虚拟地址0x40000000000,设置了堆栈并跳转到代码。我还使用syscall/sysret实现了一个系统调用处理程序,当遇到系统调用时,它会简单地打印一条消息。我注意到,每当发生PIC定时器中断时,它总是发生在CPL=0时,因为系统调用处理程序的代码比用户模式应用程序多很多,后者仅在无限循环中执行系统调用。如果我禁用打印(这需要大多数指令),在循环的几百次迭代后,定时器中断会在CPL=3时发生。但是,CPU不会调用中断处理程序,而是抛出错误代码2的页面故障(仅对应Write位设置)。在我看来,这毫无意义,问题可能是什么?
GDT:
static ref GDT: (gdt::GlobalDescriptorTable, Selectors) = {
    let mut gdt = gdt::GlobalDescriptorTable::new();
    let kernel_code_selector = gdt.add_entry(gdt::Descriptor::kernel_code_segment());
    let kernel_data_selector = gdt.add_entry(gdt::Descriptor::kernel_data_segment());
    let tss_selector = gdt.add_entry(gdt::Descriptor::tss_segment(&TSS));
    let user_data_selector = gdt.add_entry(gdt::Descriptor::user_data_segment());
    let user_code_selector = gdt.add_entry(gdt::Descriptor::user_code_segment());
    (gdt, Selectors { kernel_code_selector, kernel_data_selector, tss_selector, user_code_selector, user_data_selector })
    };

IDT:

const DOUBLE_FAULT_IST_INDEX: u16 = 0;
let mut IDT: idt::InterruptDescriptorTable = idt::InterruptDescriptorTable::new();
IDT.breakpoint.set_handler_fn(interrupts::breakpoint::breakpoint_handler);
IDT.double_fault.set_handler_fn(interrupts::double_fault::double_fault_handler).set_stack_index(DOUBLE_FAULT_IST_INDEX);
IDT.page_fault.set_handler_fn(interrupts::page_fault::page_fault_handler);
IDT.general_protection_fault.set_handler_fn(interrupts::general_protection_fault::general_protection_fault_handler);
IDT.stack_segment_fault.set_handler_fn(interrupts::stack_segment_fault::stack_segment_fault_handler);
IDT.segment_not_present.set_handler_fn(interrupts::segment_not_present::segment_not_present_handler);
IDT.invalid_tss.set_handler_fn(interrupts::invalid_tss::invalid_tss_handler);
IDT.debug.set_handler_fn(interrupts::debug::debug_handler);
IDT[interrupts::HardwareInterrupt::Timer.as_usize()].set_handler_fn(interrupts::timer::timer_handler);
IDT.load();

TSS:

let mut tss = tss::TaskStateSegment::new();
    tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
        const STACK_SIZE: usize = 4096 * 5;
        static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
        let stack_start = x86_64::VirtAddr::from_ptr(unsafe { &STACK });
        let stack_end = stack_start + STACK_SIZE;
        stack_end
    };

计时器中断处理程序:
pub extern "x86-interrupt" fn timer_handler(_stack_frame: idt::InterruptStackFrame) {
    print!(".");
    cpu::pic_end_of_interrupt(0x20);
}

用户空间应用程序:
#[naked]
#[no_mangle]
#[allow(named_asm_labels)]
pub unsafe fn userspace_app_1() {
    asm!("\
        push 0
        prog1start:
        mov rax, 1234h
        pop rdi
        inc rdi
        push rdi
        mov rsi, 3
        mov rdx, 4
        mov r8, 5
        syscall
        jmp prog1start
    ", options(noreturn));
}
     7: v=20 e=0000 i=0 cpl=3 IP=0033:0000040000000066 pc=0000040000000066 SP=002b:0000060000000ff8 env->regs[R_EAX]=00000000515ca11a
RAX=0000000000001234 RBX=0000000000006062 RCX=0000040000000066 RDX=0000000000000004
RSI=0000000000000003 RDI=00000000001e91c5 RBP=0000008040201000 RSP=0000060000000ff8
R8 =0000000000000005 R9 =0000060000000f78 R10=0000000000203080 R11=0000000000000206
R12=0000000100000000 R13=0000000000005fea R14=0000018000000000 R15=0000000000006692
RIP=0000040000000066 RFL=00000206 [-----P-] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 ffffffff 00cf1300
CS =0033 0000000000000000 ffffffff 00a0fb00 DPL=3 CS64 [-RA]
SS =002b 0000000000000000 ffffffff 00c0f300 DPL=3 DS   [-WA]
DS =002b 0000000000000000 ffffffff 00cff300 DPL=3 DS   [-WA]
FS =0000 0000000000000000 0000ffff 00009300 DPL=0 DS   [-WA]
GS =0000 0000000000000000 0000ffff 00009300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0018 0000000000276014 00000067 00008900 DPL=0 TSS64-avl
GDT=     0000000000276090 00000037
IDT=     000000000026dd80 00000fff
CR0=80010011 CR2=0000000000000000 CR3=00000000002b4018 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000004 CCD=0000060000000fa8 CCO=EFLAGS
EFER=0000000000000d01
check_exception old: 0xffffffff new 0xe
     8: v=0e e=0002 i=0 cpl=3 IP=0033:0000040000000066 pc=0000040000000066 SP=002b:0000060000000ff8 CR2=fffffffffffffff8
RAX=0000000000001234 RBX=0000000000006062 RCX=0000040000000066 RDX=0000000000000004
RSI=0000000000000003 RDI=00000000001e91c5 RBP=0000008040201000 RSP=0000060000000ff8
R8 =0000000000000005 R9 =0000060000000f78 R10=0000000000203080 R11=0000000000000206
R12=0000000100000000 R13=0000000000005fea R14=0000018000000000 R15=0000000000006692
RIP=0000040000000066 RFL=00000206 [-----P-] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 ffffffff 00cf1300
CS =0033 0000000000000000 ffffffff 00a0fb00 DPL=3 CS64 [-RA]
SS =002b 0000000000000000 ffffffff 00c0f300 DPL=3 DS   [-WA]
DS =002b 0000000000000000 ffffffff 00cff300 DPL=3 DS   [-WA]
FS =0000 0000000000000000 0000ffff 00009300 DPL=0 DS   [-WA]
GS =0000 0000000000000000 0000ffff 00009300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0018 0000000000276014 00000067 00008900 DPL=0 TSS64-avl
GDT=     0000000000276090 00000037
IDT=     000000000026dd80 00000fff
CR0=80010011 CR2=fffffffffffffff8 CR3=00000000002b4018 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000004 CCD=0000060000000fa8 CCO=EFLAGS
EFER=0000000000000d01

3
看起来你的某些地方存在损坏。由于“CPL”应该是“0”,因此似乎没有进入定时器处理程序。写错误的地址为“CR2 = ffffffffffffffff”,这使我认为您的TSS堆栈条目可能已损坏。作为测试,当我故意修改我的操作系统中的TSS“rsp”值时,当定时器触发时,我会遇到与您相同类型的页面错误。您可以在页错误处理程序处设置断点并检查TSS。 - sj95126
2个回答

6
我已经解决了问题!正如@sj95126所建议的那样,问题出在我的TSS上,它只为双重错误处理程序设置了IST条目(回过头来看是有道理的,因为以前发生在用户模式下的任何中断或异常都会导致双重错误,因为据我所知CPU无法知道要激活哪个堆栈)。 我通过为每个中断处理程序设置IST位于该TSS偏移量,现在我的代码运行得非常顺畅。
IDT:
IDT.breakpoint.set_handler_fn(interrupts::breakpoint::breakpoint_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.double_fault.set_handler_fn(interrupts::double_fault::double_fault_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.page_fault.set_handler_fn(interrupts::page_fault::page_fault_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.general_protection_fault.set_handler_fn(interrupts::general_protection_fault::general_protection_fault_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.stack_segment_fault.set_handler_fn(interrupts::stack_segment_fault::stack_segment_fault_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.segment_not_present.set_handler_fn(interrupts::segment_not_present::segment_not_present_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.debug.set_handler_fn(interrupts::debug::debug_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT[interrupts::HardwareInterrupt::Timer.as_usize()].set_handler_fn(interrupts::timer::timer_handler).set_stack_index(INTERRUPT_IST_INDEX);
IDT.load();

2
为了简单起见,为双重错误处理程序设置专用堆栈是一个好主意,因为如果异常堆栈是内核内部故障的原因,如果尝试使用相同的堆栈,则双重错误处理程序将三重错误。 - sj95126
我同意,我将继续创建一个单独的堆栈。由于我在操作系统/低级开发领域相对较新,因此我一路上不断学习新知识。 - lorinet3

1

我曾经亲身体验了这个艰难的过程。我自己遵循了第二版和第三版(只有已经编写的部分)的混合方法,而不是第一版,但基本思想是相同的。

请注意,为了避免双重故障,您不必必须索引TSS中的所有故障处理程序,只需要索引导致双重故障的先驱故障,如果未处理,则会导致双重故障。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接