自定义x86_64 OS加载GDT时在iretq指令处崩溃求助
自定义GDT加载时iretq指令崩溃问题(Limine引导,x86_64)
我用C++开发简易OS以学习内核技术,使用Limine 6.20231210.0引导加载器。内核主函数调用gdt_init()加载自定义GDT时,代码总是在iretq指令处崩溃。已确认gdt_init()标记为extern "C",排除函数名 mangling 问题;尝试过多种GDT加载代码,均在iretq处失败;调试时寄存器状态看似正常,仅加载GDT不更新段寄存器可正常执行。
相关代码与调试信息
1. 内核入口函数
extern "C" void _start(void) { // Ensure the bootloader actually understands our base revision (see spec) and fetch first fb if(LIMINE_BASE_REVISION_SUPPORTED == false) { hcf(); } struct limine_framebuffer* framebuffer = framebuffer_request.response->framebuffers[0]; if(framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) { hcf(); } KERNEL::VGA::VGA_Driver vgaDriver(framebuffer); KERNEL::TERMINAL::kernel_terminal terminal(&terfont7x14, &vgaDriver); terminal.printf("%s %s", bootloader_info_request.response->name, bootloader_info_request.response->version); gdt_init(); hcf(); }
2. GDT初始化汇编代码
bits 64 align 0x10 gdt: null_descriptor: dw 0x0000 dw 0x0000 db 0x00 db 00000000b db 00000000b db 0x00 kernel_code_64: dw 0x0000 dw 0x0000 db 0x00 db 10011010b db 00100000b db 0x00 kernel_data_64: dw 0x0000 dw 0x0000 db 0x00 db 10010010b db 00100000b db 0x00 user_code_64: dw 0x0000 dw 0x0000 db 0x00 db 11111010b db 00100000b db 0x00 user_data_64: dw 0x0000 dw 0x0000 db 0x00 db 11110010b db 00100000b db 0x00 gdt_end: gdt_ptr: dw gdt_end - gdt - 1 dq gdt CODE_SEG equ kernel_code_64 - gdt DATA_SEG equ kernel_data_64 - gdt global gdt_init gdt_init: lgdt [gdt_ptr] mov rax, rsp push DATA_SEG push rax pushfq push CODE_SEG push flush iretq flush: mov ax, DATA_SEG mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ret
3. 尝试过的替代加载代码
lgdt [rdi] ; i was passing the parameters by function here push 0x8 ; the code segment offset lea rax, [test] ; i declare the test label later in the code, after crash point push rax iretq ; fails after iretq
4. 链接脚本(linker.ld)
/* Tell the linker that we want an x86_64 ELF64 output file */ OUTPUT_FORMAT(elf64-x86-64) OUTPUT_ARCH(i386:x86-64) /* We want the symbol _start to be our entry point */ ENTRY(_start) /* Define the program headers we want so the bootloader gives us the right */ /* MMU permissions */ PHDRS { text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic PHDR for relocations */ } SECTIONS { /* We wanna be placed in the topmost 2GiB of the address space, for optimisations */ /* and because that is what the Limine spec mandates. */ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ /* that is the beginning of the region. */ . = 0xffffffff80000000; .text : { *(.text .text.*) } :text /* Move to the next memory page for .rodata */ . += CONSTANT(MAXPAGESIZE); .rodata : { *(.rodata .rodata.*) } :rodata /* Move to the next memory page for .data */ . += CONSTANT(MAXPAGESIZE); .data : { *(.data .data.*) } :data /* Dynamic section for relocations, both in its own PHDR and inside data PHDR */ .dynamic : { *(.dynamic) } :data :dynamic /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ /* unnecessary zeros will be written to the binary. */ /* If you need, for example, .init_array and .fini_array, those should be placed */ /* above this. */ .bss : { *(.bss .bss.*) *(COMMON) } :data /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ /DISCARD/ : { *(.eh_frame) *(.note .note.*) *(.comment) } }
5. 编译命令
g++ -o build/memory.o -c -std=c++17 -Wall -Wno-missing-field-initializers -Wextra -Wno-switch-bool -fno-rtti -fno-stack-protector -fno-stack-check -fno-lto -m64 -march=x86-64 -g src/memory.cpp nasm -f elf64 -o build/x86.o src/x86.asm ld -o build/kernel -m elf_x86_64 -static --no-dynamic-linker -T src/linker.ld build/kernel.o build/memory.o build/ports.o build/terminal.o build/vga.o build/gdt.o build/x86.o
6. gdt_init()反汇编结果(至iretq)
│ 0xffffffff80001608 <kernel_code_64> add BYTE PTR [rax],al │ │ 0xffffffff8000160a <kernel_code_64+2> add BYTE PTR [rax],al │ │ 0xffffffff8000160c <kernel_code_64+4> add BYTE PTR [rdx+0x20],bl │ │ 0xffffffff80001612 <kernel_data_64+2> add BYTE PTR [rax],al │ │ 0xffffffff80001614 <kernel_data_64+4> add BYTE PTR [rdx+0x20],dl │ │ 0xffffffff8000161a <user_code_64+2> add BYTE PTR [rax],al │ │ 0xffffffff8000161c <user_code_64+4> add dl,bh │ │ 0xffffffff8000161e <user_code_64+6> and BYTE PTR [rax],al │ │ 0xffffffff80001620 <user_data_64> add BYTE PTR [rax],al │ │ 0xffffffff80001622 <user_data_64+2> add BYTE PTR [rax],al │ │ 0xffffffff80001624 <user_data_64+4> add dl,dh │ │ 0xffffffff80001626 <user_data_64+6> and BYTE PTR [rax],al │ │ 0xffffffff80001628 <gdt_ptr> (bad) │ │ 0xffffffff80001629 <gdt_ptr+1> add BYTE PTR [rax],al │ │ 0xffffffff8000162b <gdt_ptr+3> (bad) │ │ 0xffffffff8000162c <gdt_ptr+4> add BYTE PTR [rax-0x1],al │ │ > 0xffffffff80001632 <gdt_init> lgdt ds:0xffffffff80001628 │ │ 0xffffffff8000163a <gdt_init+8> mov rax,rsp │ │ 0xffffffff8000163d <gdt_init+11> push 0x10 │ │ 0xffffffff8000163f <gdt_init+13> push rax │ │ 0xffffffff80001640 <gdt_init+14> pushf │ │ 0xffffffff80001641 <gdt_init+15> push 0x8 │ │ 0xffffffff80001643 <gdt_init+17> push 0xffffffff8000164a │ │ 0xffffffff80001648 <gdt_init+22> iretq
7. iretq执行前的寄存器状态
rax 0xffff800007e9ff48 -140737355579576 rbx 0x0 0 rcx 0xffff8000fd000000 -140733243719680 rdx 0x0 0 rsi 0x0 0 rdi 0xffff800007e9ffb0 -140737355579472 rbp 0xffff800007e9fff0 0xffff800007e9fff0 rsp 0xffff800007e9ff28 0xffff800007e9ff28 r8 0x0 0 r9 0x0 0 r10 0x84 132 r11 0xd 13 r12 0x0 0 r13 0x0 0 r15 0x0 0 rip 0xffffffff80001643 0xffffffff80001643 <gdt_init+17> eflags 0x46 [ IOPL=0 ZF PF ] cs 0x28 40 ss 0x30 48 ds 0x30 48 es 0x30 48 fs 0x30 48 gs 0x30 48 fs_base 0x0 0 gs_base 0x0 0 k_gs_base 0x0 0 cr0 0x80010011 [ PG WP ET PE ] cr3 0x7e8f000 [ PDBR=32399 PCID=0 ] cr4 0x20 [ PAE ] cr8 0x0 0 efer 0xd00 [ NXE LMA LME ] mxcsr 0x1f80 [ IM DM ZM OM UM PM ]
问题根源与修复方案
1. 核心问题:iretq栈帧结构错误
x86_64下,同一特权级内执行iretq时,栈帧仅需包含三个元素(从栈顶到栈底):
- 目标RIP(8字节)
- 目标CS(2字节,高6字节忽略)
- 目标RFLAGS(8字节)
你当前的代码多push了DATA_SEG(SS)和rax(RSP),导致栈帧结构完全错误,iretq会把错误的值加载到RIP寄存器,直接引发崩溃。
2. 修复后的GDT初始化汇编代码
bits 64 align 0x10 gdt: null_descriptor: dw 0x0000 dw 0x0000 db 0x00 db 00000000b db 00000000b db 0x00 kernel_code_64: dw 0x0000 dw 0x0000 db 0x00 db 10011010b db 00100000b db 0x00 kernel_data_64: dw 0x0000 dw 0x0000 db 0x00 db 10010010b db 00100000b db 0x00 user_code_64: dw 0x0000 dw 0x0000 db 0x00 db 11111010b db 00100000b db 0x00 user_data_64: dw 0x0000 dw 0x0000 db 0x00 db 11110010b db 00100000b db 0x00 gdt_end: gdt_ptr: dw gdt_end - gdt - 1 dq gdt CODE_SEG equ kernel_code_64 - gdt DATA_SEG equ kernel_data_64 - gdt global gdt_init gdt_init: lgdt [gdt_ptr] ; 同一特权级下,iretq仅需RIP、CS、RFLAGS pushfq push CODE_SEG push flush iretq flush: mov ax, DATA_SEG mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ret
3. 额外验证点
- 确认GDT描述符的
Present位(第7位)为1:你的内核代码/数据段描述符已正确设置,无需修改。 - 确认GDT指针的线性地址正确:Limine已开启分页,虚拟地址
0xffffffff80001628是有效的线性地址,lgdt指令可正常使用。
内容的提问来源于stack exchange,提问作者Beaver
相关产品推荐
相关产品推荐

