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。
问题原因与解决方法
核心原因
16位模式寄存器宽度不匹配:
实模式下仅16位寄存器生效,但insw函数的内联汇编用了32位寄存器约束=D(对应EDI),GCC-m16编译时指针是16位的,编译器优化可能把地址值放到EDI高16位,导致实际写入地址用的是低16位DI,和预期的0x7e00产生偏移。而GDB调试时优化被抑制,寄存器赋值正常,所以地址正确。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
相关产品推荐
相关产品推荐

