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

x86 32位保护模式下段寄存器值不符合预期的原因排查

GDT加载后段寄存器值不符合预期的问题排查

我已进入32位保护模式,通过C语言与汇编代码加载全局描述符表(GDT),并编写check_gdt()函数打印各段寄存器的值,但实际输出与预期不符。相关代码如下:

check_gdt()函数代码

void check_gdt() {
    uint16_t cs, ds, es, fs, gs, ss;

    // Inline assembly to get the values of segment registers
    asm volatile("mov %%cs, %0" : "=r"(cs));    
    asm volatile("mov %%ds, %0" : "=r"(ds));
    asm volatile("mov %%es, %0" : "=r"(es));
    asm volatile("mov %%fs, %0" : "=r"(fs));
    asm volatile("mov %%gs, %0" : "=r"(gs));
    asm volatile("mov %%ss, %0" : "=r"(ss));

    // Print the segment register values
    printf("CS: 0x%x\n", cs);
    printf("DS: 0x%x\n", ds);
    printf("ES: 0x%x\n", es);
    printf("FS: 0x%x\n", fs);
    printf("GS: 0x%x\n", gs);
    printf("SS: 0x%x\n", ss);
}

GDT初始化代码

void initGdt(){
    gdt_ptr.limit = (sizeof(struct gdt_entry_struct) * 5) - 1;
    gdt_ptr.base = (uint32_t)&gdt_entries;

    setGdtGate(0,0,0,0,0);                      // Null segment                           _________________                   _______________
                                                // Explanation:                          |P|DPL|S|E|DC|RW|A|                 |G|DB|L|Res|    |
    setGdtGate(1,0,0xFFFFFFFF, 0x9A, 0xCF);     // Kernel code segment : access = 0x9A =>|1|00 |1|1|0 |1 |0|, gran = 0xCF => |1|1 |0|0  |1111|
    setGdtGate(2,0,0xFFFFFFFF, 0x92, 0xCF);     // Kernel data segment : access = 0x92 =>|1|00 |1|0|0 |1 |0|, gran = 0xCF => |1|1 |0|0  |1111|                  
                                                //                                        _________________                   _______________
    setGdtGate(3,0,0xFFFFFFFF, 0xFA, 0xCF);     // User code segment   : access = 0xFA =>|1|11 |1|1|0 |1 |0|, gran = 0xCF => |1|1 |0|0  |1111|
    setGdtGate(4,0,0xFFFFFFFF, 0xF2, 0xCF);     // User data segment   : access = 0x9A =>|1|11 |1|0|0 |1 |0|, gran = 0xCF => |1|1 |0|0  |1111|
                                                //                                        -----------------                   ---------------
    gdt_flush((uint32_t) &gdt_ptr);
}


void setGdtGate(uint32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran){

    gdt_entries[num].base_low = (base & 0xFFFF);        // Assign lower base 16 bit
    gdt_entries[num].base_middle = (base >> 16) & 0xFF; // Assign middle base 8 bit
    gdt_entries[num].base_high = (base >> 24) & 0xFF;   // Assign higher base 8 bit

    gdt_entries[num].limit_low = (limit & 0xFFFF);      // Assign lower limit 16 bit
    gdt_entries[num].flags = (limit >> 16) & 0x0F;      // Assign flags lower 4 bit
    gdt_entries[num].flags |= (gran & 0xF0);            // Assign flags upper 4 bit

    gdt_entries[num].access = access;                   // Assign access 8 bit
}

gdt_flush汇编函数

gdt_flush:
    ; [esp] Return address (from CALL)
    ; Load the address of the GDT descriptor
    MOV eax, [esp + 4]  ; To load the content of [esp+4] address which is the first argument of gdt_flush((uint32_t) &gdt_ptr)
    LGDT [eax]      ; Load the GDT into the CPU

    MOV ax, 0x10    ; Load kernel data segment selector (0x10)
    MOV ds, ax      ; Set data segment register to kernel data segment
    MOV es, ax      ; Set extra segment register to kernel data segment
    MOV fs, ax      ; Set general-purpose segment to kernel data segment
    MOV gs, ax      ; Set general-purpose segment to kernel data segment
    MOV ss, ax      ; Set stack segment to kernel data segment

    JMP 0x08:.flush ;Far jump to selector 0x08 (code segment) and the address of flush
                    ; Entry 1: Kernel code segment (selector = 0x08)

