Writing Hypervisor in Zig

Ymir Ymir, the Type-1 Baremetal Hypervisor

note

This blog series has been translated to the following languages:

You can switch the language by clicking the earth icon on the top right corner of the page.

Note that Japanese version is original and all other versions can be outdated. When you see Japanese sentences in translated versions, it means the translation is not yet available. Please request a translation at the GitHub repository, or contribute to the translation if you can.


Writing Hypervisor in Zig is a blog series where we implement a Type-1 hypervisor from scratch using the Zig language. In order to implement a bare-metal Hypervisor that runs at a lower level than an operating system, we will build the bootloader, kernel, and VMM components from scratch.

This series implements a Hypervisor named Ymir. Ymir runs on Intel 64 (x86-64) CPUs and takes advantage of hardware virtualization support provided by Intel VT-x. Ymir has the following features:

  • Type-1 Hypervisor: No dependency on OS
  • Can boot Linux: Ymir implements enough functionality to boot the Linux kernel
  • Written from Scratch: No dependence on libraries, etc.
  • Thin Hypervisor: Ymir intervenes with a guest only when necessary, otherwise pass-through everything

For the simplicity, Ymir has the following constraints:

  • SMP (Symmetric Multi-Processing) is not supported
  • Development on QEMU is expected, and Ymir has not been tested on actual devices.
    • It should require some modification to run on a real machine.
  • APIC is not supported
  • Multiple guest VM at a time is not supported

The series consists of about 30 chapters in total, each containing both conceptual and implementation explanations of a topic. By reading the chapters and writing the actual code, you will finally impuement a hypervisor that can boot Linux:

Ymir

Intended Reader

This series is intended for the following readers:.

  • Some understanding of the OS or willingness to investigate on their own
  • Some understanding of the x86-64 architecture or willingness to read and examine manuals
  • Interest in, experience with, or willingness to study through references to the Zig language
  • Willingness to write a low-level code from scratch
  • CPU that supports Intel VT-x

On the other hand, this blog is not suitable for those who:

  • don't want to use a non-popular language
  • Never want to look up a language or CPU reference on your own
  • Cannot tolerate feature reductions for simplicity or sacrifice of some rigor
  • don't have Intel 64 CPU

How to Read

This series is intended to be read in order from Chapter 1. Each chapter has dependencies on the previous chapters, and code that has already appeared is treated as implemented.

It is strongly recommended that you actually wr te the code in this series by your own. The reference implementation, Ymir, is available on Github. The final code for each chapter corresponds to the whiz-* branch in the above repository. whiz stands for Writing Hypervisor in Zig. You can refer to the corresponding branch to see what was omitted in the chapter or what was not clear in the snippet.

Note that code in the whiz-* branch may have some features reduced or simplified from the one in the master branch for simplicity. Also note that the fixes will not be applied to all branches, but only to the most recent whiz-* and master branches.

Notations

A snippet of code is presented in a code block like:

zig
const x: u8 = 0xDEADBEEF;

A code block with a file name means new code to be added for that file. In such cases ... is used to omit existing code:

ymir/main.zig
fn main() !void {
    ...
    const new_code = 0xDEADBEEF;
    ...
}

Codes with .tmp. in the filename mean that they are added temporarily for experimentation. Such code should be removed at the end of the chapter:

ymir/main.tmp.zig
const tmp: u8 = 0xDEADBEEF;

Zig's struct and enum fields are denoted in the text as .field. As an example, the following structure fields is denoted as .one or Some.one:

zig
const Some = struct {
    one: u8,
    two: u16,
};

For registers, the N-th through M-th bits are represented as Register[M:N]. In this case, note that the M-th bit is included.

Contributions

Please request an update from the author if you

  • Found a technical error in the description
  • Found an expression that is difficult to understand
  • Found typo or omissions
  • Found that code presented does not work or is difficult to understand
  • Like to see new content that is still not covered in this series

You can request by creating Issue or Pull Request on GitHub. It is not necessary to create an Issue before creating a PR. We welcome any kind of requests and fixes.

References

The primary reference in this series is Intel® 64 and IA-32 Architectures Software Developer Manuals, hereafter abbreviated as SDM. For images taken from the SDM, the caption should read "SDM Vol.<Volume> <Chapter>. <Section>. <Subsection>". All images extracted from SDM are the property of © Intel Corporation.

Other than SDM, the following information is available for reference:

Other referenced information is provided on each page.

License

See License.

Privacy

This website uses Google Analytics for access analysis. You can disable cookies for this site in your browser settings.

Changelog

DateLog
2024.11.17Published