diff --git a/course/02-KVM.md b/course/02-KVM.md
index ca9d8d6866b60b038e463238c0ddc7c4daffaa85..855fd01563ab88aba7e89364b71845e873d80936 100644
--- a/course/02-KVM.md
+++ b/course/02-KVM.md
@@ -389,16 +389,23 @@ while (!done) {
 [//]: # ----------------------------------------------------------------
 ## Memory-Mapped I/O devices (MMIO)
 
+\small
+
 - Device registers are **mapped** into the CPU **physical address space**
 - **RAM and devices registers share the same address space!**
 - Called **MMIO**: Memory-Mapped Input/Output
 - Read/write from/to these devices happen exactly like memory (RAM)
 - All CPU instructions dealing with memory operands can interact with these devices
-  - e.g. `mov` instruction (x86)
+  - \footnotesize e.g. `mov` instruction (x86)
     ```{.verysmall .assembler}
-    mov al,[42]  ; reads 8-bits at MMIO address 42
+    mov al,[42]  ; reads 32-bits at MMIO address 42
                  ; and stores it into al register
     ```
+    ```{.verysmall .assembler}
+    mov ebx,42        ; store 42 into ebx register
+    mov byte[ebx],17  ; writes 8-bits value 17
+                      ; to MMIO address 42
+    ```
 
 [//]: # ----------------------------------------------------------------
 ## Port-Mapped I/O devices (PMIO)
@@ -583,22 +590,24 @@ void read_sector(int n, uint8_t *data) {
 
 \definecolor{palechestnut}{rgb}{0.87, 0.68, 0.69}
 \setlength{\fboxsep}{6pt}
-\fcolorbox{black}{palechestnut!50}{\parbox{10cm}{How to use these VMexits, to either \textbf{paravirtualize} or \textbf{emulate} a device?}}              
+\fcolorbox{black}{palechestnut!50}{\parbox{10cm}{How to use these VMexits, to either \textbf{paravirtualize} or \textbf{emulate} a device?}}
 
 [//]: # ----------------------------------------------------------------
-# Device paravirtualisation
+# Device paravirtualization
 
 [//]: # ----------------------------------------------------------------
-## From device emulation to device paravirtualization
+## Paravirtualization
 
-::: incremental :::
+::: incremental
 
-- Device emulation can be complex and difficult to implement
-- Device drivers that trigger many `VMexits` have poor performance
-  - each `VMexit` triggers a context switch
-  - many context switches lead to poor performance
-- Paravirtualized devices designed to be simple and efficient with few `VMexits`
-- How?
+- The idea behind paravirtualization is simple: the guest OS sends a service request to the VMM
+- Conceptually very similar to a user app requesting a service from the OS
+  - this mechanism is called a **syscall**
+- \textcolor{myblue}{In paravirtualization, guest requests a service from the VMM}
+  - \textcolor{myblue}{this mechanism is called an \textbf{hypercall}}
+- Device paravirtualization is simpler and much easier to implement than emulation, hence we describe it first
+\vspace{.3cm}
+- **\textcolor{mygreen}{How to implement paravirtualization?}**
 
 :::
 
@@ -607,11 +616,11 @@ void read_sector(int n, uint8_t *data) {
 
 - Mechanism for the guest OS to request the help of the VMM
 - The VMM exposes an "API" of what functions are available to the guest OS
-  - typically used to access devices
-- Examples of hypercalls:
-  - guest OS wants to read a disk sector
-  - guest OS wants to display something
-  - guest OS wants to send a network packet
+  - typically used to access and control devices
+- Hypercall examples:
+  - guest OS requests to read a disk sector
+  - guest OS requests to display something
+  - guest OS requests to send a network packet
 
 [//]: # ----------------------------------------------------------------
 ## Hypercalls vs system calls
@@ -680,22 +689,45 @@ void read_sector(int n, uint8_t *data) {
 [//]: # ----------------------------------------------------------------
 ## Hypercalls: parameters
 
-```{.verysmall}
-typedef struct {
-    uint8_t x;
-    int32_t val;
-    uint64_t msg;   // a pointer
-} __attribute__((packed)) params_t;
-```
+\small
+
+- **\textcolor{mygreen}{Idea}**: hypercall parameters are serialized/deserialized into a structure, e.g.
+  ```{.verysmall .c}
+  typedef struct {
+      uint8_t val;
+      uint64_t ptr;   // a pointer
+  } __attribute__((packed)) params_t;
+  ```
+- Fields can be used to store both inputs and/or outputs
+- Structure must be **`packed`** as it might be padded differently between VMM and guest (it depends on how code was compiled!)
+- Pointers must be declared as the **largest storage size** among VMM and guest
+  - \footnotesize otherwise risk of overflow if architectures are of different size (e.g. 32-bits guest and 64-bits VMM)
 
-IMPORTANT: if VMM and guest architecture are of different size, e.g. guest is 32-bits and VMM is 64, the above structure must use the **largest** data storage type!
+[//]: # ----------------------------------------------------------------
+## Hypercalls parameters: guest
+
+How does guest OS prepare an hypercall's parameters?
+
+- Initialize structure with required parameters
+- Copy (shallow) structure's content to shared hypercall buffer
+- Issue hypercall by writing the desired number to the dedicated hypercall address
+
+[//]: # ----------------------------------------------------------------
+## Hypercalls parameters: VMM
+
+How does VMM retrieve an hypercall's parameters?
+
+- Detect `VMExit` dedicated to an hypercall, then retrieve its number
+- Execute function corresponding to the retrieved hypercall number
 
 [//]: # ----------------------------------------------------------------
-## Writing/read to/from shared buffer
+## Hypercalls: rebasing pointers
 
-- Compiler optimization:
-  - use memcpy if VMM must write something that must be read back by the guest OS!
-  - otherwise, compiler won't read the value again (will use the existing value as it doesn't know the value was modified in another thread/process)
+- Remember that a pointer is just... an address
+- This address is relative to the process' address space
+- \textcolor{myred}{Guest OS address space $\neq$ VMM address space!}
+- VMM must convert every guest address into the address relative to the VMM's address space
+- This process is called **pointer rebasing**
 
 [//]: # ----------------------------------------------------------------
 # VMExits: retrieving values
@@ -969,6 +1001,18 @@ outb(0x3C5, 0x0F);
 :::
 ::::::
 
+<!--
+[//]: # ----------------------------------------------------------------
+## Performance consideration: emulation vs paravirtualization
+
+- Emulation typically triggers lots of `VMexits`
+- Device drivers that trigger many `VMexits` have poor performance
+  - each `VMexit` triggers a context switch
+  - many context switches lead to poor performance
+- Paravirtualized devices designed to be simple and efficient with only few `VMexits`
+  - leads to **much better performance** than emulation
+-->
+
 [//]: # ----------------------------------------------------------------
 # VMM software architecture
 
@@ -990,7 +1034,7 @@ outb(0x3C5, 0x0F);
 - Dedicate a thread for every vCPU
 - If using the SDL library to manage the display, **make sure** that only the main thread calls SDL functions[^8]
 - Dedicate a thread to interact with the user
-  - if using SDL, handle it in the same thread (main) as the one calling SDL functions
+  - if using SDL, handle it in the same thread (`main` function) as the one calling SDL functions
 - If VMM emulates a timer, have it run in a dedicated thread
 
 [^8]: \scriptsize [\textcolor{myblue}{https://documentation.help/SDL/thread.html}](https://documentation.help/SDL/thread.html)
diff --git a/labs/lab-virtual_game_machine/Makefile b/labs/lab-virtual_game_machine/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3c124efda95948aa584656a983f908d2c27a23f6
--- /dev/null
+++ b/labs/lab-virtual_game_machine/Makefile
@@ -0,0 +1 @@
+include ../lab.mk
diff --git a/labs/lab-virtual_game_machine/lab-virtual_game_machine.md b/labs/lab-virtual_game_machine/lab-virtual_game_machine.md
new file mode 100644
index 0000000000000000000000000000000000000000..6cc92654f7ea2cd02e1803adfdb2d17673846b0c
--- /dev/null
+++ b/labs/lab-virtual_game_machine/lab-virtual_game_machine.md
@@ -0,0 +1,355 @@
+---
+title       : Virtualisation
+author      :
+     - "Professeur : Florent Gluck"
+date        : \today
+papersize   : A4
+geometry    : "left=2.5cm,right=2cm,top=1.5cm,bottom=2cm"
+colorlinks  : urlcolor
+fontsize    : 11pt
+---
+
+# Virtual Game Machine
+
+## Introduction
+
+Dans ce travail pratique, vous réaliserez vous même, de zéro, un mini VMM (hyperviseur) utilisant l'API KVM de Linux. Le VMM à implémenter, bien que très rudimentaire pour des raisons de temps, vous permettra de comprendre de manière relativement approfondie ce que réalisent QEMU, VirtualBox ou VMWare Workstation. Vous virtualiserez une machine possédant des périphériques réels (physiques) et virtuels (fictifs). Enfin, grâce à une analyse de performance de votre VMM, vous serez à même de comprendre précisément pourquoi la paravirtualisation de périphérique est beaucoup plus simple à mettre en oeuvre et pourquoi elle offre des performances largement supérieures à l'émulation.
+
+**IMPORTANT**\ : si votre machine est basée sur une architecture autre qu'Intel/AMD (p.ex. ARM), assurez-vous d'exécuter Linux dans une VM gérée par un VMM réalisant de l'émulation processeur pour Intel/AMD avec virtualisation imbriquée. Cela signifie que votre solution de virtualisation doit permettre la création d'une VM exposant les instructions de virtualisation à l'OS guest. Ceci est nécessaire pour pouvoir utiliser KVM au sein de la VM Linux. Par exemple, QEMU peut réaliser de la virtualisation imbriquée via l'option `"-cpu host"`.
+
+## Objectif
+
+L'objectif général de ce travail pratique est la réalisation de deux composants\ :
+
+1. Un VMM virtualisant une plateforme matérielle spécifique (décrite plus loin)
+1. Un système d'exploitation (OS) guest dédié à cette plateforme matérielle spécifique
+
+La plateforme matérielle à virtualiser est une machine de "jeux". Celle-ci est basée sur une architecture processeur IA-32 (Intel/AMD 32-bits) et elle possède plusieurs périphériques détaillés à la section [Description de la plateforme à virtualiser](#description-de-la-plateforme-à-virtualiser).
+
+Chaque périphérique est implémenté de deux manières\ :
+
+- Comme **périphérique physique réel**
+  - coté OS guest : programmé comme périphérique réel
+  - coté VMM : émulé (**full virtualization**)
+- Comme **périphérique virtuel**
+  - coté OS guest : programmé comme périphérique virtuel (utilisation d'hypercalls)
+  - coté VMM : paravirtualisé (gestion des hypercalls) (**paravirtualization**)
+
+## Préparation
+
+Votre OS hôte est basé sur une architecture 64 bits (probablement Intel/AMD). Etant donné que l'architecture processeur de l'OS guest est Intel/AMD 32 bits (nommée IA-32), il est nécessaire d'installer les librairies nécessaires au compilateur C (`gcc`) pour le support IA-32. Une petite partie du code du guest requiert du code assembleur, ce qui nécessite l'installation du compilateur `nasm`. Pour la distribution Ubuntu 24.04, ceci implique d'installer les paquets ci-dessous (à adapter selon votre type de distribution GNU/Linux et/ou version)\ :
+\footnotesize
+```
+sudo apt-get install --no-install-recommends make gcc nasm lib32gcc-13-dev libc6-dev-i386
+```
+\normalsize
+
+## Description de la plateforme à virtualiser
+
+La plateforme à virtualiser, appelée "Game Machine", dispose d'un CPU à architecture Intel/AMD 32 bits (IA-32) et de 512KB de RAM.
+Elle dispose d'un ensemble de périphériques réels (physiques), mais présente également les mêmes périphériques sous forme virtuelle.
+
+Les périphériques exposés dans la VM sont les suivants\ :
+
+- console virtuelle
+- timer
+- initialisation affichage
+- disque
+- clavier
+- moteur de sprites
+
+Les sous-sections qui suivent décrivent le but et les fonctionnalités de chaque périphérique (physique et virtuel). Pour chaque périphérique physique, une description de ses registres est donnée ainsi que comment le programmer. Pour chaque périphérique virtuel, son numéro d'hypercall est donné, ainsi que les paramètres exacts associés à l'hypercall.
+
+### Mécanisme d'hypercall
+
+Une demande d'hypercall est signalée par le guest en écrivant, sur 8 bits, le numéro d'hypercall à l'adresse 0xABBA en PMIO (port).
+
+### (1) Console virtuelle
+
+Ce périphérique est uniquement virtuel, donc disponible de manière paravirtualisée. Il permet au guest de demander au VMM d'afficher un message (une chaîne de caractères) sur une console virtuelle. Cette console virtuelle est simplement la sortie standard du VMM.
+
+**Comportement du VMM**
+
+Le VMM affiche le texte spécifié par le guest sur la sortie standard.
+
+**Guest: accès paravirtualisé**
+
+- Numéro d'hypercall: 1
+- Paramètres de l'hypercall:
+  \footnotesize
+  ```{.c}
+  uint64_t msg;  // message address
+  ```
+  \normalsize
+
+  **Guest: accès physique**
+
+  Non existant.
+
+### (2) Timer
+
+Ce périphérique est disponible physiquement et virtuellement (de manière paravirtualisée). Il offre une seule fonctionnalité\ : attendre un certain nombre de micro-secondes.
+
+**Comportement du VMM**
+
+Le VMM attend le nombre de micro-secondes spécifié par le guest.
+
+**Guest: accès paravirtualisé**
+
+- Numéro d'hypercall: 2
+- Paramètres de l'hypercall:
+  \footnotesize
+  ```{.c}
+  uint32_t us;  // delay in micro-seconds
+  ```
+  \normalsize
+
+  **Guest: accès physique**
+
+Le périphérique se programme en PMIO.
+
+- Adresse registre de commande (write-only): 0x43
+- Adresse registre de donnée (write-only): 0x40
+
+Comment attendre `n` micro-secondes\ ?
+
+1. Ecrire la valeur 20, sur 16 bits, dans le registre de commande
+1. Ecrire la valeur `n`, sur 32 bits, dans le registre de donnée
+
+### (3) Initialisation affichage
+
+Ce périphérique est disponible physiquement et virtuellement. Il offre une seule fonctionnalité\ : initialiser l'affichage à la résolution demandée.
+
+**Comportement du VMM**
+
+Le VMM ouvre une fenêtre graphique de la résolution spécifiée par le guest.
+
+**Guest: accès paravirtualisé**
+
+- Numéro d'hypercall: 3
+- Paramètres de l'hypercall:
+  \footnotesize
+  ```{.c}
+  uint32_t width;   // horizontal resolution in pixels
+  uint32_t height;  // vertical resolution in pixels
+  ```
+  \normalsize
+
+  **Guest: accès physique**
+
+Le périphérique se programme en MMIO.
+
+- Adresse registre de statut (read-only): 0x4E700000
+- Adresse registre de commande (write-only): 0x4E700100
+- Adresse registre de donnée (write-only): 0x4E700200
+
+Comment initialiser l'affichage avec une résolution de `w` par `h`\ ?
+
+1. Lire 32 bits depuis le registre de statut ; boucler tant que la valeur lue est égale à 42
+1. Ecrire la valeur 5, sur 32 bits, dans le registre de commande
+1. Ecrire la valeur `w`, sur 32 bits, dans le registre de donnée
+1. Ecrire la valeur `h`, sur 32 bits, dans le registre de donnée
+
+### (4) Disque
+
+Ce périphérique est disponible physiquement et virtuellement. Il offre une seule fonctionnalité\ : écrire un secteur avec le contenu souhaité. Un secteur a une taille d'exactement 512 bytes. Un disque est une collection de secteurs, donc la taille d'un disque est forcément un multiple de 512 bytes. Le numéro d'un secteur à écrire est au minimu 0 et au maximum $2^{28}-1$.
+
+**Comportement du VMM**
+
+Le VMM écrit dans le secteur spécifié le contenu spécifié par le guest. Attention: une image disque doit être spécifiée au démarrage du VMM afin que celui-ci la présente comme un disque au guest.
+
+**Guest: accès paravirtualisé**
+
+- Numéro d'hypercall: 4
+- Paramètres de l'hypercall:
+  \footnotesize
+  ```{.c}
+  uint32_t sector_idx;  // sector index (or number)
+  uint64_t data;        // address of data to write (512 bytes)
+  ```
+  \normalsize
+
+  **Guest: accès physique**
+
+Le périphérique se programme en PMIO.
+
+- Adresse registre de statut (read-only): 0x1F7
+- Adresse registre de commande (write-only): 0x1F7
+- Adresse registre de donnée de base (write-only): 0x1F0
+
+Le code pour programmer l'écriture d'un secteur vous est donné dans le fichier `ide_phys.c` du code du guest.
+
+### (5) Clavier
+
+Ce périphérique est disponible physiquement et virtuellement. Il offre une seule fonctionnalité\ : initialiser l'affichage à la résolution demandée.
+
+**Comportement du VMM**
+
+Le VMM ouvre une fenêtre graphique de la résolution spécifiée par le guest.
+
+**Guest: accès paravirtualisé**
+
+- Numéro d'hypercall: 5
+- Paramètres de l'hypercall:
+  \footnotesize
+  ```{.c}
+  uint32_t key;  // code of the pressed key
+  ```
+  \normalsize
+
+  **Guest: accès physique**
+
+Le périphérique se programme en PMIO.
+
+- Adresse registre de donnée (read-only): 0x60
+
+Comment obtenir le code de la touche pressée\ ?
+
+1. Lire 32 bits (le code de la touche) depuis le registre de donnée
+
+### (6) Moteur de sprites
+
+**TODO**: cette section sera ajoutée bientôt
+
+## Cahier des charges
+
+### Processus de build
+
+Le processus de build, basé sur make, doit gérer les cibles décrites ci-dessous\ :
+
+\footnotesize
+```
+Available targets:
+    run_pv       run paravirtualized guest
+    run_phys     run physical (native) guest
+    vmm          build VMM
+    guest_pv     build paravirtualized guest
+    guest_phys   build physical (native) guest
+    clean        delete VMM and guest
+    clean_vmm    delete VMM
+    clean_guest  delete guest
+```
+\normalsize
+
+En cas d'exécution de `make` à la racine, sans argument, l'aide ci-dessus doit être affichée.
+
+Vous devez compléter les trois fichiers Makefile fournis afin que les cibles ci-dessus soient gérées correctement. Une ébauche de Makefile (probablement à modifier) se trouve à la racine, un Makefile vide se trouve dans `vmm`, et le Makefile dans `guest` vous est entièrement donné. Pensez à vous inspirer de ce dernier pour implémenter les deux autres Makefiles. Veuillez vous assurez de gérer les dépendences correctement. Par exemple pour le Makefile racine, `run_pv` doit dépendre de `guest_pv` et `vmm`.
+
+Le VMM doit bien sûre gérer le support de périphériques réels (physiques) et virtuels (paravirtualisés). Le guest, lui, utilisera la facilité de compilation conditionnelle de C pour inclure le code lié au support de périphériques physiques ou paravirtualisés. L'idée est d'utiliser la variable `PV` du préprocesseur pour définir si le guest utilise des périphériques physiques ou paravirtualisés\ :
+
+- Si `PV=1` alors le guest est compilé avec drivers paravirtualisés.
+- Sinon, le guest est compilé avec des drivers physiques.
+- Dans le cas où un périphérique n'existe qu'en une seule "version" (p.ex. paravirtualisée), alors le guest est toujours compilé avec ce driver, quelle que soit la valeur de la variable `PV`.
+
+### VMM
+
+Le code du VMM à réaliser doit se trouver dans `vmm`. Le VMM, également nommé `vmm`, doit prendre deux arguments\ : le binaire du guest à exécuter, ainsi qu'une image de disque à utiliser. La syntaxe du programme est la suivante\ :
+
+\footnotesize
+```
+Usage: vmm GUEST DISK
+GUEST is the guest OS binary the VMM must load and execute.
+DISK is a raw disk image the VMM exposes as a hard disk to the guest.
+    Its size must be a multiple of sector size (512 bytes).
+```
+\normalsize
+
+Créez l'image disque dans le Makefile racine à l'aide de l'outil `qemu-img`.
+
+### Mesures de performances
+
+**TODO**: cette section sera ajoutée bientôt
+
+## Code source à disposition
+
+Afin de vous donner une base sur laquelle démarrer, le squelette ci-dessous vous est fourni sur le git du cours\ :
+
+\footnotesize
+```
+skeleton/
+|-- Makefile
+|-- guest
+|   |-- Makefile
+|   |-- descriptors.h
+|   |-- entrypoint_asm.s
+|   |-- guest.ld
+|   |-- guest_main.c
+|   |-- ide
+|   |   |-- ide.h
+|   |   `-- ide_phys.c
+|   |-- idt.c
+|   |-- idt.h
+|   |-- idt_asm.s
+|   |-- pmio.h
+|   |-- pmio_asm.s
+|   |-- stdio.c
+|   |-- stdio.h
+|   |-- utils.c
+|   |-- utils.h
+|   `-- x86.h
+|-- shared
+|   |-- hypercall_params.h
+|   `-- ide_regs.h
+`-- vmm
+    `-- Makefile
+```
+\normalsize
+
+### Code partagé
+
+Le répertoire `shared/` contient deux fichiers donnés en guise d'exemple qui seront utiles au VMM ainsi qu'au guest\ :
+
+- `hypercall_params.h` : contient les paramètres des différents hypercalls supportés
+- `ide_regs.h` : exemple de header contenant les registres du contrôleur de disque IDE
+
+### VMM
+
+Le code lié au VMM est volontairement vide. Il se trouve dans le répertoire `vmm` et c'est à vous de l'écrire depuis zéro.
+
+**Affichage graphique**
+
+Votre VMM devra ouvrir une fenêtre graphique et afficher des sprites. Pour cela, vous utiliserez la librairie SDL2. Afin de vous aider dans cette tâche, le petit *wrapper* **`gfxlib`**, très simple d'utilisation, vous est fourni. Le projet est disponible sur [github ici](https://github.com/thxbb12/gfxlib) avec plusieurs exemples d'utilisation.
+
+**IMPORTANT** : l'appel de fonctions SDL2 en dehors du thread principal (la fonction `main`) peut mener à des comportements indéterminés. En conséquence, il est absolument vital de n'appeler AUCUNE fonction SDL2 en dehors du thread `main`!
+
+### Guest
+
+Le code lié au guest se trouve dans le répertoire `guest`. Voici la description des fichiers fournis\ :
+
+- `entrypoint_asm.s` : contient le code assembleur du point d'entrée du noyau\ ; celui-ci initialise le MMU afin de mapper les adresses virtuelles sur les adresses physiques et d'appeler la fonction `guest_main` qui est le point d'entrée du guest en C.
+- `guest_main.c` : contient la fonction point d'entrée du guest `guest_main`.
+- `utils.c, utils.h` : contient les fonctions de base `memset`, `memcpy`, `srand` et `rand`.
+- `stdio.c, stdio.h` : contient les fonctions de base `snprintf` et `vsnprintf`.
+- `pmio.s, pmio.h` : contient les fonctions assembleur `outb, inb, outw, inw, outd, ind` qui permettent d'écrire/lire dans/depuis des registres en PMIO.
+- `ide.h, ide_phys.c` : contient le code du driver permettant d'écrire un secteur sur un disque IDE physique (réel).
+- `x86.h` : contient des fonctions en assembleur *inline* permettant d'activer/désactiver les interruptions matérielles et stopper le CPU.
+- `idt.c, idt.h, idt_asm.s` : contient le code de gestion des interruptions matérielles.
+- `descriptor.h` : contient des constantes liées à l'architecture IA-32 utiles à la gestion des interruptions.
+- `guest.ld` : fichier *linker* permettant de générer un exécutable "plat" (à contrario d'un fichier ELF) où on s'assure que le début du binaire commence par la section `entrypoint` définie dans `entrypoint_asm.s`.
+
+**Sprites**
+
+Le guest ne possède aucun moyen pour charger des données depuis un support physique. Comment donc charger les contenus utilisés pour les sprites\ ? Un moyen simple pour résoudre ce problème est de simplement intégrer les données des sprites dans le code source du guest.
+
+Le petit programme **`bin2array`**, dont le code source [se trouve ici](https://github.com/thxbb12/bin2array), permet de convertir tout fichier en un tableau de bytes en C. A vous donc de l'utiliser pour convertir des fichiers sprites (images png) en code source C.
+
+## Consignes à propos de l'implémentation
+
+- Gérez toujours les erreurs potentielles\ : testez toujours le retour de fonctions qui peuvent échouer, en particulier celles qui dépendent de l'utilisateur (ex. `open`).
+- Assurez-vous que votre guest n'a aucun moyen de faire crasher[^1] votre VMM\ ; si c'est le cas, c'est que votre VMM n'est pas robuste (p.ex. exécuter un guest bidon ou volontairement buggé, n° d'hypercalls invalides, etc.).
+- Modularisez votre code afin de ne pas avoir tout le code dans un même fichier source.
+- Utilisez des noms de variables qui ont du sens et qui aident à la compréhension du code.
+- Ecrivez des fonctions courtes, lisibles et réutilisables.
+- Essayez d'être cohérent et de modulariser votre code pour qu'il soit compréhensible et maintenable.
+- Compilez avec `-Wall -Wextra` et supprimez tous les *warnings* du compilateur lorsque cela est possible.
+
+[^1]: Attention : un VMexit, même indiquant une erreur n'est pas un crash.
+
+## Méthode de travail
+
+L'écriture de l'hyperviseur vous prendra un temps non négligeable. Commencez par implémenter le périphérique le plus simple. Commencez par la paravirtualisation, car elle est bien plus simple à implémenter que l'émulation.
+
+Pour vous aider à tester votre VMM et vous assurer que son comportement est correct, différents OS guests seront mis à disposition sur le git. Vous pourrez les exécuter avec votre VMM et vérifier que vous obtenez bien les résultats attendus.
+
+Le binaire de l'hyperviseur complet à réaliser vous sera également fourni afin que vous puissiez vérifier que le code de votre guest est également correct.
+
diff --git a/labs/lab-virtual_game_machine/skeleton/Makefile b/labs/lab-virtual_game_machine/skeleton/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..ab56e5bc70861e9826d6560135460b2ad6ed7b33
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/Makefile
@@ -0,0 +1,18 @@
+DISK=disk.raw
+DISK_SIZE=256K
+
+help:
+	@echo "Available targets:"
+	@echo "    run_pv       run paravirtualized guest"
+	@echo "    run_phys     run physical (native) guest"
+	@echo "    vmm          build VMM"
+	@echo "    guest_pv     build paravirtualized guest"
+	@echo "    guest_phys   build physical (native) guest"
+	@echo "    clean        delete VMM and guest"
+	@echo "    clean_vmm    delete VMM"
+	@echo "    clean_guest  delete guest"
+
+guest:
+	$(MAKE) -C guest PV=1 OUT=guest.bin
+
+.PHONY: guest
\ No newline at end of file
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/Makefile b/labs/lab-virtual_game_machine/skeleton/guest/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3cf535c8a537bb7c30141b4c25a846a616c8e9d6
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/Makefile
@@ -0,0 +1,28 @@
+BAREMETAL_FLAGS=-m32 -ffreestanding -nostdlib -fno-builtin -fno-stack-protector -fno-pie -static -O3
+CC=gcc -std=gnu17 $(BAREMETAL_FLAGS) -Wall -Wextra -MMD -Ishared -I../.. -I..
+CC+=-DPV=$(PV)
+
+LD=gcc -Tguest.ld $(BAREMETAL_FLAGS)
+
+C_SRCS=$(shell find . -name "*.c")
+C_OBJS=$(C_SRCS:.c=.o)
+C_DEPS=$(C_OBJS:%.o=%.d)
+
+ASM_SRCS=$(shell find . -name "*.s")
+ASM_OBJS=$(ASM_SRCS:.s=.o)
+
+$(OUT): $(C_OBJS) $(ASM_OBJS)
+	$(LD) $^ -o $@
+
+%.o: %.c
+	$(CC) -c $< -o $@
+
+%.o: %.s
+	nasm -f elf32 $< -o $@
+
+clean:
+	rm -f $(C_OBJS) $(ASM_OBJS) $(C_DEPS) *.o *.d $(OUT)
+
+.PHONY: clean
+
+-include $(C_DEPS)
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/descriptors.h b/labs/lab-virtual_game_machine/skeleton/guest/descriptors.h
new file mode 100644
index 0000000000000000000000000000000000000000..5b2d54c51cf606d9e76e9b91a16f59688bb98e16
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/descriptors.h
@@ -0,0 +1,35 @@
+#ifndef _DESCRIPTORS_H_
+#define _DESCRIPTORS_H_
+
+// Privilege levels
+#define DPL_USER    0x3
+#define DPL_KERNEL  0x0
+
+// Selectors
+//#define LDT_SELECTOR    0x4
+
+// Descriptor types for code and data segments
+#define TYPE_DATA_RO    1   // read-only
+#define TYPE_DATA_RW    3   // read-write
+
+// Stack segments are data segments which must be read/write segments. Loading the
+// SS register with a segment selector for a nonwritable data segment generates a
+// general-protection exception (#GP). If the size of a stack segment needs to be
+// changed dynamically, the stack segment can be an expand-down data segment
+// (expansion-direction flag set). Here, dynamically changing the segment limit causes
+// stack space to be added to the bottom of the stack. If the size of a stack segment is
+// intended to remain static, the stack segment may be either an expand-up or expand-down type.
+#define TYPE_DATA_RW_EXPAND_DOWN  6  // stack segment type
+
+#define TYPE_CODE_EXECONLY     9
+#define TYPE_CODE_EXECREAD    11
+
+// Descriptor types for system segments and gates
+#define TYPE_LDT               2
+#define TYPE_TASK_GATE         5
+#define TYPE_TSS               9
+#define TYPE_CALL_GATE         12
+#define TYPE_TRAP_GATE         15
+#define TYPE_INTERRUPT_GATE    14
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s b/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..1c2ea58fadf03feb88df23a501cf56defbfcf8a0
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s
@@ -0,0 +1,76 @@
+CODE_SELECTOR  equ  0x08
+DATA_SELECTOR  equ  0x10
+
+extern guest_main
+
+section .entrypoint
+
+[BITS 16]
+cli                          ; disable hardware interrupts (since no IDT loaded yet)
+
+lgdt [gdt_descriptor]        ; load GDT (see segments defined after gdt_start below)
+
+; switch to protected mode (32-bits mode)
+mov     eax,cr0 
+or      eax,1                ; set PE (Protection Enable) bit in CR0 (Control Register 0)
+mov     cr0,eax
+
+jmp     CODE_SELECTOR:flush  ; far jump [selector:offset] to load code segment (cs) with code descriptor in GDT
+flush:
+
+[BITS 32]
+mov     ax,DATA_SELECTOR     ; update data (ds), stack (sp) and extra (es) segments with data descriptor in GDT
+mov     ds,ax
+mov     ss,ax
+mov     es,ax
+
+call    guest_main           ; call guest C code entrypoint
+
+@forever:
+hlt                          ; halt the CPU
+jmp @forever
+
+; Setup a GDT table to identity map the full 4GB address space (32 bits)
+gdt_start:                   ; the GDT table starts here
+    
+gdt_null:                    ; 1st descriptor: NULL descriptor (required)
+        dd 0
+        dd 0
+
+gdt_code:                    ; 2nd descriptor: code descriptor: base=0, limit=0xFFFFF (4GB)
+        dw 0xFFFF            ; limit (bits 0-15)
+        dw 0                 ; base (bits 0-15)
+        db 0                 ; base (bits 16-23)
+        db 10011001b         ; bit    7: P: present in memory
+                             ; bits 5-6: DPL: privilege level (ring level)
+                             ; bit    4: S: 1 for segments, 0 for system (TSS, LDT, gates)
+                             ; bit  0-3: Type: 1001b (9) for code exec only
+        db 11001111b         ; bits 4-7: flags:
+                             ; bit    7: G: granularity (0=1 byte, 1=4KB)
+                             ; bit    6: D: 1 for 32-bit code and data segments; 0 for system (TSS, LDT, gate)
+                             ; bit    5: must be 0
+                             ; bit    4: available for use
+                             ; bits 0-3: limit (bits 16-19)
+        db 0                 ; base (bits 24-31)
+    
+gdt_data:                    ; 3rd descriptor: data descriptor: base=0, limit=0xFFFFF (4GB)
+        dw 0xFFFF            ; limit (bits 0-15)
+        dw 0                 ; base (bits 0-15)
+        db 0                 ; base (bits 16-23)
+        db 10010011b         ; bit    7: P: present in memory
+                             ; bits 5-6: DPL: privilege level (ring level)
+                             ; bit    4: S: 1 for segments, 0 for system (TSS, LDT, gates)
+                             ; bit  0-3: Type: 0011b (3) data read/write
+        db 11001111b         ; bits 4-7: flags:
+                             ; bit    7: G: granularity (0=1 byte, 1=4KB)
+                             ; bit    6: D: 1 for 32-bit code and data segments; 0 for system (TSS, LDT, gate)
+                             ; bit    5: must be 0
+                             ; bit    4: available for use
+                             ; bits 0-3: limit (bits 16-19)
+        db 0                 ; base (bits 24-31)
+
+gdt_end:
+
+gdt_descriptor:                  ; descriptor pointing to the GDT (required by the lgdt instruction)
+    dw gdt_end - gdt_start - 1   ; GDT size minus 1
+    dd gdt_start                 ; address of the GDT table
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/guest.ld b/labs/lab-virtual_game_machine/skeleton/guest/guest.ld
new file mode 100644
index 0000000000000000000000000000000000000000..a2d26cfef0cf947e493d155786bcd46cba9880e5
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/guest.ld
@@ -0,0 +1,31 @@
+OUTPUT_FORMAT("binary")
+
+SECTIONS {
+    . = 0;         /* first section located at address 0 */
+
+    .entrypoint ALIGN(4):   /* entry point: must be located at 0 */
+    {
+        *(.entrypoint)
+    }
+
+    .text ALIGN(4) :        /* code */
+    {
+        *(.text*)
+    }
+
+    .rodata ALIGN(4) :      /* read-only data */
+    {
+        *(.rodata*)
+    }
+
+    .data ALIGN(4) :        /* initialized data */
+    {
+        *(.data*)
+    }
+
+    .bss ALIGN(4) :         /* unitialized data */
+    {
+        *(COMMON)
+        *(.bss*)
+    }
+}
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/guest_main.c b/labs/lab-virtual_game_machine/skeleton/guest/guest_main.c
new file mode 100644
index 0000000000000000000000000000000000000000..244e224ae9f5c75c07765ad16302abc683ca1300
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/guest_main.c
@@ -0,0 +1,23 @@
+#include <stdint.h>
+#include "idt.h"
+#include "utils.h"
+#include "x86.h"
+
+// If PV == 1 => uses paravirtualized drivers (when available)
+// If PV == 0 => uses physical (hardware) drivers (when available)
+#if PV == 1
+#define timer_sleep timer_sleep_pv
+#else
+#define timer_sleep timer_sleep_phys
+#endif
+
+// Dummy examples
+void timer_sleep_pv(int usec) {}
+void timer_sleep_phys(int usec) {}
+
+void guest_main() {
+    idt_init();  // Initialize interrupt subsystem
+    sti();       // Enable hardware interrupts
+
+    timer_sleep(100000);
+}
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/ide/ide.h b/labs/lab-virtual_game_machine/skeleton/guest/ide/ide.h
new file mode 100644
index 0000000000000000000000000000000000000000..d6e7c5a8c24303adfb9345e89518c2e30307dfe1
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/ide/ide.h
@@ -0,0 +1,10 @@
+#ifndef _IDE_H_
+#define _IDE_H_
+
+#define SECTOR_SIZE 512
+
+// Write a sector.
+// Implement a device driver for the true physical hardware.
+void ide_write_sector_phys(int sector_idx, void *data);
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/ide/ide_phys.c b/labs/lab-virtual_game_machine/skeleton/guest/ide/ide_phys.c
new file mode 100644
index 0000000000000000000000000000000000000000..0a49e4a8c5c3962304fb7f590336c736120b31d0
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/ide/ide_phys.c
@@ -0,0 +1,44 @@
+#include <stdint.h>
+#include "ide.h"
+#include "shared/ide_regs.h"
+#include "../pmio.h"
+
+// IDE controler is programmed in PMIO.
+
+/**
+ * Wait for the disk drive to be ready.
+ */
+static void wait_drive() {
+    while ((inb(IDE_STATUS_REG) & 0xC0) != 0x40);
+}
+
+/**
+ * Prepare the disk drive for read/write at the specified sector in 28-bit LBA mode.
+ * @param sector the sector to read or write
+ */
+static void pio_prepare(int sector) {
+    wait_drive();
+    outb(IDE_DATA_BASE_REG+2, 1);  // sector count
+    outb(IDE_DATA_BASE_REG+3, sector & 0xff);  // send bits 0-7 of LBA
+    outb(IDE_DATA_BASE_REG+4, (sector >> 8) & 0xff);  // send bits 8-15 of LBA
+    outb(IDE_DATA_BASE_REG+5, (sector >> 16) & 0xff);  // send bits 16-23 of LBA
+    outb(IDE_DATA_BASE_REG+6, ((sector >> 24) & 0x0f) | 0xe0);  // send bits 24-27 of LBA + set LBA mode; 0xe0 = 11100000b;
+}
+
+/**
+ * Read a sector from the first disk.
+ * @param first sector to read (0-indexed)
+ * @param dst address to store to read data
+ * Based on the assembly code at http://wiki.osdev.org/ATA_read/write_sectors
+ */
+void ide_write_sector_phys(int sector, void *data) {
+    pio_prepare(sector);
+    outb(IDE_CMD_REG, 0x30);  // command port: write with retry
+    wait_drive();
+
+    uint16_t *d = (uint16_t *)data;
+    for (int i = 0; i < SECTOR_SIZE/2; i++) {
+        outw(IDE_DATA_BASE_REG, *d);
+        d++;
+    }
+}
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/idt.c b/labs/lab-virtual_game_machine/skeleton/guest/idt.c
new file mode 100644
index 0000000000000000000000000000000000000000..f49386dbe0193b45cff65f8ceca1d39a40f5bc0b
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/idt.c
@@ -0,0 +1,71 @@
+#include "idt.h"
+#include "utils.h"
+#include "descriptors.h"
+#include "x86.h"
+
+#define GDT_KERNEL_CODE_SELECTOR 0x08
+
+// Structure of an IDT descriptor. There are 3 types of descriptors:
+// task-gates, interrupt-gates, trap-gates.
+// See 5.11 of Intel 64 & IA32 architectures software developer's manual for more details.
+// For task gates, offset must be 0.
+typedef struct idt_entry_st {
+    uint16_t offset15_0;   // only used by trap and interrupt gates
+    uint16_t selector;     // segment selector for trap and interrupt gates; TSS segment
+                           // selector for task gates
+    uint16_t reserved : 8;
+    uint16_t type : 5;
+    uint16_t dpl : 2;
+    uint16_t p : 1;
+    uint16_t offset31_16;  // only used by trap and interrupt gates
+} __attribute__((packed)) idt_entry_t;
+
+// Structure describing a pointer to the IDT gate table.
+// This format is required by the lidt instruction.
+typedef struct idt_ptr_st {
+    uint16_t limit;   // Limit of the table (ie. its size)
+    uint32_t base;    // Address of the first entry
+} __attribute__((packed)) idt_ptr_t;
+
+// Gates table
+static idt_entry_t idt[256];
+static idt_ptr_t   idt_ptr;
+
+// Defined in idt_asm.s
+void idt_load(idt_ptr_t *idt_ptr);
+
+// Build and return an IDT entry.
+// selector is the code segment selector to access the ISR
+// offset is the address of the ISR (for task gates, offset must be 0)
+// type indicates the IDT entry type
+// dpl is the privilege level required to call the associated ISR
+static idt_entry_t idt_build_entry(uint16_t selector, uint32_t offset, uint8_t type, uint8_t dpl) {
+    idt_entry_t entry;
+    entry.offset15_0 = offset & 0xffff;
+    entry.selector = selector;
+    entry.reserved = 0;
+    entry.type = type;
+    entry.dpl = dpl;
+    entry.p = 1;
+    entry.offset31_16 = (offset >> 16) & 0xffff;
+    return entry;
+}
+
+// Low level handler defined in idt_asm.s
+void _irq0();
+
+// IRQ handler
+void irq_handler(uint32_t irq_number) {
+}
+
+void idt_init() {
+    idt_ptr.limit = sizeof(idt)-1;
+    idt_ptr.base  = (uint32_t)&idt;
+
+    // Clear up all entries
+    memset(idt, 0, sizeof(idt));
+
+    idt[0] = idt_build_entry(GDT_KERNEL_CODE_SELECTOR, (uint32_t)_irq0, TYPE_INTERRUPT_GATE, DPL_KERNEL);
+
+    idt_load(&idt_ptr);
+}
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/idt.h b/labs/lab-virtual_game_machine/skeleton/guest/idt.h
new file mode 100644
index 0000000000000000000000000000000000000000..0f3fbce5a502c679050bd7b61bc6c7bf752a1904
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/idt.h
@@ -0,0 +1,6 @@
+#ifndef _IDT_H_
+#define _IDT_H_
+
+void idt_init();
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/idt_asm.s b/labs/lab-virtual_game_machine/skeleton/guest/idt_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..bfd31a8b6510d004419aafdac1ca81559c4adb7a
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/idt_asm.s
@@ -0,0 +1,65 @@
+section .text                      ; start of the text (code) section
+align 4                            ; the code must be 4 byte aligned
+
+global idt_load
+
+; Load the IDT
+idt_load:
+    mov     eax,[esp+4]  ; Get the pointer to the IDT, passed as a parameter. 
+    lidt    [eax]        ; Load the IDT pointer.
+    ret
+
+; Low-level hardware interrupts handler for irq0
+global _irq0
+_irq0:
+    push    0  ; put irq number on the stack
+    jmp     irq_handler_wrapper
+
+extern irq_handler
+
+irq_handler_wrapper:
+    ; Save all registers
+    push    eax
+    push    ebx
+    push    ecx
+    push    edx
+    push    esi
+    push    edi
+    push    ebp
+    push    ds
+    push    es
+    push    fs
+    push    gs
+
+    ; Load kernel data descriptor into all segments
+    mov     ax,0x10   ; 0x10 = kernel data segment selector (3rd selector)
+    mov     ds,ax
+    mov     es,ax
+    mov     fs,ax
+    mov     gs,ax
+
+    ; What's on the stack at this point, in order (4 bytes each):
+    ; gs, fs, es, ds, ebp, edi, esi, edx, ecx, ebx, eax, irq_number, eip, cs, eflags, esp, ss
+    ; Pass irq_number as argument to the C function
+    mov     eax,esp
+    add     eax,44  ; to point to irq_number that was pushed in _irqX
+    push    dword[eax]
+    call    irq_handler
+    pop     eax  ; only here to balance the "push eax" done before the call
+
+    ; Restore all registers
+    pop     gs
+    pop     fs
+    pop     es
+    pop     ds
+    pop     ebp
+    pop     edi
+    pop     esi
+    pop     edx
+    pop     ecx
+    pop     ebx
+    pop     eax
+    
+	; Fix-up the stack pointer due to the push done before the jump to irq_handler_wrapper
+    add     esp,4
+    iret
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/pmio.h b/labs/lab-virtual_game_machine/skeleton/guest/pmio.h
new file mode 100644
index 0000000000000000000000000000000000000000..745b1c3e6d0941dd4db31639386f0740b1a5d44a
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/pmio.h
@@ -0,0 +1,21 @@
+#ifndef _PMIO_H_
+#define _PMIO_H_
+
+#include <stdint.h>
+
+// Write a 8-bit value to the specified port
+void outb(uint16_t port, uint8_t data);
+// Read a 8-bit value from the specified port
+uint8_t inb(uint16_t port);
+
+// Write a 16-bit value to the specified port
+void outw(uint16_t port, uint16_t data);
+// Read a 16-bit value from the specified port
+uint16_t inw(uint16_t port);
+
+// Write a 32-bit value to the specified port
+void outd(uint16_t port, uint32_t data);
+// Read a 32-bit value from the specified port
+uint32_t ind(uint16_t port);
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/pmio_asm.s b/labs/lab-virtual_game_machine/skeleton/guest/pmio_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..cd616324a53afcc7b7e7bcf418f1a544c0483cac
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/pmio_asm.s
@@ -0,0 +1,48 @@
+global outb
+global inb
+global outw
+global inw
+global outd
+global ind
+
+section .text                      ; start of the text (code) section
+align 4                            ; the code must be 4 byte aligned
+
+; void outb(uint16_t port, uint8_t data);
+outb:
+    mov     dx,word [esp+4]    ; port (2 bytes)
+    mov     al,byte [esp+8]    ; data (1 byte)
+    out     dx,al
+    ret
+
+; uint8_t inb(uint16_t port);
+inb:
+    mov     dx,word [esp+4]    ; port (2 bytes)
+    in      al,dx
+    ret
+
+; void outw(uint16_t port, uint16_t data);
+outw:
+    mov     dx,word [esp+4]    ; port (2 bytes)
+    mov     ax,word [esp+8]    ; data (2 bytes)
+    out     dx,ax
+    ret
+
+; uint16_t inw(uint16_t port);
+inw:
+    mov     dx,word [esp+4]    ; port (2 bytes)
+    in      ax,dx
+    ret
+
+; void outd(uint16_t port, uint32_t data);
+outd:
+    mov     dx,word [esp+4]    ; port (2 bytes)
+    mov     eax,[esp+8]        ; data (4 bytes)
+    out     dx,eax
+    ret
+
+; uint32_t ind(uint16_t port);
+ind:
+    mov     dx,word [esp+4]    ; port (2 bytes)
+    in      eax,dx
+    ret
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/stdio.c b/labs/lab-virtual_game_machine/skeleton/guest/stdio.c
new file mode 100644
index 0000000000000000000000000000000000000000..9a19eaef50b54e55e0906e812d272b6b681e2bf5
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/stdio.c
@@ -0,0 +1,109 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include "stdio.h"
+
+// Print into buf in reverse, the value val in the given base.
+// The argument sign indicates whether the value is signed (true) or not (false).
+// The number of characters written is returned into length.
+static void printint_rev(char *buf, int *length, int val, int base, bool sign) {
+    static char digits[] = "0123456789ABCDEF";
+    int count = 0;
+    u_int x;
+
+    if (sign && (sign = val < 0))
+        x = -val;
+    else
+        x = val;
+
+    do {
+        buf[count++] = digits[x % base];
+    } while ((x /= base) != 0);
+
+    if (sign)
+        buf[count++] = '-';
+
+    buf[count] = 0;
+    *length = count;
+}
+
+int snprintf(char *dest, int size, char *fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int count = vsnprintf(dest, size, fmt, args);
+    va_end(args);
+    return count;
+}
+
+// Helper macro used in vsnprintf
+#define putc(c)     \
+    if (size > 0) { \
+        *dest = c;  \
+        dest++;     \
+        size--;     \
+        count++;    \
+    }
+
+int vsnprintf(char *dest, int size, const char *fmt, va_list args) {
+    char buf[size+1];
+    int len;
+    int count = 0;
+    while (*fmt) {
+        if (*fmt == '%') {  // found an argument
+            fmt++;
+            if (*fmt == 0)  // missing value after %
+                break;
+            switch (*fmt) {
+                case 'c':
+                    {
+                        int c = va_arg(args, int);
+                        putc(c)
+                    }
+                    break;
+                case 's':
+                    {
+                        char *s = va_arg(args, char *);
+                        while (*s) {
+                            putc(*s)
+                            s++;
+                        }
+                    }
+                    break;
+                case 'd':
+                    {
+                        int n = va_arg(args, int);
+                        printint_rev(buf, &len, n, 10, true);
+                        while (len) putc(buf[--len]);
+                    }
+                    break;
+                case 'u':
+                    {
+                        u_int n = va_arg(args, u_int);
+                        printint_rev(buf, &len, n, 10, false);
+                        while (len) putc(buf[--len]);
+                    }
+                    break;
+                case 'x':
+                    {
+                        int n = va_arg(args, int);
+                        printint_rev(buf, &len, n, 16, false);
+                        while (len) putc(buf[--len]);
+                    }
+                    break;
+                case '%':
+                    putc('%')
+                    break;
+                default:
+                    // unkown argument, ignore it!
+                    break;
+            }
+        }
+        else {
+            putc(*fmt)
+        }
+        fmt++;
+    }
+
+    *dest = 0;
+    return count;
+}
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/stdio.h b/labs/lab-virtual_game_machine/skeleton/guest/stdio.h
new file mode 100644
index 0000000000000000000000000000000000000000..78ab4eebae1a5756a58289bf812ed5fbc94e4262
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/stdio.h
@@ -0,0 +1,18 @@
+#ifndef _STDIO_H_
+#define _STDIO_H_
+
+#include <stdarg.h>
+
+// Writes at most size characters (including terminal 0) of the formatted string fmt
+// into buffer dest.
+// args are the variable length arguments as a va_list.
+// Returns the number of characters written.
+int vsnprintf(char *dest, int size, const char *fmt, va_list args);
+
+// Writes at most size characters (including terminal 0) of the formatted string fmt
+// into buffer dest.
+// Any arbitrary number of arguments can be passed after fmt.
+// Returns the number of characters written.
+int snprintf(char *dest, int size, char *fmt, ...);
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/utils.c b/labs/lab-virtual_game_machine/skeleton/guest/utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..42b44323f26c6f4afb2960f0647b13b9baee35e2
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/utils.c
@@ -0,0 +1,45 @@
+#include "utils.h"
+
+#define X 123456789
+#define Y 987654321
+#define Z 43219876
+
+static uint32_t seed = 6543217;
+static uint32_t x = X;
+static uint32_t y = Y;
+static uint32_t z = Z;
+
+void srand(u_int _seed) {
+    seed = _seed;
+    x = X;
+    y = Y;
+    z = Z;
+}
+
+// Implementation of Marsaglia JKISS RNG uses 64-bit value and 2x multiplies
+// Source: https://github.com/SpiNNakerManchester/spinn_common/tree/master
+u_int rand() {
+    x = 314527869 * x + 1234567;
+    y ^= y << 5;
+    y ^= y >> 7;
+    y ^= y << 22;
+    uint64_t t = 4294584393ULL * z + seed;
+    seed = t >> 32;
+    z = t;
+
+    return (uint32_t) (x + y + z);
+}
+
+void memset(void *dst, uint8_t value, u_int count) {
+    uint8_t *d = dst;
+    while (count--) {
+        *d++ = value;
+    }
+}
+
+void memcpy(void *dst, void *src, u_int count) {
+    uint8_t *d = dst;
+    uint8_t *s = src;
+    while (count--)
+        *d++ = *s++;
+}
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/utils.h b/labs/lab-virtual_game_machine/skeleton/guest/utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..8b1974f4e54928ddcd52dd6a7b386eb26841d04a
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/utils.h
@@ -0,0 +1,13 @@
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+void srand(u_int _seed);
+u_int rand();
+
+void memset(void *dst, uint8_t value, u_int count);
+void memcpy(void *dst, void *src, u_int count);
+
+#endif
\ No newline at end of file
diff --git a/labs/lab-virtual_game_machine/skeleton/guest/x86.h b/labs/lab-virtual_game_machine/skeleton/guest/x86.h
new file mode 100644
index 0000000000000000000000000000000000000000..58260d774f114317d5603b5ee222c4cf345adc36
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/guest/x86.h
@@ -0,0 +1,20 @@
+#ifndef _X86_H_
+#define _X86_H_
+
+// Disable hardware interrupts.
+static inline void cli() {
+    asm volatile("cli");
+}
+
+// Enable hardware interrupts.
+static inline void sti() {
+    asm volatile("sti");
+}
+
+// Halt the processor.
+// External interrupts wake up the CPU, hence the cli instruction.
+static inline void halt() {
+    while (1) asm volatile("cli\nhlt");
+}
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/shared/hypercall_params.h b/labs/lab-virtual_game_machine/skeleton/shared/hypercall_params.h
new file mode 100644
index 0000000000000000000000000000000000000000..b7ec132ac6572dc5b3c2514b13687770d44753b0
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/shared/hypercall_params.h
@@ -0,0 +1,10 @@
+#ifndef _HYPERCALL_PARAMS_H_
+#define _HYPERCALL_PARAMS_H_
+
+#include <stdint.h>
+
+typedef struct {
+    uint32_t us;  // delay in micro-seconds
+} __attribute__((packed)) hyper_timer_sleep_params_t;
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/shared/ide_regs.h b/labs/lab-virtual_game_machine/skeleton/shared/ide_regs.h
new file mode 100644
index 0000000000000000000000000000000000000000..a85c915f888dab636f800a5bc97167cd78d8e814
--- /dev/null
+++ b/labs/lab-virtual_game_machine/skeleton/shared/ide_regs.h
@@ -0,0 +1,13 @@
+#ifndef _IDE_REGS_H_
+#define _IDE_REGS_H_
+
+// Status register on the primary bus (read-only)
+#define IDE_STATUS_REG     0x1F7
+
+// Command register on the primary bus (write-only)
+#define IDE_CMD_REG        0x1F7
+
+// Base port on primary bus (write-only)
+#define IDE_DATA_BASE_REG  0x1F0
+
+#endif
diff --git a/labs/lab-virtual_game_machine/skeleton/vmm/Makefile b/labs/lab-virtual_game_machine/skeleton/vmm/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391