flat assembler
Message board for the users of flat assembler.

Index > OS Construction > hypervisors - AMD, Intel

Author
Thread Post new topic Reply to topic
Feryno



Joined: 23 Mar 2005
Posts: 525
Location: Czech republic, Slovak republic
Feryno 01 Sep 2026, 18:08
Hello, is anyone here wanting to develop hypervisors for AMD or Intel?
I do not know in which subsection of the forum start this thread. I found the most suitable subsection in OS development. If administrators decide to move it to better subforum please do it.
The development is closest to OS development. If you are able to write you own tiny x64 OS (look for Tomasz example 'Entering long mode' from FASM examples) you are very likely able to do hypervisor development as well.
Hypervisor or your skills could be used for these purposes: secretly watch OS, run few virtual machines on one CPU (only as a part of a team and using C programming language), research vulnerabilities of already used hypervisors.
I was doing hypervisors development for the past 15 years since AMD FX and Intel Core 2 Duo launched (core 2 duo was limited to start hypervisor only from ms win driver due to lack of real mode virtualization, core i had already features for real mode virtualization to launch hypervisor easily from BIOS and UEFI).
Hypervisor could be started from UEFI (UEFI runtime driver / UEFI boot executable / UEFI application from UEFI shell), BIOS (similarly as bootkit) or from running OS (e.g. ms win x64 driver) - I implemented all of these 3 ways.
If anyone interested, I will post at first some easy to understand examples for paging, how to write system structures (VMCB for AMD, VMCS for Intel), how a VM exit handler could look like, some examples to debug/trace in emulators (SimNow 4.6.2, BOCHS). You will need to study CPU manuals, expect something like 1000 pages for Intel and 500 pages for AMD, AMD manuals are more compact than Intel, but the amount of information is comparable).
Before even starting I would like to warn you that debugging could be a nightmare especially on hardware and best if you have a machines with serial ports connected with null modem serial cable. But the development and tests are usually done in emulators and only then you finally retest on real hardware (baremetal). If you are not very very very patient or you are unable to withstand weeks of debugging without seeing progress (trying to find a bug in your work) you should rather do something else to prevent your hair get gray or even losing your hair from head entirely.
There are some published C source code for hypervisors but most of them just copy/paste someone else' work without deep understanding. We will of course write hypervisors in FASM. To have some idea and estimation about its size, the simplest hypervisor written in FASM could be around 8 kB binary, maybe it could be pushed into 5 KB, but rather expect 8 kB.
If you would like to secretly watch/monitor ms windows OS, they added detections for hypervisors (KiErrata704Present, KiErrataSkx55Present, KiErrata671Present, KiErrata361Present) but the detections are harmless for properly written hypervisor. Also today ms windows OS enables hyper-v by default, then your hypervisor may pretend virtualization not present or disabled so hyper-v does not start or it is possible to add nesting (here you can expect your hypervisor size to grow to around 32 kB binary).
To start a hypervisor at all CPUs/cores/threads, I can explain APIC INIT-SIPI interprocessor interrupts (when started from BIOS) or multiprocessor UEFI calls (when started from UEFI), or ms win x64 interprocessor kernel calls (when started from ms win driver).
Suspend and resume from S3 also disables virtualization, but it can be hooked so hypervisor survives.
Simple hypervisor with static paging tables covering terra/petabytes of RAM running on 64 CPUs/cores/threads can consume upto 16 MB of RAM even its binary only 8 kB, every CPU core may consume upto 16-32 kB of RAM (stack + system structures), paging tables may consume slightly more than 4 MB for physical memory virtualization and slightly more than 4 MB for hypervisor virtual memory with identity map of the whole physical memory.
I won't help any newly registered member unless proven not to be malware creator.
I will help and cooperate with active forum members with long enough history of posts where it is clear that they are peaceful, helpful to other FASM forum members.
Post 01 Sep 2026, 18:08
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
Hrstka



Joined: 05 May 2008
Posts: 66
Location: Czech republic
Hrstka 02 Sep 2026, 13:54
Unfortunately I don't have time for that. But I appreciate that you are willing to share your knowledge on this advanced topic.
Post 02 Sep 2026, 13:54
View user's profile Send private message Reply with quote
six_L



Joined: 03 Jan 2005
Posts: 20
six_L 04 Sep 2026, 02:02
Hello,Feryno
I appreciate your ideal. but is this possible? This should be an act of the state, not the act of certain individuals.

1, You should maintain a development team of at least 5000 people per year, including an office and salary. this will run for 20 years.
2, M$ is there, preemptively occupied this track. Many CPU manufacturers have always supported him. could you done better than him?
Post 04 Sep 2026, 02:02
View user's profile Send private message Reply with quote
Feryno



Joined: 23 Mar 2005
Posts: 525
Location: Czech republic, Slovak republic
Feryno 05 Sep 2026, 13:15
I would just like to share knowledge so more people will be educated and skilled.
Most of hypervisors are developed by a big team (hyper-v, KVM, VMware, virtualbox, xen, ...).
I'm doing hypervisor development completely alone in the past 15 years although there are more people in the team, they are developing other parts (GUI, AI, ...). My part is single-person task since beginning.
There are few guys on internet who share code written in C on github and similar platforms and these projects also seem to be one-man work, usually copy/paste an older work of someone else and then further improving (they always mention who was their ancestor).
I do not expect to create a team or to develop here some commercial tool based on hypervisor.
If few people start to develop their own hypervisor it would be great.
It's hard to start by only reading CPU manuals or by analyzing something huge like hyper-v (closed source, only binary available) or KVM (open source). Better if someone become skilled by debugging/tracing something tiny to fully understand it so later can develop something advanced or be able to analyze something bigger and eventually find there mistakes/bugs.
I do not expect this to become any commercial project. Better if everyone involved become skilled to play with hypervisors and be able to develop their own without necessity of copy someone' source code.
I also do not like to make new posts if there will be no one interested.
If there will be interest I will post some theory to understand paging, how to setup hypervisor structures, some small binaries to debug in emulators to understand what vm entry / vm exit does, how to start at every CPU/core/thread etc.
Only x64 platform (AMD and Intel), not ARM. Only FASM, not C.
Post 05 Sep 2026, 13:15
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 4618
Location: vpcmpistri
bitRAKE 12 Sep 2026, 01:26
The first question that comes to my mind: What does the hypervisor need? Memory manager, I/O support, etc ... We can't leave UEFI and create a hypervisor directly, or can we? Or you're suggesting starting a hypervisor in UEFI? What is the expected support, if any?

