Monde virtuel Intel. Partie 2: SMP

Dans un article précédent (lien), j'ai parlé du concept de base d'un hyperviseur basé sur la technologie de virtualisation matérielle Intel. Maintenant, je propose d'étendre les capacités de l'hyperviseur en ajoutant la prise en charge de l'architecture multiprocesseur (SMP), et je considère également un exemple de la façon dont l'hyperviseur peut apporter des modifications au système d'exploitation invité.

Toutes les autres actions seront effectuées sur le PC avec la configuration suivante:

Processeur: Intel Core i7 5820K
Carte mère: Asus X99-PRO
RAM: 16 Go
Système d'exploitation invité: Windows 7 x32 avec PAE désactivé

Je vais commencer par décrire l'emplacement des composants de l'hyperviseur sur le disque dur (toutes les valeurs sont spécifiées en secteurs).

image
Le processus de chargement d'un hyperviseur ne diffère de la version précédente qu'en présence d'un nouveau module hyperviseur.ap , dont le but est l'initialisation de base du processeur AP.

Le processus de chargement des modules en mémoire:



Prise en charge SMP

J'ai implémenté un hyperviseur sur le principe du multitraitement symétrique, ce qui signifie que la même copie de VMX sera lancée sur tous les processeurs logiques présents. De plus, les tables IDT et GDT, ainsi que les tables de mémoire de pagination, seront communes à tous les processeurs logiques. J'ai fait cela parce que l'hyperviseur initialisera immédiatement la mémoire de l'espace d'adressage du système d'exploitation invité et il n'est pas nécessaire de réaffecter dynamiquement les adresses physiques des pages individuelles. De plus, avec cette approche, vous n'avez pas besoin de surveiller la correspondance des caches TLB du processeur sur le côté de l'hyperviseur.
Le processus d'initialisation pour BSP et AP sera différent. Toutes les structures majeures impliquées dans l'hyperviseur seront créées lors de l'initialisation du BSP. De plus, l'état d'activité des processeurs AP en mode non root vmx sera défini sur l'état HLT. Ainsi, l'environnement du système d'exploitation invité sera émulé conformément à ce qu'il serait sans utiliser la virtualisation.

Initialisation de BSP:

  1. Initialisation de Spinlock
  2. Initialisation et chargement des tables GDT et IDT
  3. Initialisation des tables de pagination
  4. Initialisation des structures VMCS et création d'une table EPT commune
  5. Activation des processeurs AP. Pour ce faire, une séquence d'interruption INIT - SIPI est envoyée à chaque AP. Le vecteur de l'interruption SIPI est 0x20, ce qui correspond au transfert du contrôle AP à 0x20000 (module hypervisor.ap)
  6. Démarrage du système d'exploitation invité à 0x7C00 (module win7.mbr)

AP d'initialisation:

  1. Après avoir activé l'AP, le processeur est en mode réel. Le module hypervisor.ap initialise la mémoire et les tables de pagination pour passer en mode long
  2. Téléchargez IDT, GDT, ainsi que le catalogue des tables de pagination créées lors de la phase d'initialisation BSP
  3. Initialisation des structures VMCS et chargement des tables EPT créées à l'étape d'initialisation de BSP
  4. Passage en mode non root vmx avec état HLT actif

Nous pouvons dire que la mise en œuvre du support SMP dans l'hyperviseur est assez simple, mais il y a quelques points sur lesquels je voudrais attirer l'attention.

Prise en charge de l'héritage USB

