使用G++与QEMU开发自定义OS时遇到的数组索引奇怪Bug
你的问题核心是实模式下编译器生成的变量索引寻址代码与OS内存布局不兼容:常量索引时编译器直接将数组元素值编译为立即数,跳过地址计算;而变量索引需要动态计算数组地址,此时如果编译器生成了不符合实模式规则的寻址代码,或段寄存器、内存布局配置错误,就会导致访问失败。
以下是针对性解决步骤:
1. 强制编译器生成实模式兼容的16位代码
G++默认生成32位保护模式代码,需通过编译选项强制生成16位实模式代码:
# 编译C++代码时添加这些选项 g++ -c -m16 -ffreestanding -fno-exceptions -fno-rtti main.cpp -o main.o
-m16:生成16位目标代码-ffreestanding:告知编译器当前是无标准库的独立运行环境-fno-exceptions -fno-rtti:禁用C++异常和RTTI,减少冗余代码
2. 修正const数组的存储位置
const修饰的数组会被放到.rodata只读段,若链接脚本未将该段纳入实模式可访问区域,运行时会无法读取。可二选一调整:
- 去掉
const修饰,让数组进入.data段:char scancodes[] = {'\0','%','1','2','3','4','5','6','7','8','9','0',
'-','=','~','\t','Q','W','E','R','T','Y','U','I','O','P','[',']','\e','\0','A', 'S','D',
'F','G','H','J','K','L',';','\0','`',
'\0','\','Z','X','C','V','B','N','M',',','.',
'/','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
'\0','\0','\0','\0','\0','\0','\0','\0','-','\0','\0','\0','+','\0','\0','\0','\0','~',};
- 修改链接脚本,将`.rodata`合并到`.data`段: ```ld ENTRY(boot) SECTIONS { . = 0x100000; # 内核加载到1MB位置(实模式可访问范围) .text : { *(.text) } .data : { *(.data) *(.rodata) } # 合并只读数据到数据段 .bss : { *(.bss) } }
3. 确保段寄存器正确初始化
实模式下内存访问依赖段寄存器基址,必须在启动汇编代码中将DS(数据段寄存器)设置为0:
[bits 16] boot: mov ax, 0 mov ds, ax # 设置数据段基址为0 mov es, ax # 同步设置附加段,避免视频内存访问出错 mov ss, ax # 初始化栈段 mov sp, 0x7c00 # 设置栈指针位置 call main # 调用C++的main函数 jmp $ # 无限循环挂起
4. 统一工具链的架构匹配
确保NASM和LD的架构选项与G++一致:
# 汇编启动代码时指定elf32格式(匹配i386架构) nasm -f elf32 boot.asm -o boot.o # 链接时指定elf_i386架构 ld -m elf_i386 -T link.ld boot.o main.o -o kernel.bin
完成以上调整后,变量索引数组的代码即可正常执行。
内容的提问来源于stack exchange,提问作者Ynon Slutzker

