Skip to main content

Redirect any callable objects in Python, by manipulating tp_vectorcall of a PyObject with ctypes

Project description

pydetour: Redirect any callable objects in Python

pydetour is a pure-Python library for redirecting any callable object -- builtin functions included -- to an arbitrary function, without replacing the reference to the to-be-redirected function. Works on Python 3.8 and above.

Installation

pip install pydetour

... or simply put pydetour.py in a PYTHON_PATH.

Example

Let us see what it takes to redirect calls to os.listdir using this library.

>>> from pydetour import hook
>>> def get_fake_os_listdir(os_listdir):
...   def fake_os_listdir(path):
...     print(f'fake_os_listdir({repr(path)})')
...     ret = os_listdir(path)
...     ret.insert(0, '<fake>')
...     return ret
...   return fake_os_listdir
...

get_fake_os_listdir is a function to pass to the library. Note that it takes an argument and returns a function. As you can infer, the argument is (or, more precicely, can be treated as) the original function and the returned function is the function to which calls to os.listdir are redirected.

>>> import os
>>> os.listdir('.')
['pydetour.py', 'README.md', 'setup.py']
>>> ref = os.listdir
>>> unhook = hook(os.listdir, get_fake_os_listdir)
>>> ref is os.listdir
True

We hooked os.listdir by invoking hook() function from the library, which returns another function which reverses the effect. By comparing the variable to which we had saved the reference of os.listdir with os.listdir after hooking, we can confirm that they are identical.

>>> os.listdir('.')
fake_os_listdir('.')
['<fake>', 'pydetour.py', 'README.md', 'setup.py']
>>> unhook()
>>> os.listdir('.')
['pydetour.py', 'README.md', 'setup.py']

However, calling os.listdir demonstrates that the call is indeed redirected to the function we defined earlier, as there is <fake> inserted in the beginning of the returned list. By calling the function returned by hook() we can undo the redirection.

How it works

In order to understand how this library works, it is necessary to get to know how function invocation is treated in CPython 3.8 and above. The snippet shown below is the part of CPython in charge of handling function invocations. (The explanation given here is a simplified one, as it does not mention the case in which tp_call is called. Although we omit this case here, the library handles that as well.)

Include/cpython/abstract.h

static inline PyObject *
_PyObject_VectorcallTstate(PyThreadState *tstate, PyObject *callable,
                           PyObject *const *args, size_t nargsf,
                           PyObject *kwnames)
{
    // snip
    func = PyVectorcall_Function(callable);
    // snip
    res = func(callable, args, nargsf, kwnames);
    return _Py_CheckFunctionResult(tstate, callable, res, NULL);
}

Here, callable is a pointer to a callable PyObject being invoked and PyVectorcall_Function is a function for retrieving tp_vectorcall, a function pointer, from a given PyObject. After retrieving the tp_vectorcall, it is called with the arguments passed to callable.

With this in mind, it is easy to see that if we could modify what PyVectorcall_Function returns for callable, we would be able to redirect all the invocations of callable, which is exactly what this library does.

To achieve the goal, this library makes an extensive (ab)use of ctypes for manipulating internal Python objects.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydetour-0.1.0.tar.gz (5.2 kB view details)

Uploaded Source

Built Distribution

pydetour-0.1.0-py3-none-any.whl (5.4 kB view details)

Uploaded Python 3

File details

Details for the file pydetour-0.1.0.tar.gz.

File metadata

  • Download URL: pydetour-0.1.0.tar.gz
  • Upload date:
  • Size: 5.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.7.1 importlib_metadata/4.10.0 pkginfo/1.8.2 requests/2.21.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.7.3

File hashes

Hashes for pydetour-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6cfdb594dcd578d4c189f0f69d0980149b3e9140b77d95be7c124b881ebca553
MD5 a10f1badf9df5e51f5e819c668929a0d
BLAKE2b-256 25488a099b121e87d5308c7702233b201a28bcb415e4ab0212cb2169c1833db2

See more details on using hashes here.

File details

Details for the file pydetour-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pydetour-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 5.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.7.1 importlib_metadata/4.10.0 pkginfo/1.8.2 requests/2.21.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.7.3

File hashes

Hashes for pydetour-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e95a7398262fa844ad56358a9000853c3e03d8444395a812e4ff53d13b2a49bf
MD5 546b630b88cd73cc31aef86d3344dfb2
BLAKE2b-256 8904b92d80f6a92da08e68193b6208c1d35900d3e1fe6ad0fd5cccc2a0c8273e

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page