1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
#ifndef _PY_CUSTOM_PYEVAL_SETTRACE_HPP_
#define _PY_CUSTOM_PYEVAL_SETTRACE_HPP_
#include "python.h"
#include "py_utils.hpp"
#include "py_custom_pyeval_settrace_common.hpp"
#include "py_custom_pyeval_settrace_310.hpp"
#include "py_custom_pyeval_settrace_311.hpp"
// On Python 3.7 onwards the thread state is not kept in PyThread_set_key_value (rather
// it uses PyThread_tss_set using PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, (void *)tstate)
// and we don't have access to that key from here (thus, we can't use the previous approach which
// made CPython think that the current thread had the thread state where we wanted to set the tracing).
//
// So, the solution implemented here is not faking that change and reimplementing PyEval_SetTrace.
// The implementation is mostly the same from the one in CPython, but we have one shortcoming:
//
// When CPython sets the tracing for a thread it increments _Py_TracingPossible (actually
// _PyRuntime.ceval.tracing_possible). This implementation has one issue: it only works on
// deltas when the tracing is set (so, a settrace(func) will increase the _Py_TracingPossible global value and a
// settrace(None) will decrease it, but when a thread dies it's kept as is and is not decreased).
// -- as we don't currently have access to _PyRuntime we have to create a thread, set the tracing
// and let it die so that the count is increased (this is really hacky, but better than having
// to create a local copy of the whole _PyRuntime (defined in pystate.h with several inner structs)
// which would need to be kept up to date for each new CPython version just to increment that variable).
/**
* This version is used in internalInitializeCustomPyEvalSetTrace->pyObject_FastCallDict on older
* versions of CPython (pre 3.7).
*/
static PyObject *
PyObject_FastCallDictCustom(PyObject* callback, PyObject *stack[3], int ignoredStackSizeAlways3, void* ignored)
{
PyObject *args = internalInitializeCustomPyEvalSetTrace->pyTuple_New(3);
PyObject *result;
if (args == NULL) {
return NULL;
}
IncRef(stack[0]);
IncRef(stack[1]);
IncRef(stack[2]);
// I.e.: same thing as: PyTuple_SET_ITEM(args, 0, stack[0]);
reinterpret_cast<PyTupleObject *>(args)->ob_item[0] = stack[0];
reinterpret_cast<PyTupleObject *>(args)->ob_item[1] = stack[1];
reinterpret_cast<PyTupleObject *>(args)->ob_item[2] = stack[2];
/* call the Python-level function */
result = internalInitializeCustomPyEvalSetTrace->pyEval_CallObjectWithKeywords(callback, args, (PyObject*)NULL);
/* cleanup */
DecRef(args, internalInitializeCustomPyEvalSetTrace->isDebug);
return result;
}
static PyObject *
InternalCallTrampoline(PyObject* callback,
PyFrameObjectBaseUpTo39 *frame, int what, PyObject *arg)
{
PyObject *result;
PyObject *stack[3];
// Note: this is commented out from CPython (we shouldn't need it and it adds a reasonable overhead).
// if (PyFrame_FastToLocalsWithError(frame) < 0) {
// return NULL;
// }
//
stack[0] = (PyObject *)frame;
stack[1] = InternalWhatstrings_37[what];
stack[2] = (arg != NULL) ? arg : internalInitializeCustomPyEvalSetTrace->pyNone;
// Helpers to print info.
// printf("%s\n", internalInitializeCustomPyEvalSetTrace->pyUnicode_AsUTF8(internalInitializeCustomPyEvalSetTrace->pyObject_Repr((PyObject *)stack[0])));
// printf("%s\n", internalInitializeCustomPyEvalSetTrace->pyUnicode_AsUTF8(internalInitializeCustomPyEvalSetTrace->pyObject_Repr((PyObject *)stack[1])));
// printf("%s\n", internalInitializeCustomPyEvalSetTrace->pyUnicode_AsUTF8(internalInitializeCustomPyEvalSetTrace->pyObject_Repr((PyObject *)stack[2])));
// printf("%s\n", internalInitializeCustomPyEvalSetTrace->pyUnicode_AsUTF8(internalInitializeCustomPyEvalSetTrace->pyObject_Repr((PyObject *)callback)));
// call the Python-level function
// result = _PyObject_FastCall(callback, stack, 3);
//
// Note that _PyObject_FastCall is actually a define:
// #define _PyObject_FastCall(func, args, nargs) _PyObject_FastCallDict((func), (args), (nargs), NULL)
result = internalInitializeCustomPyEvalSetTrace->pyObject_FastCallDict(callback, stack, 3, NULL);
// Note: this is commented out from CPython (we shouldn't need it and it adds a reasonable overhead).
// PyFrame_LocalsToFast(frame, 1);
if (result == NULL) {
internalInitializeCustomPyEvalSetTrace->pyTraceBack_Here(frame);
}
return result;
}
static int
InternalTraceTrampoline(PyObject *self, PyFrameObject *frameParam,
int what, PyObject *arg)
{
PyObject *callback;
PyObject *result;
PyFrameObjectBaseUpTo39 *frame = reinterpret_cast<PyFrameObjectBaseUpTo39*>(frameParam);
if (what == PyTrace_CALL){
callback = self;
} else {
callback = frame->f_trace;
}
if (callback == NULL){
return 0;
}
result = InternalCallTrampoline(callback, frame, what, arg);
if (result == NULL) {
// Note: calling the original sys.settrace here.
internalInitializeCustomPyEvalSetTrace->pyEval_SetTrace(NULL, NULL);
PyObject *temp_f_trace = frame->f_trace;
frame->f_trace = NULL;
if(temp_f_trace != NULL){
DecRef(temp_f_trace, internalInitializeCustomPyEvalSetTrace->isDebug);
}
return -1;
}
if (result != internalInitializeCustomPyEvalSetTrace->pyNone) {
PyObject *tmp = frame->f_trace;
frame->f_trace = result;
DecRef(tmp, internalInitializeCustomPyEvalSetTrace->isDebug);
}
else {
DecRef(result, internalInitializeCustomPyEvalSetTrace->isDebug);
}
return 0;
}
// Based on ceval.c (PyEval_SetTrace(Py_tracefunc func, PyObject *arg))
template<typename T>
void InternalPySetTrace_Template(T tstate, PyObjectHolder* traceFunc, bool isDebug)
{
PyObject *temp = tstate->c_traceobj;
// We can't increase _Py_TracingPossible. Everything else should be equal to CPython.
// runtime->ceval.tracing_possible += (func != NULL) - (tstate->c_tracefunc != NULL);
PyObject *arg = traceFunc->ToPython();
IncRef(arg);
tstate->c_tracefunc = NULL;
tstate->c_traceobj = NULL;
/* Must make sure that profiling is not ignored if 'temp' is freed */
tstate->use_tracing = tstate->c_profilefunc != NULL;
if(temp != NULL){
DecRef(temp, isDebug);
}
tstate->c_tracefunc = InternalTraceTrampoline;
tstate->c_traceobj = arg;
/* Flag that tracing or profiling is turned on */
tstate->use_tracing = ((InternalTraceTrampoline != NULL)
|| (tstate->c_profilefunc != NULL));
};
void InternalPySetTrace(PyThreadState* curThread, PyObjectHolder* traceFunc, bool isDebug, PythonVersion version)
{
if (PyThreadState_25_27::IsFor(version)) {
InternalPySetTrace_Template<PyThreadState_25_27*>(reinterpret_cast<PyThreadState_25_27*>(curThread), traceFunc, isDebug);
} else if (PyThreadState_30_33::IsFor(version)) {
InternalPySetTrace_Template<PyThreadState_30_33*>(reinterpret_cast<PyThreadState_30_33*>(curThread), traceFunc, isDebug);
} else if (PyThreadState_34_36::IsFor(version)) {
InternalPySetTrace_Template<PyThreadState_34_36*>(reinterpret_cast<PyThreadState_34_36*>(curThread), traceFunc, isDebug);
} else if (PyThreadState_37_38::IsFor(version)) {
InternalPySetTrace_Template<PyThreadState_37_38*>(reinterpret_cast<PyThreadState_37_38*>(curThread), traceFunc, isDebug);
} else if (PyThreadState_39::IsFor(version)) {
InternalPySetTrace_Template<PyThreadState_39*>(reinterpret_cast<PyThreadState_39*>(curThread), traceFunc, isDebug);
} else if (PyThreadState_310::IsFor(version)) {
// 3.10 has other changes on the actual algorithm (use_tracing is per-frame now), so, we have a full new version for it.
InternalPySetTrace_Template310<PyThreadState_310*>(reinterpret_cast<PyThreadState_310*>(curThread), traceFunc, isDebug);
} else if (PyThreadState_311::IsFor(version)) {
InternalPySetTrace_Template311<PyThreadState_311*>(reinterpret_cast<PyThreadState_311*>(curThread), traceFunc, isDebug);
} else {
printf("Unable to set trace to target thread with Python version: %d", version);
}
}
#endif //_PY_CUSTOM_PYEVAL_SETTRACE_HPP_
|