自定义汇编内核执行sti触发三重故障问题求助
中断启用后触发三重故障的问题排查
我基于OSDev Wiki和Nick Blundell的PDF开发汇编内核,已完成GDT配置且运行正常,IDTR也成功加载,但调用sti指令重新启用中断时,系统触发三重故障并崩溃(使用QEmu运行)。
boot_sect.asm
bits 16 KERNEL_OFFSET equ 0x1000 CODE_SEGMENT equ 0x0ffe DATA_SEGMENT equ 0x0ffc org 0x7c00 ; BIOS loads us at this position, offsets all offsets by this value automatically jmp bootstrap ; GDT gdt_start: ; The basic flat model, according to Intel Docs gdt_null: ; Null descriptor dd 0x0 dd 0x0 gdt_code: ; Code Segment dw 0xffff ; Limit (bits 0-15) dw 0x0 ; Base (bits 0-15) db 0x0 ; Base (bits 16-23) db 10011010b ; Flags for present (1), priviledge (00 : Ring 0), descriptor type (1), code (1), conforming (0), readable (1) and accessed (0) db 11001111b ; Flags for granularity (1), 32-bit (1, ignored here anyways), 64-bit (0, ignored again), AVL (0, can be used by developer for debugging purposes), Limit (bits 16-19) db 0x0 ; Base (bits 24-31) gdt_data: ; Data Segment dw 0xffff ; Limit (bits 0-15) dw 0x0 ; Base (bits 0-15) db 0x0 ; Base (bits 16-23) db 10010010b ; Flags for present (1), priviledge (00 : Ring 0), descriptor type (1), code (0), conforming (0), readable (1) and accessed (0) db 11001111b ; Flags for granularity (1), 32-bit (1, ignored here anyways), 64-bit (0, ignored again), AVL (0, can be used by developer for debugging purposes), Limit (bits 16-19) db 0x0 ; Base (bits 24-31) gdt_end: gdt_descriptor: dw gdt_end - gdt_start - 1 dd gdt_start CODE_SEG equ gdt_code - gdt_start DATA_SEG equ gdt_data - gdt_start ; We start off in 16x Real mode, we must first bootstrap the kernel, then switch to 32x protected mode bootstrap: mov bx, KERNEL_OFFSET ; Write Kernel to address KERNEL_OFFSET in memory mov ah, 0x02 ; BIOS read sector operation mov al, 31 ; No. of sectors mov ch, 0x00 ; Cylinder 0 mov dh, 0x00 ; Head 0 mov cl, 0x02 ; Start from 2nd sector (BIOS already loaded first one as MBR) int 0x13 ; Interrupt (BIOS stores where we booted from in dl, we also need to specify which disk to load data from in dl. By not changing it, we are loading from boot disk) jc bootstrap_failure ; Has the BIOS flagged any errors while reading disk? (If an error occurs, BIOS sets carry flag) cmp al, 31 ; Have we actually read 31 sectors? (No. of sectors read stored in al) jne bootstrap_failure jmp switch_to_32x bootstrap_failure: ; Alerts the user that bootstrapping failed mov ah, 0x0e ; BIOS Tele Type Output mov bx, bootstrap_failure_msg ; Point to start of msg print_bootstrap_failure: mov al, [bx] ; Load data at pointer cmp al, 0x0 ; Check if end of string je print_bootstrap_failure_end int 0x10 ; Interrupt add bx, 1 ; Advance pointer location jmp print_bootstrap_failure ; Loop print_bootstrap_failure_end: jmp $ ; Halt if bootstrap fails bootstrap_failure_msg: db "Bootstrap failure, system halted...", 0 switch_to_32x: cli ; Switch off interrupts for now lgdt [gdt_descriptor] ; Point to the GDT Descriptor mov eax, cr0 or eax, 0x1 mov cr0, eax ; Set the bit in cr0 register to complete the switch (Cannot be set directly) jmp CODE_SEG:init_pm ; Long jump to flush pipeline of processed x16 real mode instructions bits 32 ; Setting up 32x Protected Mode init_pm: mov ax, DATA_SEG ; Set segment registers mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax mov ebp, 0x7ffff ; Instantiate stack mov esp, ebp mov dx, 0x3d4 ; Disable haradware cursor (We'll be using a software cursor instead) mov al, 0xa out dx, al inc dx mov al, 0x20 out dx, al mov [CODE_SEGMENT], WORD CODE_SEG ; Storing GDT Config for the kernel to use mov [DATA_SEGMENT], WORD DATA_SEG ; Main OS (Now in 32x Protected Mode, calling Kernel jmp KERNEL_OFFSET ; Padding and MBR Identifier times 510-($-$$) db 0 dw 0xaa55
kernel.asm
bits 32 CODE_SEGMENT equ 0x0ffe DATA_SEGMENT equ 0x0ffc org 0x1000 jmp kernel ; This is the where the boot sector jumps to VIDEO_MEMORY equ 0xb8000 WHITE_ON_BLACK equ 0x0f ; Prints a null terminated string, inserting at the second last line and automatically scrolling all earlier text as well ; esp + 36 = style ; esp + 40 = string pointer print: pushad call scroll mov eax, [esp + 40] mov ebx, [esp + 36] push eax push 0x00000000 push 0x00000017 push ebx call print_text_at popad retn 8 ; Prints a null terminated string at a particular position on the screen (Used internally) ; esp + 36 = style ; esp + 40 = y coordinate ; esp + 44 = x coordinate ; esp + 48 = string pointer print_text_at: pushad ; edx = VIDEO_MEMORY + 160 * Y + 2 * x ; 160 is written as 2^7 + 2^5 (For performance reasons) mov edx, VIDEO_MEMORY mov eax, [esp + 40] shl eax, 7 add edx, eax mov eax, [esp + 40] shl eax, 5 add edx, eax mov eax, [esp + 44] shl eax, 1 add edx, eax mov ebx, [esp + 48] mov ah, [esp + 36] print_loop: mov al, [ebx] cmp al, 0 je print_done mov [edx], ax add ebx, 1 add edx, 2 jmp print_loop print_done: popad retn 16 ; Clears the screen clear: pushad mov edx, VIDEO_MEMORY mov ax, 0x0720 ; Same as ah <- 0x07 (Style) and al <- ' ' clear_loop: ; Essentially a while loop that goes to each cell and sets it to a blank cell mov [edx], ax add edx, 2 cmp edx, VIDEO_MEMORY + 3840 ; Not clearing command line je clear_done jmp clear_loop clear_done: popad ret ; Clears the command line clear_cl: pushad mov edx, VIDEO_MEMORY add edx, 3840 mov ax, 0x7020 ; Same as ah <- 0x70 (Style) and al <- ' ', we are inverting the color scheme for stylistic effect clear_cl_loop: mov [edx], ax add edx, 2 cmp edx, VIDEO_MEMORY + 4000 je clear_cl_done jmp clear_cl_loop clear_cl_done: popad ret ; Scrolls all lines upward, clearing the second last line scroll: pushad mov edx, VIDEO_MEMORY scroll_loop: mov al, [edx + 160] mov [edx], al add edx, 1 cmp edx, VIDEO_MEMORY + 3680 je clear_last_line jmp scroll_loop clear_last_line: mov ax, 0x0720 ; 0x07 is style while 0x20 represents ' ' clear_last_line_loop: mov [edx], ax add edx, 2 cmp edx, VIDEO_MEMORY + 3840 je scroll_done jmp clear_last_line_loop scroll_done: popad ret ; IDT ; An entry must contain : ; Lower 16 bits of ISR pointer (16 bits) ; Code segment (16 bits) ; Reserved (Should always be zero) (8 bits) ; ISR Attributes (Present (1 bit), DataPrivilegeLevel (2 bits), 0 (1 bit), 16 bit for a value of (0) or 32 bit for a value of (1) (1 bit), 1 (1 bit), Task gate for a value of (0) or not for a value of (1) (1 bit), Task gate if other bit is set otherwise trap gate for a value of (1) or interrupt gate for a value of (0) (1 bit)) (8 bits) ; Higher 16 bits of ISR pointer (16 bits) idt_start: times 256 dq 0 ; Reserving space for IDT entries idt_end: idt_descriptor: dw idt_end - idt_start - 1 dd idt_start initialized_interrupts: db 0 ; Registers interrupts ; esp + 36 = ISR attributes ; esp + 40 = Code segment ; esp + 44 = ISR pointer register_interrupt: pushad ; This generates where in memory to write the next registered interrupt's descriptor to the IDT mov eax, 0 mov al, [initialized_interrupts] shl eax, 3 add eax, idt_start mov ebx, [esp + 44] mov [eax], bx ; Lower 16 bits of ISR pointer add eax, 2 mov cx, [esp + 40] mov [eax], cx ; Code segment add eax, 2 mov dl, [esp + 36] mov [eax], dl ; ISR Attributes add eax, 1 mov dl, 0 mov [eax], dl ; Reserved (Zeroed out as specified) add eax, 1 shr ebx, 16 mov [eax], bx ; Higher 16 bits of ISR pointer mov al, [initialized_interrupts] inc al ; Increment the memory location that counts the number of initialized interrupts mov [initialized_interrupts], al popad retn 12 initialize_interrupts: lidt [idt_descriptor] sti ret kernel: initialize_interrupts_loop: mov eax, simple_isr push eax mov eax, 0 mov ax, [CODE_SEGMENT] push eax mov eax, 0 mov al, 11101110b push eax call register_interrupt mov al, [initialized_interrupts] cmp al, 255 je finished_registering_interrupts jmp initialize_interrupts_loop finished_registering_interrupts: call initialize_interrupts ; Serves to clear all text printed by BIOS as well as to ready the UI call clear call clear_cl push boot_text push 0x00000007 call print jmp $ ; End of kernel simple_isr: push interrupt_text push 0x00000007 call print jmp $ ; I have to implement checks for whether the interrupt is an interrupt or an exception, since we shouldn't return after encountering an exception, i have just made the system hang here boot_text: db "OS Online", 0 interrupt_text: db "An interrupt has occurred", 0
排查情况
- 注释掉
sti指令后,系统可正常运行,无崩溃 - 通过QEmu第二模拟监视器,用
info registers获取IDTR值(通常为0x10xx),再用x /4hx 0x10xx查看IDT首个条目,结果为0x19xx 0008 00ee 0000,符合文档规范(0x19xx随simple_isr标签位置变化) - 所有256个IRQ均使用同一个
simple_isr例程处理,且将所有中断视为异常(处理后不返回)
请帮忙检查代码中是否存在导致三重故障的错误。
内容的提问来源于stack exchange,提问作者HAXBABA




