磁盘加载至不同内存后汇编代码失效问题求助
问题
汇编代码在引导扇区内可成功切换至32位模式并打印字符,但通过磁盘读取加载到下一扇区后,即便使用了org指令,GDT表地址仍出现偏移,导致打印失败且程序进入启动循环。
已确认磁盘读取操作成功,两种场景下代码完全一致,仅引导扇区使用org 0x7C00,第二扇区使用org 0x7E00。尝试过移除org指令、将dd gdt_start替换为dd gdt_start + 0x7E00、硬编码精确地址,均未解决问题。
项目详情
- 编写x86_64汇编代码
- 使用QEMU运行
正常工作时GDT表地址为起始位置后3字节(即7C03);失效时GDT地址为000f61e0 00000037,与正确地址差距极大。
第二阶段代码
BITS 16 org 0x7E00 cli jmp code gdt_start: dq 0x0000000000000000 ; Null descriptor dq 0x00CF9A000000FFFF ; Code segment dq 0x00CF92000000FFFF ; Data segment gdt_end: gdt_descriptor: dw gdt_end - gdt_start - 1 ; Size dd gdt_start code: lgdt [gdt_descriptor] mov eax, cr0 or eax, 1 ; Change PE (Protection Enable) bit if 0 mov cr0, eax jmp 0x08:protected_mode [BITS 32] ; Registers are 16-bit and map to 32-bit addresses through GDT table protected_mode: mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, 0xB8000 mov byte [eax], 'D' mov byte [eax+1], 0x01 jmp $
第一阶段磁盘读取代码
BITS 16 org 0x7C00 jmp code boot_drive: db 0 code: mov byte [boot_drive], dl ; dl is loaded at boot and has the source drive xor ax, ax mov ah, 0x02 ; Disk read mov al, 3 ; Sector read count mov ch, 0 ; Cylinder mov cl, 2 ; Sector mov dh, 0 ; Head mov dl, [boot_drive] ; Drive mov es, ax ; Segment mov bx, 0x7E00 ; Offset int 0x13 ; Disk service jc error ; Jmp if CF = 1 jmp 0x0000:0x7e00 error: mov ah, 0x0E mov al, 'E' mov bh, 00 mov bl, 0x07 int 0x10 times 510 - ($ - $$) db 0 dw 0xAA55
解决方案
问题核心是实模式下未正确设置ds寄存器,导致lgdt指令计算GDT物理地址时使用了错误的段基址,最终指向了错误的内存位置。
具体原因
实模式中,lgdt [gdt_descriptor]会以当前ds寄存器的值作为段基址,加上gdt_descriptor的偏移得到物理地址。第二阶段代码跳转后,ds寄存器继承自第一阶段的未知值(BIOS启动时ds不一定为0),而非我们预期的0,所以计算出的GDT地址完全错误。
修复步骤
- 在第二阶段的
code标签开头,先将ds寄存器设置为0,确保实模式下地址计算的正确性:
code: xor ax, ax mov ds, ax ; 设置ds为0,匹配org 0x7E00的地址假设 lgdt [gdt_descriptor]
- 保留
org 0x7E00指令,汇编器会自动计算gdt_start的绝对物理地址,无需手动修改dd gdt_start。
修复后的第二阶段代码
BITS 16 org 0x7E00 cli jmp code gdt_start: dq 0x0000000000000000 ; Null descriptor dq 0x00CF9A000000FFFF ; Code segment dq 0x00CF92000000FFFF ; Data segment gdt_end: gdt_descriptor: dw gdt_end - gdt_start - 1 ; Size dd gdt_start code: xor ax, ax mov ds, ax ; 关键:确保ds段基址为0 lgdt [gdt_descriptor] mov eax, cr0 or eax, 1 ; 置位PE位,切换到保护模式 mov cr0, eax jmp 0x08:protected_mode [BITS 32] protected_mode: mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, 0xB8000 mov byte [eax], 'D' mov byte [eax+1], 0x01 jmp $
额外检查点
- 第一阶段中
mov al, 3表示读取3个扇区,若第二阶段代码小于512字节,可改为读取1个扇区,减少不必要的磁盘操作。 - 可在第二阶段开头添加实模式打印代码(如调用
int 0x10打印字符),验证代码是否在正确位置执行,同时确认ds设置是否生效。
内容的提问来源于stack exchange,提问作者Adi Mehmedičević
相关产品推荐
相关产品推荐

