I am a beginner in operating systems, and I have written a function that reads a file from disk into memory using ‘insw’, inspired by the bootloader of xv6. However, when I run qemu directly, I find that the file is not actually at the address I have set; it is always offset by a few bytes, sometimes even hundreds of bytes. But when I debug with breakpoints using GDB, the file is then located at the memory address I specified. What could be the reason for this? And how can I resolve it? Since I need to obtain memory information and graphics card information in real mode later on, I am running in real mode, which is different from xv6. Below is my code for reading the file. Thank you for reading, and I look forward to your answer!
Boot sector assembly code
.text
.code16
.globl start
start:
cli
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw $0x7c00, %sp
call bootmain16
C code to read a file from disk into a specified memory location
#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
The signature file used here is from 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;
When I run ‘make run’, and wait for execution to reach ‘hlt’, if I input ‘x/10b 0x7e00’ in qemu’s compat-monitor0, I get all zeros, but inspecting the entire memory reveals that it might have been stored at 0x7e60. However, when I use GDB, I set a breakpoint at 0x7c00, then step through with ‘stepi’ to reach ‘insw’, and use ‘continue’ to quickly reach ‘hlt’. At this point, I find that it has correctly read to 0x7e00.