You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

内嵌汇编打印函数失效求助:QEMU测试内核仅输出空格

我帮你排查出几个导致打印失效的关键问题,一步步来修复:

1. 编译模式不匹配:默认64位代码无法在实模式运行

你的bootloader是16位实模式的汇编代码,但gcc默认会编译64位(x86_64)代码。实模式下根本无法正确执行64位指令,这直接导致BIOS中断调用的寄存器操作混乱——比如你写的mov ah,0x0e是16位操作,但64位寄存器的高32位可能残留垃圾值,最终输出错误的字符(或者空格)。

修复命令:强制gcc编译32位独立环境代码(内核没有标准库支持):

gcc -c ./source/kernel.c -masm=intel -m32 -ffreestanding -nostdlib -fno-pie

每个参数的作用:

  • -m32:生成32位x86指令集代码
  • -ffreestanding:告诉gcc这是独立运行环境(没有操作系统的标准库和启动代码)
  • -nostdlib:不链接任何标准库文件
  • -fno-pie:禁用位置无关代码,确保内核的地址是固定的

2. 链接器未指定内存布局,内核加载地址错误

你之前的链接命令没有指定目标架构和起始地址,链接器默认用64位布局生成二进制,导致内核加载到内存后,执行的是完全错误的指令片段。

修复命令:指定32位ELF格式和内核的起始地址(假设你的bootloader把内核加载到物理地址0x1000,这是实模式下常见的内核加载位置):

ld --oformat binary -m elf_i386 -e main -Ttext 0x1000 kernel.o -o ./source/kernel.bin

参数说明:

  • -m elf_i386:明确告诉链接器处理32位x86目标文件
  • -Ttext 0x1000:把代码段的起始地址设为0x1000,必须和bootloader加载内核的内存地址完全一致
  • -e main:指定内核的入口点是main函数

3. Bootloader必须做好前置准备

你的bootloader需要完成两个关键操作,才能让C内核正常运行:

  • 设置栈空间:C函数调用依赖栈来保存返回地址和参数,在bootloader里添加这段代码:
    mov ax, 0x0000
    mov ss, ax
    mov sp, 0x7C00  ; 把栈设在bootloader加载地址(0x7C00)下方,避免被覆盖
    
  • 正确加载内核:确保bootloader用int 0x13中断把kernel.bin读取到内存的0x0000:0x1000位置(对应物理地址0x1000,和链接时指定的-Ttext 0x1000匹配)

另外注意:BIOS中断0x10只能在实模式下调用。如果你的内核是32位代码,需要先切换到32位保护模式,或者如果想继续用实模式,需要编译16位C代码(可以用gcc -m16,但需要配合专用的链接脚本)。

4. 快速验证:硬编码字符测试

你可以先修改printstack函数,直接硬编码'a'的ASCII码(0x61),验证中断调用本身是否正常:

void printstack(char in){
 asm ("mov ah,0x0e\n"
      "mov al,0x61\n"  ; 直接使用'a'的ASCII值
      "int 0x10\n");
}

如果修改后能正常打印'a',说明之前的参数传递问题完全是由编译/链接模式不匹配导致的。

修改完构建脚本后,重新生成os.flp,在QEMU里测试应该就能正常输出字符了。

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

火山引擎 最新活动