I read a book titled “Learning x86 Architecture by Creating Your Own Emulator.”
As a continuation, with the goal of fully running xv6, an x86 OS mainly used for educational purposes,
I plan to extend the self-made emulator.
Since I’ll be developing over a relatively long period, I want to summarize the current situation.
Currently, the bootloader has deployed the kernel body into memory,
and I can start execution from the kernel’s main function.
More precisely, it progresses from the bootloader to the kernel’s main function,
and a kernel panic occurs at some point.
Also, since I can now emulate the serial port (I/O Port 0x3F8),
by calling the cprintf function or panic function from the kernel, I can output arbitrary strings.
At this point, the CPU emulation itself is settling down,
and I feel that implementing various I/O and interrupts is becoming the main focus.
Once things come together to a certain extent, I’d like to make the code public.
For the extension, I started by transcribing the code from the book.
For missing instructions, I added them as needed, referring to Intel’s specification documents.
For the operation of Mod R/M and SIB bytes, I was implementing them randomly,
but the Intel specification documents made everything clear at once.
There are many exceptional behaviors; for example, when Base=5 in SIB byte addressing,
disp8 or disp32 is used instead of registers.
In the early development stage, to verify the operation of each instruction, with QEMU’s operation as correct, I prepared a GDB script like below to trace QEMU’s registers.
I proceeded with development while testing whether the self-made emulator and QEMU’s registers matched over 100,000 steps.
target remote localhost:1234
set architecture i8086
set confirm off
break *0x7c00
c
set variable $i = <numstep>
while $i > 0
si
info registers
set variable $i -= 1
end
quit
I’ll also briefly summarize the operation of xv6.
When you start the self-made emulator by specifying the xv6 image,
it boots in 16-bit mode and starts the bootloader of bootasm.S from the state EIP=0x7c00.
After that, it transitions to 32-bit mode and jumps to C code bootmain.c.
In bootmain.c, it reads the kernel body in ELF format from disk and deploys it into memory.
In the kernel body of main.c,
various device initializations are executed sequentially from functions like mpinit and lapicinit.
It’s straightforward since you just need to initialize them in order from the top.
I think debugging will become difficult when it progresses to context switching.
References
- 自作エミュレータで学ぶx86アーキテクチャ
- xv6
- IA-32 インテル® アーキテクチャソフトウェア・デベロッパーズ・マニュアル
- OSDev.org