Les nouveaux modèles de carte mère peuvent ne pas avoir de connecteurs PS / 2, donc la prise en charge USB Legacy est utilisée pour assurer la compatibilité descendante. Cela signifie que vous pouvez travailler avec un clavier ou une souris USB en utilisant les mêmes méthodes (ports d'entrée / sortie) qu'avec la norme PS / 2. La mise en œuvre de la prise en charge USB Legacy dépend non seulement du modèle de la carte mère, mais peut également être appelée dans différentes versions de micrologiciel. Sur ma carte mère Asus X99-PRO, la prise en charge USB Legacy est implémentée via des interruptions SMI, dans le processeur dont l'émulation PS / 2 se produit. J'écris à ce sujet en détail, car dans mon cas (version du micrologiciel 3801), USB Legacy Support n'est pas compatible avec le mode long et lorsqu'il revient de SMM, le processeur passe à l'état d'arrêt.

La solution la plus simple dans cette situation consiste à désactiver la prise en charge USB Legacy avant de passer en mode long. Cependant, sous Windows, la méthode d'interrogation du clavier PS / 2 est utilisée au stade de la sélection des options de démarrage, donc USB Legacy Support doit être réactivé avant le chargement du système d'exploitation invité.

2. Commutateur de tâches matérielles

Dans les systèmes d'exploitation modernes, le basculement entre les tâches est généralement implémenté par des méthodes logicielles. Cependant, dans Windows7, les sélecteurs pointant vers TSS sont affectés à l'interruption 2 - NMI et 8 - Double Fault, ce qui signifie que de telles interruptions entraîneront un changement de contexte matériel. Intel VMX ne prend pas en charge le commutateur de tâches matériel, et une tentative de l'exécuter conduit à VM Exit. Pour de tels cas, j'ai écrit mon gestionnaire de commutateur de tâches (fonction GuestTaskSwitch). Une interruption Double Fault se produit uniquement en cas de conflit système grave provoqué par une mauvaise manipulation des autres interruptions. Au cours du débogage, je ne l'ai pas rencontré. Mais NMI apparaît sur les processeurs AP au moment du redémarrage de Windows. Cela soulève toujours mes doutes car il n'est pas clair si ces NMI sont le résultat d'un processus de redémarrage régulier ou si ce fonctionnement incorrect de l'hyperviseur à certaines des étapes précédentes. Si vous avez des informations à ce sujet, veuillez en parler dans les commentaires ou écrivez-moi dans un message personnel.

Changements dans l'OS invité

Honnêtement, je ne pouvais pas décider pendant longtemps exactement quels changements l'hyperviseur devrait apporter au travail de l'OS invité. Le fait est que, d'une part, je voulais montrer quelque chose d'intéressant, comme l'introduction de nos gestionnaires dans les protocoles réseau de base, mais d'autre part, tout se résumerait à une grande quantité de code, et il n'y avait pas grand-chose à voir avec le sujet d'un hyperviseur. De plus, je ne voulais pas lier l'hyperviseur à un ensemble spécifique de fer.

En conséquence, le compromis suivant a été trouvé: dans cette version de l'hyperviseur, le contrôle des appels système à partir du mode utilisateur est implémenté, en d'autres termes, il sera possible de contrôler le fonctionnement des applications s'exécutant dans le système d'exploitation invité. Ce type de contrôle est assez simple à mettre en œuvre et permet en plus d'obtenir un résultat visuel du travail.

Le contrôle du fonctionnement des applications se fera au niveau des appels système. Et l'objectif principal sera de modifier le résultat de la fonction NtQuerySystemInformation afin que lorsque vous appelez avec l' argument SystemProcessInformation ( 0x05 ), vous puissiez intercepter les informations de processus.

Dans Windows 7, le programme d'application pour appeler la fonction système utilise la commande assembler sysenter, après quoi le contrôle est transféré au processeur KiFastCallEntry vers le noyau au niveau r0. Pour revenir au niveau d'application r3, utilisez la commande sysexit.
Pour accéder aux résultats de l' exécution de la fonction NtQuerySystemInformation, il est nécessaire d'enregistrer le numéro de la fonction appelée à chaque exécution de la commande sysenter. Ensuite, lors de l'exécution de sysexit, comparez la valeur stockée avec le numéro de la fonction interceptée et, en cas de correspondance, apportez des modifications aux données renvoyées par la fonction.
Intel VMX ne fournit pas de moyen direct de surveiller l'exécution de sysenter / sysexit , cependant, si vous écrivez la valeur 0 dans Guest MSR IA32_SYSENTER_CS , les commandes sysenter / sysexit lèveront une exception GP qui peut être utilisée pour appeler le gestionnaire VM Exit. Pour que l'exception GP appelle VM Exit, vous devez définir 13 bits dans le champ Bitmap d'exception de VMCS.

La structure ci-dessous est utilisée pour émuler la paire sysenter / sysexit.

typedef struct{ QWORD ServiceNumber; QWORD Guest_Sys_CS; QWORD Guest_Sys_EIP; QWORD Guest_Sys_ESP; } SysEnter_T; 

Le champ ServiceNumber contient le numéro de la fonction appelée et est mis à jour à chaque appel à sysenter.

Les champs Guest_Sys_CS, Guest_Sys_EIP, Guest_Sys_ESP sont mis à jour lorsque l'OS invité essaie d'écrire dans le registre MSR correspondant. Pour ce faire, des masques d'écriture dans l' adresse MSR-Bitmap sont définis .

 // 174H 372 IA32_SYSENTER_CS SYSENTER_CS write mask ptrMSR_BMP[0x100 + (0x174 >> 6)] |= (1UL << (0x174 & 0x3F)); // 175H 373 IA32_SYSENTER_ESP SYSENTER_ESP write mask ptrMSR_BMP[0x100 + (0x175 >> 6)] |= (1UL << (0x175 & 0x3F)); // 176H 374 IA32_SYSENTER_EIP SYSENTER_EIP write mask ptrMSR_BMP[0x100 + (0x176 >> 6)] |= (1UL << (0x176 & 0x3F)); 

L'OS invité ne doit pas voir les modifications apportées par l'hyperviseur au fonctionnement des appels de fonction système. En définissant le masque de lecture pour MSR IA32_SYSENTER_CS, vous pouvez ramener le système d'exploitation invité à sa valeur de registre d'origine lors de la lecture.

 // 174H 372 IA32_SYSENTER_CS SYSENTER_CS read mask ptrMSR_BMP[0x174 >> 6] |= (1UL << (0x174 & 0x3F)); 

Voici un schéma d'émulation de commande sysenter / sysexit .



Au stade de l' émulation sysexit , le numéro stocké de la fonction appelée est comparé au numéro NtQuerySystemInformation (0x105). Dans le cas d'une correspondance, il est vérifié que NtQuerySystemInformation est appelé avec l'argument System Process Information et si c'est le cas, la fonction ChangeProcessNames (DWORD SPI_GVA, DWORD SPI_size) modifie les structures contenant des informations sur les processus.
SPI_GVA est l'adresse virtuelle invitée de la structure SYSTEM_PROCESS_INFORMATION
SPI_size est la taille totale des structures en octets.
La structure SYSTEM_PROCESS_INFORMATION elle-même ressemble à ceci:

 typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; BYTE Reserved1[48]; UNICODE_STRING ImageName; KPRIORITY BasePriority; HANDLE UniqueProcessId; PVOID Reserved2; ULONG HandleCount; ULONG SessionId; PVOID Reserved3; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG Reserved4; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; PVOID Reserved5; SIZE_T QuotaPagedPoolUsage; PVOID Reserved6; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER Reserved7[6]; } SYSTEM_PROCESS_INFORMATION; 

Il n'y a rien de compliqué dans son analyse, l'essentiel est de ne pas oublier de traduire l'adresse virtuelle de l'invité en physique, pour cela la fonction GuestLinAddrToPhysAddr () est utilisée.

Pour plus de clarté, j'ai remplacé les deux premiers caractères des noms de tous les processus par un signe « :) ». Le résultat d'un tel remplacement est visible sur la capture d'écran.



Résumé

En général, les tâches définies au début de l'article étaient terminées. L'hyperviseur assure le fonctionnement stable du système d'exploitation invité et contrôle également l'appel des fonctions système au niveau de l'application. Je note que le principal inconvénient de l'utilisation de l'émulation de commande sysenter / sysexit est une augmentation significative des appels de sortie VM, ce qui affecte les performances et cela est particulièrement visible lorsque le système d'exploitation invité est en mode mono-processeur. Cet inconvénient peut être éliminé si vous contrôlez les appels uniquement dans le contexte des processus sélectionnés.

Et c'est tout pour l'instant. Les sources de l'article peuvent être consultées ici

Merci de votre attention.

Source: https://habr.com/ru/post/fr429918/


All Articles