Semaine Athens Linux Device Drivers


Affichage de messages

Le premier mécanisme pour déboguer (et vous l'avez déjà utilisé) consiste à afficher des messages.

API générique

Les fonctions suivantes sont définies dans include/linux/printk.h :

pr_emerg(fmt, ...);
pr_alert(fmt, ...);
pr_crit(fmt, ...);
pr_err(fmt, ...);
pr_warning(fmt, ...);
pr_notice(fmt, ...);
pr_info(fmt, ...);

Ces fonctions se comportent de la même manière que la fonction C printf. Leur premier argument est une chaîne de caractères pouvant inclure des caractères spéciaux qui seront remplacés par les éventuels arguments ultérieurs de la fonction.

Les messages sont stockés dans tampon particulier : __log_buf déclaré dans kernel/printk/printk.c. Le contenu de ce tampon peut être consulté depuis l'espace utilisateur grâce à la commande dmesg.

La différence entre les fonctions ci-dessus est la priorité du message. Si la priorité dépasse un certain seuil, le message sera affiché directement sur la console. Ce seuil peut être fixé au démarrage du noyau à l'aide du paramètre loglevel passé à la ligne de commande du noyau (voir Documentation/admin-guide/kernel-parameters.txt ou dynamiquement grâce à la variable kernel.printk qui peut être modifiée via sysctl (voir Documentation/sysctl/kernel.txt et man sysctl).

La fonction pr_debug(fmt, ...) fonctionne de façon différente. L'appel à la fonction sera supprimé (non compilé) sauf si le symbole DEBUG est défini lors de la compilation du module. Ce dernier peut être défini au début du fichier (via #define DEBUG) ou dans le Makefile (voir Documentation/kbuild/makefiles.txt comment définir un drapeau de compilation spécifique à un module). Il existe enfin un mécanisme permettant d'activer dynamiquement les messages de debug (voir Documentation/admin-guide/dynamic-debug-howto.rst).

API spéciale pilotes

Pour les pilotes de périphérique, il est recommandé d'utiliser, à la place des fonctions pr_*, les fonctions suivantes, définies dans include/linux/device.h :

dev_emerg(const struct device *dev, const char *fmt, ...);
dev_alert(const struct device *dev, const char *fmt, ...);
dev_crit(const struct device *dev, const char *fmt, ...);
dev_err(const struct device *dev, const char *fmt, ...);
dev_warn(const struct device *dev, const char *fmt, ...);
dev_notice(const struct device *dev, const char *fmt, ...);
dev_info(const struct device *dev, const char *fmt, ...);
dev_dbg(const struct device *dev, const char *fmt, ...);

Elles fonctionnent de la même manière que les fonctions pr_* présentées précédemment. Elles prennent simplement un argument supplémentaire (le premier) qui est un pointeur vers la structure struct device représentant le périphérique. En plus du message, elles affichent le nom du périphérique pour identifier la provenance.

Debugfs

Debugfs est un système de fichier spécial destiné à faciliter la mise à disposition d'informations sur l'état interne d'un module vers l'espace utilisateur.

Pour pouvoir l'utiliser, il doit d'abord avoir été compilé dans le noyau (CONFIG_DEBUG_FS dans Kernel hacking ---> Debug Filesystem, ce qui est le cas pour le noyau fourni pour la carte de TP).

Il faut ensuite monter le système de fichier (traditionnellement, le point de montage est sys/kernel/debug) :

# mount -t debugfs none /sys/kernel/debug

Au niveau du pilote, la première chose à faire pour pouvoir exporter des informations via debugfs est de créer un répertoire dans ce système de fichier à l'aide de la fonction debugfs_create_dir (définie dans include/linux/debugfs.h) :

struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);

