C++编写的Bootloader异常:内联汇编函数仅单次生效,QEMU仅显示'H'
Bootloader内联汇编函数仅生效一次,仅打印单个字符问题
我用C++编写Bootloader,预期功能是设置视频模式为0x03并打印"Hello",但在QEMU中仅显示一个'H',内联汇编实现的函数似乎只能生效一次。
相关代码
boot.cpp
asm(".code16gcc\n"); asm(".text"); asm("jmpl $0x0000, $main\n"); #include "../libs/os.hpp" void main() { x16::video::setvideomode(0x03); x16::video::printchar('H'); x16::video::printchar('e'); x16::video::printchar('l'); x16::video::printchar('l'); x16::video::printchar('o'); }
os.hpp
#ifndef OS_HPP #define OS_HPP typedef unsigned char byte; typedef unsigned short word; typedef unsigned long dword; namespace x16 { namespace assembly { namespace regs { const int ax = 0, ah = 1, al = 2; const int bx = 3, bh = 4, bl = 5; const int cx = 6, ch = 7, cl = 8; const int dx = 9, dh = 10, dl = 11; } void setreg8(int reg, byte value) { if (reg == regs::ah) { asm volatile("movb %0, %%ah" : : "r"(value)); } if (reg == regs::al) { asm volatile("movb %0, %%al" : : "r"(value)); } if (reg == regs::bh) { asm volatile("movb %0, %%bh" : : "r"(value)); } if (reg == regs::bl) { asm volatile("movb %0, %%bl" : : "r"(value)); } if (reg == regs::ch) { asm volatile("movb %0, %%ch" : : "r"(value)); } if (reg == regs::cl) { asm volatile("movb %0, %%cl" : : "r"(value)); } if (reg == regs::dh) { asm volatile("movb %0, %%dh" : : "r"(value)); } if (reg == regs::dl) { asm volatile("movb %0, %%dl" : : "r"(value)); } } void interrupt(int interrupt) { if (interrupt == 0x10) { asm("int $0x10"); } if (interrupt == 0x13) { asm("int $0x13"); } if (interrupt == 0x16) { asm("int $0x16"); } } } namespace video { void setvideomode(byte videomode) { x16::assembly::setreg8(x16::assembly::regs::ah, 0x00); x16::assembly::setreg8(x16::assembly::regs::al, videomode); x16::assembly::interrupt(0x10); } void printchar(byte chr) { x16::assembly::setreg8(x16::assembly::regs::ah, 0x0E); x16::assembly::setreg8(x16::assembly::regs::al, chr); x16::assembly::interrupt(0x10); } } } #endif
boot.ld
ENTRY(main) OUTPUT_FORMAT(binary) SECTIONS { . = 0x7C00; .text : AT(0x7C00) { *(.text) } .sig : AT(0x7DFE) { SHORT(0xAA55); } }
编译命令
g++ -c -g -Os -ffreestanding -fwhole-program -Wall -Werror src/boot/boot.cpp -o tmp/boot.o ld -static -T src/boot/boot.ld -nostdlib --nmagic -o tmp/boot.bin tmp/boot.o
问题分析与修复方案
核心问题
- 中断指令被编译器优化:
interrupt函数中的asm代码未加volatile修饰,编译器在-Os优化级别下会认为该指令无副作用,将后续重复调用的中断指令直接移除。 - 寄存器操作的分支优化问题:
setreg8函数使用多个if分支设置寄存器,编译器优化后可能导致后续的寄存器赋值指令未正确生成,或寄存器被意外覆盖。 - 未告知编译器寄存器修改情况:内联汇编未声明被修改的寄存器,导致编译器的寄存器分配逻辑与手动操作冲突,破坏后续指令执行环境。
修复步骤
1. 阻止中断指令被优化
修改interrupt函数,给内联汇编添加volatile关键字,确保编译器不会优化掉中断调用:
void interrupt(int interrupt) { if (interrupt == 0x10) { asm volatile("int $0x10"); } if (interrupt == 0x13) { asm volatile("int $0x13"); } if (interrupt == 0x16) { asm volatile("int $0x16"); } }
2. 简化寄存器操作,避免分支优化问题
直接在视频操作函数中编写内联汇编,去掉中间的setreg8函数,同时明确告知编译器哪些寄存器会被修改:
namespace video { void setvideomode(byte videomode) { asm volatile( "movb $0x00, %%ah\n" "movb %0, %%al\n" "int $0x10" : : "r"(videomode) : "ah", "al" // 声明被修改的寄存器 ); } void printchar(byte chr) { asm volatile( "movb $0x0E, %%ah\n" "movb %0, %%al\n" "int $0x10" : : "r"(chr) : "ah", "al" ); } }
3. 确保16位代码生成正确
在编译命令中添加-m16参数(需gcc支持),强制生成16位代码:
g++ -c -g -Os -ffreestanding -fwhole-program -Wall -Werror -m16 src/boot/boot.cpp -o tmp/boot.o
4. 检查Bootloader大小
MBR格式的Bootloader必须严格控制在512字节以内(最后2字节为0xAA55签名),可通过以下命令检查:
ls -l tmp/boot.bin
若超过512字节,需精简代码或调整优化选项。
修复原理
volatile关键字告诉编译器内联汇编有副作用,不能被优化移除。- 直接编写内联汇编避免了分支逻辑带来的优化不确定性,同时通过寄存器约束告知编译器操作的寄存器,避免冲突。
-m16确保生成的代码符合16位实模式的执行要求。
内容的提问来源于stack exchange,提问作者00toad00
相关产品推荐
相关产品推荐