The other question would be simplification on modern hardware. We have UEFI, ECAM, etc. ... creating a modern OS is very simple code if we abandon all the legacy cruft. Is it the same with hypervisors?

Traffic is low and specialized here. Gauging interest is difficult.

(Most my hypervisor experience is through WHP in Windows - no concern for the low-level details is required, but it's really just a different abstraction. My current understanding is that a hypervisor cannot be created in user mode on Windows 11, but there are a number of Windows features that rely on hypervisors. This is why all the emulators in Windows are built on WHP.)

SVM support for fasmg/2:
Code:
; svm.inc -- AMD-V instruction encodings (APM vol.3)
; Requires CPUID Fn8000_0001_ECX[SVM] = 1.

; Add to start of `include\x86-2.inc`:
;       ; CPUID.(EAX=80000001H):ECX
;       SVM:                    ext | 224 + 2

calminstruction x86.store_address_prefix size*
        check   (size = 2 & x86.mode = 32) | (size = 4 & x86.mode <> 32)
        jyes    fix
        check   size shl 3 = x86.mode
        jyes    ok
        err     'invalid operand size'
ok:     exit
fix:    emit    1, 67h
end calminstruction

iterate <instr,opcode>, vmrun,0xD8, vmload,0xDA, vmsave,0xDB

        calminstruction instr? dest*
                call    x86.require.SVM
                call    x86.parse_operand@dest, dest
                check   @dest.type = 'reg' & @dest.rm = 0
                jno     invalid
                call    x86.store_address_prefix, @dest.size
                emit    3, 0x010F or (opcode shl 16)
                exit
        invalid:
                err     'only rAX register supported'
        end calminstruction

end iterate

iterate <instr,opcode>,\
        vmmcall,        0xD9010F,\
        stgi,           0xDC010F,\
        clgi,           0xDD010F

        calminstruction instr?
                call    x86.require.SVM
                emit    3, opcode
        end calminstruction

end iterate

calminstruction skinit? base:eax ; EAX = SLB base
        call    x86.require.SVM
        call    x86.parse_operand@dest, base
        check   @dest.type = 'reg' & @dest.rm = 0 & @dest.size = 4
        jno     invalid
        emit 3, 0xDE010F
        exit
invalid:
        err 'register EAX expected'
end calminstruction

calminstruction invlpga? addr:rax,asid:ecx
        call    x86.require.SVM
        call    x86.parse_operand@dest, addr
        call    x86.parse_operand@src, asid
        check   @dest.type = 'reg' & @dest.rm = 0
        jno     invalid
        check   @src.type = 'reg' & @src.rm = 1 & @src.size = 4
        jno     invalid
        call    x86.store_address_prefix, @dest.size
        emit    3, 0xDF010F
        exit
invalid:
        err 'invlpga operands must be rAX and ECX'
end calminstruction    
Edit: corrected. Still under testing.


Last edited by bitRAKE on 12 Sep 2026, 23:42; edited 2 times in total
Post 12 Sep 2026, 01:26
View user's profile Send private message Visit poster's website Reply with quote
Feryno



Joined: 23 Mar 2005
Posts: 525
Location: Czech republic, Slovak republic
Feryno 12 Sep 2026, 12:09
Hi bitRAKE.
Mem manager - for simplest hypervisor it is unnecessary. During startup it is enough to create static paging tables and do not change them anymore. The pages could be constructed for identity mapping so hypervisor can access physical memory at the same linear/virtual memory address. For more advanced hypervisor (e.g. running more operating systems on the CPU) the hypervisor will require memory manager and adjusting paging tables on demand.
IO support is not essential or only minimal, hypervisor can let it be almost fully handled by operating system. Hypervisor may reserve e.g. serial port IO to send diagnostic data and then prevent OS to access these io ports.
I can post methods how to start hypervisor from UEFI, from BIOS, from running ms win x64 (as a driver). After the hypervisor starts the code execution returns back to UEFI/BIOS/running OS so the execution continues further (UEFI loads OS, BIOS loads MBR, running OS continues to run). From running OS the hypervisor can be started by a ring0 driver, never from only usermode.
We can discuss here how to create and start hypervisor for AMD and also for Intel. I can create some small hypervisor samples to debug in emulators (Bochs, SimNow 4.6.2) to better understand how they operate. I can fix your mistakes / errors if someone will develop their own hypervisor.
We can create here something like teaching course with lessons, where some basics will be explained with some trivial homework at the end to check whether you understood the topic and correct answers will be in the next post, you may ask if something not clear.
The AMD support you posted is correct, the simplest AMD hypervisor may use 4 instructions clgi, vmsave, vmload, vmrun during startup and then instruction vmmcall to trigger vm exit to check whether everything is working well (the vm exit handler will execute vmsave, vmload, vmrun).
On Intel the minimal instructions required are vmxon, vmclear, vmptrld, vmwrite, vmlaunch, then vmread, vmwrite, vmresume in exit handler and vmcall to trigger vm exit (but also cpuid is capable to cause vm exit).
Post 12 Sep 2026, 12:09
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
Feryno



Joined: 23 Mar 2005
Posts: 525
Location: Czech republic, Slovak republic
Feryno 12 Sep 2026, 12:32
First step to start - you need CPU manuals.

AMD64 Architecture
Programmer’s Manual
Volume 2:
System Programming
it is here:
https://docs.amd.com/v/u/en-US/24593_3.45_APM_Vol2_PUB

Intel® 64 and IA-32 Architectures
Software Developer’s Manual
Volume 3 (3A, 3B, 3C, & 3D):
System Programming Guide
it is here:
https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
you can download the complete manual (Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4) or only volume 3 (Combined Volumes 3A, 3B, 3C, and 3D)
Post 12 Sep 2026, 12:32
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
Core i7



Joined: 14 Nov 2024
Posts: 175
Location: Socket on motherboard
Core i7 12 Sep 2026, 13:26
I think the main problem here isn't how to launch the hypervisor (the structures are well known), but rather how to implement its core functionality. Let's say the hypervisor code has gained control—what should it do next? For instance, consider the virtualization of a network adapter or some other device.
Post 12 Sep 2026, 13:26
View user's profile Send private message Reply with quote
Feryno



Joined: 23 Mar 2005
Posts: 525
Location: Czech republic, Slovak republic
Feryno 12 Sep 2026, 20:33
Core i7 - virtualization of a network adapter or some other device is possible. Hypervisor can catch OS reads/writes of certain IO ports, intercept and create an IRQ assigned to the virtualized device.
Around 10 years ago I tried to emulate SMC device of Apple machine (IO ports 300h-31Fh) on Intel non-apple machines (hackintosh), not fully functional, just to pretend to be present to cheat installer to install Apple OS to a non-apple machine without SMC.
MS added support for last branch record and single step on branching instructions into kernel for older CPUs and later did not update the kernel for recent CPUs, I used hypervisor to pretend a recent CPU was an older model (suppored by ms kernel) so ms kernel enabled these debugging features for the new unsupported CPU (this was done be cheating some CPUID instructions by modifying their return values).
You can do various cool things to the OS.
We will start lessons from basics so even beginners understand.
First lessons will be about paging, how to create paging structures. Then lessons how to setup hypervisor structures (which are well known but beginners do not know). Then lessons how to construct vm exit handler. Then few lessons with some binaries available which you can single step in emulators to understand how everything works together, what causes vm exit and how does it behave, how to do vm entry (intel) / vmrun (amd). Then some lessons how to do something advances which you asked in discussion.
Post 12 Sep 2026, 20:33
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
Feryno



Joined: 23 Mar 2005
Posts: 525
Location: Czech republic, Slovak republic
Feryno 12 Sep 2026, 21:42
Paging.
This topic is split into 2 lessons.
In the first lesson only pointers of paging tables will be explained and in the second lesson the remaining bits of paging structures.

Abbreviation PA is used for Physical Address which is address somewhere in RAM. MBR is loaded by BIOS to PA 7C00h. Reset vector of BIOS is at PA FFFFFFF0h which is truncated in real mode to FFFF:0000 so the instruction 'jmp FFFF:0000' resets computer running in real mode. BIOS creates real mode IDT at PA in range 0000-03FFh.
When paging is enabled CR0.PG=1 (bit 31.) which also requires CR0.PE=1 (bit 0.) the memory reference instructions, e.g. 'cmp gs:[rcx],esi' , 'mov [rdx],al' do not access directly physical memory PA (RAM) but the memory is translated through paging tables. These paging tables translate linear / virtual memory address (gs_base + rcx in the first previous sample, rdx in the second previous sample) into physical memory address.
Abbreviation VA is used for Virtual Address which is equal to linear memory address in x64 as this architecture uses flat memory model. Correctly Linear Address should be used but everyone uses VA as they are equal.

The x64 paging uses pages of size 4096 bytes (4 KiB) aligned at 4 KiB boundary. Every page contains 512 entries of qwords size (8 bytes = 64 bits).
512 * 8 = 4096 (decimal) = 1000h (hexadecimal). Not all 512 entries have to be set, but at least one entry properly set in every level of paging tables is necessary. If only one entry in every level is properly set then the memory translation is capable to translate only 1 page, the more entries are set the more virtual memory VA could be translated into physical memory PA.
Default x64 paging uses 4 levels of paging tables.
All pointers in paging structures contain physical memory addresses PA (RAM addresses), these pointer are in bits 12 and higher bits of each qword. Bits 11-0 are used as properties of pages translation (e.g. readonly, writeable, uncacheable), sometimes highest bits are used as attributes too (e.g. bit 63. to mark page as not executable). To make things simpler and easier to understand (for beginners) we will ignore bits 11-0 in the first lesson and here we focus only on pointer part (bits 63-12). The low bits 11-0 will be explained in the next lesson.

CR3 register contains a pointer to first level of paging tables and the table has a name PML4. It is 4096 bytes table containing 512 entries, each entry of qword size = 8 bytes = 64 bits. The entries of PML4 are indexed from 0 to 511 so the first entry is PML4E[0], the second PML4E[1], the third PML4E[2] and the last PML4E[511].
Let at least one entry of PML4 is properly set so the PML4 entry with index 'a' PML4E[a] contains a pointer to the next level of paging tables. The next level of paging tables has a name PDP.

The PDP has size 4096 bytes, is aligned at 4096 bytes boundary and contains 512 entries, each entry of qword size = 8 bytes = 64 bits. The entries are indexed from 0 to 511, so the first entry is PDPE[0], the second PDPE[1]... the last PDPE[511].
Let at least one entry of PDP is properly set, PDP entry with index 'b' PDPE[ b] contains a pointer to the next level of paging tables. The next level of paging tables has a name PD.

The PD has size 4096 bytes, is aligned at 4096 bytes boundary, contains 512 entries, each entry of qword size = 8 bytes = 64 bits. Entries are indexed from 0 to 511, first entry PDE[0], the last entry PDE[511].
Let at least one entry of PD is properly set, PD entry with index 'c' PDE[c] contains a pointer to the next level of paging tables. The next level of paging tables has a name PT.

The PT has size 4096 bytes, is aligned at 4096 bytes boundary, contains 512 entries, each entry of qword size = 8 bytes = 64 bits. Entries are indexed from 0 to 511, first entry PTE[0], the last entry PTE[511].
Let at least one entry of PT is properly set, PT entry with index 'd' PTE[d] contains the final physical memory address PA (RAM address) where the virtual memory is translated so the PT is the last level of paging structures.

The indices a, b, c, d originate from VA and the formula is:

'shr x' means the same as FASM instruction shift to right by x bits = divide by 2^x
511 (decimal) = 111111111b (binary)
a = (VA shr 39) and 511
b = (VA shr 30) and 511
c = (VA shr 21) and 511
d = (VA shr 12) and 511

bits 47-39 of VA are index 'a'
bits 38-30 of VA are index 'b'
bits 29-21 of VA are index 'c'
bits 20-12 of VA are index 'd'
bits 63-48 of VA are sign extension of bit 47, so if bit 47 is zero then also bits 63-48 of VA are all zeros, if bit 47 is 1 then bits 63-48 of VA are all 1 (canonical address form)
bits 11-0 of VA are offset into PA (e.g. access of VA xyz62Ch is translated into accessing PA klm62Ch)

now some examples

you want to construct paging tables such that VA 0000h - 0FFFh (1 page of 4 KiB) is translated into physical memory address 0000h-0FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access a physical memory at the same virtual memory address
then:
a = (0 shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (0 shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the first qword of the PDP
c = (0 shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the first qword of the PD
d = (0 shr 12) and 511 = 0 and you need to properly set first entry of PT with index 0 which is PTE[0] which is the first qword of the PT and the entry will reference the physical memory (RAM) where is the memory access translated so the entry in bits 63-12 will contain the PA which is 0

you want to construct paging tables such that VA 1000h - 1FFFh (1 page of 4 KiB) is translated into physical memory address 1000h-1FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (1000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (1000h shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the first qword of the PDP
c = (1000h shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the first qword of the PD
d = (1000h shr 12) and 511 = 1 and you need to properly set second entry of PT with index 1 which is PTE[1] which is the second qword of the PT and the entry will reference the physical memory (RAM) = 1000h where is the memory access translated

you want to construct paging tables such that VA 200000h - 200FFFh (1 page of 4 KiB) is translated into physical memory address 200000h-200FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (200000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (200000h shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the first qword of the PDP
c = (200000h shr 21) and 511 = 1 and you need to properly set second entry of PD with index 1 which is PDE[1] which is the second qword of the PD
d = (200000h shr 12) and 511 = 0 and you need to properly set first entry of PT with index 0 which is PTE[0] which is the first qword of the PT and the entry will reference the physical memory (RAM) = 200000h where is the memory access translated

you want to construct paging tables such that VA 201000h - 201FFFh (1 page of 4 KiB) is translated into physical memory address 201000h-201FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (201000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (201000h shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the first qword of the PDP
c = (201000h shr 21) and 511 = 1 and you need to properly set second entry of PD with index 1 which is PDE[1] which is the second qword of the PD
d = (201000h shr 12) and 511 = 1 and you need to properly set second entry of PT with index 1 which is PTE[1] which is the second qword of the PT and the entry will reference the physical memory (RAM) = 201000h where is the memory access translated

you want to construct paging tables such that VA 40000000h - 40000FFFh (1 page of 4 KiB) is translated into physical memory address 40000000h-40000FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (40000000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (40000000h shr 30) and 511 = 1 and you need to properly set second entry of PDP with index 1 which is PDPE[1] which is the second qword of the PDP
c = (40000000h shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the first qword of the PD
d = (40000000h shr 12) and 511 = 0 and you need to properly set first entry of PT with index 0 which is PTE[0] which is the first qword of the PT and the entry will reference the physical memory (RAM) = 40000000h where is the memory access translated

you want to construct paging tables such that VA 40001000h - 40001FFFh (1 page of 4 KiB) is translated into physical memory address 40001000h-40001FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (40001000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (40001000h shr 30) and 511 = 1 and you need to properly set second entry of PDP with index 1 which is PDPE[1] which is the second qword of the PDP
c = (40001000h shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the first qword of the PD
d = (40001000h shr 12) and 511 = 1 and you need to properly set second entry of PT with index 1 which is PTE[1] which is the second qword of the PT and the entry will reference the physical memory (RAM) = 40001000h where is the memory access translated

you want to construct paging tables such that VA 40200000h - 40200FFFh (1 page of 4 KiB) is translated into physical memory address 40200000h-40200FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (40200000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (40200000h shr 30) and 511 = 1 and you need to properly set second entry of PDP with index 0 which is PDPE[1] which is the second qword of the PDP
c = (40200000h shr 21) and 511 = 1 and you need to properly set second entry of PD with index 1 which is PDE[1] which is the second qword of the PD
d = (40200000h shr 12) and 511 = 0 and you need to properly set first entry of PT with index 0 which is PTE[0] which is the first qword of the PT and the entry will reference the physical memory (RAM) = 40200000h where is the memory access translated

you want to construct paging tables such that VA 40201000h - 40201FFFh (1 page of 4 KiB) is translated into physical memory address 40201000h-40201FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (40201000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (40201000h shr 30) and 511 = 1 and you need to properly set second entry of PDP with index 0 which is PDPE[1] which is the second qword of the PDP
c = (40201000h shr 21) and 511 = 1 and you need to properly set second entry of PD with index 1 which is PDE[1] which is the second qword of the PD
d = (40201000h shr 12) and 511 = 1 and you need to properly set second of PT with index 1 which is PTE[1] which is the second qword of the PT and the entry will reference the physical memory (RAM) = 40201000h where is the memory access translated

you want to construct paging tables such that VA 40202000h - 40202FFFh (1 page of 4 KiB) is translated into physical memory address 40202000h-40202FFFh, this is an example of identity mapping where you constructed such paging tables where VA = PA so you can access physical memory at the same virtual memory address
then:
a = (40200000h shr 39) and 511 = 0 and you need to properly set first entry of PML4 with index 0 which is PML4E[0] which is the first qword of the PML4
b = (40200000h shr 30) and 511 = 1 and you need to properly set second entry of PDP with index 0 which is PDPE[1] which is the second qword of the PDP
c = (40200000h shr 21) and 511 = 1 and you need to properly set second entry of PD with index 1 which is PDE[1] which is the second qword of the PD
d = (40200000h shr 12) and 511 = 2 and you need to properly set third entry of PT with index 2 which is PTE[2] which is the third qword of the PT and the entry will reference the physical memory (RAM) = 40202000h where is the memory access translated

your tiny kernel written in FASM has size not more than 4 KiB (so it fits into 1 memory page), it is loaded in physical memory (RAM) address 9000h-9FFFh and you want to construct paging tables such that it is accessed from VA FFFF800000000000h - FFFF800000000FFFh (1 page of 4 KiB)
then:
a = (FFFF800000000000h shr 39) and 511 = 256 (decimal) = 100h (hexadecimal) and you need to properly set 257th entry of PML4 with index 256 which is PML4E[256] which is the 257th qword of the PML4
b = (FFFF800000000000h shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the first qword of the PDP
c = (FFFF800000000000h shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the first qword of the PD
d = (FFFF800000000000h shr 12) and 511 = 0 and you need to properly set first entry of PT with index 0 which is PTE[0] which is the first qword of the PT and the entry will reference the physical memory (RAM) = 9000h where is the memory access translated
for example CR3 contains value 2000h which is PA of PML4
the PML4 is thus located at PA=2000h and it looks like this:
PML4E[0] qword at PA 2000h = 0 (not used)
PML4E[1] qword at PA 2008h = 0 (not used)
...
PML4E[255] qword at PA 27F8h = 0 (not used)
PML4E[256] qword at PA 2800h = 3000h which is PA of next level of paging tables = address of PDP
PDPE[0] qword at PA 3000h = 4000h which is PA of next level of paging tables = address of PD
PDE[0] qword at PA 4000h = 5000h which is PA of next level of paging tables = address of PT
PTE[0] qword at PA = 9000h which is PA of the kernel loaded in physical memory (RAM) at 9000h

your kernel written in FASM has size not more than 8 KiB (so it fits into 2 memory pages), it is already loaded in physical memory (RAM) address 5000h-6FFFh, you want your kernel be located at VA FFFF800000000000h - FFFF800000001FFFh (2 pages of 4 KiB), you want to construct paging tables such that CR3 contains value 1000h so PML4E is located at PA=1000h-1FFFh, you want to create PDP at PA=2000h-2FFFh, PD at 3000-3FFFh, PT at 4000h-4FFFh
then:
a = (FFFF800000000000h shr 39) and 511 = 256 (decimal) = 100h (hexadecimal) and you need to properly set 257th entry of PML4 with index 256 which is PML4E[256] which is the 257th qword of the PML4
b = (FFFF800000000000h shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the first qword of the PDP
c = (FFFF800000000000h shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the first qword of the PD
d = (FFFF800000000000h shr 12) and 511 = 0 and you need to properly set first and second entry of PT with indices 0, 1 which are PTE[0], PTE[1] which are the first and second qwords of the PT and the entries will reference the physical memory (RAM) = 5000h (PTE[0]) and 6000h (PTE[1]) into which is the memory access translated
CR3 = 1000h which is PA of PML4
the PML4 is located at PA=1000h and it looks like this:
PML4E[0] qword at PA 1000h = 0
PML4E[1] qword at PA 1008h = 0
...
PML4E[255] qword at PA 17F8h = 0
PML4E[256] qword at PA 1800h = 2000h which is PA of next level of paging tables = address of PDP
PDPE[0] qword at PA 2000h = 3000h which is PA of next level of paging tables = address of PD
PDE[0] qword at PA 3000h = 4000h which is PA of next level of paging tables = address of PT
PTE[0] qword at PA 4000h = 5000h which is PA of the first page of the kernel loaded in physical memory (RAM) at 5000h
PTE[1] qword at PA 4008h = 6000h which is PA of the second page of the kernel loaded in physical memory (RAM) 2 pages of kernel are loaded in PA range 5000h-6FFFh so the second page starts from PA 6000h

your kernel fits into 12 KiB (3 memory pages), it is already loaded in physical memory (RAM) address 5000h-7FFFh, you want your kernel be located at VA FFFFFFFFFFFFD000h - FFFFFFFFFFFFFFFFh (3 pages of 4 KiB), you want to construct paging tables such that CR3 contains value 1000h so PML4E is located at PA=1000h-1FFFh, you want to create PDP at PA=2000h-2FFFh, PD at 3000-3FFFh, PT at 4000h-4FFFh
then:
a = (FFFFFFFFFFFFD000h shr 39) and 511 = 511 (decimal) = 1FFh (hexadecimal) and you need to properly set 512th entry of PML4 with index 511 which is PML4E[511] which is the 512th qword of the PML4E
b = (FFFFFFFFFFFFD000h shr 30) and 511 = 0 and you need to properly set first entry of PDP with index 0 which is PDPE[0] which is the second qword of the PDP
c = (FFFFFFFFFFFFD000h shr 21) and 511 = 0 and you need to properly set first entry of PD with index 0 which is PDE[0] which is the second qword of the PD
d = (FFFFFFFFFFFFD000h shr 12) and 511 = 509 and you need to properly set entries of PT with indices 509, 510, 511 which are PTE[509], PTE[510], PTE[511] which are the last 3 qwords of the PT and the entries will reference the physical memory (RAM) 5000h (PTE[509]), 6000h (PTE[510]), 7000h (PTE[511]) into which is the memory access translated
CR3 = 1000h which is PA of PML4
the PML4 is located at PA=1000h and it looks like this:
PML4E[0] qword at PA 1000h = 0 (not used)
PML4E[1] qword at PA 1008h = 0 (not used)
...
PML4E[510] qword at PA 1FF0h = 0 (not used)
PML4E[511] qword at PA 1FF8h = 2000h which is PA of next level of paging tables = address of PDP
PDPE[0] qword at PA 2000h = 0 (not used)
...
PDPE[510] qword at PA 2FF0h = 0 (not used)
PDPE[511] qword at PA 2FF8h = 3000h which is PA of next level of paging tables = address of PD
PDE[0] qword at PA 3000h = 0 (not used)
...
PDE[510] qword at 3FF0h = 0 (not used)
PDE[511] qword at 3FF8h = 4000h which is PA of next level of paging tables = address of PT
PTE[0] qword at 4000h = 0 (not used)
...
PTE[508] qword at 4FE0h = 0 (not used)
PTE[509] qword at PA 4FE8h = 5000h which is PA of the first page of the kernel loaded in physical memory (RAM) range 5000h-7FFFh
PTE[510] qword at PA 4FF0h = 6000h which is PA of the second page of the kernel loaded in physical memory (RAM) range 5000h-7FFFh
PTE[511] qword at PA 4FF8h = 7000h which is PA of the third page of the kernel loaded in physical memory (RAM) range 5000h-7FFFh


now we will play with live OS and explore its paging tables using different sight, we will perform paging tables walk from CR3 to the last level and find at which VA is APIC mapped
Code:
Microsoft (R) Windows Debugger Version 10.0.22621.1778 AMD64
Connected to Windows Server 2003 3790 x64 target at (Sat Sep 12 17:14:16.963 2026 (UTC + 2:00)), ptr64 TRUE
0: kd> r cr3
cr3=0000000000146000
0: kd> dq /p 0146000+FF8 L1
00000000`00146ff8  00000000`00124063
0: kd> dq /p 124000+FF8 L1
00000000`00124ff8  00000000`00086063
0: kd> dq /p 86000+FF8 L1
00000000`00086ff8  00000000`0001e163
0: kd> dq /p 1e000+f00 L1
00000000`0001ef00  00000000`fee0017b
0: kd> dq /p 00000000fee00000 L40
00000000`fee00000  00000000`00000000 00000000`00000000
00000000`fee00010  00000000`00000000 00000000`00000000
00000000`fee00020  00000000`00000000 00000000`00000000
00000000`fee00030  00000000`00050015 00000000`00050015
00000000`fee00040  00000000`00000000 00000000`00000000
00000000`fee00050  00000000`00000000 00000000`00000000
00000000`fee00060  00000000`00000000 00000000`00000000
00000000`fee00070  00000000`00000000 00000000`00000000
00000000`fee00080  00000000`000000f0 00000000`000000f0
00000000`fee00090  00000000`00000000 00000000`00000000
00000000`fee000a0  00000000`000000f0 00000000`000000f0
00000000`fee000b0  00000000`00000000 00000000`00000000
00000000`fee000c0  00000000`00000000 00000000`00000000
00000000`fee000d0  00000000`01000000 00000000`01000000
00000000`fee000e0  00000000`ffffffff 00000000`ffffffff
00000000`fee000f0  00000000`0000013f 00000000`0000013f
00000000`fee00100  00000000`00000000 00000000`00000000
00000000`fee00110  00000000`00000000 00000000`00000000
00000000`fee00120  00000000`00000000 00000000`00000000
00000000`fee00130  00000000`00000000 00000000`00000000
00000000`fee00140  00000000`00000000 00000000`00000000
00000000`fee00150  00000000`00000000 00000000`00000000
00000000`fee00160  00000000`00020000 00000000`00020000
00000000`fee00170  00000000`00000000 00000000`00000000
00000000`fee00180  00000000`00000000 00000000`00000000
00000000`fee00190  00000000`00000000 00000000`00000000
00000000`fee001a0  00000000`00000000 00000000`00000000
00000000`fee001b0  00000000`00000002 00000000`00000002
00000000`fee001c0  00000000`00040000 00000000`00040000
00000000`fee001d0  00000000`00020004 00000000`00020004
00000000`fee001e0  00000000`00000000 00000000`00000000
00000000`fee001f0  00000000`00000000 00000000`00000000
0: kd> dq FFFFFFFFFFFE0000 L40
ffffffff`fffe0000  00000000`00000000 00000000`00000000
ffffffff`fffe0010  00000000`00000000 00000000`00000000
ffffffff`fffe0020  00000000`00000000 00000000`00000000
ffffffff`fffe0030  00000000`00050015 00000000`00050015
ffffffff`fffe0040  00000000`00000000 00000000`00000000
ffffffff`fffe0050  00000000`00000000 00000000`00000000
ffffffff`fffe0060  00000000`00000000 00000000`00000000
ffffffff`fffe0070  00000000`00000000 00000000`00000000
ffffffff`fffe0080  00000000`000000f0 00000000`000000f0
ffffffff`fffe0090  00000000`00000000 00000000`00000000
ffffffff`fffe00a0  00000000`000000f0 00000000`000000f0
ffffffff`fffe00b0  00000000`00000000 00000000`00000000
ffffffff`fffe00c0  00000000`00000000 00000000`00000000
ffffffff`fffe00d0  00000000`01000000 00000000`01000000
ffffffff`fffe00e0  00000000`ffffffff 00000000`ffffffff
ffffffff`fffe00f0  00000000`0000013f 00000000`0000013f
ffffffff`fffe0100  00000000`00000000 00000000`00000000
ffffffff`fffe0110  00000000`00000000 00000000`00000000
ffffffff`fffe0120  00000000`00000000 00000000`00000000
ffffffff`fffe0130  00000000`00000000 00000000`00000000
ffffffff`fffe0140  00000000`00000000 00000000`00000000
ffffffff`fffe0150  00000000`00000000 00000000`00000000
ffffffff`fffe0160  00000000`00020000 00000000`00020000
ffffffff`fffe0170  00000000`00000000 00000000`00000000
ffffffff`fffe0180  00000000`00000000 00000000`00000000
ffffffff`fffe0190  00000000`00000000 00000000`00000000
ffffffff`fffe01a0  00000000`00000000 00000000`00000000
ffffffff`fffe01b0  00000000`00000002 00000000`00000002
ffffffff`fffe01c0  00000000`00040000 00000000`00040000
ffffffff`fffe01d0  00000000`00020004 00000000`00020004
ffffffff`fffe01e0  00000000`00000000 00000000`00000000
ffffffff`fffe01f0  00000000`00000000 00000000`00000000    


command 'r cr3' prints the value of CR3 register which contains pointer to PML4 which is located at PA 146000h
command 'dq /p' prints the value of qword located at some PA, 'L1' tells to print only 1 qword while 'L40' tells to print 40h = 64d qwords
command 'dq /p 0146000+FF8 L1' prints the last qword of PML4E located at PA=146000 + offset FF8 (FF8 hexadecimal / 8 = 511 decimal so it is PML4E[511]), it contains value 124063h, we will neglect for now lowest bits containing value 63h (they will be explained in the next lesson) and are insterested only in bits 63-12 which contain physical address of next level of paging tables, the value is 124000h, so the PDP is located at PA=124000h
command 'dq /p 124000+FF8 L1' prints the last qword of PDP located at PA=124000 + offset FF8 (FF8 hexadecimal / 8 = 511 decimal so it is PDPE[511]), it contains value 86063h, we will neglect for now lowest bits containing value 63h (they will be explained in the next lesson) and are insterested only in bits 63-12 which contain physical address of next level of paging tables, the value is 86000h, so the PD is located at PA=86000h
command 'dq /p 86000+FF8 L1' prints the last qword of PD located at PA=86000 + offset FF8 (FF8 hexadecimal / 8 = 511 decimal so it is PDE[511]), it contains value 1e163h, we will neglect for now lowest bits containing value 163h (they will be explained in the next lesson) and are insterested only in bits 63-12 which contain physical address of next level of paging tables, the value is 1e000h, so the PT is located at PA=1e000h
command 'dq /p 1e000+f00 L1' prints the 481th qword of PT located at PA=1e000 + offset F00 (F00 hexadecimal / 8 = 480 decimal so it is PTE[480]), it contains value 00000000`fee0017b, we will neglect lowest bits 11-0 containing value 17Bh (they will be explained in the next lesson) and are interested only in bits 63-12 which contain physical address with value 00000000fee00000 which is typical PA of APIC
command 'dq /p 00000000fee00000 L40' prints first 40h qwords of the APIC page
from the above it is possible to reconstruct VA where the APIC page is mapped:
a = 0FF8h / 8 = 1FFh (bits 47-39 of VA) = 111111111b (binary)
b = 0FF8h / 8 = 1FFh (bits 38-30 of VA) = 111111111b (binary)
c = 0FF8h / 8 = 1FFh (bits 29-21 of VA) = 111111111b (binary)
d = 0F00h / 8 = 1E0h (bits 20-12 of VA) = 111100000b (binary)
VA = (a shl 39) + (b shl 30) + (c shl 21) + (d shl 12)
VA = (111111111b shl 39) + (111111111b shl 30) + (111111111b shl 21) + (111100000b shl 12)
VA = 111111111111111111111111111111100000000000000000b
VA = FFFFFFFE0000h
but because highest bit 47. is set to 1, we must sign extend the bit 47. into bits 63-48 so all these bits are also 1 (canonical address form)
so the cannonical address of VA is:
VA = 1111111111111111111111111111111111111111111111100000000000000000b
VA = FFFFFFFFFFFE0000h
we decoded the VA where is APIC mapped so we can access it by the VA, the command is also 'dq' just without the '/p' switch, the '/p' switch means physical address and without the switch the dump is by virtual address
command 'dq FFFFFFFFFFFE0000 L40' prints first 40h qwords of the APIC page using its VA
as you can see, the output of dq commands using PA and VA is the same (well some of the values may meanwhile change as OS writes values into the APIC)

homework:
you already obtained physical memory address of APIC by this code:

Code:
MSR_AMD64_APIC_BASE             =       0000001Bh

; Support for an enabled APIC feature is indicated by EDX bit 9 as returned by CPUID function 0000_0001h.
        xor     eax,eax
        cpuid
        cmp     eax,1
        jc      APIC_missing            ; we can't execute CPUID with input eax=1
        mov     eax,1
        cpuid
        test    dh,1 shl (9-8 )
        jz      APIC_missing

        mov     ecx,MSR_AMD64_APIC_BASE
        rdmsr
        and     eax,0FFFFF000h
        shl     rdx,32                  ; shift edx into upper half of RDX (bits 63-32) and erase low part of RDX (bits 31-0)
        or      rax,rax
; RAX now contains APIC base which is PA of the APIC page    


your paging tables are constructed as such:
CR3 contains value 3000h so PML4 is located at PA 3000h
PDP is located at PA 4000h
PD is located at PA 5000h
PT is located at PA 6000h

you want to access APIC page (1 page of 4 kB which PA is now in RAX) at virtual address FFFF800000000000h - FFFF800000000FFFh

calculate the indices a, b, c, d
what value of pointer will be at qword PML4E[a] which is qword at PA [3000h + a*8]
what value of pointer will be at qword PDPE[ b] which is qword at PA [4000h + b*8]
what value of pointer will be at qword PDE[c] which is qword at PA [5000h + c*8]
what value of pointer will be at qword PTE[d] which is qword at PA [6000h + d*8]

answer the same above questions when you want to create paging tables such that APIC is accessed from VA FFFFFFFFFFE00000h - FFFFFFFFFFE00FFFh

answer the same above questions when you want to create paging tables such that APIC is accessed at VA FFFFFFFFFFFFF000h - FFFFFFFFFFFFFFFFh (highest possible last 4KiB page of virtual address space)
Post 12 Sep 2026, 21:42
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
Ali.Z



Joined: 08 Jan 2018
Posts: 898
Ali.Z 15 Sep 2026, 04:48
why not take existing hypervisor and modify it?
e.g. Cheat Engine hypervisor (windows-only) starts in ring0 and virtualizes the whole OS.

_________________
Asm For Wise Humans
Post 15 Sep 2026, 04:48
View user's profile Send private message Reply with quote
Core i7



Joined: 14 Nov 2024
Posts: 175
Location: Socket on motherboard
Core i7 17 Sep 2026, 19:40
Feryno, allow me to express my opinion on this matter.
Hypervisors are certainly not a topic intended for beginners. Those who don't know what a byte, word, or qword is won't understand a thing you've said here, no matter how much they might want to. Therefore, I think you should address users who already have the necessary background knowledge and avoid getting sidetracked by basics. Otherwise, you end up with a lot of text but very little substance.

Now, regarding the homework assignment...
Code:
format  pe64 console
include 'win64ax.inc'
entry   start
;--------------
section '.text' code readable executable
start:  sub     rsp,8

        mov     rcx,0xFFFF800000000000
        call    GetIndex

        mov     rcx,0xFFFFFFFFFFE00000
        call    GetIndex

        mov     rcx,0xFFFFFFFFFFFFF000
        call    GetIndex

       cinvoke  getch
       cinvoke  exit,0

;--------------
proc GetIndex  uses rax rbx r10 r11, va
        mov    [va],rcx
        xchg   rax,rcx
        push   rax rax rax
        shr    rax,39
        and    rax,511

        pop    rbx
        shr    rbx,30
        and    rbx,511

        pop    r10
        shr    r10,21
        and    r10,511

        pop    r11
        shr    r11,12
        and    r11,511

       cinvoke printf,<10,' %p: a=%03d, b=%03d, c=%03d, d=%03d',0>,\
                      [va],rax,rbx,r10,r11
        ret
endp
;--------------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll'
import   msvcrt,printf,'printf',scanf,'scanf',\
                getch,'_getch',exit,'exit'
    


Description:
Filesize: 912 Bytes
Viewed: 146 Time(s)

idx.png


Post 17 Sep 2026, 19:40
View user's profile Send private message Reply with quote
Core i7



Joined: 14 Nov 2024
Posts: 175
Location: Socket on motherboard
Core i7 17 Sep 2026, 19:46
Incidentally, the Windows kernel employs a "Self-Reference Entry" technique within x64 page tables. Thanks to this trick, the entire page table hierarchy becomes accessible to the kernel as a contiguous array of virtual addresses.

This is achieved by having one of the entries in the PML4 (or PXE) point back to the PML4 table itself. Consequently, when accessing a virtual address that uses the same index across all four levels (PML4, PDPT, PD, PT), the processor traverses the hierarchy and lands back in the PML4. This allows the directory to reference itself, enabling access to the tables just like a standard array in memory.

In Win7_x64, this self-referential entry is located at index 0x1ED, corresponding to the virtual address 0xFFFFF6FB7DBED000. In newer versions like Win10/11, this index is selected randomly during system boot, in conjunction with KASLR randomization. This specific entry was the root cause of the CVE-2018-1038 vulnerability ("Total Meltdown"), where a patch erroneously permitted user-mode access to it.
https://www.bleepingcomputer.com/news/microsoft/meltdown-patch-opened-bigger-security-hole-on-windows-7/#1

I happen to be running Win7, and by using the `!cmkd` extension from the Codemachine group, I can generate a map like this: https://codemachine.com/downloads/cmkd.html
Code:
0: kd> !cmkd.ptelist -v 0xFFFFF6FB7DBED000
ptelist : Using fffff6fb7dbed000 as VA
VA=FFFFF6FB7DBED000
  PXE Idx=1ED  Va=FFFFF6FB7DBEDF68 Contents=000000000001B063  Hard Pfn=0000001B  Attr=---DA--KWEV
  PPE Idx=1ED  Va=FFFFF6FB7DBEDF68 Contents=000000000001B063  Hard Pfn=0000001B  Attr=---DA--KWEV
  PDE Idx=1ED  Va=FFFFF6FB7DBEDF68 Contents=000000000001B063  Hard Pfn=0000001B  Attr=---DA--KWEV
  PTE Idx=1ED  Va=FFFFF6FB7DBEDF68 Contents=000000000001B063  Hard Pfn=0000001B  Attr=---DA--KWEV
0: kd>    
Post 17 Sep 2026, 19:46
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.