name est le nom que portera le répertoire et parent un pointeur vers le répertoire parent ou NULL si on crée le répertoire à la racine de debugfs. La fonction renvoie un pointeur vers la structure représentant le répertoire ainsi créé (ou NULL en cas d'erreur).

struct dentry *debugfs_create_u8(const char *name, umode_t mode,
                                 struct dentry *parent, u8 *value);
struct dentry *debugfs_create_u16(const char *name, umode_t mode,
                                  struct dentry *parent, u16 *value);
struct dentry *debugfs_create_u32(const char *name, umode_t mode,
                                  struct dentry *parent, u32 *value);
struct dentry *debugfs_create_u64(const char *name, umode_t mode,
                                  struct dentry *parent, u64 *value);
struct dentry *debugfs_create_x8(const char *name, umode_t mode,
                                 struct dentry *parent, u8 *value);
struct dentry *debugfs_create_x16(const char *name, umode_t mode,
                                  struct dentry *parent, u16 *value);
struct dentry *debugfs_create_x32(const char *name, umode_t mode,
                                  struct dentry *parent, u32 *value);
struct dentry *debugfs_create_x64(const char *name, umode_t mode,
                                  struct dentry *parent, u64 *value);

Ces fonctions permettent d'exposer un entier non signé (en base 10 pour la version u et en base 16 pour la version x) dans un fichier nommé name dans le répertoire parent. Les permissions de ce fichier sont définies par mode (exemple : S_IRUSR pour des droits 400, c'est-à-dire lecture seule pour l'utilisateur, ou S_IRUSR|S_IWUSR pour des droits 600, c'est-à-dire lecture et écriture pour l'utilisateur ; l'utilisateur propriétaire des fichiers est forcément root).

struct debugfs_blob_wrapper {
    void *data;
    unsigned long size;
};

struct dentry *debugfs_create_blob(const char *name, umode_t mode,
                                   struct dentry *parent,
                                   struct debugfs_blob_wrapper *blob);

Cette fonction permet d'exporter (en lecture uniquement) n'importe quelle structure de données. blob est un pointeur vers une structure struct debugfs_blob_wrapper qui indique quelles données sont à exporter et quelle est leur taille.

Il est enfin possible de créer un fichier plus générique à l'aide de la fonction debugfs_create_file (voir la documentation).

void debugfs_remove(struct dentry *dentry);
void debugfs_remove_recursive(struct dentry *dentry);

La première fonction permet de supprimer un fichier ou un répertoire vide. La seconde, plus utile, permet de supprimer récursivement tout le contenu d'un répertoire. Cette dernière, avec comme paramètre le répertoire parent créé au début, est par exemple appelée dans la fonction de nettoyage du module pour libérer automatiquement tous les répertoires et fichiers créés.

Pour plus d'informations sur debugfs : Documentation/filesystems/debugfs.txt

kgdb

kgdb permet de déboguer un noyau en cours d'exécution. Il requiert deux machines connectées entre elles par une liaison série. Sur une machine tourne le noyau à déboguer et sur l'autre gdb.

La documentation complète de kgdb est disponible ici : https://www.kernel.org/doc/htmldocs/kgdb/index.html.

Il est nécessaire de compiler le noyau avec les options suivantes :

Il faut ensuite configurer kgdb : * Soit au démarrage du noyau en passant kgdboc=<tty-device>,[baud] sur la ligne de commande du noyau (exemple : kgdboc=ttyS0,115200 * Soit dynamiquement durant l'exécution :

# echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc

Enfin, il faut stopper l'exécution du noyau pour que gdb puisse s'y connecter. Cela peut être réalisé de plusieurs façons :

Une fois le noyau interrompu, lancer gdb sur l'autre machine :

$ gdb ./vmlinux
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0

vmlinux est l'image du noyau à déboguer.

JTAG

Enfin, sur de nombreuses cartes embarquées, il est possible d'utiliser le protocole JTAG (ou sur les cartes à base de processeurs ARM, Serial Wire Debug (SWD)) pour déboguer le noyau. Cela requiert la plupart du temps une sonde particulière et un pilote particulier pour cette sonde (par exemple openocd).


Retour au sommaire du cours


© Copyright 2017 Guillaume Duc. Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International (à l'exception des exemples de code tirés du noyau Linux et qui sont distribués sous leurs licences d'origine).

Licence
Creative Commons