如何正确设置Bootloader、Linker与GDT间的栈工作流?
保护模式下Bootloader、链接脚本与GDT的栈配置工作流
一、GDT栈段描述符的正确配置
- 栈段属于向下扩展的数据段,描述符Type字段需设为
0x92(二进制10010010:段存在、特权级0、向下扩展、可读写) - 基址(Base)必须完全覆盖栈的物理内存区域,限长(Limit)按栈大小计算:
- 128KB栈大小为
0x20000,开启粒度位(G位=1,单位4KB)时,限长需设为0x1FFFF(公式:(限长+1)*4KB = 栈大小) - 示例配置(对应基址
0x80000的128KB栈):set_gdt_gate_access(5, 0x80000, 0x1FFFF, 0x92, 0xCF); // 第5个GDT项(索引从0开始),基址0x80000,限长0x1FFFF,Type=0x92,G/D/B位等=0xCF
- 128KB栈大小为
- 栈段DPL(特权级)必须与当前运行特权级一致(Bootloader/内核通常为0),否则会触发保护异常
二、链接脚本中栈段的地址分配
- 链接脚本需明确
.stack段的物理地址,必须落在GDT栈段描述符的基址+限长范围内:SECTIONS { . = 0x100000; /* 内核加载基址 */ .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } /* 将栈放在内核之后,分配128KB空间 */ .stack : ALIGN(4) { stack_start = .; . += 0x20000; stack_end = .; } } - 若栈放在独立内存区域(如
0x80000~0xA0000),需修改.stack起始地址为0x80000,确保与GDT基址完全匹配
三、Bootloader(osload.asm)中的栈初始化流程
进入保护模式后,按以下顺序执行:
- 加载GDT(
lgdt指令)并切换到保护模式; - 加载栈段选择器到SS:选择器值为GDT中栈段项的索引×8(如第5项为
5*8=0x28); - 设置ESP为栈顶地址(即链接脚本导出的
stack_end);
示例代码:; 加载栈段选择器 mov ax, 0x28 mov ss, ax ; 设置栈顶地址 mov esp, stack_end
- 注意:
mov ss, ax和mov esp, stack_end需连续执行,CPU会自动屏蔽这两条指令间的中断,避免栈未初始化完成就触发异常
四、内核端的注意事项
- 不要在内核中重复修改GDT栈段描述符:Bootloader已完成GDT和栈的初始化,内核直接复用即可;
- 若内核必须重新加载GDT,需保证新GDT的栈段描述符与原配置完全一致(基址、限长、特权级),且栈地址仍在有效范围内
五、常见错误排查
- SS bound violation:ESP超出栈段有效范围。检查:
- ESP初始值是否为
基址 + 栈大小(向下扩展栈的栈顶是段最高地址,push操作会先减ESP再写入数据); - 链接脚本的
.stack段是否落在GDT栈段的基址+限长范围内;
- ESP初始值是否为
- mov ss, ax执行错误:GDT栈段描述符的DPL与当前特权级不匹配,或Type字段未设为向下扩展数据段;
- 栈地址与GDT基址不匹配:链接脚本分配的栈物理地址不在GDT栈段描述符覆盖区域内,需同步修改两者配置
内容的提问来源于stack exchange,提问作者raven_lee
相关产品推荐
相关产品推荐

