You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qemu直接运行与GDB调试时内存写入偏移问题求助

问题

作为操作系统初学者,参考xv6的bootloader编写了一个用insw指令读取磁盘文件到内存的函数。直接运行Qemu时,文件没加载到指定内存地址,总是有几字节到几百字节的偏移;但用GDB断点调试时,文件能准确加载到指定地址。当前运行在实模式(后续要获取内存与显卡信息,和xv6不同)。相关代码及现象如下:

引导扇区汇编代码

.text
.code16
globl start
start:
  cli

  xorw %ax, %ax
  movw %ax, %ds
  movw %ax, %es

  movw $0x7c00, %sp

  call    bootmain16  

磁盘文件读取C代码

#define SECTSIZE 512

static inline void outb(uint16_t port, uint8_t data)
{
  __asm__ volatile("out %0, %1" : : "a"(data), "d"(port));
}
static inline uint8_t in(uint16_t port)
{
  uint8_t data;
  __asm__ volatile("in %1, %0" : "=a"(data) : "d"(port));

  return data;
}
static inline void insw(uint16_t port, uint8_t* addr, int32_t cnt)
{
  __asm__ volatile(
      "cld\n"                          
      "rep insw"                       
      : "=D"(addr), "=c"(cnt)           
      : "d"(port), "0"(addr), "1"(cnt)  
      : "memory", "cc"                  
  );
}
void waitdisk(void)
{
    while (in(0x1F7) & 0xC0 != 0x40);
}
void readsect(uint8_t* dst, uint32_t offset)
{
    waitdisk();
    outb(0x1F2, 0x1);                   
    outb(0x1F3, offset);               
    outb(0x1F4, offset >> 8);          
    outb(0x1F5, offset >> 16);         
    outb(0x1F6, (offset >> 24) | 0xE0);
    outb(0x1F7, 0x20);                 
    waitdisk();
    //insl(0x1F0, dst, SECTSIZE / 4);
    insw(0x1F0, dst, SECTSIZE / 2);//Here I am using ‘insw’ in 16-bit mode
}
//Here I am using the offset directly as the sector number, which is different from xv6.
void readfile(uint8_t* pa, uint32_t count, uint32_t offset) 
{
    uint8_t* epa = pa + count;
    for (; pa < epa; pa += SECTSIZE, offset++)
        readsect(pa, offset);
}
void bootmain16(void)
{
    print('$');
    uint8_t *pa = (uint8_t *)(0x7e00);
    uint32_t count = 0x4000;
    uint32_t offset = 1;
    readfile(pa, count, offset);
    print('S');
    //Here I added ‘hlt’ to observe the memory at this point.
    __asm__ volatile(
        "hlt\n"
        "jmp $0x7e00,$0\n"
    );
}

Makefile

IMAGE = os.img

$(IMAGE): bootlock boot16.bin
    dd if=/dev/zero of=$(IMAGE) bs=512 count=2048
    dd if=bootlock of=$(IMAGE) seek=0 bs=512 conv=notrunc
    dd if=boot16.bin of=$(IMAGE) seek=1 bs=512 conv=notrunc
    objdump -D -b binary -m i386:x86-64 os.img > os.asm

bootasm.o: bootasm.S
    gcc -m16 -std=c11 -I. -fno-pic  -fno-stack-protector -fcf-protection=none -nostdinc -nostdlib -ffreestanding -fno-builtin -ggdb -O2 -static -g -fno-leading-underscore -c bootasm.S -o bootasm.o

bootmain.o: bootmain.c
    gcc -m16 -std=c11 -I. -fno-pic  -fno-stack-protector -fcf-protection=none -nostdinc -nostdlib -ffreestanding -fno-builtin -ggdb -O2 -static -g -fno-leading-underscore -c bootmain.c -o bootmain.o

boot16.o: boot16.S
    gcc -m16 -std=c11 -I. -fno-pic  -fno-stack-protector -fcf-protection=none -nostdinc -nostdlib -ffreestanding -fno-builtin -ggdb -O2  -static -g -fno-leading-underscore -c boot16.S -o boot16.o