.flush:
    RET

预期结果

CS : 0x08
DS : 0x10
ES : 0X10
FS : 0X10
GS : 0X10
SS : 0X10

但实际得到错误结果,请问原因是什么?


问题排查与解决方案

1. 内联汇编的寄存器覆盖问题

你当前的内联汇编是分6个独立块执行的,编译器可能会复用寄存器,导致前面读取的段寄存器值被后面的赋值覆盖。比如第一次读取CS到某个寄存器,第二次读取DS时又用同一个寄存器,就会把CS的值冲掉。

修复方案:将所有段寄存器读取合并到一个内联汇编块中,一次性完成所有赋值,避免寄存器复用:

void check_gdt() {
    uint16_t cs, ds, es, fs, gs, ss;
    asm volatile(
        "mov %%cs, %0\n"
        "mov %%ds, %1\n"
        "mov %%es, %2\n"
        "mov %%fs, %3\n"
        "mov %%gs, %4\n"
        "mov %%ss, %5\n"
        : "=r"(cs), "=r"(ds), "=r"(es), "=r"(fs), "=r"(gs), "=r"(ss)
    );
    printf("CS: 0x%x\n", cs);
    printf("DS: 0x%x\n", ds);
    printf("ES: 0x%x\n", es);
    printf("FS: 0x%x\n", fs);
    printf("GS: 0x%x\n", gs);
    printf("SS: 0x%x\n", ss);
}

2. GDT结构体的内存对齐问题

如果struct gdt_entry_struct和struct gdt_ptr_struct没有添加__attribute__((packed))属性,编译器会自动添加填充字节,导致GDT条目或GDT指针的内存结构不符合CPU要求的格式,CPU无法正确解析GDT。

检查点:确认结构体定义是否包含打包属性,比如:

struct gdt_entry_struct {
    uint16_t base_low;
    uint16_t limit_low;
    uint8_t base_middle;
    uint8_t access;
    uint8_t flags; // 包含4位limit高和4位flags
    uint8_t base_high;
} __attribute__((packed));

struct gdt_ptr_struct {
    uint16_t limit;
    uint32_t base;
} __attribute__((packed));

3. GDT条目成员顺序错误

GDT条目的标准内存布局是:
[16位limit低] → [16位base低] → [8位base中] → [8位access] → [4位limit高+4位flags] → [8位base高]

如果你的struct gdt_entry_struct成员顺序和上面不一致(比如把limit_low和base_low放反,或者flags和access顺序错误),会导致CPU加载的段描述符完全错误,段寄存器的值自然异常。

4. printf的干扰

如果你的printf依赖的C库没有适配32位保护模式,可能会在内部修改段寄存器(比如切换到用户段、或者使用错误的段选择符)。建议先用底层方式(比如直接写VGA显存)打印段寄存器值,排除printf的影响:

void print_hex(uint16_t val) {
    // 简化的VGA显存打印十六进制值,假设VGA显存基地址是0xB8000
    char *vga = (char*)0xB8000;
    char hex[] = "0123456789ABCDEF";
    vga[0] = '0'; vga[1] = 0x07;
    vga[2] = 'x'; vga[3] = 0x07;
    vga[4] = hex[(val >> 12) & 0xF]; vga[5] = 0x07;
    vga[6] = hex[(val >> 8) & 0xF]; vga[7] = 0x07;
    vga[8] = hex[(val >> 4) & 0xF]; vga[9] = 0x07;
    vga[10] = hex[val & 0xF]; vga[11] = 0x07;
}

void check_gdt() {
    uint16_t cs, ds;
    asm volatile("mov %%cs, %0\nmov %%ds, %1" : "=r"(cs), "=r"(ds));
    print_hex(cs);
}

5. GDT加载后的上下文干扰

确保调用initGdt()后立即调用check_gdt(),中间不要插入其他可能修改段寄存器的代码(比如异常处理、库函数调用)。


内容的提问来源于stack exchange,提问作者baponkar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 05:24:50