Blog Haypo

Aller au contenu | Aller au menu | Aller à la recherche

vendredi 4 décembre 2009

Compiler PyPy trunk en activant le compilateur à la volée (JIT)

PyPy est une interprète Python écrit en Python. Il implémente Python 2.5 et offre quelques fonctionnalités supplémentaires. Je m'intéresse surtout au compilateur à la volée, car il laisse espérer une vitesse d'exécution des programmes Python bien meilleure. Lire ces benchmarks du blog PyPy pour se faire une idée :

Prérequis

  • Linux
  • Processeur 32 bits (le compilateur à la volée ne gère pas encore le 64 bits)
  • 2 Go de mémoire (1,5 Go semblent suffisant selon ce que j'ai lu)
  • Une bonne heure pour la compilation

Téléchargement

svn co http://codespeak.net/svn/pypy/trunk/ pypy-trunk

Installer les dépendances

Sous Debian / Ubuntu :

sudo apt-get install libffi-dev libbz2-dev libexpat1-dev libexpat1-dev

La compilation proprement dite

cd pypy-trunk/pypy/translate/goal
python translate.py -Ojit

Prévoir 60 minutes sur un CPU Intel @ 3 GHz et 2 Go de mémoire. Pour faire patienter, PyPy dessine de jolies fractales : C

Le résultat est le programme testing-1 (environ 18 Mo) dans le dossier /tmp/usession-trunk-0/testing_1/. Je vous conseille de le déplacer le fichier testing-1 à la racine du projet PyPy sous le nom pypy-c-jit.

C'est prêt !

Voilà, vous avez un PyPy prêt à l'emploi. Vous pouvez jouer avec l'option --jit :

$ ./pypy-c-jit --help
usage: /home/haypo/prog/SVN/pypy-trunk/pypy-c-jit [options]

options:
 (...)
 --jit debug=N              low-level JIT parameter (default 2)
 --jit hash_bits=N          low-level JIT parameter (default 14)
 --jit inlining=N           low-level JIT parameter (default False)
 --jit optimizer=N          low-level JIT parameter (default 1)
 --jit threshold=N          low-level JIT parameter (default 1000)
 --jit trace_eagerness=N    low-level JIT parameter (default 200)
 --jit trace_limit=N        low-level JIT parameter (default 10000)

Un sprint PyPy est prévu fin janvier 2010 (dans un chalet en Suisse) pour finaliser la version 1.2 qui devrait donc sortir peu après (je l'espère).

Unladen Swallow

J'attend aussi beaucoup d'Unladen Swallow qui a pris une autre voie. C'est un fork de CPython 2.6 qui utilise LLVM pour la boucle d'évaluation du bytecode Python. LLVM intègre notamment un compilateur à la volée. Contrairement à PyPy qui redéveloppe tout depuis zéro dans leur coin, LLVM est un projet très populaire utilisé par Apple pour ses effets graphiques, dans les pilotes 3D Xorg récents (Gallium), pour compiler des programmes en C, etc. Non pas que les développeurs de PyPy soient moins bons, j'ai plus confiance dans un projet (LLVM) utilisé par un grand nombre des personnes pour des usages très différents. C'est un gage de pérénité, et LLVM semble plus actif.

D'ailleurs, PyPy avait un backend LLVM pendant un temps, mais ils l'ont abandonné. Il me semble que la raison avancée par les auteurs de PyPy était que LLVM n'était pas adapté à Python, langage trop dynamique.

Unladen Swallow a aussi l'avantage d'être 100% compatible avec CPython... vu que c'est un fork de CPython, notamment au niveau des modules tiers écrits en C (utilisant l'API C de Python) comme PyQt ou numpy.

mardi 27 janvier 2009

Hors-série Linux Mag : Explorez les richesses du langage Python

Depuis un an, Philippe Biondi parcourait la France sans relâche à la recherche d'auteurs pour écrire un hors-série dédié à Python. Grâce à sa persévérance, le hors-série dédié à Python est enfin en vente dans toutes les librairies (en France) !

J'ai écrit quatre articles dans ce hors-série.

Nouveautés de Python 2.6

Version améliorée, corrigée et illustrée (d'exemples) de ma dépêche linuxfr.org (elle-même basé sur What’s New in Python 2.6 que j'ai traduit).

Nouveautés de Python 3.0

Article écrit avant la sortie de Python 3 par ma propre expérience, et avant que la documentation What’s New In Python 3.0 ne soit écrite par Guido van Rossum (... mais finalement publié après).

Trucs et astuces

Article écrit co-écrit par Philippe Biondi même s'il est trop humble pour l'avouer !

Ctypes et Python

Guide pratique pour écrire un binding C en Python avec ctypes. C'est aussi un retour d'expérience sur mes bindings pynetfilter_conntrack et python-ptrace.

--

Ruez-vous en librairie, il n'y en aura pas pour tout le monde ! Je me suis déjà empressé d'acheter mon exemplaire. J'ai particulièrement apprécié l'article de Gaël Varoquaux : Python comme langage scientifique, article qui sent le vécu et l'utilisation concrète de Python.

vendredi 22 août 2008

Déboguer un programme Python avec gdb

Python est un langage interprété par une machine virtuelle appelée CPython. Dumoins, CPython est l'implémentation de référence, il en existe d'autres moins répendues (PyPy, Jython, IronPython, etc.). Pour ceux qui ne le savent pas encore, CPython est écrit en langage C. Lorsque CPython plante (« Fatal error: ... », erreur de segmentation ou autre), il est difficile de connaître les raisons du plantage. Ce billet devrait vous éclairer un peu si vous en êtes arrivé à passer par gdb pour déboguer Python.

Lire la suite

mercredi 9 juillet 2008

Publication de Fusil le fuzzer version 0.9, fuzzing de CPython et de PyPy

Fuzzer Python

Suite à ma conférence sur l'assurance qualité et fuzzing aux RMLL, je me suis remis à jouer avec mon fuzzer Fusil. Dans le TGV retour, 8h quand même pour rentrer à Strasbourg, j'ai écrit un fuzzer pour le langage Python. L'idée est de récupérer la liste des fonctions, classes et méthodes d'une module, et les appeler/instancier avec un nombre d'arguments aléatoires de type aléatoire. Comme je m'y attendais, une fois le fuzzer python écrit, CPython 2.5 a rapidement planté : erreur de segmentation dans le module imageop.

Rapports de bugs Python 2.6

Pour écrire un rapport de bug correct, j'ai reproduit les bugs avec la version trunk (futur CPython 2.6) compilée avec l'option pydebug et... mince, ça plante plus : le bug imageop est déjà corrigé. J'ai donc amélioré le fuzzer pour qu'il ne teste non plus uniquement un seul module, mais toute une liste de modules (j'ai noté ceux écrits en langage C), et j'ai rajouté d'autres types d'argument (nombre flottant, objet, unicode, etc.). Voilà CPython qui plante, ouf, l'honneur du fuzzing est sauf :-) J'ai alors rapporté une quinzaine de bugs (cherchez les bugs rapportés entre le 6 et le 9 juillet), à chaque fois accompagné d'un patch et d'un exemple pour reproduire le crash. Deux patchs sont déjà appliqués.

Publication de Fusil 0.9

Pour fêter ce succès, j'ai publié la version 0.9 de Fusil. Cette nouvelle version contient le nouveau fuzzer pour Python, peut s'exécuter dans l'interprète Python PyPy, gère mieux le logging (sortie plus concise mais contient le nombre de crash et un fichier project.log est conservé dans le dossier du projet), et l'outil IncrMangle est plus rapide et précis.

Fuzzing de PyPy

Enfin, j'ai fuzzé un peu PyPy, mais le seul vrai bug que j'ai trouvé est dans un module que j'ai écrit (module pwd écrit avec ctypes) ! Par contre, Carl (cfbolz sur le salon IRC #pypy du serveur Freenode) a trouvé d'autres bugs dans PyPy en utilisant Fusil.

Classement des interprètes Python

Pour résumer, vu les résultats face aux tests de fuzzing, on peut classer la qualité des interprètes Python comme ceci : CPython 2.5 < CPython trunk << PyPy où << veut dire très supérieur. Effectivement, je n'ai trouvé qu'un seul bug PyPy après une nuit de fuzzing contre une quinzaine dans CPython rapidement détectés.

jeudi 8 mai 2008

Pycon FR : les Journées Python Francophones édition 2008 (JPF08)

Comme l'année passée, je participe aux Journées Python Francophones organisées par l'AFPy à Paris (à la Cité des Sciences et de l'Industrie pour être exact). Contrairement aux conférences telle que Blackhat où l'entrée coûte plusieurs milliers d'euros, Pycon FR est entièrement gratuit !

Pour les gens qui ne peuvent pas se rendre à Paris ou qui sont déjà pris ce week-end là, aucun soucis ! Les conférences seront diffusées en direct sur Internet (ils ont pensé à tout). Elles seront très certainement disponibles en téléchargement un peu plus tard.

Au niveau du programme, je vous laisse le consulter vous même. Je donne une conférence sur l'interprète PyPy et une autre sur Python 3000. Il faudrait d'ailleurs que je commence à les préparer... Pour l'anecdote, j'ai deux collègues INL qui donnent des conférences (seb et misc) :-)

Pycon FR est aussi l'occasion de rencontrer des programmeurs Python et de discuter autour d'un café ou d'une bière. Alors, viendez !

mercredi 21 novembre 2007

Nouvelle implémentation de Fusil sous forme d'un système multi-agents

Cet article détaille l'architecture d'une nouvelle implémentation de mon outil de fuzzing « Fusil » : un système multi-agents qui devrait éviter la lourdeur des applications monolithiques.

Système multi-agents

Pour avoir mis les doigts dans de grosses applications (plus de 10.000 lignes de codes), je peux dire qu'une application monolithique est difficile à déboguer et à faire évoluer. L'architecture d'un logiciel repose sur un graphe d'objets liés les uns et autres à la sauce spaghetti. Modifier une classe ou le prototype d'une fonction impacte des dizaines d'autres classes et fonctions. Bien que des outils de refactoring facilitent la répercution des changements sur l'ensemble du projet, une architecture plus modulaire nous délesterait de cette tâche ingrate.

Un système multi-agents (SMA) est une simulation qui implique des dizaines d'agents simples qui communiquent entre eux par des événements. Le gros avantage est qu'un agent est (quasiment) aveugle : sa vision de l'environnement est volontairement limitée. En particulier, un agent n'a pas accès aux données des autres agents. Ce concept issu de la recherche en intelligence artificielle peut être adapté à des programmes plus communs. Comme j'ai repris mon projet Fusil depuis zéro (pour la 3e fois), j'ai décidé d'implémenter un SMA simple en Python pour mes besoins.

Agents dans Fusil

Pour rappel, Fusil est un générateur d'erreurs servant à rechercher des bugs dans un logiciel (Fusil est un fuzzer). Dans Fusil, tout est agent. Lorsqu'un agent émet un événement, ce dernier est mis en attente dans le MTA (Mail Transfer Agent). L'événement n'est transmis aux destinataires qu'au cycle de simulation suivant. L'agent Project est responsable d'exécuter la simulation : il demande à chaque agent de lire sa boîte de messages puis d'exécuter leur méthode live(). MTA et Project sont eux-même des agent et peuvent donc émettre et recevoir des événements. Exemples d'événements : « session_start », « process_create », etc.

Les agents de type Session sont crées pendant une session et sont automatiquement détruits à la fin d'une session. Les agents de type Project sont conservés toute la durée de vie du projet. Par contre, ils sont désactivés quand aucune session n'est active : ils ne peuvent plus communiquer (ni émettre ni recevoir de messages) et leur méthode live() n'est plus appelée. Enfin, lorsque le projet est fermé, les agents Project sont détruits. Ce découpage (Project/Session) permet de gérer simplement la durée de vie des données. En particulier, on s'assure que les données sont détruites au bon moment.

Exemples d'agent

  • CreateProcess : lance un programme. Tue le processus en cas de timeout (10 secondes par défaut).
  • WatchProcess: surveille un processus et en particulier sa mort (le processus a quitté ou a été tué par un signal fatal)
  • Syslog : surveille les logs systèmes (/var/log/message et /var/log/syslog)
  • TimeWatch : surveille la durée d'une session
  • MangleFile : injecte des erreurs dans un fichier

Système de notation

Les agents de surveillance attribuent une note à la session entre -100% et +100%. La note globale est la somme de toutes les notes. Si elle dépasse un seuil (+50% par défaut), la session est considérée comme un succès et est stoppée. Certains agents peuvent demander explicitement la fin d'une session : après un timeout ou la mort du processus par exemple.

Projets de fuzzing

Un projet Fusil est une configuration pour fuzzer une application. Une douzaine de projets sont disponible : ClamAV, printf, MySQL, rpm, etc. Les projets sont beaucoup plus diversifiés que les exemples des précédentes implémentations de Fusil. La nouvelle architecture plus modulaire et générique permet de configurer très finement chaque composant (agent).

Le moteur du SMA de Fusil est encore en gros chantier. Il risque encore de beaucoup évoluer ces prochaines semaines pour s'adapter au mieux aux besoins du logiciel. Note : Fusil s'autorise de multiple digressions par rapport au SMA parfait pour simplifier la vie du programmeur.

samedi 15 septembre 2007

graphdep

Pour visualiser le trafic réseau d'un ordinateur, il existe de nombreux outils de visualisation. Philippe Biondi a par exemple développé l'outil RT Graph 3D qui utilise scapy pour dessiner en 3D les liens entre les éléments du réseau. Mais c'est la bibliothèque Python GVGlue de toady qui m'a donné envie de faire des schémas. J'ai alors écrit graphdep qui utilise GVGlue. Pour commencer, voici le graphe du programme graphdep :

Le schéma représente les dépendances entre les différents modules d'un projet. L'utilisation de graphdep ne nécessite pas de modification du programme : les modules sont découverts automatiquement. La version actuelle supporte les langages C, C++, PHP et Python. Comme à mon habitude, je distribue ce programme sous licence GNU GPL, vous pouvez le télécharger gratuitement sur graphdep.

Beaucoup d'options sont disponibles pour filtrer les modules à afficher ou cacher, ou non car le schéma peut rapidement devenir énorme. Exemple avec le même projet mais en affichant tous les modules :

On peut imaginer d'autres schémas : hiérarchie des classes, dépendances entre les fonctions, etc. Pour les fonctions, le logiciel Doxygen génère déjà de jolis graphes. Exemple : documentation de NuFW (libnuclient).

mardi 14 août 2007

État des lieux de Python 3000

Profitant de mes vacances, j'ai testé Python 3000 (relire mon billet précédent sur Python 3000) pour voir si c'était utilisable. Autant ne pas se mentir : Python 3000 n'est pas prêt pour un usage quotidient. Selon moi, il y avait deux évolutions majeures et pénibles (pour les développeurs CPython) : fusion des types int et long, et la distinction entre les types bytes et str (anciennement str et unicode).

Types int et long

Pour les types int et long, en fait c'est le type long est renommé int et le l'ancien type int disparait. Pour les programmmeurs Python, on aura plus besoin d'écrire « isinstance(valeur, (int, long)) » pour vérifier si une valeur est un entier : « isinstance(valeur, int) » est désormais suffisant. Le suffixe L indiquant qu'un nombre est un entier long disparait également.

Pour la manipulation des entiers, Python 2.4 était déjà une avancée majeure. Si le résultat d'un calcul sur des entiers courts (int) dépasse la capacité d'un entier court, une conversion automatique en entier long est réalisée. Exemple : 2 ** 100 (où 2 et 100 sont des entiers courts) retourne 1267650600228229401496703205376L (type long).

Selon moi, Python va dans le bon sens : s'abstraire des limitations matérielles pour simplifier la vie au développeur. Python s'occupe tout seul des tâches pénibles : allouer la mémoire, faire des calculs sur les entiers longs, etc.

Types bytes et str

La distinction entre octet et caractère est un changement majeur dans Python. La compatibilité est brisée car le type bytes n'a plus les méthodes relatives à la manipulation de caractère telle que lower() et splitlines(), et un élément d'une chaîne bytes est un entier et non pas une sous-chaîne bytes. Exemple : b'xyz'[0] retourne 120. C'est d'ailleurs déroutant car le test « b'xyz'[0] == b'x' » est faux (120 == b'x' est faux). Il faut écrire explicitement « b'xyz'[0] == ord(b'x') » ou « b'xyz'[0] == 120 ».

La tâche est quasiment terminée : la branche subversion py3k-struni qui lui était dédiée a été fusionnée dans la branche py3k (branche python 3000 officielle). Il reste quelques bogues en particulier le module « email » qui est loin de fonctionnner en Python 3000.

J'ai aidé en corrigeant les modules ctypes, imghdr et sndhdr. Voir les commits 56838 (ctypes) et 56987 (imghdr et sndhdr). J'ai tenté avec beaucoup de mal de migrer le module email, mais comme il est assez gros et mélange allégrement octet et caractère, c'est loin d'être terminé. J'ai envoyé plusieurs emails à la liste de diffusion email-sig mais j'ai eu peu de retours.

Python 3000 ça sort quand ?

Une partie du wiki Python est dédiée à Python 3000 où on trouve en particulier les articles Py3kToDo (ce qu'il reste à faire dans Python 3000) et Py3kStrUniTests (tests unitaires cassés par la distinction bytes/str). La PEP 3100 (Miscellaneous Python 3.0 Plans) liste également toutes les tâches à faire pour Python 3000 avec leur état : [done] (fait), [UNLIKELY] (tâché en discussion), [no] (tâche refusée).

lundi 30 juillet 2007

Changements apportés par Python 3000

Je suis de très près le développement du langage Python et en particulier de la prochaine version majeure : Python 3000. Cette version casse la compatibilité mais pour de bonnes raisons.

Types bytes et str

Dans Python 3000, une chaîne de caractères sera du type « str » (de l'anglais string) et non plus « unicode » (qui prêtait à confusion). L'actuel type « str » de Python 2.x devient le type « bytes » qui comme son nom l'indique contient des octets (bytes en anglais). Pour avoir tenté d'expliquer à de nombreuses reprises Unicode et la différence entre caractère et octet, je pense que c'est une très bonne chose. Je vous conseille la lecture de la PEP 358 (The "bytes" Object) pour les détails. Le type bytes sera disponible dans Python 2.6 et Python 3000.

Annotation des fonctions

Critique récurrente à Python : le prototype d'une fonction n'indique pas son type. De mon point de vue, c'est une qualité : celà permet de duck-typing (If it looks like a duck and quacks like a duck, it must be a duck : Si ça ressemble à canard et que ça fait le bruit d'un canard, ce doit être un canard). Mais le gros problème est qu'on n'a pas la possibilité de déterminer à la compilation si la fonction va échouer ou non car le type d'un argument est incompatible.

La PEP 3107 (Function Annotations) propose d'ajouter des annotations optionnelles aux arguments et au type de retour. Une fois que ça sera implémenté, je suis sûr que ça sera rapidement exploité par les outils de vérification automatiques tel que pychecker.

Exemple d'annotation :

def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
    ...

Argument sous forme de mot-clé

Un autre défaut de Python 2.x : on ne peut pas indiquer qu'un argument ne peut être passé que sous la forme « mot-clé=valeur ». Exemple :

def afficheProduit(nom, prix, monnaie=u"€", note=None):
   print "%s: %s %s" % (nom, prix, monnaie)
   if note:
      print "(note: %s)" % note

Cette notation prête à confusion, on peut se tromper on oubliant de forcer d'indiquer qu'on veut spécifier une note : « afficheProduit("voiture", 100, "petite voiture rouge") ». Notre note sera passée en valeur de « monnaie », il aurait fallu écrire « afficheProduit("voiture", 100, note="petite voiture rouge") ».

La PEP 3102 (Keyword-Only Arguments) répond à ce problème avec la syntaxe suivante :

def afficheProduit(nom, prix, *, monnaie=u"€", note=None):
   print "%s: %s %s" % (nom, prix, monnaie)
   if note:
      print "(note: %s)" % note

L'appel « afficheProduit("disque", 50, "US$") » sera alors interdit, il faudra explicitement (Explicit is better than implicit) écrire « afficheProduit("disque", 50, monnaie="US$") ».

Dans mon exemple, ce n'est pas parlant mais lorsqu'on a 6 arguments ou plus, c'est bien pratique.

Documents sur Python 3000

Je ne voulais insister que sur les changements qui me semblaient les plus importants, mais Python 3000 change bien plus de choses ! Je vous conseille la présentation de Guido van Rossum : les slides « Python 3000 » (PowerPoint) et la vidéo « Python 3000 ».

Autres documents sur Python 3000 :

Et enfin, Python 3000 Status Update (19 juin 2007) par Guido Van Rossum.

samedi 19 mai 2007

Journées Python le 2 et 3 juin 2007 à Paris

L'AFPy organise pour la première année les Journées Python Francophones. Au programme : de nombreuses conférences de 9h à 17h (voir plus si on est motivé), consultez le programme complet .

Au début je n'avais pas prévu d'y aller : Strasbourg - Paris ça fait encore 4h de train même si « le TGV Est arrive »... le 10 juin. Mais comme je vais au SSTIC, je vais m'arrêter à Paris au retour. Le SSTIC, c'est 3 jours de conférences, du 30 mai au 1er Juin à Rennes, sur le thème de la sécurité. C'est ma boîte, INL, qui me paie l'inscription (environ 200€ de mémoire) et le voyage. J'y serai avec 3 collègues et vu le programme, ça risque d'être vraiment chouette. En plus, je ferai un présentation éclair (5 minutes) sur le fuzzing ;-)

Pour revenir aux Journées Python (qui elles sont gratuites), je vais également y faire présentations : « Projet Hachoir et bonnes pratiques » et « Automatisation des tests logiciels ». Hum, ça a l'air intéressant, mais il va falloir trouver du contenu pour tenir 25 minutes chaque fois :-) Je mettrais toutes mes présentations en ligne une fois qu'elles seront écrites.

Présentations Python qui me tentent bien :

  • TraitsUI pour développer des applications graphiques : j'en avais déjà entendu parler et ça semble très prometteur comme technologie
  • Atelier http://ipython.scipy.org/ : interpréteur Python intégrant un débogueur, la coloration syntaxique, un petit éditeur, etc.
  • Lightning Talks : On peut s'attendre à plein de trucs chouettes :-)
  • Python et visualisation 3D : La 3D m'a toujours attirée
  • Dr. Gumby, the brain specialist (IA avec SciPy) : Intelligence artificielle et SciPy, ça ne peut qu'être intéressant.

En fait, il y a un peu tout qui me tente et je suis sûr que j'aurai de bonnes surprises. Allez, viendez aux Journées Python ;-)

MISE A JOUR POST WEEK-END : Vous pouvez maintenant consulter ma présentation du projet Fusil (PDF) et mes présentations Python (HTML).

mardi 24 avril 2007

Imposer des limites arbitraires

Durant mes essais de fuzzing, j'ai compris assez vite qu'espérer écrire un programme parfait n'est qu'un idéal. Plutôt que de corriger les erreurs, je pense qu'il vaut mieux écrire du code tolérant aux erreurs. Je veux dire par là que le programme continuera à fonctionner même si une procédure échoue.

Utiliser les exceptions

On peut utiliser pour ça le couple try/except en Python. Exemple trivial :

value = (...)
try:
   print "Date : %s" % datetime.datetime.fromtimestamp(value)
except ValueError:
   print "Date invalide (%r) !" % value

Mais le fuzzing mène souvent à une situation d'épuisement (monopolisation) des ressources : votre programme va manger tout le temps processeur, toute la mémoire, remplir le disque dur, utiliser toute sa pile, etc. J'ai alors cherché comment détecter ces situations de crise. D'ailleurs elles ne doivent pas être vue comme critiques mais normales et il faut les avoir à l'esprit en écrivant un programme. Effectivement, les ressources sont limitées : il faut apprendre à partager.

Limiter la mémoire

Sous Linux, on peut utiliser resource.setrlimit(RLIMIT_AS, ...). Si la mémoire totale dépasse max_mem, une exception MemoryError est émise par Python.

J'ai implémenté une fonction limitedMemory() qui va limiter temporairement la mémoire : lire memory.py d'hachoir_core. L'erreur apparait si la mémoire grossit de la quantité d'octets indiquée. Il suffit alors d'utiliser « try: limitedMemory(maxmem, ...) except MemoryError: ... ».

Limiter le temps processeur

Pour éviter que le programme reste bloqué au même endroit pendant un temps excessif (cas typique : une boucle infinie), il faut pouvoir appeler une fonction avec une durée maximale. Sous Linux, on peut utiliser au choix : time.alarm() ou resource.setrlimit(RLIMIT_CPU, ...). À noter que pour la seconde solution, les pauses (time.sleep()) et le temps passé dans le noyau ne sont pas pris en compte : il vaut donc mieux utiliser une alarme. Une alarme déclanche un signal SIGALRM alors que RLIMIT_CPU va générer un signal SIGXCPU.

J'ai implémenté les deux méthodes dans la fonction limitedTime(sec) : lire timeout.py d'hachoir_core.

Lorsque c'est possible, il vaut mieux utiliser des fonctions offrant déjà cette fonctionnalité comme par exemple la fonction select().

Limiter la pile

En testant dpkg, j'ai réussi à le planter avec « COLUMNS=10000000 dpkg -l ». Après investigation, il s'est avéré que l'erreur venait de la libc (chose qui semblait impensable à mes yeux). En creusant encore, j'ai vu que vfprintf() utilisait massiment la pile pour écrire la sortie de dpkg (qui configure stdout pour ne pas utiliser de tampon).

Bref, j'ai cherché à voir s'il était possible d'attraper l'erreur « épuisement de la pile ». Et bien sûr que oui : c'est possible ! Par contre, quand la pile est hors-service, hors de question d'utiliser printf() ou autre fonction succeptible de réutiliser la pile. Linux permet d'utiliser une pile dédiée aux gestionnaires de signaux. Ah là là, il est quand même fort ce système d'exploitation, hein !

Les fonctions clés sont sigaltstack() pour créer une pile dédiée à notre gestionnaire de signal, sigaction() pour appeler notre fonction quand le signal SIGSEGV est émis, setjmp()/longjmp() pour quitter le code bogué et revenir à la « borne de sauvegarde » (renseignée par setjmp()).

Exemple d'implémentation : stack.c.

En réunissant tous ces élements (try/except, limiter la mémoire, temps et pile), je pense qu'on peut commencer à écrire des programmes robustes. Bien sûr, rien ne vaut un audit minutieux du code source.

jeudi 12 avril 2007

Mes correctifs Python intégrés dans le trunk officiel

La nuit porte conseil. J'ai continué à traquer les bugs que j'avais trouvé dans Python (rappel : plantage de Python lorsque la mémoire est épuisée) et j'en ai trouvé un autre (comparaison d'un entier court et d'un entier long). Je les ai isolés et corrigés.

J'ai alors écrit un rapport de bug sur Sourceforge et j'ai contacté des développeurs Python sur IRC (salon #python-dev du serveur Freenode). Ils sont très sympas et réactifs.

Finalement, mes correctifs ont été appliqués dans le trunk de Python (la version de développement) et feront partie de Python 2.5.2 (la version 2.5.1 étant en cours de finalisation). 48h pour corriger un bug, je trouve ça tout de même très court comme délai quand on sait que Microsoft Windows et Internet Explorer ont des bugs vieux de plus de 6 ans :-)

mercredi 4 avril 2007

Nouvelles d'hachoir-metadata

hachoir-metadata est un programme permettant de lire les métadonnées d'un fichier : taille d'une image, auteur d'une vidéo, durée d'un son, etc. Il repose sur hachoir-parser pour lire les informations d'un fichier.

Traitements automatiques

hachoir-metadata réalise de plus en plus de traitements automatiques haut niveaux tels que :

  • supprimer les espaces inutiles
  • ignorer les chaînes de caractère vides
  • filtrer les valeurs : ignore les valeurs abbérantes (ex: image ayant une largeur nulle)
  • supprimer les doublons

La suppression des doublons ne concerne pas simplement les valeurs identiques. Pour les chaînes de caractère, hachoir-metadata est capable de reconnaître qu'une chaîne est le début d'une autre. Exemple : si on trouve les deux auteurs "James Brown" et "James Br" pour une chanson, seule la chaîne la plus longue est conservée (James Brown !).

Réutilisation des valeurs

L'extracteur de métadonnée est de plus en plus rigoureux : les valeurs doivent être d'un type précis. Par exemple, la durée d'une chanson est maintenant du type Python « timedelta ». Avant les dates, durées, débit en bit/sec, nombre de canaux audios étaient tantôt une chaîne de caractère, tantôt un entier, tantôt une date, ...

Le fait que le type des données soit strict a permis de faire des calculs sur les documents multimédias. On peut maintenant obtenir le débit en bits par seconde pour du son et de la vidéo, et le taux de compression pour une image et du son. Ceci permet de comparer la qualité d'un codec.

Option --quality

J'ai également ajouté l'option --quality permettant de choisir la « qualité » des métadonnées extraites. En fait, cette option détermine la vitesse d'extraction : les opérations lourdes ne seront faites que pour quality=1.0, alors que pour quality=0.0 toutes les opérations lentes sont ignorées. Cette option influe par exemple sur le calcul de la durée d'un MP3 à débit variable : pour un calcul exact, il faut lire le fichier en entier mais ceci est très long. L'option quality va donc faire varier le nombre de champs traités.

Lire la suite

Nouvelles d'Hachoir (core et parser)

Je viens de me rendre compte que ça fait pas mal de temps que je code sans écrire de journal sur les derniers développement de mon projet Hachoir. Voici donc un premier billet donnant des nouvelles du front.

hachoir-core

hachoir-core est le cœur d'Hachoir : la partie bas niveau qui va découper un fichier en une multitudes de champs. Mais ce composant contient également énormément d'outils divers comme une humanDuration() qui va convertir une durée en une représentation « humaine » (ex: "22 sec 320 ms"). Aujourd'hui, hachoir-core évolue peu car il commence à couvrir l'ensemble des besoins d'un parseur.

Le plus gros changement récent est la tolérance aux erreurs. En fait, l'erreur n'est pas corrigée mais rattrapée. Par exemple, si une erreur est détectée durant la génération de la description d'un champ : l'erreur est affichée et la description devient une chaîne vide. Ceci peut sembler naturel, mais ce n'était pas le cas avant. Précédemment, si la génération d'une description échouait, on perdait beaucoup d'informations car l'erreur déclanchait une cascade d'autres erreurs et finalement plusieurs champs étaient détruits. Ce principe de rattrapage d'erreurs est utilisé dans un maximum de code. Il reste peu de fonctions qui ne sont pas « protégées » ce qui rend le code toujours plus robustes.

hachoir-parser

hachoir-parser est un ensemble de parseurs de fichiers (images, vidéos, archives, programmes, ...). Ce composant est celui auquel contribue le plus de monde car il est simple de le modifier (corriger) ou d'ajouter son propre parseur. J'ai notamment reçu un gros coup de main de Christophe GISQUET qui a écrit les parseurs ACE, RAR, Torrent et d'autres. Mike Melanson, le chef de projet du greffon Flash pour Linux, a également écrit des parseurs pour les formats Real Audio et Real Media. Enfin, Olivier SCHWAB a écrit un parseur 7-zip.

La liste des ajouts récents de parseur montre la forte activité de ce composant :

  • Archive : archive ACE, Microsoft cabinet (CAB), Roshal archive (RAR), archive Microsoft (MAR)
  • Audio : Uncompressed amiga module (MOD), ScreamTracker3 module (S3M), FastTracker II Extended Module (XM), Audio Interchange File Format (AIFF et AIFC), Real audio (RA), image Targa (TGA)
  • Image : Photoshop (PSD), icone animé Windows (ANI), Aldus Placeable Metafile (APM), Microsoft Enhanced Metafile (EMF) et Microsoft Windows Metafile (WMF)
  • Divers : BitTorrent (.torrent), police de caractère TrueType (TTF), document PDF, exécutable Windows 16-bit (NE), vidéo MPEG-2 Transport Stream (MPEG TS), raccourci Windows (LNK), X11 Portable Compiled Font (PCF), aide Windows HTML (CHM), ...

À l'heure actuelle il y a 70 parseurs dans la version de développement d'Hachoir. J'ai du mal à savoir si c'est beaucoup ou peu. En consultant la liste complète des parseurs, je pense tout de même que c'est assez conséquent pour ne pas dire énorme. Il faut d'ailleurs savoir que certains parseurs gèrent plusieurs formats. Exemples : RIFF parse les formats AVI, WAV, ANI et CDA; WMF parse les formats AMF, EMF et WMF; etc.

vendredi 17 novembre 2006

Hachoir collection printemps-hiver

Hachoir version 0.6

Mon dernier billet sur Hachoir datant de début Septembre, je me dois d'écrire au sujet des dernières actualités de ce projet. Pour commencer, fin octobre est sorti la version 0.6 que j'ai annoncée sur linuxfr.org. Au niveau fonctionnel, cette version permet maintenant d'éditer un fichier et également de manipuler un fichier corrompu et/ou tronqué. Le projet a été découpé en de nombreux composants (hachoir, hachoir-parser, hachoir-metadata, ...), ce qui permet une meilleure visibilité de l'ensemble des fonctionnalités, et d'un point de vue pratique de mettre à jour plus régulièrement et de travailler sur de petits projets.

Création d'une nouvelle version avec SubVersion

Depuis cette sortie, j'ai déjà sorti 3 versions mineures corrigeant des bogues. Subversion permet de travailler très simplement sur plusieurs versions à la fois. La version « trunk » est la plus à jour, tandis que le dossier « tags » contient les versions stables. Pour tagguer une version, rien de plus simple : c'est la commmande svn cp trunk tags/hachoir-0.6.0. Et pour créer une nouvelle version à partir d'un tag existant, idem : svn cp tags/hachoir-0.6.1 tags/hachoir-0.6.2. Enfin, le fait de conserver toutes les versions dans SubVersion permet de les comparer en utilisant le superbe outil meld.

Contributions

Je reçois de plus en plus de contributions. Le français Julien Muchembled, qui héberge hachoir.org (trac et subversion), s'est retiré du projet. Il a développé la superbe interface hachoir-urwid qui m'a beaucoup aidée. Il a également travaillé sur les données fractionnées et désordonnées (système de fichier, flux multimédias tels que Matroska et Ogg). Enfin, il avait patché Hachoir pour pouvoir hacher un fichier alimenté par un pipe Unix.

Récemment, le canadien Cyril Zorin a rejoint le projet pour développer une interface graphique : hachoir-wx. Son objectif est un outil permettant d'aider à la rétro-ingénierie, c'est-à-dire d'arriver à deviner le format d'un fichier inconnu. Son interface est encore en développement, mais permet déjà de naviguer dans l'arborescence des champs.

Je reçois de plus en plus de contribution. J'ai par exemple reçu un parseur de vidéo du jeu Spider-Man écrit par Mike Melanson (le chef de projet de Flash pour Linux !), un parseur de sauvegarde ZSNES écrit par la canadien Jason Gorski, et enfin un parseur complet de classe Java (fichier .class) écrit par le français Thomas de Grenier de Latour. Pour la peine, j'ai crée une liste de diffusion : plus d'information sur la page de contact de hachoir.

Mes développements récents

J'ai récement écrit des parseurs pour les formats Flash : SWF (animation Flash) et FLV (conteneur vidéo Flash). Au passage, j'ai écrit un outil (swf_extractor.py) permettant d'extraire toutes les images et tous les sons d'un animation SWF. J'ai réussi à réutiliser les parseurs JPEG et MPEG audio dans ces deux parseurs. Cela fait plaisir car ça montre qu'Hachoir devient exhaustif.

Hier, j'ai ajouté le support des fichiers encodés et en particulier de fichiers compressés. On peut dès lors ouvrir une animation Flash compressée et lire le contenu d'un fichier compressé au format gzip ou bzip2. À terme, on pourra imaginer n'importe quel type de conversion : déchiffrement RSA, décodage base64, déchiffrage XOR, etc.

Petit à petit, je sens qu'Hachoir s'approche de la version 1.0. J'ai atteint la majorité des objectifs que je m'étais fixé. Hachoir fait d'ailleurs parler de lui : Thierry Stoer a écrit un bref article sur Hachoir dans le blog des formats ouverts et Mike Melanson en parle dans son blog.