Le mécanisme dynamic_debug est une fonctionnalité puissante du noyau Linux permettant d'activer ou de désactiver sélectivement les messages de débogage (pr_debug, dev_dbg) au moment de l'exécution, sans redémarrer le système ni recompiler le code. Contrairement aux macros statiques, cette méthode offre une flexibilité totale pour isoler des problèmes dans des modules spécifiques.
1. Configuration et Activation
Pour exploiter le débogage dynamique, le noyau doit être compilé avec les options appropriées dans le fichier .config :
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DEBUG_FS=y
Une fois le noyau démarré, le système de fichiers debugfs doit être monté pour accéder à l'interface de contrôle. Vous pouvez l'ajouter au fichier /etc/fstab pour un montage automatique :
debugfs /sys/kernel/debug debugfs defaults 0 0
L'interface principale se situe alors dans /sys/kernel/debug/dynamic_debug/control. Ce fichier permet de lister toutes les instructions de débogage disponibles et de modifier leur état.
2. Manipulation de l'interface de contrôle
Le contrôle s'effectue en envoyant des commandes au fichier control. Les filtres peuvent porter sur des fichiers source, des modules ou des fonctions spécifiques.
Activation par fichier ou module
Pour activer les logs d'un module réseau ou d'un fichier de pilote spécifique :
# Activation pour un module complet
echo "module nom_du_module +p" > /sys/kernel/debug/dynamic_debug/control
# Activation pour un fichier source précis
echo "file pilote_usb.c +p" > /sys/kernel/debug/dynamic_debug/control
Désactivation des logs
Il suffit de remplacer le flag +p (print) par -p :
echo "file pilote_usb.c -p" > /sys/kernel/debug/dynamic_debug/control
Filtrage par fonction
Il est également possible de cibler une seule fonction au sein d'un fichier :
echo "func initialisation_peripherique +p" > /sys/kernel/debug/dynamic_debug/control
3. Fonctions standards de journalisation (dev_*)
Dans le développement de pilotes, il est fortement recommandé d'utiliser la famille de fonctions dev_err, dev_warn et dev_info définie dans <linux/device.h>. Ces fonctions incluent automatiquement des informations sur le périphérique, facilitant la lecture des logs.
En interne, ces macros s'appuient sur les niveaux de priorité de printk :
#define pr_err(fmt, ...) \
printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn(fmt, ...) \
printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...) \
printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
4. Macros à arguments variables
Pour créer des outils de diagnostic personnalisés, on utilise souvent les macros à arguments variabels (__VA_ARGS__). Voici une structure typique pour un wrapper de log personnalisé incluant le nom de la fonction et la ligne :
#define LOG_K_DBG(fmt, ...) \
printk(KERN_DEBUG "[%s:%d] " fmt, __func__, __LINE__, ##__VA_ARGS__)
// Exemple d'utilisation
int status = -EIO;
LOG_K_DBG("Erreur détectée avec le code : %d\n", status);
5. Contrôle granulaire par paliers ou masques
Parfois, le système dynamic_debug est trop global. On peut implémenter un contrôle par niveau numérique ou par masque de bits pour un module spécifique.
Contrôle par seuil (Niveaux)
#define LVL_ERROR 1
#define LVL_UNUSED 2
#define LVL_INFO 3
#define LVL_VERBOSE 4
u32 module_log_threshold = LVL_INFO;
#define MODULE_TRACE(level, fmt, ...) \
do { \
if (level <= module_log_threshold) { \
pr_info("MOD_LOG [%d]: " fmt, level, ##__VA_ARGS__); \
} \
} while (0)
Contrôle par masque (Bitmask)
Cette approche permet d'activer simultanément plusieurs catégories de logs (ex: logs d'interruption et logs d'init) sans activer le reste.
enum log_category {
CAT_INIT = 0x01,
CAT_IO = 0x02,
CAT_IRQ = 0x04,
};
u32 active_mask = CAT_INIT | CAT_IRQ;
#define CATEGORY_DEBUG(mask, fmt, ...) \
do { \
if (active_mask & mask) { \
pr_debug("CAT_DBG: " fmt, ##__VA_ARGS__); \
} \
} while (0)
6. Extension vers l'espace utilisateur
Pour maintenir une cohérence entre le noyau et les services applicatifs, une structure similaire peut être adoptée en utilisant syslog. Voici un exemple d'implémentation pour un module applicatif :
#include <syslog.h>
#include <stdio.h>
typedef struct {
int module_id;
int current_level;
} log_manager_t;
#define APP_LOG(level, mod_ptr, fmt, ...) \
do { \
if (level <= mod_ptr->current_level) { \
syslog(LOG_USER | level, "[Mod:%d] " fmt, mod_ptr->module_id, ##__VA_ARGS__); \
} \
} while (0)
void example_usage() {
log_manager_t network_mod = { .module_id = 1, .current_level = LOG_DEBUG };
APP_LOG(LOG_ERR, (&network_mod), "Échec de connexion réseau sur l'interface %s", "eth0");
}
Cette approche permet une gestion centralisée des logs où le niveau de verbosité peut être ajusté dynamiquement via des fichiers de configuration ou des appels API en cours d'exécution.