bootlock: bootasm.o bootmain.o
    ld -m elf_i386 -Ttext=0x7c00 -e start  bootasm.o bootmain.o -o bootlock.o
    objdump -S -m i386 -M i8086 bootlock.o > bootlock.asm
    objcopy -S -O binary -j .text bootlock.o bootlock
    ./sign.pl bootlock


boot16.bin: boot16.o 
    ld -m elf_i386 -Ttext=0x7e00  boot16.o -o bootm16.o
    objdump -S bootm16.o > boot16.asm
    objcopy -O binary bootm16.o boot16.bin


clean:
    rm -f *.o *.elf *.bin *.d *.img *.asm bootlock

run: $(IMAGE)
    qemu-system-x86_64 -drive file=$(IMAGE),format=raw
run2: $(IMAGE)
    qemu-system-x86_64 -drive file=$(IMAGE),format=raw -s -S  

debug:
    qemu-system-x86_64 -hda $(IMAGE)  -s -S &
    gdb -tui -x init.gdb

签名脚本(来自xv6)

#!/usr/bin/perl

open(SIG, $ARGV[0]) || die "open $ARGV[0]: $!";

$n = sysread(SIG, $buf, 1000);

print $n, "\n";

if($n > 510){
  print STDERR "boot block too large: $n bytes (max 510)\n";
  exit 1;
}

print STDERR "boot block is $n bytes (max 510)\n";

$buf .= "\0" x (510-$n);
$buf .= "\x55\xAA";

open(SIG, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
print SIG $buf;
close SIG;

现象说明

  • 直接执行make run:程序运行到hlt时,在Qemu监视器输入x/10b 0x7e00显示全0,文件实际被存在0x7e60附近;
  • GDB调试:在0x7c00设断点,单步到insw后运行到hlt,文件能正确加载到0x7e00。

问题原因与解决方法

核心原因

  1. 16位模式寄存器宽度不匹配:
    实模式下仅16位寄存器生效,但insw函数的内联汇编用了32位寄存器约束=D(对应EDI),GCC -m16编译时指针是16位的,编译器优化可能把地址值放到EDI高16位,导致实际写入地址用的是低16位DI,和预期的0x7e00产生偏移。而GDB调试时优化被抑制,寄存器赋值正常,所以地址正确。

  2. waitdisk逻辑优先级错误:
    表达式in(0x1F7) & 0xC0 != 0x40中!=优先级高于&,实际执行in(0x1F7) & (0xC0 != 0x40),也就是in(0x1F7) & 0,永远为0,导致waitdisk没有正确等待磁盘就绪。调试时单步给了磁盘足够响应时间,能正确读取;直接运行时磁盘未就绪就开始读,数据错乱或地址偏移。

解决步骤

1. 修复insw函数的寄存器约束

改用16位寄存器约束,同时调整计数变量为16位,匹配实模式寄存器宽度:

static inline void insw(uint16_t port, uint8_t* addr, int16_t cnt)
{
  __asm__ volatile(
      "cld\n"
      "rep insw"
      : "=DI"(addr), "=CX"(cnt)
      : "dx"(port), "0"(addr), "1"(cnt)
      : "memory", "cc"
  );
}

2. 修复waitdisk的逻辑优先级

给位运算加括号,确保正确判断磁盘状态:

void waitdisk(void)
{
    while( (in(0x1F7) & 0xC0) != 0x40 );
}

3. 关闭不必要的编译优化

编译bootmain.o时将-O2改为-O0,避免16位代码被意外优化:

bootmain.o: bootmain.c
    gcc -m16 -std=c11 -I. -fno-pic  -fno-stack-protector -fcf-protection=none -nostdinc -nostdlib -ffreestanding -fno-builtin -ggdb -O0 -static -g -fno-leading-underscore -c bootmain.c -o bootmain.o

4. 调整栈地址避免覆盖引导扇区

当前栈设置在0x7c00(引导扇区加载地址),栈向下生长可能覆盖代码,可将栈移到更高地址:

movw $0x9000, %ax
movw %ax, %ss
movw $0x0000, %sp

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 04:33:11