Python Language
ctypes
Recherche…
Introduction
ctypes
est une bibliothèque intégrée python qui appelle les fonctions exportées à partir de bibliothèques compilées natives.
Remarque: Comme cette bibliothèque gère le code compilé, il est relativement dépendant du système d'exploitation.
Utilisation de base
Disons que nous voulons utiliser la fonction ntohl
libc
.
Tout d'abord, nous devons charger libc.so
:
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
Ensuite, nous obtenons l'objet function:
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
Et maintenant, nous pouvons simplement invoquer la fonction:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
Ce qui fait exactement ce que nous attendons de lui.
Pièges communs
Ne pas charger un fichier
La première erreur possible échoue lors du chargement de la bibliothèque. Dans ce cas, une erreur OSE est généralement déclenchée.
C'est soit parce que le fichier n'existe pas (ou ne peut pas être trouvé par le système d'exploitation):
>>> cdll.LoadLibrary("foobar.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: foobar.so: cannot open shared object file: No such file or directory
Comme vous pouvez le voir, l'erreur est claire et assez indicative.
La deuxième raison est que le fichier est trouvé mais n’a pas le bon format.
>>> cdll.LoadLibrary("libc.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: /usr/lib/i386-linux-gnu/libc.so: invalid ELF header
Dans ce cas, le fichier est un fichier script et non un fichier .so
. Cela peut également se produire lorsque vous essayez d'ouvrir un fichier .dll
sur une machine Linux ou un fichier 64 bits sur un interpréteur Python 32 bits. Comme vous pouvez le voir, dans ce cas, l'erreur est un peu plus vague et nécessite des fouilles.
Ne pas accéder à une fonction
En supposant que nous avons chargé avec succès le fichier .so
, nous devons ensuite accéder à notre fonction comme nous l'avons fait sur le premier exemple.
Lorsqu'une fonction non existante est utilisée, un AttributeError
est déclenché:
>>> libc.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 360, in __getattr__
func = self.__getitem__(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 365, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /lib/i386-linux-gnu/libc.so.6: undefined symbol: foo
Objet ctypes de base
L'objet le plus fondamental est un int:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
Maintenant, obj
réfère à un morceau de mémoire contenant la valeur 12.
Cette valeur est accessible directement et même modifiée:
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
Depuis obj
réfère à une partie de la mémoire, nous pouvons également trouver sa taille et son emplacement:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
tableaux de type ctypes
Comme tout bon programmeur C le sait, une seule valeur ne vous mènera pas aussi loin. Ce qui va vraiment nous faire avancer, ce sont les tableaux!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
Ce n'est pas un tableau réel, mais c'est assez proche! Nous avons créé une classe qui dénote un tableau de 16 int
s.
Il ne reste plus qu'à l'initialiser:
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
Maintenant, arr
est un tableau qui contient les nombres de 0 à 15.
Ils sont accessibles comme n'importe quelle liste:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
Et tout comme n'importe quel autre objet ctypes
, il a également une taille et un emplacement:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
Fonctions d'emballage pour les types
Dans certains cas, une fonction C accepte un pointeur de fonction. En tant ctypes
avides de ctypes
, nous aimerions utiliser ces fonctions, et même passer la fonction python en argument.
Définissons une fonction:
>>> def max(x, y):
return x if x >= y else y
Maintenant, cette fonction prend deux arguments et renvoie un résultat du même type. Pour l'exemple, supposons que ce type est un int.
Comme nous l'avons fait sur l'exemple du tableau, nous pouvons définir un objet qui dénote ce prototype:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
Ce prototype dénote une fonction qui retourne un c_int
(le premier argument) et accepte deux arguments c_int
(les autres arguments).
Maintenant, enveloppons la fonction:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
Les prototypes de fonctions ont plus d'usage: ils peuvent envelopper la fonction ctypes
(comme libc.ntohl
) et vérifier que les arguments corrects sont utilisés lors de l'appel de la fonction.
>>> libc.ntohl() # garbage in - garbage out
>>> CFUNCTYPE(c_int, c_int)(libc.ntohl)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: this function takes at least 1 argument (0 given)
Utilisation complexe
lfind
tous les exemples ci-dessus en un scénario complexe: utiliser la fonction lfind
libc
.
Pour plus de détails sur la fonction, lisez la page de manuel . Je vous exhorte à le lire avant de continuer.
Tout d'abord, nous définirons les prototypes appropriés:
>>> compar_proto = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>> lfind_proto = CFUNCTYPE(c_void_p, c_void_p, c_void_p, POINTER(c_uint), c_uint, compar_proto)
Ensuite, créons les variables:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
Et maintenant, nous définissons la fonction de comparaison:
>>> def compar(x, y):
return x.contents.value - y.contents.value
Notez que x
et y
sont POINTER(c_int)
, nous devons donc les déréférencer et prendre leurs valeurs afin de comparer la valeur stockée dans la mémoire.
Maintenant, nous pouvons tout combiner:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
est le pointeur vide retourné. Si la key
n'a pas été trouvée dans arr
, la valeur serait None
, mais dans ce cas, nous avons une valeur valide.
Maintenant, nous pouvons le convertir et accéder à la valeur:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
En outre, nous pouvons voir que ptr
pointe vers la valeur correcte à l'intérieur d' arr
:
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True