├── MANIFEST.in ├── README.md ├── demos └── test.py ├── setup.py └── src ├── Makefile ├── ceval.c ├── ceval_gil.h ├── condvar.h ├── hook.c ├── hook.h ├── opcode_targets.h ├── rwatch.c └── test.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | recursive-include src *.h 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Disclaimer 2 | 3 | dutc = Don't Use This Code (!!) 4 | 5 | # Compatibility 6 | 7 | * Python 3.5 or higher 8 | * Linux or OS X 9 | * need build toolchain (prob. need `gcc`) 10 | 11 | # Read Watches in Python 12 | 13 | ```python 14 | >>> import rwatch # enable functionality 15 | >>> from sys import setrwatch, getrwatch 16 | >>> x, y, z = object(), object(), object() 17 | >>> def view(frame, obj): 18 | ... print(frame, obj) 19 | ... return obj 20 | ... 21 | >>> setrwatch({id(x): view, id(y): view}) 22 | >>> getrwatch() 23 | {139825758638208: , 139825758638224: } 24 | >>> x 25 | 26 | 27 | >>> y 28 | 29 | 30 | >>> z 31 | 32 | ``` 33 | # Lessons: 34 | * read watches are a very useful tool for debugging 35 | * it's actually very useful for a language to have a runtime 36 | * there are some hidden equivalencies between, e.g., read watches and perfect proxy objects 37 | -------------------------------------------------------------------------------- /demos/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from sys import version_info, exit 4 | 5 | if version_info.major < 3 or version_info.minor < 5: 6 | exit('Sorry, Python 3.5 or above only') 7 | 8 | try: 9 | from sys import setrwatch, getrwatch 10 | except ImportError: 11 | try: 12 | import rwatch 13 | from sys import setrwatch, getrwatch 14 | except ImportError: 15 | exit('http://github.com/dutc/rwatch or `pip install dutc-rwatch`') 16 | 17 | ############ Wouldn't this be (very!) useful for debugging? ############ 18 | 19 | def debug_demo(x='some data'): 20 | from inspect import getframeinfo 21 | def view(frame, obj): 22 | info = getframeinfo(frame) 23 | msg = 'Access to {!r} (@{}) at {}:{}:{}' 24 | print(msg.format(obj, hex(id(obj)), info.filename, info.lineno, info.function)) 25 | return obj 26 | 27 | setrwatch({id(x): view}) 28 | print(getrwatch()) 29 | 30 | def f(x): 31 | x # ← here 32 | f(x) # ← here 33 | 34 | def f(x): 35 | def g(): 36 | x # ← here 37 | return g 38 | f(x)() # ← here 39 | 40 | class Foo: 41 | def __init__(self, x): 42 | self.x = x # ← here 43 | def __call__(self): 44 | return self.x # ← here 45 | @property 46 | def value(self): 47 | return self.x # ← here 48 | 49 | foo = Foo(x) # ← here 50 | foo() 51 | foo.value 52 | 53 | x = 10 # not here (rebind, no push) 54 | 55 | ########## But maybe also? ########## 56 | 57 | def sandbox_demo(x='some data'): 58 | def forbid(frame, obj): 59 | raise SystemError("can't touch this") 60 | 61 | setrwatch({id(x): forbid}) 62 | 63 | try: 64 | # x 65 | pass 66 | except: 67 | pass 68 | 69 | ########## Some weird things are possible... ########## 70 | 71 | def defer_demo(): 72 | def lazy(frame, obj): 73 | obj.load() 74 | return obj 75 | 76 | class Lazily: 77 | def load(self): 78 | self.value = 10 79 | 80 | x = Lazily() 81 | 82 | setrwatch({id(x): lazy}) 83 | print(x.value) 84 | 85 | def promise(frame, obj): 86 | return obj.value 87 | 88 | class Promise: 89 | @property 90 | def value(self): 91 | return 10 92 | 93 | x = Promise() 94 | 95 | setrwatch({id(x): promise}) 96 | print(x) 97 | 98 | ########## But it can get even weirder... ########## 99 | 100 | def indirection_demo(): 101 | def pointer(frame, obj): 102 | return obj() 103 | 104 | def make_pointer(x): 105 | cell = [x] 106 | pointer = lambda: cell[0] 107 | def repoint(x): 108 | cell[0] = x 109 | return pointer, repoint 110 | 111 | y, z = 1234, 4567 112 | x, x_ = make_pointer(y) 113 | 114 | setrwatch({id(x): pointer}) 115 | 116 | print(x) 117 | x_(z) 118 | print(x) 119 | 120 | ########## By the way, the mapping is mutable ########## 121 | 122 | def mutable_demo(x='some data', y='other data'): 123 | def view(frame, obj): 124 | print('Saw access to {!r}'.format(obj)) 125 | return obj 126 | 127 | watches = {id(x): view} 128 | setrwatch(watches) 129 | x 130 | watches[id(y)] = view 131 | y 132 | 133 | class Predicate(dict): 134 | def dispatch(self, frame, obj): 135 | for pred, func in self.items(): 136 | if pred(obj): 137 | func(frame, obj) 138 | def __getitem__(self, _): 139 | return self.dispatch 140 | 141 | from collections.abc import Sized 142 | watches = Predicate({lambda o: isinstance(o, Sized) and len(o) > 9: view}) 143 | setrwatch(watches) 144 | x, y 145 | 146 | if __name__ == '__main__': 147 | debug_demo() 148 | sandbox_demo() 149 | defer_demo() 150 | indirection_demo() 151 | mutable_demo() 152 | 153 | # Interestingly, the above shows the equivalent between the provision 154 | # of a perfect proxy object (wherein Proxy(x) is in all ways 155 | # indistiguishable from x) and read watches. 156 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # rwatch - read watches 2 | 3 | from setuptools import setup, find_packages, Extension 4 | from os.path import join, dirname 5 | from sys import version_info, exit 6 | 7 | if version_info.major < 3 or version_info.minor < 5: 8 | exit("Sorry, Python 3.5 or above only") 9 | 10 | rwatch = Extension('rwatch', 11 | sources=['src/rwatch.c', 12 | 'src/hook.c', 13 | 'src/ceval.c',], 14 | include_dirs=['src'], ) 15 | 16 | setup( 17 | name='dutc-rwatch', 18 | version='0.1.0', 19 | description='Read Watches', 20 | long_description=''.join(open(join(dirname(__file__),'README.md'))), 21 | author='James Powell', 22 | author_email='james@dontusethiscode.com', 23 | url='https://github.com/dutc/rwatch', 24 | packages=find_packages(exclude=['*demos*']), 25 | ext_modules=[rwatch], 26 | ) 27 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | 3 | PYTHON_CONFIG = python3-config 4 | 5 | CFLAGS = `$(PYTHON_CONFIG) --cflags` 6 | INCLUDES = `$(PYTHON_CONFIG) --includes` 7 | LIBS = `$(PYTHON_CONFIG) --libs` 8 | LD_PATH = `$(PYTHON_CONFIG) --prefix`/lib 9 | 10 | rwatch.so: rwatch.c hook.c ceval.o 11 | gcc $(CFLAGS) $(INCLUDES) -L$(LD_PATH) -Wl,--export-dynamic -fPIC -shared -o $@ $^ -ldl $(LIBS) 12 | 13 | ceval.o: ceval.c 14 | gcc $(CFLAGS) $(INCLUDES) -fPIC -DPy_BUILD_CORE -c -o $@ $^ 15 | 16 | clean: 17 | rm -f rwatch.so ceval.o 18 | -------------------------------------------------------------------------------- /src/ceval_gil.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Implementation of the Global Interpreter Lock (GIL). 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | 9 | /* First some general settings */ 10 | 11 | /* microseconds (the Python API uses seconds, though) */ 12 | #define DEFAULT_INTERVAL 5000 13 | static unsigned long gil_interval = DEFAULT_INTERVAL; 14 | #define INTERVAL (gil_interval >= 1 ? gil_interval : 1) 15 | 16 | /* Enable if you want to force the switching of threads at least every `gil_interval` */ 17 | #undef FORCE_SWITCHING 18 | #define FORCE_SWITCHING 19 | 20 | 21 | /* 22 | Notes about the implementation: 23 | 24 | - The GIL is just a boolean variable (gil_locked) whose access is protected 25 | by a mutex (gil_mutex), and whose changes are signalled by a condition 26 | variable (gil_cond). gil_mutex is taken for short periods of time, 27 | and therefore mostly uncontended. 28 | 29 | - In the GIL-holding thread, the main loop (PyEval_EvalFrameEx) must be 30 | able to release the GIL on demand by another thread. A volatile boolean 31 | variable (gil_drop_request) is used for that purpose, which is checked 32 | at every turn of the eval loop. That variable is set after a wait of 33 | `interval` microseconds on `gil_cond` has timed out. 34 | 35 | [Actually, another volatile boolean variable (eval_breaker) is used 36 | which ORs several conditions into one. Volatile booleans are 37 | sufficient as inter-thread signalling means since Python is run 38 | on cache-coherent architectures only.] 39 | 40 | - A thread wanting to take the GIL will first let pass a given amount of 41 | time (`interval` microseconds) before setting gil_drop_request. This 42 | encourages a defined switching period, but doesn't enforce it since 43 | opcodes can take an arbitrary time to execute. 44 | 45 | The `interval` value is available for the user to read and modify 46 | using the Python API `sys.{get,set}switchinterval()`. 47 | 48 | - When a thread releases the GIL and gil_drop_request is set, that thread 49 | ensures that another GIL-awaiting thread gets scheduled. 50 | It does so by waiting on a condition variable (switch_cond) until 51 | the value of gil_last_holder is changed to something else than its 52 | own thread state pointer, indicating that another thread was able to 53 | take the GIL. 54 | 55 | This is meant to prohibit the latency-adverse behaviour on multi-core 56 | machines where one thread would speculatively release the GIL, but still 57 | run and end up being the first to re-acquire it, making the "timeslices" 58 | much longer than expected. 59 | (Note: this mechanism is enabled with FORCE_SWITCHING above) 60 | */ 61 | 62 | #include "condvar.h" 63 | #ifndef Py_HAVE_CONDVAR 64 | #error You need either a POSIX-compatible or a Windows system! 65 | #endif 66 | 67 | #define MUTEX_T PyMUTEX_T 68 | #define MUTEX_INIT(mut) \ 69 | if (PyMUTEX_INIT(&(mut))) { \ 70 | Py_FatalError("PyMUTEX_INIT(" #mut ") failed"); }; 71 | #define MUTEX_FINI(mut) \ 72 | if (PyMUTEX_FINI(&(mut))) { \ 73 | Py_FatalError("PyMUTEX_FINI(" #mut ") failed"); }; 74 | #define MUTEX_LOCK(mut) \ 75 | if (PyMUTEX_LOCK(&(mut))) { \ 76 | Py_FatalError("PyMUTEX_LOCK(" #mut ") failed"); }; 77 | #define MUTEX_UNLOCK(mut) \ 78 | if (PyMUTEX_UNLOCK(&(mut))) { \ 79 | Py_FatalError("PyMUTEX_UNLOCK(" #mut ") failed"); }; 80 | 81 | #define COND_T PyCOND_T 82 | #define COND_INIT(cond) \ 83 | if (PyCOND_INIT(&(cond))) { \ 84 | Py_FatalError("PyCOND_INIT(" #cond ") failed"); }; 85 | #define COND_FINI(cond) \ 86 | if (PyCOND_FINI(&(cond))) { \ 87 | Py_FatalError("PyCOND_FINI(" #cond ") failed"); }; 88 | #define COND_SIGNAL(cond) \ 89 | if (PyCOND_SIGNAL(&(cond))) { \ 90 | Py_FatalError("PyCOND_SIGNAL(" #cond ") failed"); }; 91 | #define COND_WAIT(cond, mut) \ 92 | if (PyCOND_WAIT(&(cond), &(mut))) { \ 93 | Py_FatalError("PyCOND_WAIT(" #cond ") failed"); }; 94 | #define COND_TIMED_WAIT(cond, mut, microseconds, timeout_result) \ 95 | { \ 96 | int r = PyCOND_TIMEDWAIT(&(cond), &(mut), (microseconds)); \ 97 | if (r < 0) \ 98 | Py_FatalError("PyCOND_WAIT(" #cond ") failed"); \ 99 | if (r) /* 1 == timeout, 2 == impl. can't say, so assume timeout */ \ 100 | timeout_result = 1; \ 101 | else \ 102 | timeout_result = 0; \ 103 | } \ 104 | 105 | 106 | 107 | /* Whether the GIL is already taken (-1 if uninitialized). This is atomic 108 | because it can be read without any lock taken in ceval.c. */ 109 | static _Py_atomic_int gil_locked = {-1}; 110 | /* Number of GIL switches since the beginning. */ 111 | static unsigned long gil_switch_number = 0; 112 | /* Last PyThreadState holding / having held the GIL. This helps us know 113 | whether anyone else was scheduled after we dropped the GIL. */ 114 | static _Py_atomic_address gil_last_holder = {NULL}; 115 | 116 | /* This condition variable allows one or several threads to wait until 117 | the GIL is released. In addition, the mutex also protects the above 118 | variables. */ 119 | static COND_T gil_cond; 120 | static MUTEX_T gil_mutex; 121 | 122 | #ifdef FORCE_SWITCHING 123 | /* This condition variable helps the GIL-releasing thread wait for 124 | a GIL-awaiting thread to be scheduled and take the GIL. */ 125 | static COND_T switch_cond; 126 | static MUTEX_T switch_mutex; 127 | #endif 128 | 129 | 130 | static int gil_created(void) 131 | { 132 | return _Py_atomic_load_explicit(&gil_locked, _Py_memory_order_acquire) >= 0; 133 | } 134 | 135 | static void create_gil(void) 136 | { 137 | MUTEX_INIT(gil_mutex); 138 | #ifdef FORCE_SWITCHING 139 | MUTEX_INIT(switch_mutex); 140 | #endif 141 | COND_INIT(gil_cond); 142 | #ifdef FORCE_SWITCHING 143 | COND_INIT(switch_cond); 144 | #endif 145 | _Py_atomic_store_relaxed(&gil_last_holder, NULL); 146 | _Py_ANNOTATE_RWLOCK_CREATE(&gil_locked); 147 | _Py_atomic_store_explicit(&gil_locked, 0, _Py_memory_order_release); 148 | } 149 | 150 | static void destroy_gil(void) 151 | { 152 | /* some pthread-like implementations tie the mutex to the cond 153 | * and must have the cond destroyed first. 154 | */ 155 | COND_FINI(gil_cond); 156 | MUTEX_FINI(gil_mutex); 157 | #ifdef FORCE_SWITCHING 158 | COND_FINI(switch_cond); 159 | MUTEX_FINI(switch_mutex); 160 | #endif 161 | _Py_atomic_store_explicit(&gil_locked, -1, _Py_memory_order_release); 162 | _Py_ANNOTATE_RWLOCK_DESTROY(&gil_locked); 163 | } 164 | 165 | static void recreate_gil(void) 166 | { 167 | _Py_ANNOTATE_RWLOCK_DESTROY(&gil_locked); 168 | /* XXX should we destroy the old OS resources here? */ 169 | create_gil(); 170 | } 171 | 172 | static void drop_gil(PyThreadState *tstate) 173 | { 174 | if (!_Py_atomic_load_relaxed(&gil_locked)) 175 | Py_FatalError("drop_gil: GIL is not locked"); 176 | /* tstate is allowed to be NULL (early interpreter init) */ 177 | if (tstate != NULL) { 178 | /* Sub-interpreter support: threads might have been switched 179 | under our feet using PyThreadState_Swap(). Fix the GIL last 180 | holder variable so that our heuristics work. */ 181 | _Py_atomic_store_relaxed(&gil_last_holder, tstate); 182 | } 183 | 184 | MUTEX_LOCK(gil_mutex); 185 | _Py_ANNOTATE_RWLOCK_RELEASED(&gil_locked, /*is_write=*/1); 186 | _Py_atomic_store_relaxed(&gil_locked, 0); 187 | COND_SIGNAL(gil_cond); 188 | MUTEX_UNLOCK(gil_mutex); 189 | 190 | #ifdef FORCE_SWITCHING 191 | if (_Py_atomic_load_relaxed(&gil_drop_request) && tstate != NULL) { 192 | MUTEX_LOCK(switch_mutex); 193 | /* Not switched yet => wait */ 194 | if ((PyThreadState*)_Py_atomic_load_relaxed(&gil_last_holder) == tstate) { 195 | RESET_GIL_DROP_REQUEST(); 196 | /* NOTE: if COND_WAIT does not atomically start waiting when 197 | releasing the mutex, another thread can run through, take 198 | the GIL and drop it again, and reset the condition 199 | before we even had a chance to wait for it. */ 200 | COND_WAIT(switch_cond, switch_mutex); 201 | } 202 | MUTEX_UNLOCK(switch_mutex); 203 | } 204 | #endif 205 | } 206 | 207 | static void take_gil(PyThreadState *tstate) 208 | { 209 | int err; 210 | if (tstate == NULL) 211 | Py_FatalError("take_gil: NULL tstate"); 212 | 213 | err = errno; 214 | MUTEX_LOCK(gil_mutex); 215 | 216 | if (!_Py_atomic_load_relaxed(&gil_locked)) 217 | goto _ready; 218 | 219 | while (_Py_atomic_load_relaxed(&gil_locked)) { 220 | int timed_out = 0; 221 | unsigned long saved_switchnum; 222 | 223 | saved_switchnum = gil_switch_number; 224 | COND_TIMED_WAIT(gil_cond, gil_mutex, INTERVAL, timed_out); 225 | /* If we timed out and no switch occurred in the meantime, it is time 226 | to ask the GIL-holding thread to drop it. */ 227 | if (timed_out && 228 | _Py_atomic_load_relaxed(&gil_locked) && 229 | gil_switch_number == saved_switchnum) { 230 | SET_GIL_DROP_REQUEST(); 231 | } 232 | } 233 | _ready: 234 | #ifdef FORCE_SWITCHING 235 | /* This mutex must be taken before modifying gil_last_holder (see drop_gil()). */ 236 | MUTEX_LOCK(switch_mutex); 237 | #endif 238 | /* We now hold the GIL */ 239 | _Py_atomic_store_relaxed(&gil_locked, 1); 240 | _Py_ANNOTATE_RWLOCK_ACQUIRED(&gil_locked, /*is_write=*/1); 241 | 242 | if (tstate != (PyThreadState*)_Py_atomic_load_relaxed(&gil_last_holder)) { 243 | _Py_atomic_store_relaxed(&gil_last_holder, tstate); 244 | ++gil_switch_number; 245 | } 246 | 247 | #ifdef FORCE_SWITCHING 248 | COND_SIGNAL(switch_cond); 249 | MUTEX_UNLOCK(switch_mutex); 250 | #endif 251 | if (_Py_atomic_load_relaxed(&gil_drop_request)) { 252 | RESET_GIL_DROP_REQUEST(); 253 | } 254 | if (tstate->async_exc != NULL) { 255 | _PyEval_SignalAsyncExc(); 256 | } 257 | 258 | MUTEX_UNLOCK(gil_mutex); 259 | errno = err; 260 | } 261 | 262 | void _PyEval_SetSwitchInterval(unsigned long microseconds) 263 | { 264 | gil_interval = microseconds; 265 | } 266 | 267 | unsigned long _PyEval_GetSwitchInterval() 268 | { 269 | return gil_interval; 270 | } 271 | -------------------------------------------------------------------------------- /src/condvar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Portable condition variable support for windows and pthreads. 3 | * Everything is inline, this header can be included where needed. 4 | * 5 | * APIs generally return 0 on success and non-zero on error, 6 | * and the caller needs to use its platform's error mechanism to 7 | * discover the error (errno, or GetLastError()) 8 | * 9 | * Note that some implementations cannot distinguish between a 10 | * condition variable wait time-out and successful wait. Most often 11 | * the difference is moot anyway since the wait condition must be 12 | * re-checked. 13 | * PyCOND_TIMEDWAIT, in addition to returning negative on error, 14 | * thus returns 0 on regular success, 1 on timeout 15 | * or 2 if it can't tell. 16 | * 17 | * There are at least two caveats with using these condition variables, 18 | * due to the fact that they may be emulated with Semaphores on 19 | * Windows: 20 | * 1) While PyCOND_SIGNAL() will wake up at least one thread, we 21 | * cannot currently guarantee that it will be one of the threads 22 | * already waiting in a PyCOND_WAIT() call. It _could_ cause 23 | * the wakeup of a subsequent thread to try a PyCOND_WAIT(), 24 | * including the thread doing the PyCOND_SIGNAL() itself. 25 | * The same applies to PyCOND_BROADCAST(), if N threads are waiting 26 | * then at least N threads will be woken up, but not necessarily 27 | * those already waiting. 28 | * For this reason, don't make the scheduling assumption that a 29 | * specific other thread will get the wakeup signal 30 | * 2) The _mutex_ must be held when calling PyCOND_SIGNAL() and 31 | * PyCOND_BROADCAST(). 32 | * While e.g. the posix standard strongly recommends that the mutex 33 | * associated with the condition variable is held when a 34 | * pthread_cond_signal() call is made, this is not a hard requirement, 35 | * although scheduling will not be "reliable" if it isn't. Here 36 | * the mutex is used for internal synchronization of the emulated 37 | * Condition Variable. 38 | */ 39 | 40 | #ifndef _CONDVAR_H_ 41 | #define _CONDVAR_H_ 42 | 43 | #include "Python.h" 44 | 45 | #ifndef _POSIX_THREADS 46 | /* This means pthreads are not implemented in libc headers, hence the macro 47 | not present in unistd.h. But they still can be implemented as an external 48 | library (e.g. gnu pth in pthread emulation) */ 49 | # ifdef HAVE_PTHREAD_H 50 | # include /* _POSIX_THREADS */ 51 | # endif 52 | #endif 53 | 54 | #ifdef _POSIX_THREADS 55 | /* 56 | * POSIX support 57 | */ 58 | #define Py_HAVE_CONDVAR 59 | 60 | #include 61 | 62 | #define PyCOND_ADD_MICROSECONDS(tv, interval) \ 63 | do { /* TODO: add overflow and truncation checks */ \ 64 | tv.tv_usec += (long) interval; \ 65 | tv.tv_sec += tv.tv_usec / 1000000; \ 66 | tv.tv_usec %= 1000000; \ 67 | } while (0) 68 | 69 | /* We assume all modern POSIX systems have gettimeofday() */ 70 | #ifdef GETTIMEOFDAY_NO_TZ 71 | #define PyCOND_GETTIMEOFDAY(ptv) gettimeofday(ptv) 72 | #else 73 | #define PyCOND_GETTIMEOFDAY(ptv) gettimeofday(ptv, (struct timezone *)NULL) 74 | #endif 75 | 76 | /* The following functions return 0 on success, nonzero on error */ 77 | #define PyMUTEX_T pthread_mutex_t 78 | #define PyMUTEX_INIT(mut) pthread_mutex_init((mut), NULL) 79 | #define PyMUTEX_FINI(mut) pthread_mutex_destroy(mut) 80 | #define PyMUTEX_LOCK(mut) pthread_mutex_lock(mut) 81 | #define PyMUTEX_UNLOCK(mut) pthread_mutex_unlock(mut) 82 | 83 | #define PyCOND_T pthread_cond_t 84 | #define PyCOND_INIT(cond) pthread_cond_init((cond), NULL) 85 | #define PyCOND_FINI(cond) pthread_cond_destroy(cond) 86 | #define PyCOND_SIGNAL(cond) pthread_cond_signal(cond) 87 | #define PyCOND_BROADCAST(cond) pthread_cond_broadcast(cond) 88 | #define PyCOND_WAIT(cond, mut) pthread_cond_wait((cond), (mut)) 89 | 90 | /* return 0 for success, 1 on timeout, -1 on error */ 91 | Py_LOCAL_INLINE(int) 92 | PyCOND_TIMEDWAIT(PyCOND_T *cond, PyMUTEX_T *mut, PY_LONG_LONG us) 93 | { 94 | int r; 95 | struct timespec ts; 96 | struct timeval deadline; 97 | 98 | PyCOND_GETTIMEOFDAY(&deadline); 99 | PyCOND_ADD_MICROSECONDS(deadline, us); 100 | ts.tv_sec = deadline.tv_sec; 101 | ts.tv_nsec = deadline.tv_usec * 1000; 102 | 103 | r = pthread_cond_timedwait((cond), (mut), &ts); 104 | if (r == ETIMEDOUT) 105 | return 1; 106 | else if (r) 107 | return -1; 108 | else 109 | return 0; 110 | } 111 | 112 | #elif defined(NT_THREADS) 113 | /* 114 | * Windows (XP, 2003 server and later, as well as (hopefully) CE) support 115 | * 116 | * Emulated condition variables ones that work with XP and later, plus 117 | * example native support on VISTA and onwards. 118 | */ 119 | #define Py_HAVE_CONDVAR 120 | 121 | 122 | /* include windows if it hasn't been done before */ 123 | #define WIN32_LEAN_AND_MEAN 124 | #include 125 | 126 | /* options */ 127 | /* non-emulated condition variables are provided for those that want 128 | * to target Windows Vista. Modify this macro to enable them. 129 | */ 130 | #ifndef _PY_EMULATED_WIN_CV 131 | #define _PY_EMULATED_WIN_CV 1 /* use emulated condition variables */ 132 | #endif 133 | 134 | /* fall back to emulation if not targeting Vista */ 135 | #if !defined NTDDI_VISTA || NTDDI_VERSION < NTDDI_VISTA 136 | #undef _PY_EMULATED_WIN_CV 137 | #define _PY_EMULATED_WIN_CV 1 138 | #endif 139 | 140 | 141 | #if _PY_EMULATED_WIN_CV 142 | 143 | /* The mutex is a CriticalSection object and 144 | The condition variables is emulated with the help of a semaphore. 145 | Semaphores are available on Windows XP (2003 server) and later. 146 | We use a Semaphore rather than an auto-reset event, because although 147 | an auto-resent event might appear to solve the lost-wakeup bug (race 148 | condition between releasing the outer lock and waiting) because it 149 | maintains state even though a wait hasn't happened, there is still 150 | a lost wakeup problem if more than one thread are interrupted in the 151 | critical place. A semaphore solves that, because its state is counted, 152 | not Boolean. 153 | Because it is ok to signal a condition variable with no one 154 | waiting, we need to keep track of the number of 155 | waiting threads. Otherwise, the semaphore's state could rise 156 | without bound. This also helps reduce the number of "spurious wakeups" 157 | that would otherwise happen. 158 | 159 | This implementation still has the problem that the threads woken 160 | with a "signal" aren't necessarily those that are already 161 | waiting. It corresponds to listing 2 in: 162 | http://birrell.org/andrew/papers/ImplementingCVs.pdf 163 | 164 | Generic emulations of the pthread_cond_* API using 165 | earlier Win32 functions can be found on the Web. 166 | The following read can be give background information to these issues, 167 | but the implementations are all broken in some way. 168 | http://www.cse.wustl.edu/~schmidt/win32-cv-1.html 169 | */ 170 | 171 | typedef CRITICAL_SECTION PyMUTEX_T; 172 | 173 | Py_LOCAL_INLINE(int) 174 | PyMUTEX_INIT(PyMUTEX_T *cs) 175 | { 176 | InitializeCriticalSection(cs); 177 | return 0; 178 | } 179 | 180 | Py_LOCAL_INLINE(int) 181 | PyMUTEX_FINI(PyMUTEX_T *cs) 182 | { 183 | DeleteCriticalSection(cs); 184 | return 0; 185 | } 186 | 187 | Py_LOCAL_INLINE(int) 188 | PyMUTEX_LOCK(PyMUTEX_T *cs) 189 | { 190 | EnterCriticalSection(cs); 191 | return 0; 192 | } 193 | 194 | Py_LOCAL_INLINE(int) 195 | PyMUTEX_UNLOCK(PyMUTEX_T *cs) 196 | { 197 | LeaveCriticalSection(cs); 198 | return 0; 199 | } 200 | 201 | /* The ConditionVariable object. From XP onwards it is easily emulated with 202 | * a Semaphore 203 | */ 204 | 205 | typedef struct _PyCOND_T 206 | { 207 | HANDLE sem; 208 | int waiting; /* to allow PyCOND_SIGNAL to be a no-op */ 209 | } PyCOND_T; 210 | 211 | Py_LOCAL_INLINE(int) 212 | PyCOND_INIT(PyCOND_T *cv) 213 | { 214 | /* A semaphore with a "large" max value, The positive value 215 | * is only needed to catch those "lost wakeup" events and 216 | * race conditions when a timed wait elapses. 217 | */ 218 | cv->sem = CreateSemaphore(NULL, 0, 100000, NULL); 219 | if (cv->sem==NULL) 220 | return -1; 221 | cv->waiting = 0; 222 | return 0; 223 | } 224 | 225 | Py_LOCAL_INLINE(int) 226 | PyCOND_FINI(PyCOND_T *cv) 227 | { 228 | return CloseHandle(cv->sem) ? 0 : -1; 229 | } 230 | 231 | /* this implementation can detect a timeout. Returns 1 on timeout, 232 | * 0 otherwise (and -1 on error) 233 | */ 234 | Py_LOCAL_INLINE(int) 235 | _PyCOND_WAIT_MS(PyCOND_T *cv, PyMUTEX_T *cs, DWORD ms) 236 | { 237 | DWORD wait; 238 | cv->waiting++; 239 | PyMUTEX_UNLOCK(cs); 240 | /* "lost wakeup bug" would occur if the caller were interrupted here, 241 | * but we are safe because we are using a semaphore wich has an internal 242 | * count. 243 | */ 244 | wait = WaitForSingleObjectEx(cv->sem, ms, FALSE); 245 | PyMUTEX_LOCK(cs); 246 | if (wait != WAIT_OBJECT_0) 247 | --cv->waiting; 248 | /* Here we have a benign race condition with PyCOND_SIGNAL. 249 | * When failure occurs or timeout, it is possible that 250 | * PyCOND_SIGNAL also decrements this value 251 | * and signals releases the mutex. This is benign because it 252 | * just means an extra spurious wakeup for a waiting thread. 253 | * ('waiting' corresponds to the semaphore's "negative" count and 254 | * we may end up with e.g. (waiting == -1 && sem.count == 1). When 255 | * a new thread comes along, it will pass right throuhgh, having 256 | * adjusted it to (waiting == 0 && sem.count == 0). 257 | */ 258 | 259 | if (wait == WAIT_FAILED) 260 | return -1; 261 | /* return 0 on success, 1 on timeout */ 262 | return wait != WAIT_OBJECT_0; 263 | } 264 | 265 | Py_LOCAL_INLINE(int) 266 | PyCOND_WAIT(PyCOND_T *cv, PyMUTEX_T *cs) 267 | { 268 | int result = _PyCOND_WAIT_MS(cv, cs, INFINITE); 269 | return result >= 0 ? 0 : result; 270 | } 271 | 272 | Py_LOCAL_INLINE(int) 273 | PyCOND_TIMEDWAIT(PyCOND_T *cv, PyMUTEX_T *cs, PY_LONG_LONG us) 274 | { 275 | return _PyCOND_WAIT_MS(cv, cs, (DWORD)(us/1000)); 276 | } 277 | 278 | Py_LOCAL_INLINE(int) 279 | PyCOND_SIGNAL(PyCOND_T *cv) 280 | { 281 | /* this test allows PyCOND_SIGNAL to be a no-op unless required 282 | * to wake someone up, thus preventing an unbounded increase of 283 | * the semaphore's internal counter. 284 | */ 285 | if (cv->waiting > 0) { 286 | /* notifying thread decreases the cv->waiting count so that 287 | * a delay between notify and actual wakeup of the target thread 288 | * doesn't cause a number of extra ReleaseSemaphore calls. 289 | */ 290 | cv->waiting--; 291 | return ReleaseSemaphore(cv->sem, 1, NULL) ? 0 : -1; 292 | } 293 | return 0; 294 | } 295 | 296 | Py_LOCAL_INLINE(int) 297 | PyCOND_BROADCAST(PyCOND_T *cv) 298 | { 299 | int waiting = cv->waiting; 300 | if (waiting > 0) { 301 | cv->waiting = 0; 302 | return ReleaseSemaphore(cv->sem, waiting, NULL) ? 0 : -1; 303 | } 304 | return 0; 305 | } 306 | 307 | #else 308 | 309 | /* Use native Win7 primitives if build target is Win7 or higher */ 310 | 311 | /* SRWLOCK is faster and better than CriticalSection */ 312 | typedef SRWLOCK PyMUTEX_T; 313 | 314 | Py_LOCAL_INLINE(int) 315 | PyMUTEX_INIT(PyMUTEX_T *cs) 316 | { 317 | InitializeSRWLock(cs); 318 | return 0; 319 | } 320 | 321 | Py_LOCAL_INLINE(int) 322 | PyMUTEX_FINI(PyMUTEX_T *cs) 323 | { 324 | return 0; 325 | } 326 | 327 | Py_LOCAL_INLINE(int) 328 | PyMUTEX_LOCK(PyMUTEX_T *cs) 329 | { 330 | AcquireSRWLockExclusive(cs); 331 | return 0; 332 | } 333 | 334 | Py_LOCAL_INLINE(int) 335 | PyMUTEX_UNLOCK(PyMUTEX_T *cs) 336 | { 337 | ReleaseSRWLockExclusive(cs); 338 | return 0; 339 | } 340 | 341 | 342 | typedef CONDITION_VARIABLE PyCOND_T; 343 | 344 | Py_LOCAL_INLINE(int) 345 | PyCOND_INIT(PyCOND_T *cv) 346 | { 347 | InitializeConditionVariable(cv); 348 | return 0; 349 | } 350 | Py_LOCAL_INLINE(int) 351 | PyCOND_FINI(PyCOND_T *cv) 352 | { 353 | return 0; 354 | } 355 | 356 | Py_LOCAL_INLINE(int) 357 | PyCOND_WAIT(PyCOND_T *cv, PyMUTEX_T *cs) 358 | { 359 | return SleepConditionVariableSRW(cv, cs, INFINITE, 0) ? 0 : -1; 360 | } 361 | 362 | /* This implementation makes no distinction about timeouts. Signal 363 | * 2 to indicate that we don't know. 364 | */ 365 | Py_LOCAL_INLINE(int) 366 | PyCOND_TIMEDWAIT(PyCOND_T *cv, PyMUTEX_T *cs, PY_LONG_LONG us) 367 | { 368 | return SleepConditionVariableSRW(cv, cs, (DWORD)(us/1000), 0) ? 2 : -1; 369 | } 370 | 371 | Py_LOCAL_INLINE(int) 372 | PyCOND_SIGNAL(PyCOND_T *cv) 373 | { 374 | WakeConditionVariable(cv); 375 | return 0; 376 | } 377 | 378 | Py_LOCAL_INLINE(int) 379 | PyCOND_BROADCAST(PyCOND_T *cv) 380 | { 381 | WakeAllConditionVariable(cv); 382 | return 0; 383 | } 384 | 385 | 386 | #endif /* _PY_EMULATED_WIN_CV */ 387 | 388 | #endif /* _POSIX_THREADS, NT_THREADS */ 389 | 390 | #endif /* _CONDVAR_H_ */ 391 | -------------------------------------------------------------------------------- /src/hook.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "hook.h" 7 | 8 | /* TODO: make less ugly! 9 | * there's got to be a nicer way to do this! */ 10 | #pragma pack(push, 1) 11 | static struct { 12 | char push_rax; 13 | char mov_rax[2]; 14 | char addr[8]; 15 | char jmp_rax[2]; } 16 | jump_asm = { 17 | .push_rax = 0x50, 18 | .mov_rax = {0x48, 0xb8}, 19 | .jmp_rax = {0xff, 0xe0} }; 20 | #pragma pack(pop) 21 | 22 | static int unprotect_page(void* addr) { 23 | int pagesize = sysconf(_SC_PAGE_SIZE); 24 | int pagemask = ~(pagesize -1); 25 | char* page = (char *)((size_t)addr & pagemask); 26 | return mprotect(page, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); 27 | } 28 | 29 | int hook_function(void* target, void* replace) { 30 | int count; 31 | 32 | if(unprotect_page(replace)) { 33 | fprintf(stderr, "Could not unprotect replace mem: %p\n", replace); 34 | return 1; 35 | } 36 | 37 | if(unprotect_page(target)) { 38 | fprintf(stderr, "Could not unprotect target mem: %p\n", target); 39 | return 1; 40 | } 41 | 42 | /* find the NOP */ 43 | for(count = 0; count < 255 && ((unsigned char*)replace)[count] != 0x90; ++count); 44 | 45 | if(count == 255) { 46 | fprintf(stderr, "Couldn't find the NOP.\n"); 47 | return 1; 48 | } 49 | 50 | /* shift everything down one */ 51 | memmove(replace+1, replace, count); 52 | 53 | /* add in `pop %rax` */ 54 | *((unsigned char *)replace) = 0x58; 55 | 56 | /* set up the address */ 57 | memcpy(jump_asm.addr, &replace, sizeof (void *)); 58 | 59 | /* smash the target function */ 60 | memcpy(target, &jump_asm, sizeof jump_asm); 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/hook.h: -------------------------------------------------------------------------------- 1 | #ifndef HOOK_H 2 | #define HOOK_H 3 | 4 | #if !(__x86_64__) 5 | #error "This only works on x86_64" 6 | #endif 7 | 8 | int hook_function(void* target, void* replace); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/opcode_targets.h: -------------------------------------------------------------------------------- 1 | static void *opcode_targets[256] = { 2 | &&_unknown_opcode, 3 | &&TARGET_POP_TOP, 4 | &&TARGET_ROT_TWO, 5 | &&TARGET_ROT_THREE, 6 | &&TARGET_DUP_TOP, 7 | &&TARGET_DUP_TOP_TWO, 8 | &&_unknown_opcode, 9 | &&_unknown_opcode, 10 | &&_unknown_opcode, 11 | &&TARGET_NOP, 12 | &&TARGET_UNARY_POSITIVE, 13 | &&TARGET_UNARY_NEGATIVE, 14 | &&TARGET_UNARY_NOT, 15 | &&_unknown_opcode, 16 | &&_unknown_opcode, 17 | &&TARGET_UNARY_INVERT, 18 | &&TARGET_BINARY_MATRIX_MULTIPLY, 19 | &&TARGET_INPLACE_MATRIX_MULTIPLY, 20 | &&_unknown_opcode, 21 | &&TARGET_BINARY_POWER, 22 | &&TARGET_BINARY_MULTIPLY, 23 | &&_unknown_opcode, 24 | &&TARGET_BINARY_MODULO, 25 | &&TARGET_BINARY_ADD, 26 | &&TARGET_BINARY_SUBTRACT, 27 | &&TARGET_BINARY_SUBSCR, 28 | &&TARGET_BINARY_FLOOR_DIVIDE, 29 | &&TARGET_BINARY_TRUE_DIVIDE, 30 | &&TARGET_INPLACE_FLOOR_DIVIDE, 31 | &&TARGET_INPLACE_TRUE_DIVIDE, 32 | &&_unknown_opcode, 33 | &&_unknown_opcode, 34 | &&_unknown_opcode, 35 | &&_unknown_opcode, 36 | &&_unknown_opcode, 37 | &&_unknown_opcode, 38 | &&_unknown_opcode, 39 | &&_unknown_opcode, 40 | &&_unknown_opcode, 41 | &&_unknown_opcode, 42 | &&_unknown_opcode, 43 | &&_unknown_opcode, 44 | &&_unknown_opcode, 45 | &&_unknown_opcode, 46 | &&_unknown_opcode, 47 | &&_unknown_opcode, 48 | &&_unknown_opcode, 49 | &&_unknown_opcode, 50 | &&_unknown_opcode, 51 | &&_unknown_opcode, 52 | &&TARGET_GET_AITER, 53 | &&TARGET_GET_ANEXT, 54 | &&TARGET_BEFORE_ASYNC_WITH, 55 | &&_unknown_opcode, 56 | &&_unknown_opcode, 57 | &&TARGET_INPLACE_ADD, 58 | &&TARGET_INPLACE_SUBTRACT, 59 | &&TARGET_INPLACE_MULTIPLY, 60 | &&_unknown_opcode, 61 | &&TARGET_INPLACE_MODULO, 62 | &&TARGET_STORE_SUBSCR, 63 | &&TARGET_DELETE_SUBSCR, 64 | &&TARGET_BINARY_LSHIFT, 65 | &&TARGET_BINARY_RSHIFT, 66 | &&TARGET_BINARY_AND, 67 | &&TARGET_BINARY_XOR, 68 | &&TARGET_BINARY_OR, 69 | &&TARGET_INPLACE_POWER, 70 | &&TARGET_GET_ITER, 71 | &&TARGET_GET_YIELD_FROM_ITER, 72 | &&TARGET_PRINT_EXPR, 73 | &&TARGET_LOAD_BUILD_CLASS, 74 | &&TARGET_YIELD_FROM, 75 | &&TARGET_GET_AWAITABLE, 76 | &&_unknown_opcode, 77 | &&TARGET_INPLACE_LSHIFT, 78 | &&TARGET_INPLACE_RSHIFT, 79 | &&TARGET_INPLACE_AND, 80 | &&TARGET_INPLACE_XOR, 81 | &&TARGET_INPLACE_OR, 82 | &&TARGET_BREAK_LOOP, 83 | &&TARGET_WITH_CLEANUP_START, 84 | &&TARGET_WITH_CLEANUP_FINISH, 85 | &&TARGET_RETURN_VALUE, 86 | &&TARGET_IMPORT_STAR, 87 | &&_unknown_opcode, 88 | &&TARGET_YIELD_VALUE, 89 | &&TARGET_POP_BLOCK, 90 | &&TARGET_END_FINALLY, 91 | &&TARGET_POP_EXCEPT, 92 | &&TARGET_STORE_NAME, 93 | &&TARGET_DELETE_NAME, 94 | &&TARGET_UNPACK_SEQUENCE, 95 | &&TARGET_FOR_ITER, 96 | &&TARGET_UNPACK_EX, 97 | &&TARGET_STORE_ATTR, 98 | &&TARGET_DELETE_ATTR, 99 | &&TARGET_STORE_GLOBAL, 100 | &&TARGET_DELETE_GLOBAL, 101 | &&_unknown_opcode, 102 | &&TARGET_LOAD_CONST, 103 | &&TARGET_LOAD_NAME, 104 | &&TARGET_BUILD_TUPLE, 105 | &&TARGET_BUILD_LIST, 106 | &&TARGET_BUILD_SET, 107 | &&TARGET_BUILD_MAP, 108 | &&TARGET_LOAD_ATTR, 109 | &&TARGET_COMPARE_OP, 110 | &&TARGET_IMPORT_NAME, 111 | &&TARGET_IMPORT_FROM, 112 | &&TARGET_JUMP_FORWARD, 113 | &&TARGET_JUMP_IF_FALSE_OR_POP, 114 | &&TARGET_JUMP_IF_TRUE_OR_POP, 115 | &&TARGET_JUMP_ABSOLUTE, 116 | &&TARGET_POP_JUMP_IF_FALSE, 117 | &&TARGET_POP_JUMP_IF_TRUE, 118 | &&TARGET_LOAD_GLOBAL, 119 | &&_unknown_opcode, 120 | &&_unknown_opcode, 121 | &&TARGET_CONTINUE_LOOP, 122 | &&TARGET_SETUP_LOOP, 123 | &&TARGET_SETUP_EXCEPT, 124 | &&TARGET_SETUP_FINALLY, 125 | &&_unknown_opcode, 126 | &&TARGET_LOAD_FAST, 127 | &&TARGET_STORE_FAST, 128 | &&TARGET_DELETE_FAST, 129 | &&_unknown_opcode, 130 | &&_unknown_opcode, 131 | &&_unknown_opcode, 132 | &&TARGET_RAISE_VARARGS, 133 | &&TARGET_CALL_FUNCTION, 134 | &&TARGET_MAKE_FUNCTION, 135 | &&TARGET_BUILD_SLICE, 136 | &&TARGET_MAKE_CLOSURE, 137 | &&TARGET_LOAD_CLOSURE, 138 | &&TARGET_LOAD_DEREF, 139 | &&TARGET_STORE_DEREF, 140 | &&TARGET_DELETE_DEREF, 141 | &&_unknown_opcode, 142 | &&TARGET_CALL_FUNCTION_VAR, 143 | &&TARGET_CALL_FUNCTION_KW, 144 | &&TARGET_CALL_FUNCTION_VAR_KW, 145 | &&TARGET_SETUP_WITH, 146 | &&TARGET_EXTENDED_ARG, 147 | &&TARGET_LIST_APPEND, 148 | &&TARGET_SET_ADD, 149 | &&TARGET_MAP_ADD, 150 | &&TARGET_LOAD_CLASSDEREF, 151 | &&TARGET_BUILD_LIST_UNPACK, 152 | &&TARGET_BUILD_MAP_UNPACK, 153 | &&TARGET_BUILD_MAP_UNPACK_WITH_CALL, 154 | &&TARGET_BUILD_TUPLE_UNPACK, 155 | &&TARGET_BUILD_SET_UNPACK, 156 | &&TARGET_SETUP_ASYNC_WITH, 157 | &&_unknown_opcode, 158 | &&_unknown_opcode, 159 | &&_unknown_opcode, 160 | &&_unknown_opcode, 161 | &&_unknown_opcode, 162 | &&_unknown_opcode, 163 | &&_unknown_opcode, 164 | &&_unknown_opcode, 165 | &&_unknown_opcode, 166 | &&_unknown_opcode, 167 | &&_unknown_opcode, 168 | &&_unknown_opcode, 169 | &&_unknown_opcode, 170 | &&_unknown_opcode, 171 | &&_unknown_opcode, 172 | &&_unknown_opcode, 173 | &&_unknown_opcode, 174 | &&_unknown_opcode, 175 | &&_unknown_opcode, 176 | &&_unknown_opcode, 177 | &&_unknown_opcode, 178 | &&_unknown_opcode, 179 | &&_unknown_opcode, 180 | &&_unknown_opcode, 181 | &&_unknown_opcode, 182 | &&_unknown_opcode, 183 | &&_unknown_opcode, 184 | &&_unknown_opcode, 185 | &&_unknown_opcode, 186 | &&_unknown_opcode, 187 | &&_unknown_opcode, 188 | &&_unknown_opcode, 189 | &&_unknown_opcode, 190 | &&_unknown_opcode, 191 | &&_unknown_opcode, 192 | &&_unknown_opcode, 193 | &&_unknown_opcode, 194 | &&_unknown_opcode, 195 | &&_unknown_opcode, 196 | &&_unknown_opcode, 197 | &&_unknown_opcode, 198 | &&_unknown_opcode, 199 | &&_unknown_opcode, 200 | &&_unknown_opcode, 201 | &&_unknown_opcode, 202 | &&_unknown_opcode, 203 | &&_unknown_opcode, 204 | &&_unknown_opcode, 205 | &&_unknown_opcode, 206 | &&_unknown_opcode, 207 | &&_unknown_opcode, 208 | &&_unknown_opcode, 209 | &&_unknown_opcode, 210 | &&_unknown_opcode, 211 | &&_unknown_opcode, 212 | &&_unknown_opcode, 213 | &&_unknown_opcode, 214 | &&_unknown_opcode, 215 | &&_unknown_opcode, 216 | &&_unknown_opcode, 217 | &&_unknown_opcode, 218 | &&_unknown_opcode, 219 | &&_unknown_opcode, 220 | &&_unknown_opcode, 221 | &&_unknown_opcode, 222 | &&_unknown_opcode, 223 | &&_unknown_opcode, 224 | &&_unknown_opcode, 225 | &&_unknown_opcode, 226 | &&_unknown_opcode, 227 | &&_unknown_opcode, 228 | &&_unknown_opcode, 229 | &&_unknown_opcode, 230 | &&_unknown_opcode, 231 | &&_unknown_opcode, 232 | &&_unknown_opcode, 233 | &&_unknown_opcode, 234 | &&_unknown_opcode, 235 | &&_unknown_opcode, 236 | &&_unknown_opcode, 237 | &&_unknown_opcode, 238 | &&_unknown_opcode, 239 | &&_unknown_opcode, 240 | &&_unknown_opcode, 241 | &&_unknown_opcode, 242 | &&_unknown_opcode, 243 | &&_unknown_opcode, 244 | &&_unknown_opcode, 245 | &&_unknown_opcode, 246 | &&_unknown_opcode, 247 | &&_unknown_opcode, 248 | &&_unknown_opcode, 249 | &&_unknown_opcode, 250 | &&_unknown_opcode, 251 | &&_unknown_opcode, 252 | &&_unknown_opcode, 253 | &&_unknown_opcode, 254 | &&_unknown_opcode, 255 | &&_unknown_opcode, 256 | &&_unknown_opcode, 257 | &&_unknown_opcode 258 | }; 259 | -------------------------------------------------------------------------------- /src/rwatch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pythread.h" 3 | #include "hook.h" 4 | #include "frameobject.h" 5 | 6 | PyObject * hooked_PyEval_EvalFrameEx(PyFrameObject *f, int throwflag); 7 | 8 | static PyObject * 9 | sys_setrwatch(PyObject *self, PyObject *args, PyObject *kwds) 10 | { 11 | static char *kwlist[] = {"targets", 0}; 12 | PyObject *targets = NULL; 13 | PyObject *dict; 14 | 15 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:setrwatch", 16 | kwlist, &targets)) 17 | return NULL; 18 | 19 | if (!targets) 20 | targets = Py_None; 21 | Py_INCREF(targets); 22 | 23 | dict = PyThreadState_GetDict(); 24 | PyDict_SetItemString(dict, "watchtargets", targets); 25 | 26 | Py_INCREF(Py_None); 27 | return Py_None; 28 | } 29 | 30 | PyDoc_STRVAR(setrwatch_doc, 31 | "setrwatch(targets)\n\ 32 | \n\ 33 | Set read-watches for the specified objects.\n\ 34 | The targets should be a mapping of object ids to functions." 35 | ); 36 | 37 | static PyObject * 38 | sys_getrwatch(PyObject *self, PyObject *args) 39 | { 40 | PyObject *dict = PyThreadState_GetDict(); 41 | PyObject *targets = PyDict_GetItemString(dict, "watchtargets"); 42 | 43 | if (targets == NULL) 44 | targets = Py_None; 45 | 46 | Py_INCREF(targets); 47 | return targets; 48 | } 49 | 50 | PyDoc_STRVAR(getrwatch_doc, 51 | "getrwatch()\n\ 52 | \n\ 53 | Return the targets set with sys.setrwatch." 54 | ); 55 | 56 | PyDoc_STRVAR(module_doc, 57 | "This module implements a \"read watches\" in Python for debugging."); 58 | 59 | static PyMethodDef module_methods[] = { 60 | {"setrwatch", (PyCFunction)sys_setrwatch, 61 | METH_VARARGS | METH_KEYWORDS, setrwatch_doc}, 62 | {"getrwatch", sys_getrwatch, METH_NOARGS, getrwatch_doc}, 63 | {NULL} /* Sentinel */ 64 | }; 65 | 66 | static struct PyModuleDef moduledef = { 67 | PyModuleDef_HEAD_INIT, 68 | "rwatch", 69 | module_doc, 70 | -1, 71 | module_methods, 72 | NULL, 73 | NULL, 74 | NULL, 75 | NULL 76 | }; 77 | 78 | PyMODINIT_FUNC 79 | PyInit_rwatch(void) { 80 | __asm__(""); 81 | 82 | PyObject *m = PyModule_Create(&moduledef); 83 | if (!m) goto finally; 84 | 85 | if(hook_function(PyEval_EvalFrameEx, hooked_PyEval_EvalFrameEx)) 86 | fprintf(stderr, "Function hooking failed.\n"); 87 | 88 | PyObject* sys_str = PyUnicode_FromString("sys"); 89 | PyObject* sys_mod = PyImport_Import(sys_str); 90 | if(0 != PyModule_AddFunctions(sys_mod, module_methods)) 91 | fprintf(stderr, "Couldn't add methods to sys module.\n"); 92 | 93 | /* PyThreadState_GetDict is the right approach, but I would like to note 94 | * that it would have been much more fun to have had: 95 | * typedef struct { 96 | * PyThreadState ts; 97 | * PyObject *c_watchtargets; 98 | * } PyThreadState2; 99 | * 100 | * PyInterpreterState *is = PyThreadState_GET()->interp; 101 | * PyThreadState *head = PyInterpreterState_ThreadHead(is); 102 | * PyThreadState *ts; 103 | * for (ts = head; ts; ts = PyThreadState_Next(ts)) { 104 | * // (: 105 | * } 106 | */ 107 | 108 | Py_DECREF(sys_str); 109 | Py_DECREF(sys_mod); 110 | 111 | finally: 112 | return m; 113 | } 114 | -------------------------------------------------------------------------------- /src/test.py: -------------------------------------------------------------------------------- 1 | import rwatch 2 | from sys import getrwatch, setrwatch 3 | 4 | def view(f, o): 5 | print(f,o) 6 | return o 7 | 8 | def f(): 9 | x = object() 10 | setrwatch({id(x): view}) 11 | print({hex(k): v for k,v in (getrwatch() or {}).items()}) 12 | print('x =', x) 13 | 14 | f() 15 | --------------------------------------------------------------------------------