be concatenated to the :exc:`RecursionError` message caused by the recursion
depth limit.
+ .. seealso::
+ The :c:func:`PyUnstable_ThreadState_SetStackProtection` function.
+
.. versionchanged:: 3.9
This function is now also available in the :ref:`limited API <limited-c-api>`.
.. versionadded:: 3.11
+.. c:function:: int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size)
+
+ Set the stack protection start address and stack protection size
+ of a Python thread state.
+
+ On success, return ``0``.
+ On failure, set an exception and return ``-1``.
+
+ CPython implements :ref:`recursion control <recursion>` for C code by raising
+ :py:exc:`RecursionError` when it notices that the machine execution stack is close
+ to overflow. See for example the :c:func:`Py_EnterRecursiveCall` function.
+ For this, it needs to know the location of the current thread's stack, which it
+ normally gets from the operating system.
+ When the stack is changed, for example using context switching techniques like the
+ Boost library's ``boost::context``, you must call
+ :c:func:`~PyUnstable_ThreadState_SetStackProtection` to inform CPython of the change.
+
+ Call :c:func:`~PyUnstable_ThreadState_SetStackProtection` either before
+ or after changing the stack.
+ Do not call any other Python C API between the call and the stack
+ change.
+
+ See :c:func:`PyUnstable_ThreadState_ResetStackProtection` for undoing this operation.
+
+ .. versionadded:: next
+
+
+.. c:function:: void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)
+
+ Reset the stack protection start address and stack protection size
+ of a Python thread state to the operating system defaults.
+
+ See :c:func:`PyUnstable_ThreadState_SetStackProtection` for an explanation.
+
+ .. versionadded:: next
+
+
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
Get the current interpreter.
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
(Contributed by Victor Stinner in :gh:`111489`.)
+* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and
+ :c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set
+ the stack protection base address and stack protection size of a Python
+ thread state.
+ (Contributed by Victor Stinner in :gh:`139653`.)
+
Changed C APIs
--------------
*/
PyAPI_FUNC(PyObject*) _PyThread_CurrentFrames(void);
+// Set the stack protection start address and stack protection size
+// of a Python thread state
+PyAPI_FUNC(int) PyUnstable_ThreadState_SetStackProtection(
+ PyThreadState *tstate,
+ void *stack_start_addr, // Stack start address
+ size_t stack_size); // Stack size (in bytes)
+
+// Reset the stack protection start address and stack protection size
+// of a Python thread state
+PyAPI_FUNC(void) PyUnstable_ThreadState_ResetStackProtection(
+ PyThreadState *tstate);
+
/* Routines for advanced debuggers, requested by David Beazley.
Don't use unless you know what you are doing! */
PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Main(void);
# define _PyOS_STACK_MARGIN_SHIFT (_PyOS_LOG2_STACK_MARGIN + 2)
#endif
+#ifdef _Py_THREAD_SANITIZER
+# define _PyOS_MIN_STACK_SIZE (_PyOS_STACK_MARGIN_BYTES * 6)
+#else
+# define _PyOS_MIN_STACK_SIZE (_PyOS_STACK_MARGIN_BYTES * 3)
+#endif
+
#ifdef __cplusplus
}
uintptr_t c_stack_soft_limit;
uintptr_t c_stack_hard_limit;
+ // PyUnstable_ThreadState_ResetStackProtection() values
+ uintptr_t c_stack_init_base;
+ uintptr_t c_stack_init_top;
+
PyObject *asyncio_running_loop; // Strong reference
PyObject *asyncio_running_task; // Strong reference
--- /dev/null
+Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and
+:c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the
+stack protection base address and stack protection size of a Python thread
+state. Patch by Victor Stinner.
return result;
}
+
+static void
+check_threadstate_set_stack_protection(PyThreadState *tstate,
+ void *start, size_t size)
+{
+ assert(PyUnstable_ThreadState_SetStackProtection(tstate, start, size) == 0);
+ assert(!PyErr_Occurred());
+
+ _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
+ assert(ts->c_stack_top == (uintptr_t)start + size);
+ assert(ts->c_stack_hard_limit <= ts->c_stack_soft_limit);
+ assert(ts->c_stack_soft_limit < ts->c_stack_top);
+}
+
+
+static PyObject *
+test_threadstate_set_stack_protection(PyObject *self, PyObject *Py_UNUSED(args))
+{
+ PyThreadState *tstate = PyThreadState_GET();
+ _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
+ assert(!PyErr_Occurred());
+
+ uintptr_t init_base = ts->c_stack_init_base;
+ size_t init_top = ts->c_stack_init_top;
+
+ // Test the minimum stack size
+ size_t size = _PyOS_MIN_STACK_SIZE;
+ void *start = (void*)(_Py_get_machine_stack_pointer() - size);
+ check_threadstate_set_stack_protection(tstate, start, size);
+
+ // Test a larger size
+ size = 7654321;
+ assert(size > _PyOS_MIN_STACK_SIZE);
+ start = (void*)(_Py_get_machine_stack_pointer() - size);
+ check_threadstate_set_stack_protection(tstate, start, size);
+
+ // Test invalid size (too small)
+ size = 5;
+ start = (void*)(_Py_get_machine_stack_pointer() - size);
+ assert(PyUnstable_ThreadState_SetStackProtection(tstate, start, size) == -1);
+ assert(PyErr_ExceptionMatches(PyExc_ValueError));
+ PyErr_Clear();
+
+ // Test PyUnstable_ThreadState_ResetStackProtection()
+ PyUnstable_ThreadState_ResetStackProtection(tstate);
+ assert(ts->c_stack_init_base == init_base);
+ assert(ts->c_stack_init_top == init_top);
+
+ Py_RETURN_NONE;
+}
+
+
static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
{"simple_pending_call", simple_pending_call, METH_O},
{"set_vectorcall_nop", set_vectorcall_nop, METH_O},
{"module_get_gc_hooks", module_get_gc_hooks, METH_O},
+ {"test_threadstate_set_stack_protection",
+ test_threadstate_set_stack_protection, METH_NOARGS},
{NULL, NULL} /* sentinel */
};
#endif
static void
-hardware_stack_limits(uintptr_t *top, uintptr_t *base)
+hardware_stack_limits(uintptr_t *base, uintptr_t *top)
{
#ifdef WIN32
ULONG_PTR low, high;
#endif
}
-void
-_Py_InitializeRecursionLimits(PyThreadState *tstate)
+static void
+tstate_set_stack(PyThreadState *tstate,
+ uintptr_t base, uintptr_t top)
{
- uintptr_t top;
- uintptr_t base;
- hardware_stack_limits(&top, &base);
+ assert(base < top);
+ assert((top - base) >= _PyOS_MIN_STACK_SIZE);
+
#ifdef _Py_THREAD_SANITIZER
// Thread sanitizer crashes if we use more than half the stack.
uintptr_t stacksize = top - base;
- base += stacksize/2;
+ base += stacksize / 2;
#endif
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
_tstate->c_stack_top = top;
_tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES;
_tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2;
+
+#ifndef NDEBUG
+ // Sanity checks
+ _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
+ assert(ts->c_stack_hard_limit <= ts->c_stack_soft_limit);
+ assert(ts->c_stack_soft_limit < ts->c_stack_top);
+#endif
+}
+
+
+void
+_Py_InitializeRecursionLimits(PyThreadState *tstate)
+{
+ uintptr_t base, top;
+ hardware_stack_limits(&base, &top);
+ assert(top != 0);
+
+ tstate_set_stack(tstate, base, top);
+ _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
+ ts->c_stack_init_base = base;
+ ts->c_stack_init_top = top;
+
+ // Test the stack pointer
+#if !defined(NDEBUG) && !defined(__wasi__)
+ uintptr_t here_addr = _Py_get_machine_stack_pointer();
+ assert(ts->c_stack_soft_limit < here_addr);
+ assert(here_addr < ts->c_stack_top);
+#endif
+}
+
+
+int
+PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate,
+ void *stack_start_addr, size_t stack_size)
+{
+ if (stack_size < _PyOS_MIN_STACK_SIZE) {
+ PyErr_Format(PyExc_ValueError,
+ "stack_size must be at least %zu bytes",
+ _PyOS_MIN_STACK_SIZE);
+ return -1;
+ }
+
+ uintptr_t base = (uintptr_t)stack_start_addr;
+ uintptr_t top = base + stack_size;
+ tstate_set_stack(tstate, base, top);
+ return 0;
}
+
+void
+PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)
+{
+ _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
+ if (ts->c_stack_init_top != 0) {
+ tstate_set_stack(tstate,
+ ts->c_stack_init_base,
+ ts->c_stack_init_top);
+ return;
+ }
+
+ _Py_InitializeRecursionLimits(tstate);
+}
+
+
/* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall()
if the recursion_depth reaches recursion_limit. */
int
_tstate->c_stack_top = 0;
_tstate->c_stack_hard_limit = 0;
+ _tstate->c_stack_init_base = 0;
+ _tstate->c_stack_init_top = 0;
+
_tstate->asyncio_running_loop = NULL;
_tstate->asyncio_running_task = NULL;