<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged pvh</title>
<link>https://coderlegion.com/tag/pvh</link>
<description>Powered by Question2Answer</description>
<item>
<title>Notes on x86 PVH boot</title>
<link>https://coderlegion.com/28620/notes-on-x86-pvh-boot</link>
<description>&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;My Rust KVM project now being able to boot Linux on ARM64, I decided to try and&lt;br&gt;
boot Linux on x86. I have been trying in the past to set up the boot procedure&lt;br&gt;
via &lt;code&gt;boot_params&lt;/code&gt; struct. At the time it was a lot more complex and now I wanted&lt;br&gt;
to have some result as soon as possible. So I turned to the PVH&lt;br&gt;
(Para-Virtualized Hardware) Linux boot which simplifies this process a lot. As&lt;br&gt;
a Xen Project protocol, PVH provides a relatively simple interface between a&lt;br&gt;
VMM and the Linux kernel, without requiring the VMM to reproduce the&lt;br&gt;
traditional firmware/bootloader path.&lt;/p&gt;
&lt;h3 id=&quot;entering-protected-mode-via-bios&quot;&gt;Entering protected mode via BIOS&lt;/h3&gt;
&lt;p&gt;The traditional way of booting an operating system on an x86 machine is to start&lt;br&gt;
in real mode and then proceed to protected mode. From there, if CPU is 64-bit,&lt;br&gt;
we can go to long mode. In the PVH Linux entry path, Linux performs the&lt;br&gt;
transition from the required 32-bit protected-mode entry state into 64-bit mode&lt;br&gt;
itself.&lt;/p&gt;
&lt;h4 id=&quot;setting-up-real-mode&quot;&gt;Setting up real mode&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;x86 starts at reset vector (physical address &lt;code&gt;0xFFFFFFF0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CPU is in 16-bit real mode and firmware like SeaBIOS takes over&lt;/li&gt;
&lt;li&gt;SeaBIOS looks at first sector (512 bytes) of a bootable device&lt;/li&gt;
&lt;li&gt;if the sector contains &lt;code&gt;0xAA55&lt;/code&gt; at 510-byte offset, it's loaded at &lt;code&gt;0x7C00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;load the rest of data manually from our boot device (e.g. on &lt;code&gt;0x8000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;jump to the code section we just loaded (e.g. to address &lt;code&gt;0x8000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;if necessary, set up Stack Pointer and Stack Segment&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;getting-to-protected-mode&quot;&gt;Getting to protected mode&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;make sure interrupts are disabled&lt;/li&gt;
&lt;li&gt;set up and load a minimal GDT (Global Descriptor Table)&lt;/li&gt;
&lt;li&gt;set PE (Protected Mode Enable) bit in CR0 (Control Register)&lt;/li&gt;
&lt;li&gt;perform far jump selecting a proper GDT entry&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;gdt-example&quot;&gt;GDT example&lt;/h4&gt;
&lt;p&gt;A minimal GDT code set up looks like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;gdt_start:
dq 0
gdt_code:; index 1
dw 0xFFFF; segment limit (low)
dw 0x0000; segment base (low)
db 0x00; base address (middle)
db 10011010b; access bits (read/write, executable, code or data, present)
db 11001111b; flags (32-bit protected, 4k blocks) - segment limit (high)
db 0x00; base address (high)

gdt_data:; index 2
dw 0xFFFF; segment limit (low)
dw 0x0000; segment base (low)
db 0x00; base address (middle)
db 10010010b; access bits (read/write, code or data, present)
db 11001111b; flags (32-bit protected, 4k blocks) - segment limit (high)
db 0x00; base address (high)
gdt_end:

gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we enter protected mode by executing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;cli
lgdt [gdt_descriptor]

mov eax, cr0
or eax, 1
mov cr0, eax

jmp 0x08:protected_mode_start ; 0x08 is 1 &amp;lt;&amp;lt; 3 (select index 1 in GDT)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The far jump reloads &lt;code&gt;CS&lt;/code&gt; using the descriptor selected by &lt;code&gt;0x08&lt;/code&gt;. The other&lt;br&gt;
segment registers must be loaded with appropriate protected-mode selectors as&lt;br&gt;
well. Each segment register has a visible selector and hidden descriptor-cache&lt;br&gt;
state containing information such as the base, limit and access rights.&lt;/p&gt;
&lt;h3 id=&quot;how-pvh-helps&quot;&gt;How PVH helps&lt;/h3&gt;
&lt;p&gt;If we are in a virtual machine, we don't really need to go through all of these&lt;br&gt;
steps. We don't need to have firmware or BIOS to set up CPU state properly, we&lt;br&gt;
can simply do this from the VM.&lt;/p&gt;
&lt;p&gt;Here, we can manipulate the vCPU to our will, and therefore we can set segment&lt;br&gt;
registers ourselves to valid values, skipping GDT setup. We do need to do some&lt;br&gt;
administration to help Linux understand what's going on and this is exactly&lt;br&gt;
where PVH comes in.&lt;/p&gt;
&lt;h4 id=&quot;elf-pvh-note&quot;&gt;ELF PVH note&lt;/h4&gt;
&lt;p&gt;To determine whether an x86 Linux ELF image supports the PVH boot ABI, we can&lt;br&gt;
inspect its Xen ELF notes and look for &lt;code&gt;XEN_ELFNOTE_PHYS32_ENTRY&lt;/code&gt; (note type&lt;br&gt;
&lt;code&gt;18&lt;/code&gt;). Its descriptor gives the physical 32-bit entry point at which the guest&lt;br&gt;
should be started.&lt;/p&gt;
&lt;p&gt;Newer kernels may also contain &lt;code&gt;XEN_ELFNOTE_PHYS32_RELOC&lt;/code&gt; (type &lt;code&gt;19&lt;/code&gt;), which&lt;br&gt;
describes alignment and physical-address constraints for relocatable PVH&lt;br&gt;
kernels.&lt;/p&gt;
&lt;h3 id=&quot;pvh-boot-memory-setup&quot;&gt;PVH boot memory setup&lt;/h3&gt;
&lt;p&gt;Boot procedure from PVH is more straightforward, but requires more memory&lt;br&gt;
management from VM:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;get RIP from ELF PVH note entry&lt;/li&gt;
&lt;li&gt;load Linux kernel ELF segments into memory&lt;/li&gt;
&lt;li&gt;copy kernel command line to memory&lt;/li&gt;
&lt;li&gt;construct an E820-like memory map using &lt;code&gt;HvmMemEntry&lt;/code&gt; entries&lt;/li&gt;
&lt;li&gt;construct and fill out &lt;code&gt;HvmStartInfo&lt;/code&gt; struct&lt;/li&gt;
&lt;li&gt;load memory entries and &lt;code&gt;HvmStartInfo&lt;/code&gt; to memory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If using &lt;code&gt;initramfs&lt;/code&gt; you also need to load it into memory, along with its&lt;br&gt;
separate &lt;code&gt;initramfs&lt;/code&gt; command line. We also need to fill out &lt;code&gt;HvmModuleEntry&lt;/code&gt;&lt;br&gt;
struct to point to &lt;code&gt;initramfs&lt;/code&gt; and its command line address. Note that&lt;br&gt;
here we are not talking about kernel modules, but rather Xen modules, and&lt;br&gt;
for Linux that only means &lt;code&gt;initramfs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9777136250809845640&quot; alt=&quot;ELF with PVH&quot;&gt;&lt;/p&gt;
&lt;p&gt;A good reference for the structures above is actually in the Linux kernel&lt;br&gt;
headers and can be found on:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/linux/v7.1.3/source/include/xen/interface/hvm/start_info.h&quot;&gt;https://elixir.bootlin.com/linux/v7.1.3/source/include/xen/interface/hvm/start_info.h&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;setting-up-registers&quot;&gt;Setting up registers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;RIP points to entrypoint from ELF note &lt;code&gt;18&lt;/code&gt; entry&lt;/li&gt;
&lt;li&gt;RBX points to &lt;code&gt;HvmStartInfo&lt;/code&gt; physical address (this is PVH specific)&lt;/li&gt;
&lt;li&gt;set up segment registers to valid Protected Mode state&lt;/li&gt;
&lt;li&gt;enable PE bit in CR0 (as in ordinary boot)&lt;/li&gt;
&lt;li&gt;initialize CR4 (Control Register 4) to zero&lt;/li&gt;
&lt;li&gt;clear VM (bit 17), IF (bit 9) and TF (bit 8) in RFLAGS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Linux kernel-side PVH register setup is described here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/linux/v7.1.3/source/arch/x86/platform/pvh/head.S#L30&quot;&gt;https://elixir.bootlin.com/linux/v7.1.3/source/arch/x86/platform/pvh/head.S#L30&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;segment-registers&quot;&gt;Segment registers&lt;/h4&gt;
&lt;p&gt;I think the best source for how to set up segment registers is actually my own&lt;br&gt;
code from my Rust KVM project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let code = kvm_segment {
base: 0,
limit: 0xffff_ffff,
selector: 1 &amp;lt;&amp;lt; 3,
type_: 0b1010, // 3rd bit: code/data = 1
   // 2nd bit: conforming = 0
   // 1st bit: readable = 1
   // 0th bit: accessed = 0
present: 1,
dpl: 0, // descriptor privilege level (0: kernel .. 3: userspace)
db: 1,  // default operand / address size (0: 16bit, 1: 32bit)
s: 1,   // system (1: code/data segment, 0: system segment)
l: 0,   // long mode (0: not 64bit segment, 1: 64bit segment)
g: 1,   // granularity (0: limit in bytes, 1: limit in 4Kb blocks)
avl: 0, // available for software (reserved for custom OS purposes)
unusable: 0,
padding: 0,
};

let data = kvm_segment {
selector: 2 &amp;lt;&amp;lt; 3,
type_: 0b0010, // 3rd bit: code/data = 0
   // 2nd bit: conforming = 0
   // 1st bit: writable = 1
   // 0th bit: accessed = 0
..code };

sregs2.tr = kvm_segment {
base: 0,
limit: 0x67, // as per Xen project PVH protocol
g: 0,
..code };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With BIOS boot, these segment descriptors would live in a GDT in guest memory&lt;br&gt;
and the CPU would obtain their hidden descriptor state by loading the&lt;br&gt;
corresponding selectors. With KVM, we can bypass the GDT entirely and directly&lt;br&gt;
initialize the vCPU's segment state through &lt;code&gt;KVM_SET_SREGS&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;PVH turned out to be a convenient way of getting my x86 KVM implementation to&lt;br&gt;
the point where it could boot a real Linux kernel without first implementing&lt;br&gt;
the traditional x86 boot protocol.&lt;/p&gt;
&lt;p&gt;There is still plenty left to implement around the x86 side of the VMM, but&lt;br&gt;
being able to reach the Linux kernel through PVH gives me a much more useful&lt;br&gt;
starting point than reproducing the complete BIOS boot sequence first.&lt;/p&gt;
&lt;p&gt;The implementation is available in the x86 initialization code in my &lt;code&gt;kvm-rust&lt;/code&gt;&lt;br&gt;
repository:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/blob/main/src/arch/x86_64/init.rs&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/blob/main/src/arch/x86_64/init.rs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I plan to continue extending functionality of the x86 side of my VMM.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28620/notes-on-x86-pvh-boot</guid>
<pubDate>Fri, 25 Sep 2026 14:47:35 +0000</pubDate>
</item>
</channel>
</rss>