Difference between revisions of "Qemu"

From air
Jump to navigation Jump to search
 
(27 intermediate revisions by 2 users not shown)
Line 1: Line 1:
  +
=Qemu=
Un émulateur de référence, la page wikipédia [http://en.wikipedia.org/wiki/QEMU]
 
  +
 
Qemu est un émulateur de référence dont on trouve une définition plus exhaustive sur la page wikipédia de [http://en.wikipedia.org/wiki/QEMU QEMU]
  +
  +
Le logiciel Qemu à été utilisé pour différents projets innovant dans le cadre de la formation RICM4 de Polytech Grenoble :
  +
* Projet d'émulation de la carte STMicroelectronics [[STM32_Discovery#STM32L-DISCOVERY | STM32L-Discovery]] ([[Proj-2011-2012-qemu |fiche]])
  +
* Projet d'amélioration de l'émulation de la carte [[Stellaris Luminary Lm3s6965]] ([[Fiche_Projet_Qemu_Stellaris | fiche]])
  +
   
 
== Installation de qemu ==
 
== Installation de qemu ==
Line 17: Line 24:
   
 
== Composants ==
 
== Composants ==
  +
Un exemple détaillé de la structure d'un composant est disponible sur la [[Proj-2011-2012-qemu#Exemple|fiche projet]] de l'émulation de la carte STM32L-Discovery.
  +
 
=== Assemblage ===
 
=== Assemblage ===
Qemu est un programme modulaire. Chaque fichier représente l'émulation d'un périphérique.
+
Qemu est un programme modulaire. Chaque fichier présent dans le dossier hardware (src/hw) représente l'émulation d'un ou plusieurs périphériques. On trouve par exemple dans src/hw/ les fichiers suivants :
Ex:
 
 
*pl022.c : [http://fr.wikipedia.org/wiki/Port_s%C3%A9rie Port série] synchrone
 
*pl022.c : [http://fr.wikipedia.org/wiki/Port_s%C3%A9rie Port série] synchrone
 
*pl050.c : Interface clavier
 
*pl050.c : Interface clavier
Line 25: Line 33:
   
 
Pour émuler un système complet, il faut donc le constituer composant par composant.
 
Pour émuler un système complet, il faut donc le constituer composant par composant.
  +
Chaque composant est crée grâce à la fonction :
Ex:
 
  +
DeviceState *sysbus_create_simple(const char *name,
dev = sysbus_create_varargs("device-name", 0x40020000, irq1, irq2, irq3, NULL);
 
  +
target_phys_addr_t addr,
adc = qdev_get_gpio_in(dev, 0);
 
  +
qemu_irq irq)
  +
ou, s'il y plusieurs irq :
  +
DeviceState *sysbus_create_varargs(const char *name,
  +
target_phys_addr_t addr, ...);
  +
  +
Un composant est représenté dans Qemu par la structure suivante contenant les liens avec les autres composants et les zones de la mémoire émulée qu'il utilise:
  +
struct SysBusDevice {
  +
'''DeviceState qdev''';
  +
int num_irq;
  +
qemu_irq irqs[QDEV_MAX_IRQ];
  +
qemu_irq *irqp[QDEV_MAX_IRQ];
  +
int num_mmio;
  +
struct {
  +
target_phys_addr_t addr;
  +
target_phys_addr_t size;
  +
mmio_mapfunc cb;
  +
mmio_mapfunc unmap;
  +
ram_addr_t iofunc;
  +
MemoryRegion *memory;
  +
} mmio[QDEV_MAX_MMIO];
  +
int num_pio;
  +
pio_addr_t pio[QDEV_MAX_PIO];
  +
};
  +
  +
Cette structure contient une autre structure appelée '''qdev''' gérant le lien avec les autres composants. C'est au travers de l'api définie dans '''qdev.h''' que ces liens sont définis :
 
qemu_irq qdev_get_gpio_in(DeviceState *dev, int n);
  +
void qdev_connect_gpio_out(DeviceState *dev, int n, qemu_irq pin);
  +
  +
''Remarque :''L'appel à cette API devrait disparaitre dans les prochaines versions de qemu au profit d'un fichier de configuration définissant le composant ainsi que ses connexions avec les autres composants.
  +
  +
Un lecteur attentif aura remarqué l'utilisation de la structure de données '''qemu_irq''' dans les fonctions précédentes.
  +
Une '''qemu_irq''' est en fait la représentation d'une interruption matériel à l’intérieur de Qemu, '''mais''' elle est couramment utilisée pour envoyer des signaux entre les différents composants. Elle est de la forme suivante :
  +
typedef struct IRQState *qemu_irq;
  +
struct IRQState {
  +
qemu_irq_handler handler;
  +
void *opaque;
  +
int n;
  +
};
   
 
=== Mémoire ===
 
=== Mémoire ===
Pour fonctionner, un composant peut avoir besoin d'utiliser une zone de la mémoire fournie par le système. L'adresse de cette zone est définie lors de l'appel à la fonction sysbus_create_*.
+
Pour fonctionner, un composant peut avoir besoin d'utiliser une zone de la mémoire fournie par le système. L'adresse de cette zone est définie lors de l'appel des fonction sysbus_create.
Une fonction d'initialisation dans le composant est alors chargée de contacter qemu afin qu'il lui délègue la gestion de cette zone.
+
Une fonction d'initialisation dans le composant est alors chargée de contacter Qemu afin qu'il lui délègue la gestion de cette zone.
iomemtype = cpu_register_io_memory(lectureHandler, ecritureHandler, deviceData, NATIVE_ENDIAN);
+
iomemtype = cpu_register_io_memory(readHandler, writeHandler, deviceData, NATIVE_ENDIAN);
sysbus_init_mmio(dev, 0x1000, iomemtype);
+
sysbus_init_mmio(dev, '''0x1000''', iomemtype);
   
  +
Ou 0x1000 est la taille réservée en mémoire pour le composant.
   
 
== Compilation et lancement du programme émulé==
 
== Compilation et lancement du programme émulé==
  +
Ce chapitre est '''très résumé'''. Si vous cherchez un document plus exhaustif [http://www.bravegnu.org/gnu-eprog/index.html ce site] très bien fait explique tout de A à Z.
  +
 
=== Cross compilation ===
 
Dans le cas le système émulé n'a pas la même architecture que le système hôte, il est nécessaire d'utiliser un utilitaire de cross-compilation à la place de l'utilitaire de compilation standard de l'hôte.
  +
 
=== Emulation ===
 
=== Emulation ===
*Compiler les sources du programme (dans le cas ou l'architecture du système hote est différente de cette du système cible, voir la rubrique: cross compilation)
+
*Compiler les sources du programme (dans le cas ou l'architecture du système hôte est différente de celle du système cible, voir la rubrique: cross compilation)
 
*Lancement de Qemu (dans le cas d'une cible [http://fr.wikipedia.org/wiki/Architecture_ARM ARM])
 
*Lancement de Qemu (dans le cas d'une cible [http://fr.wikipedia.org/wiki/Architecture_ARM ARM])
 
./qemu-system-arm -M stm32l152rbt6 -nographic -kernel ./main.bin
 
./qemu-system-arm -M stm32l152rbt6 -nographic -kernel ./main.bin
Options
+
Options :
*-M xxxx : Sélection de la machine émulée
+
* -M xxxx : Sélection de la machine émulée
  +
* -nographic : désactive les entrées/sorties (I/O) graphiques et redirige les I/O séries vers la console
*--kernel xxxx : Programme à émuler
+
* -kernel xxxx : Programme à émuler
 
  +
La liste d'options peut être retrouvée grâce à l'option --help
=== Cross compilation ===
 
Dans le cas ou le système émulé n'a pas la même architecture que le système hôte, il est nécessaire d'utiliser un utilitaire de cross-compilation à la place de l'utilitaire de compilation.
 
 
   
 
== Qemu et GDB ==
 
== Qemu et GDB ==
 
=== Lancement en mode debug ===
 
=== Lancement en mode debug ===
Il est possible d'interfacer Qemu avec [http://fr.wikipedia.org/wiki/GNU_Debugger GDB]. Ainsi, il sera aisé de suivre le déroulement du programme émulé.
+
Il est possible de lier Qemu à [http://fr.wikipedia.org/wiki/GNU_Debugger GDB]. Ainsi, il sera aisé de suivre le déroulement du programme émulé.
 
./qemu-system-arm -M stm32l152rbt6 -nographic -kernel ./main.bin -S -gdb tcp::51234
 
./qemu-system-arm -M stm32l152rbt6 -nographic -kernel ./main.bin -S -gdb tcp::51234
 
Options:
 
Options:
 
*-S : Active le mode debug
 
*-S : Active le mode debug
 
*-gdb tcp::<port> : Spécifie l'interface de débbogage
 
*-gdb tcp::<port> : Spécifie l'interface de débbogage
  +
  +
''Remarque :'' Il faut également que le programme éxécuté par l'émulateur ai été compilé avec les options de débogage
   
 
=== Connexion avec GDB ===
 
=== Connexion avec GDB ===
 
Il suffit maintenant de connecter gdb à qemu.
 
Il suffit maintenant de connecter gdb à qemu.
arm-none-eabi
+
arm-none-eabi-gdb
 
target remote localhost:51234
 
target remote localhost:51234
 
load ./main.elf
 
load ./main.elf
Line 67: Line 119:
 
== CharDev ==
 
== CharDev ==
 
=== Présentation ===
 
=== Présentation ===
Qemu offre un système de communication avec l'environnement exterieur: les char devices.
+
Qemu offre un système de communication avec l'environnement extérieur: les char devices.
Ce sont des flux offert par qemu pour qu'un composant puisse lire ou écrire.
+
Ce sont des flux offerts par Qemu sur lesquels un composant peut lire ou écrire.
Lors du lancement de l'émulation, il sera possible de connecter ces flux à des structure informatique (telnet, socket, fichier, ...)
+
Lors du lancement de l'émulation, il sera possible de connecter ces flux à des structure informatique (telnet, socket, fichier, ...).
D'un coté il faudra implémenté le chardev dans un composant de qemu, puis de l'autre coté il faudra fournir au démarrage de l'application les connexion nécessaires aux chardev.
+
D'un coté il faudra implémenter le chardev dans un composant de Qemu, puis de l'autre coté il faudra fournir au démarrage de l'application les connexions nécessaires aux chardev.
   
=== Développement ===
+
=== Implémentation ===
 
*Initialisation du chardev
 
*Initialisation du chardev
 
chrdev = qemu_chr_find("idCharDev");
 
chrdev = qemu_chr_find("idCharDev");
Line 80: Line 132:
 
*Envoi de caractères dans le chardev. Le buffer est un tableau de uint8_t
 
*Envoi de caractères dans le chardev. Le buffer est un tableau de uint8_t
 
qemu_chr_fe_write(chrdev, buffer, bufferSize);
 
qemu_chr_fe_write(chrdev, buffer, bufferSize);
  +
*La réception des données se fait de la même manière à travers la fonction receiveHandler de type void IOReadHandler définie par la signature suivante:
  +
void IOReadHandler(void *opaque, const uint8_t *buf, int size);
   
 
=== Utilisation ===
 
=== Utilisation ===
  +
Il suffit de lancer Qemu avec l'option -chardev. Voici un exemple d'utilisation:
La documentation de qemu expose les différentes options à passer pour connecter les chardev:
 
  +
./src/arm-softmmu/qemu-system-arm -M stm32l152rbt6 -nographic '''-chardev socket,id=B,port=4242,host=localhost,nodelay''' -kernel ./main.bin
 
La documentation de Qemu expose les différentes options à passer pour connecter les chardev:
 
[http://wiki.qemu.org/download/qemu-doc.html UserDoc]
 
[http://wiki.qemu.org/download/qemu-doc.html UserDoc]

Latest revision as of 14:35, 26 April 2012

Qemu

Qemu est un émulateur de référence dont on trouve une définition plus exhaustive sur la page wikipédia de QEMU

Le logiciel Qemu à été utilisé pour différents projets innovant dans le cadre de la formation RICM4 de Polytech Grenoble :


Installation de qemu

  • Récuperer la dernière version de qemu ici (v1.0 lors de la rédaction de cet article)
  • Décompresser le dossier Qemu-x.xtar.gz
  • Configuration de compilation

Dans le dossier extrait, taper la commande :

./configure --target-list=arm-softmmu --python=/usr/bin/python2.7

La première option permet de compiler qemu uniquement pour l'émulation de programmes destinés à une architecture ARM. La deuxième option permet de forcer l'utilisation de la version 2.7 de python.

  • Compiler les sources

Executer la commande :

make

Le résultat de la compilation sera placé dans un sous-répertoire en fonction des options utilisées dans la configuation (Ex: arm-softmmu)


Composants

Un exemple détaillé de la structure d'un composant est disponible sur la fiche projet de l'émulation de la carte STM32L-Discovery.

Assemblage

Qemu est un programme modulaire. Chaque fichier présent dans le dossier hardware (src/hw) représente l'émulation d'un ou plusieurs périphériques. On trouve par exemple dans src/hw/ les fichiers suivants :

  • pl022.c : Port série synchrone
  • pl050.c : Interface clavier
  • pl061.c : GPIO

Pour émuler un système complet, il faut donc le constituer composant par composant. Chaque composant est crée grâce à la fonction :

DeviceState *sysbus_create_simple(const char *name,
                                             target_phys_addr_t addr,
                                             qemu_irq irq)

ou, s'il y plusieurs irq :

DeviceState *sysbus_create_varargs(const char *name,
                                target_phys_addr_t addr, ...);

Un composant est représenté dans Qemu par la structure suivante contenant les liens avec les autres composants et les zones de la mémoire émulée qu'il utilise:

struct SysBusDevice {
   DeviceState qdev;
   int num_irq;
   qemu_irq irqs[QDEV_MAX_IRQ];
   qemu_irq *irqp[QDEV_MAX_IRQ];
   int num_mmio;
   struct {
       target_phys_addr_t addr;
       target_phys_addr_t size;
       mmio_mapfunc cb;
       mmio_mapfunc unmap;
       ram_addr_t iofunc;
       MemoryRegion *memory;
   } mmio[QDEV_MAX_MMIO];
   int num_pio;
   pio_addr_t pio[QDEV_MAX_PIO];
};

Cette structure contient une autre structure appelée qdev gérant le lien avec les autres composants. C'est au travers de l'api définie dans qdev.h que ces liens sont définis :

qemu_irq qdev_get_gpio_in(DeviceState *dev, int n);
void qdev_connect_gpio_out(DeviceState *dev, int n, qemu_irq pin);

Remarque :L'appel à cette API devrait disparaitre dans les prochaines versions de qemu au profit d'un fichier de configuration définissant le composant ainsi que ses connexions avec les autres composants.

Un lecteur attentif aura remarqué l'utilisation de la structure de données qemu_irq dans les fonctions précédentes. Une qemu_irq est en fait la représentation d'une interruption matériel à l’intérieur de Qemu, mais elle est couramment utilisée pour envoyer des signaux entre les différents composants. Elle est de la forme suivante :

typedef struct IRQState *qemu_irq;
struct IRQState {
   qemu_irq_handler handler;
   void *opaque;
   int n;
};

Mémoire

Pour fonctionner, un composant peut avoir besoin d'utiliser une zone de la mémoire fournie par le système. L'adresse de cette zone est définie lors de l'appel des fonction sysbus_create. Une fonction d'initialisation dans le composant est alors chargée de contacter Qemu afin qu'il lui délègue la gestion de cette zone.

iomemtype = cpu_register_io_memory(readHandler, writeHandler, deviceData, NATIVE_ENDIAN);
sysbus_init_mmio(dev, 0x1000, iomemtype);

Ou 0x1000 est la taille réservée en mémoire pour le composant.

Compilation et lancement du programme émulé

Ce chapitre est très résumé. Si vous cherchez un document plus exhaustif ce site très bien fait explique tout de A à Z.

Cross compilation

Dans le cas où le système émulé n'a pas la même architecture que le système hôte, il est nécessaire d'utiliser un utilitaire de cross-compilation à la place de l'utilitaire de compilation standard de l'hôte.

Emulation

  • Compiler les sources du programme (dans le cas ou l'architecture du système hôte est différente de celle du système cible, voir la rubrique: cross compilation)
  • Lancement de Qemu (dans le cas d'une cible ARM)
./qemu-system-arm -M stm32l152rbt6 -nographic -kernel ./main.bin

Options :

  • -M xxxx : Sélection de la machine émulée
  • -nographic : désactive les entrées/sorties (I/O) graphiques et redirige les I/O séries vers la console
  • -kernel xxxx : Programme à émuler

La liste d'options peut être retrouvée grâce à l'option --help

Qemu et GDB

Lancement en mode debug

Il est possible de lier Qemu à GDB. Ainsi, il sera aisé de suivre le déroulement du programme émulé.

./qemu-system-arm -M stm32l152rbt6 -nographic -kernel ./main.bin -S -gdb tcp::51234

Options:

  • -S : Active le mode debug
  • -gdb tcp::<port> : Spécifie l'interface de débbogage

Remarque : Il faut également que le programme éxécuté par l'émulateur ai été compilé avec les options de débogage

Connexion avec GDB

Il suffit maintenant de connecter gdb à qemu.

arm-none-eabi-gdb
target remote localhost:51234
load ./main.elf
continue


CharDev

Présentation

Qemu offre un système de communication avec l'environnement extérieur: les char devices. Ce sont des flux offerts par Qemu sur lesquels un composant peut lire ou écrire. Lors du lancement de l'émulation, il sera possible de connecter ces flux à des structure informatique (telnet, socket, fichier, ...). D'un coté il faudra implémenter le chardev dans un composant de Qemu, puis de l'autre coté il faudra fournir au démarrage de l'application les connexions nécessaires aux chardev.

Implémentation

  • Initialisation du chardev
chrdev = qemu_chr_find("idCharDev");
if (s->chr) {
    chr_add_handlers(chrdev, canReceiveHandler, receiveHandler, eventHandler, deviceData);
}
  • Envoi de caractères dans le chardev. Le buffer est un tableau de uint8_t
qemu_chr_fe_write(chrdev, buffer, bufferSize);
  • La réception des données se fait de la même manière à travers la fonction receiveHandler de type void IOReadHandler définie par la signature suivante:
void IOReadHandler(void *opaque, const uint8_t *buf, int size);

Utilisation

Il suffit de lancer Qemu avec l'option -chardev. Voici un exemple d'utilisation:

./src/arm-softmmu/qemu-system-arm -M stm32l152rbt6 -nographic -chardev socket,id=B,port=4242,host=localhost,nodelay -kernel ./main.bin

La documentation de Qemu expose les différentes options à passer pour connecter les chardev: UserDoc