Python Language
ctypes
Ricerca…
introduzione
ctypes
è una libreria incorporata in Python che richiama le funzioni esportate da librerie compilate native.
Nota: poiché questa libreria gestisce il codice compilato, è relativamente dipendente dal SO.
Utilizzo di base
Diciamo che vogliamo usare la funzione ntohl
libc
.
Per prima cosa, dobbiamo caricare libc.so
:
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
Quindi, otteniamo l'oggetto funzione:
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
E ora, possiamo semplicemente invocare la funzione:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
Che fa esattamente quello che ci aspettiamo che faccia.
Insidie comuni
Impossibile caricare un file
Il primo possibile errore non riesce a caricare la libreria. In tal caso viene sollevato di solito un OSError.
Questo perché il file non esiste (o non può essere trovato dal sistema operativo):
>>> 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
Come puoi vedere, l'errore è chiaro e piuttosto indicativo.
La seconda ragione è che il file è stato trovato, ma non è del formato corretto.
>>> 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
In questo caso, il file è un file di script e non un file .so
. Ciò potrebbe anche accadere quando si tenta di aprire un file .dll
su una macchina Linux o un file 64bit su un interprete python a 32 bit. Come puoi vedere, in questo caso l'errore è un po 'più vago e richiede alcuni scavi.
Impossibile accedere a una funzione
Supponendo di aver caricato correttamente il file .so
, dobbiamo quindi accedere alla nostra funzione come abbiamo fatto nel primo esempio.
Quando viene utilizzata una funzione non esistente, viene sollevato un AttributeError
:
>>> 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
Oggetto ctypes di base
L'oggetto più elementare è un int:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
Ora, obj
riferisce a un blocco di memoria contenente il valore 12.
È possibile accedere direttamente a questo valore e persino modificarlo:
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
Poiché obj
riferisce a un blocco di memoria, possiamo anche scoprire la sua dimensione e posizione:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
array di tipi
Come ogni buon programmatore C sa, un singolo valore non ti porterà lontano. Ciò che ci farà davvero andare avanti sono gli array!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
Questo non è un array reale, ma è dannatamente vicino! Abbiamo creato una classe che denota un array di 16 int
s.
Ora tutto ciò che dobbiamo fare è inizializzarlo:
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
Ora arr
è un array reale che contiene i numeri da 0 a 15.
Possono essere consultati come qualsiasi elenco:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
E proprio come qualsiasi altro oggetto ctypes
, ha anche una dimensione e una posizione:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
Funzioni di avvolgimento per i tipi
In alcuni casi, una funzione C accetta un puntatore a funzione. Come utenti accaniti di ctypes
, vorremmo usare quelle funzioni e persino passare la funzione python come argomenti.
Definiamo una funzione:
>>> def max(x, y):
return x if x >= y else y
Ora, quella funzione accetta due argomenti e restituisce un risultato dello stesso tipo. Per l'esempio, supponiamo che type sia un int.
Come abbiamo fatto sull'esempio dell'array, possiamo definire un oggetto che denota quel prototipo:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
Quel prototipo denota una funzione che restituisce un c_int
(il primo argomento) e accetta due argomenti c_int
(gli altri argomenti).
Ora avvolgiamo la funzione:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
Prototipi di funzione hanno in più l'utilizzo: possono avvolgere ctypes
funzione (come libc.ntohl
) e verificare che gli argomenti corretti vengono utilizzati quando si richiama la funzione.
>>> 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)
Utilizzo complesso
Combiniamo tutti gli esempi sopra in uno scenario complesso: usando la funzione lfind
libc
.
Per maggiori dettagli sulla funzione, leggi la pagina man . Vi esorto a leggerlo prima di andare avanti.
Innanzitutto, definiremo i prototipi corretti:
>>> 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)
Quindi, creiamo le variabili:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
E ora definiamo la funzione di confronto:
>>> def compar(x, y):
return x.contents.value - y.contents.value
Si noti che x
y
sono POINTER(c_int)
, quindi è necessario dereferenziarli e prendere i loro valori per confrontare effettivamente il valore memorizzato nella memoria.
Ora possiamo combinare tutto insieme:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
è il puntatore vuoto restituito. Se la key
non è stata trovata in arr
, il valore sarebbe None
, ma in questo caso abbiamo ottenuto un valore valido.
Ora possiamo convertirlo e accedere al valore:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
Inoltre, possiamo vedere che ptr
punta al valore corretto all'interno di arr
:
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True