Les Crash-Devs d'un Passionné

Analyser le bytecode python

/Catégorie/python

Temps de lecture : 3 minutes

Le module dis permet d'analyser le bytecode python en le désassemblant.

Pour rappel un bytecode est le code compilé par l'interpréteur python. C'est-à-dire à chaque fois que vous lancez un script python, l'interpréteur (généralement CPython) va généré le bytecode, avec les instructions comprises par le systeme. Depuis python 3.2 ces binaires sont maintenant dans un dossier __pycache__, toujours avec les extensions pyc.

Cela permet lors du prochain appel de charger plus rapidement le module.

Prenons comme exemple la fonction suivante, avec l'utilisation de dis

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
>>> def is_good(val):
... if val is None:
...   return False
... return True
...
>>> dis.dis(is_good(True))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\FX29620\AppData\Local\Programs\Python\Python37\lib\dis.py", li
e 77, in dis
type(x).__name__)
TypeError: don't know how to disassemble bool objects
>>> dis.dis('is_good(True)')
1 0 LOAD_NAME 0 (is_good)
2 LOAD_CONST 0 (True)
4 CALL_FUNCTION 1
6 RETURN_VALUE

Les données affichées par colonnes correspondent aux informations suivantes,

  • 1 => Le numéro de la ligne dans le code
  • 2 => L'instruction courante executée
  • 3 => Un label indiquant un possible JUMP d'une instruction précédente
  • 4 => L'adresse dans le bytecode, cela sera toujours un multiple de 2 en python > 3.6 car chaque instruction prend 2 octets
  • 5 => opname qui est l'instruction bytecode, on trouvera la liste sur la doc python
  • 6 => Les arguments de l'instruction
  • 7 => La partie de code python liée à l'instruction

Chaque instruction du bytecode est découpé en "Code Object" et écrit en C (pour CPython).

Pour visualiser ce code object il faut simplement ajouter __code__ à la fin d'une instruction python

clipboard
Copier le code
1
2
3
4
5
6
>>> is_good.__code__
<code object is_good at 0x0000000001D2F9C0, file "<stdin>", line 1>

# On peut aussi passé par le built-in compile qui retournera un Code Object
>>> compile('is_good(True)', '<string>', 'exec')
<code object <module> at 0x0000000002133F60, file "<string>", line 1>

Pour exécuter un Code Object il faut soit passer par un exec() soit par eval(), le 1er permet de gérer le code python sur plusieurs lignes tandis que eval() gère uniquement les instructions simples.

clipboard
Copier le code
1
2
3
>>> obj = compile('is_good(True)', '<string>', 'exec')
>>> print(exec(obj))
None

On peut également obtenir des informations sur le code compilé

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# On accède ensuite à des informations comme le bytecode généré en byte
>>> compile('is_good(True)', '<string>', 'exec').co_code
b'e\x00d\x00\x83\x01\x01\x00d\x01S\x00'

# ... les constantes de la fonction
>>> compile('is_good(True)', '<string>', 'exec').co_consts
(True, None)

# ... le nom de l'instruction python
>>> compile('is_good(True)', '<string>', 'exec').co_names
('is_good',)

# ... la liste éventuelles des symboles locales
>>> compile('is_good(True)', '<string>', 'exec').co_varnames

Voilà les informations minimales à avoir pour faire de l'analyse de bytecode.

Si vous cherchez un moyen d'obtenir le code à partir d'un bytecode il existe aussi un décompileur facilement accessible

clipboard
Copier le code
1
2
pipenv install uncompyle6 
uncompyle6 file.pyc