You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 21:19:50