aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Modules
diff options
context:
space:
mode:
Diffstat (limited to 'Modules')
-rw-r--r--Modules/_asynciomodule.c2
-rw-r--r--Modules/_datetimemodule.c2
-rw-r--r--Modules/_functoolsmodule.c192
-rw-r--r--Modules/_hashopenssl.c21
-rw-r--r--Modules/_remote_debugging_module.c1012
-rw-r--r--Modules/_ssl.c208
-rw-r--r--Modules/_testbuffer.c3
-rw-r--r--Modules/_testcapi/monitoring.c2
-rw-r--r--Modules/_testcapi/time.c3
-rw-r--r--Modules/_testcapimodule.c3
-rw-r--r--Modules/_testinternalcapi.c3
-rw-r--r--Modules/_threadmodule.c2
-rw-r--r--Modules/clinic/_remote_debugging_module.c.h47
-rw-r--r--Modules/clinic/_ssl.c.h116
-rw-r--r--Modules/clinic/mathmodule.c.h108
-rw-r--r--Modules/mathmodule.c36
16 files changed, 1152 insertions, 608 deletions
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c
index 5f9181395c4..99408e60721 100644
--- a/Modules/_asynciomodule.c
+++ b/Modules/_asynciomodule.c
@@ -821,7 +821,7 @@ future_add_done_callback(asyncio_state *state, FutureObj *fut, PyObject *arg,
Invariants:
* callbacks != NULL:
- There are some callbacks in in the list. Just
+ There are some callbacks in the list. Just
add the new callback to it.
* callbacks == NULL and callback0 == NULL:
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index eb90be81c8d..7a6426593d0 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -1934,7 +1934,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
}
replacement = freplacement;
}
-#ifdef Py_NORMALIZE_CENTURY
+#ifdef _Py_NORMALIZE_CENTURY
else if (ch == 'Y' || ch == 'G'
|| ch == 'F' || ch == 'C'
) {
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index d3dabd58b89..1c888295cb0 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -367,19 +367,6 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type)
return PyMethod_New(self, obj);
}
-/* Merging keyword arguments using the vectorcall convention is messy, so
- * if we would need to do that, we stop using vectorcall and fall back
- * to using partial_call() instead. */
-Py_NO_INLINE static PyObject *
-partial_vectorcall_fallback(PyThreadState *tstate, partialobject *pto,
- PyObject *const *args, size_t nargsf,
- PyObject *kwnames)
-{
- pto->vectorcall = NULL;
- Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
- return _PyObject_MakeTpCall(tstate, (PyObject *)pto, args, nargs, kwnames);
-}
-
static PyObject *
partial_vectorcall(PyObject *self, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
@@ -388,10 +375,7 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
PyThreadState *tstate = _PyThreadState_GET();
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
- /* pto->kw is mutable, so need to check every time */
- if (PyDict_GET_SIZE(pto->kw)) {
- return partial_vectorcall_fallback(tstate, pto, args, nargsf, kwnames);
- }
+ /* Placeholder check */
Py_ssize_t pto_phcount = pto->phcount;
if (nargs < pto_phcount) {
PyErr_Format(PyExc_TypeError,
@@ -400,50 +384,143 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
return NULL;
}
- Py_ssize_t nargskw = nargs;
- if (kwnames != NULL) {
- nargskw += PyTuple_GET_SIZE(kwnames);
- }
-
PyObject **pto_args = _PyTuple_ITEMS(pto->args);
Py_ssize_t pto_nargs = PyTuple_GET_SIZE(pto->args);
+ Py_ssize_t pto_nkwds = PyDict_GET_SIZE(pto->kw);
+ Py_ssize_t nkwds = kwnames == NULL ? 0 : PyTuple_GET_SIZE(kwnames);
+ Py_ssize_t nargskw = nargs + nkwds;
+
+ /* Special cases */
+ if (!pto_nkwds) {
+ /* Fast path if we're called without arguments */
+ if (nargskw == 0) {
+ return _PyObject_VectorcallTstate(tstate, pto->fn, pto_args,
+ pto_nargs, NULL);
+ }
- /* Fast path if we're called without arguments */
- if (nargskw == 0) {
- return _PyObject_VectorcallTstate(tstate, pto->fn,
- pto_args, pto_nargs, NULL);
+ /* Use PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single
+ * positional argument. */
+ if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) {
+ PyObject **newargs = (PyObject **)args - 1;
+ PyObject *tmp = newargs[0];
+ newargs[0] = pto_args[0];
+ PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, newargs,
+ nargs + 1, kwnames);
+ newargs[0] = tmp;
+ return ret;
+ }
}
- /* Fast path using PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single
- * positional argument */
- if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) {
- PyObject **newargs = (PyObject **)args - 1;
- PyObject *tmp = newargs[0];
- newargs[0] = pto_args[0];
- PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn,
- newargs, nargs + 1, kwnames);
- newargs[0] = tmp;
- return ret;
- }
+ /* Total sizes */
+ Py_ssize_t tot_nargs = pto_nargs + nargs - pto_phcount;
+ Py_ssize_t tot_nkwds = pto_nkwds + nkwds;
+ Py_ssize_t tot_nargskw = tot_nargs + tot_nkwds;
- PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
- PyObject **stack;
+ PyObject *pto_kw_merged = NULL; // pto_kw with duplicates merged (if any)
+ PyObject *tot_kwnames;
- Py_ssize_t tot_nargskw = pto_nargs + nargskw - pto_phcount;
- if (tot_nargskw <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) {
+ /* Allocate Stack
+ * Note, _PY_FASTCALL_SMALL_STACK is optimal for positional only
+ * This case might have keyword arguments
+ * furthermore, it might use extra stack space for temporary key storage
+ * thus, double small_stack size is used, which is 10 * 8 = 80 bytes */
+ PyObject *small_stack[_PY_FASTCALL_SMALL_STACK * 2];
+ PyObject **tmp_stack, **stack;
+ Py_ssize_t init_stack_size = tot_nargskw;
+ if (pto_nkwds) {
+ // If pto_nkwds, allocate additional space for temporary new keys
+ init_stack_size += nkwds;
+ }
+ if (init_stack_size <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) {
stack = small_stack;
}
else {
- stack = PyMem_Malloc(tot_nargskw * sizeof(PyObject *));
+ stack = PyMem_Malloc(init_stack_size * sizeof(PyObject *));
if (stack == NULL) {
- PyErr_NoMemory();
- return NULL;
+ return PyErr_NoMemory();
}
}
- Py_ssize_t tot_nargs;
+ /* Copy keywords to stack */
+ if (!pto_nkwds) {
+ tot_kwnames = kwnames;
+ if (nkwds) {
+ /* if !pto_nkwds & nkwds, then simply append kw */
+ memcpy(stack + tot_nargs, args + nargs, nkwds * sizeof(PyObject*));
+ }
+ }
+ else {
+ /* stack is now [<positionals>, <pto_kwds>, <kwds>, <kwds_keys>]
+ * Will resize later to [<positionals>, <merged_kwds>] */
+ PyObject *key, *val;
+
+ /* Merge kw to pto_kw or add to tail (if not duplicate) */
+ Py_ssize_t n_tail = 0;
+ for (Py_ssize_t i = 0; i < nkwds; ++i) {
+ key = PyTuple_GET_ITEM(kwnames, i);
+ val = args[nargs + i];
+ if (PyDict_Contains(pto->kw, key)) {
+ if (pto_kw_merged == NULL) {
+ pto_kw_merged = PyDict_Copy(pto->kw);
+ if (pto_kw_merged == NULL) {
+ goto error;
+ }
+ }
+ if (PyDict_SetItem(pto_kw_merged, key, val) < 0) {
+ Py_DECREF(pto_kw_merged);
+ goto error;
+ }
+ }
+ else {
+ /* Copy keyword tail to stack */
+ stack[tot_nargs + pto_nkwds + n_tail] = val;
+ stack[tot_nargskw + n_tail] = key;
+ n_tail++;
+ }
+ }
+ Py_ssize_t n_merges = nkwds - n_tail;
+
+ /* Create total kwnames */
+ tot_kwnames = PyTuple_New(tot_nkwds - n_merges);
+ if (tot_kwnames == NULL) {
+ Py_XDECREF(pto_kw_merged);
+ goto error;
+ }
+ for (Py_ssize_t i = 0; i < n_tail; ++i) {
+ key = Py_NewRef(stack[tot_nargskw + i]);
+ PyTuple_SET_ITEM(tot_kwnames, pto_nkwds + i, key);
+ }
+
+ /* Copy pto_keywords with overlapping call keywords merged
+ * Note, tail is already coppied. */
+ Py_ssize_t pos = 0, i = 0;
+ while (PyDict_Next(n_merges ? pto_kw_merged : pto->kw, &pos, &key, &val)) {
+ assert(i < pto_nkwds);
+ PyTuple_SET_ITEM(tot_kwnames, i, Py_NewRef(key));
+ stack[tot_nargs + i] = val;
+ i++;
+ }
+ assert(i == pto_nkwds);
+ Py_XDECREF(pto_kw_merged);
+
+ /* Resize Stack if the removing overallocation saves some noticable memory
+ * NOTE: This whole block can be removed without breaking anything */
+ Py_ssize_t noveralloc = n_merges + nkwds;
+ if (stack != small_stack && noveralloc > 6 && noveralloc > init_stack_size / 10) {
+ tmp_stack = PyMem_Realloc(stack, (tot_nargskw - n_merges) * sizeof(PyObject *));
+ if (tmp_stack == NULL) {
+ Py_DECREF(tot_kwnames);
+ if (stack != small_stack) {
+ PyMem_Free(stack);
+ }
+ return PyErr_NoMemory();
+ }
+ stack = tmp_stack;
+ }
+ }
+
+ /* Copy Positionals to stack */
if (pto_phcount) {
- tot_nargs = pto_nargs + nargs - pto_phcount;
Py_ssize_t j = 0; // New args index
for (Py_ssize_t i = 0; i < pto_nargs; i++) {
if (pto_args[i] == pto->placeholder) {
@@ -455,22 +532,31 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
}
}
assert(j == pto_phcount);
- if (nargskw > pto_phcount) {
- memcpy(stack + pto_nargs, args + j, (nargskw - j) * sizeof(PyObject*));
+ /* Add remaining args from new_args */
+ if (nargs > pto_phcount) {
+ memcpy(stack + pto_nargs, args + j, (nargs - j) * sizeof(PyObject*));
}
}
else {
- tot_nargs = pto_nargs + nargs;
- /* Copy to new stack, using borrowed references */
memcpy(stack, pto_args, pto_nargs * sizeof(PyObject*));
- memcpy(stack + pto_nargs, args, nargskw * sizeof(PyObject*));
+ memcpy(stack + pto_nargs, args, nargs * sizeof(PyObject*));
}
- PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn,
- stack, tot_nargs, kwnames);
+
+ PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, stack,
+ tot_nargs, tot_kwnames);
if (stack != small_stack) {
PyMem_Free(stack);
}
+ if (pto_nkwds) {
+ Py_DECREF(tot_kwnames);
+ }
return ret;
+
+ error:
+ if (stack != small_stack) {
+ PyMem_Free(stack);
+ }
+ return NULL;
}
/* Set pto->vectorcall depending on the parameters of the partial object */
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 90a7391ebb0..1a6c831e483 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -64,11 +64,15 @@
#define PY_EVP_MD_fetch(algorithm, properties) EVP_MD_fetch(NULL, algorithm, properties)
#define PY_EVP_MD_up_ref(md) EVP_MD_up_ref(md)
#define PY_EVP_MD_free(md) EVP_MD_free(md)
+
+#define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_get0_md(CTX)
#else
#define PY_EVP_MD const EVP_MD
#define PY_EVP_MD_fetch(algorithm, properties) EVP_get_digestbyname(algorithm)
#define PY_EVP_MD_up_ref(md) do {} while(0)
#define PY_EVP_MD_free(md) do {} while(0)
+
+#define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_md(CTX)
#endif
/* hash alias map and fast lookup
@@ -308,6 +312,14 @@ class _hashlib.HMAC "HMACobject *" "&PyType_Type"
/* LCOV_EXCL_START */
+/* Thin wrapper around ERR_reason_error_string() returning non-NULL text. */
+static const char *
+py_wrapper_ERR_reason_error_string(unsigned long errcode)
+{
+ const char *reason = ERR_reason_error_string(errcode);
+ return reason ? reason : "no reason";
+}
+
/* Set an exception of given type using the given OpenSSL error code. */
static void
set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode)
@@ -317,8 +329,13 @@ set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode)
/* ERR_ERROR_STRING(3) ensures that the messages below are ASCII */
const char *lib = ERR_lib_error_string(errcode);
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+ // Since OpenSSL 3.0, ERR_func_error_string() always returns NULL.
+ const char *func = NULL;
+#else
const char *func = ERR_func_error_string(errcode);
- const char *reason = ERR_reason_error_string(errcode);
+#endif
+ const char *reason = py_wrapper_ERR_reason_error_string(errcode);
if (lib && func) {
PyErr_Format(exc_type, "[%s: %s] %s", lib, func, reason);
@@ -838,7 +855,7 @@ static PyObject *
_hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure))
{
HASHobject *self = HASHobject_CAST(op);
- const EVP_MD *md = EVP_MD_CTX_md(self->ctx);
+ const EVP_MD *md = PY_EVP_MD_CTX_md(self->ctx);
if (md == NULL) {
notify_ssl_error_occurred("missing EVP_MD for HASH context");
return NULL;
diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c
index ce7189637c2..d72031137e0 100644
--- a/Modules/_remote_debugging_module.c
+++ b/Modules/_remote_debugging_module.c
@@ -257,20 +257,25 @@ is_frame_valid(
uintptr_t code_object_addr
);
-static int
-parse_tasks_in_set(
+typedef int (*thread_processor_func)(
RemoteUnwinderObject *unwinder,
- uintptr_t set_addr,
- PyObject *awaited_by,
- int recurse_task
+ uintptr_t thread_state_addr,
+ unsigned long tid,
+ void *context
+);
+
+typedef int (*set_entry_processor_func)(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t key_addr,
+ void *context
);
+
static int
parse_task(
RemoteUnwinderObject *unwinder,
uintptr_t task_address,
- PyObject *render_to,
- int recurse_task
+ PyObject *render_to
);
static int
@@ -285,9 +290,27 @@ static int parse_frame_object(
RemoteUnwinderObject *unwinder,
PyObject** result,
uintptr_t address,
+ uintptr_t* address_of_code_object,
uintptr_t* previous_frame
);
+static int
+parse_async_frame_chain(
+ RemoteUnwinderObject *unwinder,
+ PyObject *calls,
+ uintptr_t address_of_thread,
+ uintptr_t running_task_code_obj
+);
+
+static int read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr);
+static int read_Py_ssize_t(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t *size);
+
+static int process_task_and_waiters(RemoteUnwinderObject *unwinder, uintptr_t task_addr, PyObject *result);
+static int process_task_awaited_by(RemoteUnwinderObject *unwinder, uintptr_t task_address, set_entry_processor_func processor, void *context);
+static int find_running_task_in_thread(RemoteUnwinderObject *unwinder, uintptr_t thread_state_addr, uintptr_t *running_task_addr);
+static int get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr);
+static int append_awaited_by(RemoteUnwinderObject *unwinder, unsigned long tid, uintptr_t head_addr, PyObject *result);
+
/* ============================================================================
* UTILITY FUNCTIONS AND HELPERS
* ============================================================================ */
@@ -405,32 +428,149 @@ validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets)
return 0;
}
-/* ============================================================================
- * MEMORY READING FUNCTIONS
- * ============================================================================ */
+// Generic function to iterate through all threads
+static int
+iterate_threads(
+ RemoteUnwinderObject *unwinder,
+ thread_processor_func processor,
+ void *context
+) {
+ uintptr_t thread_state_addr;
+ unsigned long tid = 0;
-static inline int
-read_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr)
-{
- int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(void*), ptr_addr);
- if (result < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read pointer from remote memory");
+ if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ unwinder->interpreter_addr + unwinder->debug_offsets.interpreter_state.threads_main,
+ sizeof(void*),
+ &thread_state_addr))
+ {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state");
return -1;
}
+
+ while (thread_state_addr != 0) {
+ if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ thread_state_addr + unwinder->debug_offsets.thread_state.native_thread_id,
+ sizeof(tid),
+ &tid))
+ {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread ID");
+ return -1;
+ }
+
+ // Call the processor function for this thread
+ if (processor(unwinder, thread_state_addr, tid, context) < 0) {
+ return -1;
+ }
+
+ // Move to next thread
+ if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ thread_state_addr + unwinder->debug_offsets.thread_state.next,
+ sizeof(void*),
+ &thread_state_addr))
+ {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read next thread state");
+ return -1;
+ }
+ }
+
return 0;
}
-static inline int
-read_Py_ssize_t(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t *size)
-{
- int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(Py_ssize_t), size);
- if (result < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read Py_ssize_t from remote memory");
+// Generic function to iterate through set entries
+static int
+iterate_set_entries(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t set_addr,
+ set_entry_processor_func processor,
+ void *context
+) {
+ char set_object[SIZEOF_SET_OBJ];
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, set_addr,
+ SIZEOF_SET_OBJ, set_object) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set object");
return -1;
}
+
+ Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used);
+ Py_ssize_t set_len = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask) + 1;
+ uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table);
+
+ Py_ssize_t i = 0;
+ Py_ssize_t els = 0;
+ while (i < set_len && els < num_els) {
+ uintptr_t key_addr;
+ if (read_py_ptr(unwinder, table_ptr, &key_addr) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry key");
+ return -1;
+ }
+
+ if ((void*)key_addr != NULL) {
+ Py_ssize_t ref_cnt;
+ if (read_Py_ssize_t(unwinder, table_ptr, &ref_cnt) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry ref count");
+ return -1;
+ }
+
+ if (ref_cnt) {
+ // Process this valid set entry
+ if (processor(unwinder, key_addr, context) < 0) {
+ return -1;
+ }
+ els++;
+ }
+ }
+ table_ptr += sizeof(void*) * 2;
+ i++;
+ }
+
return 0;
}
+// Processor function for task waiters
+static int
+process_waiter_task(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t key_addr,
+ void *context
+) {
+ PyObject *result = (PyObject *)context;
+ return process_task_and_waiters(unwinder, key_addr, result);
+}
+
+// Processor function for parsing tasks in sets
+static int
+process_task_parser(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t key_addr,
+ void *context
+) {
+ PyObject *awaited_by = (PyObject *)context;
+ return parse_task(unwinder, key_addr, awaited_by);
+}
+
+/* ============================================================================
+ * MEMORY READING FUNCTIONS
+ * ============================================================================ */
+
+#define DEFINE_MEMORY_READER(type_name, c_type, error_msg) \
+static inline int \
+read_##type_name(RemoteUnwinderObject *unwinder, uintptr_t address, c_type *result) \
+{ \
+ int res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(c_type), result); \
+ if (res < 0) { \
+ set_exception_cause(unwinder, PyExc_RuntimeError, error_msg); \
+ return -1; \
+ } \
+ return 0; \
+}
+
+DEFINE_MEMORY_READER(ptr, uintptr_t, "Failed to read pointer from remote memory")
+DEFINE_MEMORY_READER(Py_ssize_t, Py_ssize_t, "Failed to read Py_ssize_t from remote memory")
+DEFINE_MEMORY_READER(char, char, "Failed to read char from remote memory")
+
static int
read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr)
{
@@ -442,17 +582,6 @@ read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_ad
return 0;
}
-static int
-read_char(RemoteUnwinderObject *unwinder, uintptr_t address, char *result)
-{
- int res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(char), result);
- if (res < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read char from remote memory");
- return -1;
- }
- return 0;
-}
-
/* ============================================================================
* PYTHON OBJECT READING FUNCTIONS
* ============================================================================ */
@@ -799,39 +928,9 @@ parse_task_name(
static int parse_task_awaited_by(
RemoteUnwinderObject *unwinder,
uintptr_t task_address,
- PyObject *awaited_by,
- int recurse_task
+ PyObject *awaited_by
) {
- // Read the entire TaskObj at once
- char task_obj[SIZEOF_TASK_OBJ];
- if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
- unwinder->async_debug_offsets.asyncio_task_object.size,
- task_obj) < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object in awaited_by parsing");
- return -1;
- }
-
- uintptr_t task_ab_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by);
-
- if ((void*)task_ab_addr == NULL) {
- return 0;
- }
-
- char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set);
-
- if (awaited_by_is_a_set) {
- if (parse_tasks_in_set(unwinder, task_ab_addr, awaited_by, recurse_task)) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse tasks in awaited_by set");
- return -1;
- }
- } else {
- if (parse_task(unwinder, task_ab_addr, awaited_by, recurse_task)) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse single awaited_by task");
- return -1;
- }
- }
-
- return 0;
+ return process_task_awaited_by(unwinder, task_address, process_task_parser, awaited_by);
}
static int
@@ -940,11 +1039,16 @@ parse_coro_chain(
// Parse the previous frame using the gi_iframe from local copy
uintptr_t prev_frame;
uintptr_t gi_iframe_addr = coro_address + unwinder->debug_offsets.gen_object.gi_iframe;
- if (parse_frame_object(unwinder, &name, gi_iframe_addr, &prev_frame) < 0) {
+ uintptr_t address_of_code_object = 0;
+ if (parse_frame_object(unwinder, &name, gi_iframe_addr, &address_of_code_object, &prev_frame) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in coro chain");
return -1;
}
+ if (!name) {
+ return 0;
+ }
+
if (PyList_Append(render_to, name)) {
Py_DECREF(name);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain");
@@ -962,8 +1066,7 @@ parse_coro_chain(
static PyObject*
create_task_result(
RemoteUnwinderObject *unwinder,
- uintptr_t task_address,
- int recurse_task
+ uintptr_t task_address
) {
PyObject* result = NULL;
PyObject *call_stack = NULL;
@@ -979,11 +1082,7 @@ create_task_result(
}
// Create task name/address for second tuple element
- if (recurse_task) {
- tn = parse_task_name(unwinder, task_address);
- } else {
- tn = PyLong_FromUnsignedLongLong(task_address);
- }
+ tn = PyLong_FromUnsignedLongLong(task_address);
if (tn == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task name/address");
goto error;
@@ -1036,8 +1135,7 @@ static int
parse_task(
RemoteUnwinderObject *unwinder,
uintptr_t task_address,
- PyObject *render_to,
- int recurse_task
+ PyObject *render_to
) {
char is_task;
PyObject* result = NULL;
@@ -1053,7 +1151,7 @@ parse_task(
}
if (is_task) {
- result = create_task_result(unwinder, task_address, recurse_task);
+ result = create_task_result(unwinder, task_address);
if (!result) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task result");
goto error;
@@ -1080,11 +1178,11 @@ parse_task(
PyStructSequence_SetItem(result, 0, empty_list); // This steals the reference
PyStructSequence_SetItem(result, 1, task_name); // This steals the reference
}
-
if (PyList_Append(render_to, result)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task result to render list");
goto error;
}
+
Py_DECREF(result);
return 0;
@@ -1094,153 +1192,10 @@ error:
}
static int
-process_set_entry(
- RemoteUnwinderObject *unwinder,
- uintptr_t table_ptr,
- PyObject *awaited_by,
- int recurse_task
-) {
- uintptr_t key_addr;
- if (read_py_ptr(unwinder, table_ptr, &key_addr)) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry key");
- return -1;
- }
-
- if ((void*)key_addr != NULL) {
- Py_ssize_t ref_cnt;
- if (read_Py_ssize_t(unwinder, table_ptr, &ref_cnt)) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry reference count");
- return -1;
- }
-
- if (ref_cnt) {
- // if 'ref_cnt=0' it's a set dummy marker
- if (parse_task(unwinder, key_addr, awaited_by, recurse_task)) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task in set entry");
- return -1;
- }
- return 1; // Successfully processed a valid entry
- }
- }
- return 0; // Entry was NULL or dummy marker
-}
-
-static int
-parse_tasks_in_set(
- RemoteUnwinderObject *unwinder,
- uintptr_t set_addr,
- PyObject *awaited_by,
- int recurse_task
-) {
- char set_object[SIZEOF_SET_OBJ];
- int err = _Py_RemoteDebug_PagedReadRemoteMemory(
- &unwinder->handle,
- set_addr,
- SIZEOF_SET_OBJ,
- set_object);
- if (err < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set object");
- return -1;
- }
-
- Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used);
- Py_ssize_t set_len = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask) + 1; // The set contains the `mask+1` element slots.
- uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table);
-
- Py_ssize_t i = 0;
- Py_ssize_t els = 0;
- while (i < set_len && els < num_els) {
- int result = process_set_entry(unwinder, table_ptr, awaited_by, recurse_task);
-
- if (result < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process set entry");
- return -1;
- }
- if (result > 0) {
- els++;
- }
-
- table_ptr += sizeof(void*) * 2;
- i++;
- }
- return 0;
-}
-
-
-static int
-setup_async_result_structure(RemoteUnwinderObject *unwinder, PyObject **result, PyObject **calls)
-{
- *result = PyList_New(1);
- if (*result == NULL) {
- set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create async result structure");
- return -1;
- }
-
- *calls = PyList_New(0);
- if (*calls == NULL) {
- Py_DECREF(*result);
- *result = NULL;
- set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create calls list in async result");
- return -1;
- }
-
- if (PyList_SetItem(*result, 0, *calls)) { /* steals ref to 'calls' */
- Py_DECREF(*calls);
- Py_DECREF(*result);
- *result = NULL;
- *calls = NULL;
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to set calls list in async result");
- return -1;
- }
-
- return 0;
-}
-
-static int
-add_task_info_to_result(
- RemoteUnwinderObject *unwinder,
- PyObject *result,
- uintptr_t running_task_addr
-) {
- PyObject *tn = parse_task_name(unwinder, running_task_addr);
- if (tn == NULL) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task name for result");
- return -1;
- }
-
- if (PyList_Append(result, tn)) {
- Py_DECREF(tn);
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task name to result");
- return -1;
- }
- Py_DECREF(tn);
-
- PyObject* awaited_by = PyList_New(0);
- if (awaited_by == NULL) {
- set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by list for result");
- return -1;
- }
-
- if (PyList_Append(result, awaited_by)) {
- Py_DECREF(awaited_by);
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by to result");
- return -1;
- }
- Py_DECREF(awaited_by);
-
- if (parse_task_awaited_by(
- unwinder, running_task_addr, awaited_by, 1) < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by for result");
- return -1;
- }
-
- return 0;
-}
-
-static int
process_single_task_node(
RemoteUnwinderObject *unwinder,
uintptr_t task_addr,
+ PyObject **task_info,
PyObject *result
) {
PyObject *tn = NULL;
@@ -1268,7 +1223,7 @@ process_single_task_node(
goto error;
}
- if (parse_task(unwinder, task_addr, coroutine_stack, 0) < 0) {
+ if (parse_task(unwinder, task_addr, coroutine_stack) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task coroutine stack in single task node");
goto error;
}
@@ -1302,11 +1257,14 @@ process_single_task_node(
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append result item in single task node");
return -1;
}
+ if (task_info != NULL) {
+ *task_info = result_item;
+ }
Py_DECREF(result_item);
// Get back current_awaited_by reference for parse_task_awaited_by
current_awaited_by = PyStructSequence_GetItem(result_item, 3);
- if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by, 0) < 0) {
+ if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by in single task node");
// No cleanup needed here since all references were transferred to result_item
// and result_item was already added to result list and decreffed
@@ -1324,6 +1282,247 @@ error:
return -1;
}
+// Thread processor for get_all_awaited_by
+static int
+process_thread_for_awaited_by(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t thread_state_addr,
+ unsigned long tid,
+ void *context
+) {
+ PyObject *result = (PyObject *)context;
+ uintptr_t head_addr = thread_state_addr + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head;
+ return append_awaited_by(unwinder, tid, head_addr, result);
+}
+
+// Generic function to process task awaited_by
+static int
+process_task_awaited_by(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_address,
+ set_entry_processor_func processor,
+ void *context
+) {
+ // Read the entire TaskObj at once
+ char task_obj[SIZEOF_TASK_OBJ];
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
+ unwinder->async_debug_offsets.asyncio_task_object.size,
+ task_obj) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object");
+ return -1;
+ }
+
+ uintptr_t task_ab_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by);
+ if ((void*)task_ab_addr == NULL) {
+ return 0; // No tasks waiting for this one
+ }
+
+ char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set);
+
+ if (awaited_by_is_a_set) {
+ return iterate_set_entries(unwinder, task_ab_addr, processor, context);
+ } else {
+ // Single task waiting
+ return processor(unwinder, task_ab_addr, context);
+ }
+}
+
+static int
+process_running_task_chain(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t running_task_addr,
+ uintptr_t thread_state_addr,
+ PyObject *result
+) {
+ uintptr_t running_task_code_obj = 0;
+ if(get_task_code_object(unwinder, running_task_addr, &running_task_code_obj) < 0) {
+ return -1;
+ }
+
+ // First, add this task to the result
+ PyObject *task_info = NULL;
+ if (process_single_task_node(unwinder, running_task_addr, &task_info, result) < 0) {
+ return -1;
+ }
+
+ // Get the chain from the current frame to this task
+ PyObject *coro_chain = PyStructSequence_GET_ITEM(task_info, 2);
+ assert(coro_chain != NULL);
+ if (PyList_GET_SIZE(coro_chain) != 1) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Coro chain is not a single item");
+ return -1;
+ }
+ PyObject *coro_info = PyList_GET_ITEM(coro_chain, 0);
+ assert(coro_info != NULL);
+ PyObject *frame_chain = PyStructSequence_GET_ITEM(coro_info, 0);
+ assert(frame_chain != NULL);
+
+ // Clear the coro_chain
+ if (PyList_Clear(frame_chain) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to clear coroutine chain");
+ return -1;
+ }
+
+ // Add the chain from the current frame to this task
+ if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_code_obj) < 0) {
+ return -1;
+ }
+
+ // Now find all tasks that are waiting for this task and process them
+ if (process_task_awaited_by(unwinder, running_task_addr, process_waiter_task, result) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+// Thread processor for get_async_stack_trace
+static int
+process_thread_for_async_stack_trace(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t thread_state_addr,
+ unsigned long tid,
+ void *context
+) {
+ PyObject *result = (PyObject *)context;
+
+ // Find running task in this thread
+ uintptr_t running_task_addr;
+ if (find_running_task_in_thread(unwinder, thread_state_addr, &running_task_addr) < 0) {
+ return 0;
+ }
+
+ // If we found a running task, process it and its waiters
+ if ((void*)running_task_addr != NULL) {
+ PyObject *task_list = PyList_New(0);
+ if (task_list == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create task list for thread");
+ return -1;
+ }
+
+ if (process_running_task_chain(unwinder, running_task_addr, thread_state_addr, task_list) < 0) {
+ Py_DECREF(task_list);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process running task chain");
+ return -1;
+ }
+
+ // Create AwaitedInfo structure for this thread
+ PyObject *tid_py = PyLong_FromUnsignedLong(tid);
+ if (tid_py == NULL) {
+ Py_DECREF(task_list);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
+ return -1;
+ }
+
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ PyObject *awaited_info = PyStructSequence_New(state->AwaitedInfo_Type);
+ if (awaited_info == NULL) {
+ Py_DECREF(tid_py);
+ Py_DECREF(task_list);
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create AwaitedInfo");
+ return -1;
+ }
+
+ PyStructSequence_SetItem(awaited_info, 0, tid_py); // steals ref
+ PyStructSequence_SetItem(awaited_info, 1, task_list); // steals ref
+
+ if (PyList_Append(result, awaited_info)) {
+ Py_DECREF(awaited_info);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append AwaitedInfo to result");
+ return -1;
+ }
+ Py_DECREF(awaited_info);
+ }
+
+ return 0;
+}
+
+static int
+process_task_and_waiters(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_addr,
+ PyObject *result
+) {
+ // First, add this task to the result
+ if (process_single_task_node(unwinder, task_addr, NULL, result) < 0) {
+ return -1;
+ }
+
+ // Now find all tasks that are waiting for this task and process them
+ return process_task_awaited_by(unwinder, task_addr, process_waiter_task, result);
+}
+
+static int
+find_running_task_in_thread(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t thread_state_addr,
+ uintptr_t *running_task_addr
+) {
+ *running_task_addr = (uintptr_t)NULL;
+
+ uintptr_t address_of_running_loop;
+ int bytes_read = read_py_ptr(
+ unwinder,
+ thread_state_addr + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop,
+ &address_of_running_loop);
+ if (bytes_read == -1) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running loop address");
+ return -1;
+ }
+
+ // no asyncio loop is now running
+ if ((void*)address_of_running_loop == NULL) {
+ return 0;
+ }
+
+ int err = read_ptr(
+ unwinder,
+ thread_state_addr + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task,
+ running_task_addr);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task address");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr) {
+ uintptr_t running_coro_addr = 0;
+
+ if(read_py_ptr(
+ unwinder,
+ task_addr + unwinder->async_debug_offsets.asyncio_task_object.task_coro,
+ &running_coro_addr) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro read failed");
+ return -1;
+ }
+
+ if (running_coro_addr == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro address is NULL");
+ return -1;
+ }
+
+ // note: genobject's gi_iframe is an embedded struct so the address to
+ // the offset leads directly to its first field: f_executable
+ if (read_py_ptr(
+ unwinder,
+ running_coro_addr + unwinder->debug_offsets.gen_object.gi_iframe, code_obj_addr) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object");
+ return -1;
+ }
+
+ if (*code_obj_addr == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL");
+ return -1;
+ }
+
+ return 0;
+}
+
/* ============================================================================
* TLBC CACHING FUNCTIONS
* ============================================================================ */
@@ -1904,45 +2103,13 @@ populate_initial_state_data(
return 0;
}
+
static int
find_running_frame(
RemoteUnwinderObject *unwinder,
- uintptr_t runtime_start_address,
+ uintptr_t address_of_thread,
uintptr_t *frame
) {
- uint64_t interpreter_state_list_head =
- unwinder->debug_offsets.runtime_state.interpreters_head;
-
- uintptr_t address_of_interpreter_state;
- int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
- &unwinder->handle,
- runtime_start_address + interpreter_state_list_head,
- sizeof(void*),
- &address_of_interpreter_state);
- if (bytes_read < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state for running frame");
- return -1;
- }
-
- if (address_of_interpreter_state == 0) {
- PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
- set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL in running frame search");
- return -1;
- }
-
- uintptr_t address_of_thread;
- bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
- &unwinder->handle,
- address_of_interpreter_state +
- unwinder->debug_offsets.interpreter_state.threads_main,
- sizeof(void*),
- &address_of_thread);
- if (bytes_read < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread address for running frame");
- return -1;
- }
-
- // No Python frames are available for us (can happen at tear-down).
if ((void*)address_of_thread != NULL) {
int err = read_ptr(
unwinder,
@@ -1959,133 +2126,6 @@ find_running_frame(
return 0;
}
-static int
-find_running_task(
- RemoteUnwinderObject *unwinder,
- uintptr_t *running_task_addr
-) {
- *running_task_addr = (uintptr_t)NULL;
-
- uint64_t interpreter_state_list_head =
- unwinder->debug_offsets.runtime_state.interpreters_head;
-
- uintptr_t address_of_interpreter_state;
- int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
- &unwinder->handle,
- unwinder->runtime_start_address + interpreter_state_list_head,
- sizeof(void*),
- &address_of_interpreter_state);
- if (bytes_read < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state for running task");
- return -1;
- }
-
- if (address_of_interpreter_state == 0) {
- PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
- set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL in running task search");
- return -1;
- }
-
- uintptr_t address_of_thread;
- bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
- &unwinder->handle,
- address_of_interpreter_state +
- unwinder->debug_offsets.interpreter_state.threads_head,
- sizeof(void*),
- &address_of_thread);
- if (bytes_read < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread head for running task");
- return -1;
- }
-
- uintptr_t address_of_running_loop;
- // No Python frames are available for us (can happen at tear-down).
- if ((void*)address_of_thread == NULL) {
- return 0;
- }
-
- bytes_read = read_py_ptr(
- unwinder,
- address_of_thread
- + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop,
- &address_of_running_loop);
- if (bytes_read == -1) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running loop address");
- return -1;
- }
-
- // no asyncio loop is now running
- if ((void*)address_of_running_loop == NULL) {
- return 0;
- }
-
- int err = read_ptr(
- unwinder,
- address_of_thread
- + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task,
- running_task_addr);
- if (err) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task address");
- return -1;
- }
-
- return 0;
-}
-
-static int
-find_running_task_and_coro(
- RemoteUnwinderObject *unwinder,
- uintptr_t *running_task_addr,
- uintptr_t *running_coro_addr,
- uintptr_t *running_task_code_obj
-) {
- *running_task_addr = (uintptr_t)NULL;
- if (find_running_task(
- unwinder, running_task_addr) < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Running task search failed");
- return -1;
- }
-
- if ((void*)*running_task_addr == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "No running task found");
- set_exception_cause(unwinder, PyExc_RuntimeError, "Running task address is NULL");
- return -1;
- }
-
- if (read_py_ptr(
- unwinder,
- *running_task_addr + unwinder->async_debug_offsets.asyncio_task_object.task_coro,
- running_coro_addr) < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro read failed");
- return -1;
- }
-
- if ((void*)*running_coro_addr == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
- set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro address is NULL");
- return -1;
- }
-
- // note: genobject's gi_iframe is an embedded struct so the address to
- // the offset leads directly to its first field: f_executable
- if (read_py_ptr(
- unwinder,
- *running_coro_addr + unwinder->debug_offsets.gen_object.gi_iframe,
- running_task_code_obj) < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object");
- return -1;
- }
-
- if ((void*)*running_task_code_obj == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
- set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL");
- return -1;
- }
-
- return 0;
-}
-
-
/* ============================================================================
* FRAME PARSING FUNCTIONS
* ============================================================================ */
@@ -2122,9 +2162,11 @@ parse_frame_object(
RemoteUnwinderObject *unwinder,
PyObject** result,
uintptr_t address,
+ uintptr_t* address_of_code_object,
uintptr_t* previous_frame
) {
char frame[SIZEOF_INTERP_FRAME];
+ *address_of_code_object = 0;
Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
@@ -2154,77 +2196,32 @@ parse_frame_object(
}
#endif
- return parse_code_object(unwinder, result, code_object,instruction_pointer, previous_frame, tlbc_index);
-}
-
-static int
-parse_async_frame_object(
- RemoteUnwinderObject *unwinder,
- PyObject** result,
- uintptr_t address,
- uintptr_t* previous_frame,
- uintptr_t* code_object
-) {
- char frame[SIZEOF_INTERP_FRAME];
-
- Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
- &unwinder->handle,
- address,
- SIZEOF_INTERP_FRAME,
- frame
- );
- if (bytes_read < 0) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read async frame");
- return -1;
- }
-
- *previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
- *code_object = GET_MEMBER_NO_TAG(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable);
- int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, *code_object);
- if (frame_valid != 1) {
- return frame_valid;
- }
-
- uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
-
- // Get tlbc_index for free threading builds
- int32_t tlbc_index = 0;
-#ifdef Py_GIL_DISABLED
- if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
- tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
- }
-#endif
-
- if (parse_code_object(
- unwinder, result, *code_object, instruction_pointer, previous_frame, tlbc_index)) {
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse code object in async frame");
- return -1;
- }
-
- return 1;
+ *address_of_code_object = code_object;
+ return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
}
static int
-parse_async_frame_chain(
+ parse_async_frame_chain(
RemoteUnwinderObject *unwinder,
PyObject *calls,
+ uintptr_t address_of_thread,
uintptr_t running_task_code_obj
) {
uintptr_t address_of_current_frame;
- if (find_running_frame(unwinder, unwinder->runtime_start_address, &address_of_current_frame) < 0) {
+ if (find_running_frame(unwinder, address_of_thread, &address_of_current_frame) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Running frame search failed in async chain");
return -1;
}
- uintptr_t address_of_code_object;
while ((void*)address_of_current_frame != NULL) {
PyObject* frame_info = NULL;
- int res = parse_async_frame_object(
+ uintptr_t address_of_code_object;
+ int res = parse_frame_object(
unwinder,
&frame_info,
address_of_current_frame,
- &address_of_current_frame,
- &address_of_code_object
+ &address_of_code_object,
+ &address_of_current_frame
);
if (res < 0) {
@@ -2290,7 +2287,7 @@ append_awaited_by_for_thread(
uintptr_t task_addr = (uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next)
- unwinder->async_debug_offsets.asyncio_task_object.task_node;
- if (process_single_task_node(unwinder, task_addr, result) < 0) {
+ if (process_single_task_node(unwinder, task_addr, NULL, result) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process task node in awaited_by");
return -1;
}
@@ -2385,7 +2382,8 @@ process_frame_chain(
// Try chunks first, fallback to direct memory read
if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, chunks) < 0) {
PyErr_Clear();
- if (parse_frame_object(unwinder, &frame, frame_addr, &next_frame_addr) < 0) {
+ uintptr_t address_of_code_object = 0;
+ if (parse_frame_object(unwinder, &frame, frame_addr, &address_of_code_object ,&next_frame_addr) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain");
return -1;
}
@@ -2762,6 +2760,7 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
}
exit:
+ _Py_RemoteDebug_ClearCache(&self->handle);
return result;
}
@@ -2825,53 +2824,12 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s
goto result_err;
}
- uintptr_t thread_state_addr;
- unsigned long tid = 0;
- if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
- &self->handle,
- self->interpreter_addr
- + self->debug_offsets.interpreter_state.threads_main,
- sizeof(void*),
- &thread_state_addr))
- {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to read main thread state in get_all_awaited_by");
+ // Process all threads
+ if (iterate_threads(self, process_thread_for_awaited_by, result) < 0) {
goto result_err;
}
- uintptr_t head_addr;
- while (thread_state_addr != 0) {
- if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
- &self->handle,
- thread_state_addr
- + self->debug_offsets.thread_state.native_thread_id,
- sizeof(tid),
- &tid))
- {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to read thread ID in get_all_awaited_by");
- goto result_err;
- }
-
- head_addr = thread_state_addr
- + self->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head;
-
- if (append_awaited_by(self, tid, head_addr, result))
- {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to append awaited_by for thread in get_all_awaited_by");
- goto result_err;
- }
-
- if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
- &self->handle,
- thread_state_addr + self->debug_offsets.thread_state.next,
- sizeof(void*),
- &thread_state_addr))
- {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to read next thread state in get_all_awaited_by");
- goto result_err;
- }
- }
-
- head_addr = self->interpreter_addr
+ uintptr_t head_addr = self->interpreter_addr
+ self->async_debug_offsets.asyncio_interpreter_state.asyncio_tasks_head;
// On top of a per-thread task lists used by default by asyncio to avoid
@@ -2885,9 +2843,11 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s
goto result_err;
}
+ _Py_RemoteDebug_ClearCache(&self->handle);
return result;
result_err:
+ _Py_RemoteDebug_ClearCache(&self->handle);
Py_XDECREF(result);
return NULL;
}
@@ -2896,32 +2856,50 @@ result_err:
@critical_section
_remote_debugging.RemoteUnwinder.get_async_stack_trace
-Returns information about the currently running async task and its stack trace.
+Get the currently running async tasks and their dependency graphs from the remote process.
-Returns a tuple of (task_info, stack_frames) where:
-- task_info is a tuple of (task_id, task_name) identifying the task
-- stack_frames is a list of tuples (function_name, filename, line_number) representing
- the Python stack frames for the task, ordered from most recent to oldest
+This returns information about running tasks and all tasks that are waiting for them,
+forming a complete dependency graph for each thread's active task.
-Example:
- ((4345585712, 'Task-1'), [
- ('run_echo_server', 'server.py', 127),
- ('serve_forever', 'server.py', 45),
- ('main', 'app.py', 23)
- ])
+For each thread with a running task, returns the running task plus all tasks that
+transitively depend on it (tasks waiting for the running task, tasks waiting for
+those tasks, etc.).
+
+Returns a list of per-thread results, where each thread result contains:
+- Thread ID
+- List of task information for the running task and all its waiters
+
+Each task info contains:
+- Task ID (memory address)
+- Task name
+- Call stack frames: List of (func_name, filename, lineno)
+- List of tasks waiting for this task (recursive structure)
Raises:
RuntimeError: If AsyncioDebug section is not available in the target process
- RuntimeError: If there is an error copying memory from the target process
- OSError: If there is an error accessing the target process
- PermissionError: If access to the target process is denied
- UnicodeDecodeError: If there is an error decoding strings from the target process
+ MemoryError: If memory allocation fails
+ OSError: If reading from the remote process fails
+
+Example output (similar structure to get_all_awaited_by but only for running tasks):
+[
+ # Thread 140234 results
+ (140234, [
+ # Running task and its complete waiter dependency graph
+ (4345585712, 'main_task',
+ [("run_server", "server.py", 127), ("main", "app.py", 23)],
+ [
+ # Tasks waiting for main_task
+ (4345585800, 'worker_1', [...], [...]),
+ (4345585900, 'worker_2', [...], [...])
+ ])
+ ])
+]
[clinic start generated code]*/
static PyObject *
_remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self)
-/*[clinic end generated code: output=6433d52b55e87bbe input=11b7150c59d4c60f]*/
+/*[clinic end generated code: output=6433d52b55e87bbe input=8744b47c9ec2220a]*/
{
if (!self->async_debug_offsets_available) {
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
@@ -2929,34 +2907,21 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject
return NULL;
}
- PyObject *result = NULL;
- PyObject *calls = NULL;
-
- if (setup_async_result_structure(self, &result, &calls) < 0) {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to setup async result structure");
- goto cleanup;
- }
-
- uintptr_t running_task_addr, running_coro_addr, running_task_code_obj;
- if (find_running_task_and_coro(self, &running_task_addr,
- &running_coro_addr, &running_task_code_obj) < 0) {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to find running task and coro");
- goto cleanup;
- }
-
- if (parse_async_frame_chain(self, calls, running_task_code_obj) < 0) {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to parse async frame chain");
- goto cleanup;
+ PyObject *result = PyList_New(0);
+ if (result == NULL) {
+ set_exception_cause(self, PyExc_MemoryError, "Failed to create result list in get_async_stack_trace");
+ return NULL;
}
- if (add_task_info_to_result(self, result, running_task_addr) < 0) {
- set_exception_cause(self, PyExc_RuntimeError, "Failed to add task info to result");
- goto cleanup;
+ // Process all threads
+ if (iterate_threads(self, process_thread_for_async_stack_trace, result) < 0) {
+ goto result_err;
}
+ _Py_RemoteDebug_ClearCache(&self->handle);
return result;
-
-cleanup:
+result_err:
+ _Py_RemoteDebug_ClearCache(&self->handle);
Py_XDECREF(result);
return NULL;
}
@@ -2982,6 +2947,7 @@ RemoteUnwinder_dealloc(PyObject *op)
}
#endif
if (self->handle.pid != 0) {
+ _Py_RemoteDebug_ClearCache(&self->handle);
_Py_RemoteDebug_CleanupProcHandle(&self->handle);
}
PyObject_Del(self);
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 014e624f6c2..24c243e330d 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -75,6 +75,33 @@
#endif
+#ifdef BIO_get_ktls_send
+# ifdef MS_WINDOWS
+typedef long long Py_off_t;
+# else
+typedef off_t Py_off_t;
+# endif
+
+static int
+Py_off_t_converter(PyObject *arg, void *addr)
+{
+#ifdef HAVE_LARGEFILE_SUPPORT
+ *((Py_off_t *)addr) = PyLong_AsLongLong(arg);
+#else
+ *((Py_off_t *)addr) = PyLong_AsLong(arg);
+#endif
+ return PyErr_Occurred() ? 0 : 1;
+}
+
+/*[python input]
+
+class Py_off_t_converter(CConverter):
+ type = 'Py_off_t'
+ converter = 'Py_off_t_converter'
+
+[python start generated code]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=3fd9ca8ca6f0cbb8]*/
+#endif /* BIO_get_ktls_send */
struct py_ssl_error_code {
const char *mnemonic;
@@ -2444,6 +2471,184 @@ PySSL_select(PySocketSockObject *s, int writing, PyTime_t timeout)
/*[clinic input]
@critical_section
+_ssl._SSLSocket.uses_ktls_for_send
+
+Check if the Kernel TLS data-path is used for sending.
+[clinic start generated code]*/
+
+static PyObject *
+_ssl__SSLSocket_uses_ktls_for_send_impl(PySSLSocket *self)
+/*[clinic end generated code: output=f9d95fbefceb5068 input=8d1ce4a131190e6b]*/
+{
+#ifdef BIO_get_ktls_send
+ int uses = BIO_get_ktls_send(SSL_get_wbio(self->ssl));
+ // BIO_get_ktls_send() returns 1 if kTLS is used and 0 if not.
+ // Also, it returns -1 for failure before OpenSSL 3.0.4.
+ return Py_NewRef(uses == 1 ? Py_True : Py_False);
+#else
+ Py_RETURN_FALSE;
+#endif
+}
+
+/*[clinic input]
+@critical_section
+_ssl._SSLSocket.uses_ktls_for_recv
+
+Check if the Kernel TLS data-path is used for receiving.
+[clinic start generated code]*/
+
+static PyObject *
+_ssl__SSLSocket_uses_ktls_for_recv_impl(PySSLSocket *self)
+/*[clinic end generated code: output=ce38b00317a1f681 input=a13778a924fc7d44]*/
+{
+#ifdef BIO_get_ktls_recv
+ int uses = BIO_get_ktls_recv(SSL_get_rbio(self->ssl));
+ // BIO_get_ktls_recv() returns 1 if kTLS is used and 0 if not.
+ // Also, it returns -1 for failure before OpenSSL 3.0.4.
+ return Py_NewRef(uses == 1 ? Py_True : Py_False);
+#else
+ Py_RETURN_FALSE;
+#endif
+}
+
+#ifdef BIO_get_ktls_send
+/*[clinic input]
+@critical_section
+_ssl._SSLSocket.sendfile
+ fd: int
+ offset: Py_off_t
+ size: size_t
+ flags: int = 0
+ /
+
+Write size bytes from offset in the file descriptor fd to the SSL connection.
+
+This method uses the zero-copy technique and returns the number of bytes
+written. It should be called only when Kernel TLS is used for sending data in
+the connection.
+
+The meaning of flags is platform dependent.
+[clinic start generated code]*/
+
+static PyObject *
+_ssl__SSLSocket_sendfile_impl(PySSLSocket *self, int fd, Py_off_t offset,
+ size_t size, int flags)
+/*[clinic end generated code: output=0c6815b0719ca8d5 input=dfc1b162bb020de1]*/
+{
+ Py_ssize_t retval;
+ int sockstate;
+ _PySSLError err;
+ PySocketSockObject *sock = GET_SOCKET(self);
+ PyTime_t timeout, deadline = 0;
+ int has_timeout;
+
+ if (sock != NULL) {
+ if ((PyObject *)sock == Py_None) {
+ _setSSLError(get_state_sock(self),
+ "Underlying socket connection gone",
+ PY_SSL_ERROR_NO_SOCKET, __FILE__, __LINE__);
+ return NULL;
+ }
+ Py_INCREF(sock);
+ /* just in case the blocking state of the socket has been changed */
+ int nonblocking = (sock->sock_timeout >= 0);
+ BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+ BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+ }
+
+ timeout = GET_SOCKET_TIMEOUT(sock);
+ has_timeout = (timeout > 0);
+ if (has_timeout) {
+ deadline = _PyDeadline_Init(timeout);
+ }
+
+ sockstate = PySSL_select(sock, 1, timeout);
+ switch (sockstate) {
+ case SOCKET_HAS_TIMED_OUT:
+ PyErr_SetString(PyExc_TimeoutError,
+ "The write operation timed out");
+ goto error;
+ case SOCKET_HAS_BEEN_CLOSED:
+ PyErr_SetString(get_state_sock(self)->PySSLErrorObject,
+ "Underlying socket has been closed.");
+ goto error;
+ case SOCKET_TOO_LARGE_FOR_SELECT:
+ PyErr_SetString(get_state_sock(self)->PySSLErrorObject,
+ "Underlying socket too large for select().");
+ goto error;
+ }
+
+ do {
+ PySSL_BEGIN_ALLOW_THREADS
+ retval = SSL_sendfile(self->ssl, fd, (off_t)offset, size, flags);
+ err = _PySSL_errno(retval < 0, self->ssl, (int)retval);
+ PySSL_END_ALLOW_THREADS
+ self->err = err;
+
+ if (PyErr_CheckSignals()) {
+ goto error;
+ }
+
+ if (has_timeout) {
+ timeout = _PyDeadline_Get(deadline);
+ }
+
+ switch (err.ssl) {
+ case SSL_ERROR_WANT_READ:
+ sockstate = PySSL_select(sock, 0, timeout);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ sockstate = PySSL_select(sock, 1, timeout);
+ break;
+ default:
+ sockstate = SOCKET_OPERATION_OK;
+ break;
+ }
+
+ if (sockstate == SOCKET_HAS_TIMED_OUT) {
+ PyErr_SetString(PyExc_TimeoutError,
+ "The sendfile operation timed out");
+ goto error;
+ }
+ else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+ PyErr_SetString(get_state_sock(self)->PySSLErrorObject,
+ "Underlying socket has been closed.");
+ goto error;
+ }
+ else if (sockstate == SOCKET_IS_NONBLOCKING) {
+ break;
+ }
+ } while (err.ssl == SSL_ERROR_WANT_READ
+ || err.ssl == SSL_ERROR_WANT_WRITE);
+
+ if (err.ssl == SSL_ERROR_SSL
+ && ERR_GET_REASON(ERR_peek_error()) == SSL_R_UNINITIALIZED)
+ {
+ /* OpenSSL fails to return SSL_ERROR_SYSCALL if an error
+ * happens in sendfile(), and returns SSL_ERROR_SSL with
+ * SSL_R_UNINITIALIZED reason instead. */
+ _setSSLError(get_state_sock(self),
+ "Some I/O error occurred in sendfile()",
+ PY_SSL_ERROR_SYSCALL, __FILE__, __LINE__);
+ goto error;
+ }
+ Py_XDECREF(sock);
+ if (retval < 0) {
+ return PySSL_SetError(self, __FILE__, __LINE__);
+ }
+ if (PySSL_ChainExceptions(self) < 0) {
+ return NULL;
+ }
+ return PyLong_FromSize_t(retval);
+error:
+ Py_XDECREF(sock);
+ (void)PySSL_ChainExceptions(self);
+ return NULL;
+}
+#endif /* BIO_get_ktls_send */
+
+/*[clinic input]
+@critical_section
_ssl._SSLSocket.write
b: Py_buffer
/
@@ -3017,6 +3222,9 @@ static PyGetSetDef ssl_getsetlist[] = {
static PyMethodDef PySSLMethods[] = {
_SSL__SSLSOCKET_DO_HANDSHAKE_METHODDEF
+ _SSL__SSLSOCKET_USES_KTLS_FOR_SEND_METHODDEF
+ _SSL__SSLSOCKET_USES_KTLS_FOR_RECV_METHODDEF
+ _SSL__SSLSOCKET_SENDFILE_METHODDEF
_SSL__SSLSOCKET_WRITE_METHODDEF
_SSL__SSLSOCKET_READ_METHODDEF
_SSL__SSLSOCKET_PENDING_METHODDEF
diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c
index 7fc4d61db29..d2e61e9d6ac 100644
--- a/Modules/_testbuffer.c
+++ b/Modules/_testbuffer.c
@@ -1855,8 +1855,7 @@ ndarray_subscript(PyObject *op, PyObject *key)
type_error:
PyErr_Format(PyExc_TypeError,
- "cannot index memory using \"%.200s\"",
- Py_TYPE(key)->tp_name);
+ "cannot index memory using \"%T\"", key);
err_occurred:
Py_DECREF(nd);
return NULL;
diff --git a/Modules/_testcapi/monitoring.c b/Modules/_testcapi/monitoring.c
index 08a2055c51b..e041943492d 100644
--- a/Modules/_testcapi/monitoring.c
+++ b/Modules/_testcapi/monitoring.c
@@ -109,7 +109,7 @@ static PyTypeObject PyCodeLike_Type = {
};
#define RAISE_UNLESS_CODELIKE(v) if (!Py_IS_TYPE((v), &PyCodeLike_Type)) { \
- PyErr_Format(PyExc_TypeError, "expected a code-like, got %s", Py_TYPE(v)->tp_name); \
+ PyErr_Format(PyExc_TypeError, "expected a code-like, got %T", v); \
return NULL; \
}
diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c
index 464cf5c3125..4ca6ff587b9 100644
--- a/Modules/_testcapi/time.c
+++ b/Modules/_testcapi/time.c
@@ -5,8 +5,7 @@ static int
pytime_from_nanoseconds(PyTime_t *tp, PyObject *obj)
{
if (!PyLong_Check(obj)) {
- PyErr_Format(PyExc_TypeError, "expect int, got %s",
- Py_TYPE(obj)->tp_name);
+ PyErr_Format(PyExc_TypeError, "expect int, got %T", obj);
return -1;
}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 71fffedee14..334f2a53041 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -515,8 +515,7 @@ test_thread_state(PyObject *self, PyObject *args)
return NULL;
if (!PyCallable_Check(fn)) {
- PyErr_Format(PyExc_TypeError, "'%s' object is not callable",
- Py_TYPE(fn)->tp_name);
+ PyErr_Format(PyExc_TypeError, "'%T' object is not callable", fn);
return NULL;
}
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index 8027f0015c7..533e7dd3a7e 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -2207,8 +2207,7 @@ get_code(PyObject *obj)
return (PyCodeObject *)PyFunction_GetCode(obj);
}
return (PyCodeObject *)PyErr_Format(
- PyExc_TypeError, "expected function or code object, got %s",
- Py_TYPE(obj)->tp_name);
+ PyExc_TypeError, "expected function or code object, got %T", obj);
}
static PyObject *
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index 8886a9d6bd0..3540fead8e8 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -2330,7 +2330,7 @@ thread_excepthook(PyObject *module, PyObject *args)
}
PyDoc_STRVAR(excepthook_doc,
-"_excepthook($module, (exc_type, exc_value, exc_traceback, thread), /)\n\
+"_excepthook($module, args, /)\n\
--\n\
\n\
Handle uncaught Thread.run() exception.");
diff --git a/Modules/clinic/_remote_debugging_module.c.h b/Modules/clinic/_remote_debugging_module.c.h
index e80b24b54c0..f6a51cdba6b 100644
--- a/Modules/clinic/_remote_debugging_module.c.h
+++ b/Modules/clinic/_remote_debugging_module.c.h
@@ -235,26 +235,41 @@ PyDoc_STRVAR(_remote_debugging_RemoteUnwinder_get_async_stack_trace__doc__,
"get_async_stack_trace($self, /)\n"
"--\n"
"\n"
-"Returns information about the currently running async task and its stack trace.\n"
+"Get the currently running async tasks and their dependency graphs from the remote process.\n"
"\n"
-"Returns a tuple of (task_info, stack_frames) where:\n"
-"- task_info is a tuple of (task_id, task_name) identifying the task\n"
-"- stack_frames is a list of tuples (function_name, filename, line_number) representing\n"
-" the Python stack frames for the task, ordered from most recent to oldest\n"
+"This returns information about running tasks and all tasks that are waiting for them,\n"
+"forming a complete dependency graph for each thread\'s active task.\n"
"\n"
-"Example:\n"
-" ((4345585712, \'Task-1\'), [\n"
-" (\'run_echo_server\', \'server.py\', 127),\n"
-" (\'serve_forever\', \'server.py\', 45),\n"
-" (\'main\', \'app.py\', 23)\n"
-" ])\n"
+"For each thread with a running task, returns the running task plus all tasks that\n"
+"transitively depend on it (tasks waiting for the running task, tasks waiting for\n"
+"those tasks, etc.).\n"
+"\n"
+"Returns a list of per-thread results, where each thread result contains:\n"
+"- Thread ID\n"
+"- List of task information for the running task and all its waiters\n"
+"\n"
+"Each task info contains:\n"
+"- Task ID (memory address)\n"
+"- Task name\n"
+"- Call stack frames: List of (func_name, filename, lineno)\n"
+"- List of tasks waiting for this task (recursive structure)\n"
"\n"
"Raises:\n"
" RuntimeError: If AsyncioDebug section is not available in the target process\n"
-" RuntimeError: If there is an error copying memory from the target process\n"
-" OSError: If there is an error accessing the target process\n"
-" PermissionError: If access to the target process is denied\n"
-" UnicodeDecodeError: If there is an error decoding strings from the target process");
+" MemoryError: If memory allocation fails\n"
+" OSError: If reading from the remote process fails\n"
+"\n"
+"Example output (similar structure to get_all_awaited_by but only for running tasks):\n"
+"[\n"
+" (140234, [\n"
+" (4345585712, \'main_task\',\n"
+" [(\"run_server\", \"server.py\", 127), (\"main\", \"app.py\", 23)],\n"
+" [\n"
+" (4345585800, \'worker_1\', [...], [...]),\n"
+" (4345585900, \'worker_2\', [...], [...])\n"
+" ])\n"
+" ])\n"
+"]");
#define _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ASYNC_STACK_TRACE_METHODDEF \
{"get_async_stack_trace", (PyCFunction)_remote_debugging_RemoteUnwinder_get_async_stack_trace, METH_NOARGS, _remote_debugging_RemoteUnwinder_get_async_stack_trace__doc__},
@@ -273,4 +288,4 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace(PyObject *self, PyObject
return return_value;
}
-/*[clinic end generated code: output=a37ab223d5081b16 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=0dd1e6e8bab2a8b1 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h
index c6e2abd4d93..7027d873792 100644
--- a/Modules/clinic/_ssl.c.h
+++ b/Modules/clinic/_ssl.c.h
@@ -7,6 +7,7 @@ preserve
# include "pycore_runtime.h" // _Py_ID()
#endif
#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
+#include "pycore_long.h" // _PyLong_Size_t_Converter()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_ssl__SSLSocket_do_handshake__doc__,
@@ -442,6 +443,115 @@ _ssl__SSLSocket_owner_set(PyObject *self, PyObject *value, void *Py_UNUSED(conte
return return_value;
}
+PyDoc_STRVAR(_ssl__SSLSocket_uses_ktls_for_send__doc__,
+"uses_ktls_for_send($self, /)\n"
+"--\n"
+"\n"
+"Check if the Kernel TLS data-path is used for sending.");
+
+#define _SSL__SSLSOCKET_USES_KTLS_FOR_SEND_METHODDEF \
+ {"uses_ktls_for_send", (PyCFunction)_ssl__SSLSocket_uses_ktls_for_send, METH_NOARGS, _ssl__SSLSocket_uses_ktls_for_send__doc__},
+
+static PyObject *
+_ssl__SSLSocket_uses_ktls_for_send_impl(PySSLSocket *self);
+
+static PyObject *
+_ssl__SSLSocket_uses_ktls_for_send(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _ssl__SSLSocket_uses_ktls_for_send_impl((PySSLSocket *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_ssl__SSLSocket_uses_ktls_for_recv__doc__,
+"uses_ktls_for_recv($self, /)\n"
+"--\n"
+"\n"
+"Check if the Kernel TLS data-path is used for receiving.");
+
+#define _SSL__SSLSOCKET_USES_KTLS_FOR_RECV_METHODDEF \
+ {"uses_ktls_for_recv", (PyCFunction)_ssl__SSLSocket_uses_ktls_for_recv, METH_NOARGS, _ssl__SSLSocket_uses_ktls_for_recv__doc__},
+
+static PyObject *
+_ssl__SSLSocket_uses_ktls_for_recv_impl(PySSLSocket *self);
+
+static PyObject *
+_ssl__SSLSocket_uses_ktls_for_recv(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _ssl__SSLSocket_uses_ktls_for_recv_impl((PySSLSocket *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
+#if defined(BIO_get_ktls_send)
+
+PyDoc_STRVAR(_ssl__SSLSocket_sendfile__doc__,
+"sendfile($self, fd, offset, size, flags=0, /)\n"
+"--\n"
+"\n"
+"Write size bytes from offset in the file descriptor fd to the SSL connection.\n"
+"\n"
+"This method uses the zero-copy technique and returns the number of bytes\n"
+"written. It should be called only when Kernel TLS is used for sending data in\n"
+"the connection.\n"
+"\n"
+"The meaning of flags is platform dependent.");
+
+#define _SSL__SSLSOCKET_SENDFILE_METHODDEF \
+ {"sendfile", _PyCFunction_CAST(_ssl__SSLSocket_sendfile), METH_FASTCALL, _ssl__SSLSocket_sendfile__doc__},
+
+static PyObject *
+_ssl__SSLSocket_sendfile_impl(PySSLSocket *self, int fd, Py_off_t offset,
+ size_t size, int flags);
+
+static PyObject *
+_ssl__SSLSocket_sendfile(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ int fd;
+ Py_off_t offset;
+ size_t size;
+ int flags = 0;
+
+ if (!_PyArg_CheckPositional("sendfile", nargs, 3, 4)) {
+ goto exit;
+ }
+ fd = PyLong_AsInt(args[0]);
+ if (fd == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!Py_off_t_converter(args[1], &offset)) {
+ goto exit;
+ }
+ if (!_PyLong_Size_t_Converter(args[2], &size)) {
+ goto exit;
+ }
+ if (nargs < 4) {
+ goto skip_optional;
+ }
+ flags = PyLong_AsInt(args[3]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _ssl__SSLSocket_sendfile_impl((PySSLSocket *)self, fd, offset, size, flags);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
+}
+
+#endif /* defined(BIO_get_ktls_send) */
+
PyDoc_STRVAR(_ssl__SSLSocket_write__doc__,
"write($self, b, /)\n"
"--\n"
@@ -2893,6 +3003,10 @@ exit:
#endif /* defined(_MSC_VER) */
+#ifndef _SSL__SSLSOCKET_SENDFILE_METHODDEF
+ #define _SSL__SSLSOCKET_SENDFILE_METHODDEF
+#endif /* !defined(_SSL__SSLSOCKET_SENDFILE_METHODDEF) */
+
#ifndef _SSL_ENUM_CERTIFICATES_METHODDEF
#define _SSL_ENUM_CERTIFICATES_METHODDEF
#endif /* !defined(_SSL_ENUM_CERTIFICATES_METHODDEF) */
@@ -2900,4 +3014,4 @@ exit:
#ifndef _SSL_ENUM_CRLS_METHODDEF
#define _SSL_ENUM_CRLS_METHODDEF
#endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */
-/*[clinic end generated code: output=748650909fec8906 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=1adc3780d8ca682a input=a9049054013a1b77]*/
diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h
index a443c48faaa..246019f2206 100644
--- a/Modules/clinic/mathmodule.c.h
+++ b/Modules/clinic/mathmodule.c.h
@@ -84,6 +84,112 @@ PyDoc_STRVAR(math_floor__doc__,
#define MATH_FLOOR_METHODDEF \
{"floor", (PyCFunction)math_floor, METH_O, math_floor__doc__},
+PyDoc_STRVAR(math_fmax__doc__,
+"fmax($module, x, y, /)\n"
+"--\n"
+"\n"
+"Return the larger of two floating-point arguments.");
+
+#define MATH_FMAX_METHODDEF \
+ {"fmax", _PyCFunction_CAST(math_fmax), METH_FASTCALL, math_fmax__doc__},
+
+static double
+math_fmax_impl(PyObject *module, double x, double y);
+
+static PyObject *
+math_fmax(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ double x;
+ double y;
+ double _return_value;
+
+ if (!_PyArg_CheckPositional("fmax", nargs, 2, 2)) {
+ goto exit;
+ }
+ if (PyFloat_CheckExact(args[0])) {
+ x = PyFloat_AS_DOUBLE(args[0]);
+ }
+ else
+ {
+ x = PyFloat_AsDouble(args[0]);
+ if (x == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ if (PyFloat_CheckExact(args[1])) {
+ y = PyFloat_AS_DOUBLE(args[1]);
+ }
+ else
+ {
+ y = PyFloat_AsDouble(args[1]);
+ if (y == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ _return_value = math_fmax_impl(module, x, y);
+ if ((_return_value == -1.0) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyFloat_FromDouble(_return_value);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(math_fmin__doc__,
+"fmin($module, x, y, /)\n"
+"--\n"
+"\n"
+"Return the smaller of two floating-point arguments.");
+
+#define MATH_FMIN_METHODDEF \
+ {"fmin", _PyCFunction_CAST(math_fmin), METH_FASTCALL, math_fmin__doc__},
+
+static double
+math_fmin_impl(PyObject *module, double x, double y);
+
+static PyObject *
+math_fmin(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ double x;
+ double y;
+ double _return_value;
+
+ if (!_PyArg_CheckPositional("fmin", nargs, 2, 2)) {
+ goto exit;
+ }
+ if (PyFloat_CheckExact(args[0])) {
+ x = PyFloat_AS_DOUBLE(args[0]);
+ }
+ else
+ {
+ x = PyFloat_AsDouble(args[0]);
+ if (x == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ if (PyFloat_CheckExact(args[1])) {
+ y = PyFloat_AS_DOUBLE(args[1]);
+ }
+ else
+ {
+ y = PyFloat_AsDouble(args[1]);
+ if (y == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ _return_value = math_fmin_impl(module, x, y);
+ if ((_return_value == -1.0) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyFloat_FromDouble(_return_value);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(math_signbit__doc__,
"signbit($module, x, /)\n"
"--\n"
@@ -1212,4 +1318,4 @@ math_ulp(PyObject *module, PyObject *arg)
exit:
return return_value;
}
-/*[clinic end generated code: output=4e3fa94d026f027b input=a9049054013a1b77]*/
+/*[clinic end generated code: output=4fb180d4c25ff8fa input=a9049054013a1b77]*/
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 033de0b2907..7c2a421dd6a 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1214,6 +1214,40 @@ math_floor(PyObject *module, PyObject *number)
return PyLong_FromDouble(floor(x));
}
+/*[clinic input]
+math.fmax -> double
+
+ x: double
+ y: double
+ /
+
+Return the larger of two floating-point arguments.
+[clinic start generated code]*/
+
+static double
+math_fmax_impl(PyObject *module, double x, double y)
+/*[clinic end generated code: output=00692358d312fee2 input=021596c027336ffe]*/
+{
+ return fmax(x, y);
+}
+
+/*[clinic input]
+math.fmin -> double
+
+ x: double
+ y: double
+ /
+
+Return the smaller of two floating-point arguments.
+[clinic start generated code]*/
+
+static double
+math_fmin_impl(PyObject *module, double x, double y)
+/*[clinic end generated code: output=3d5b7826bd292dd9 input=d12e64ccc33f878a]*/
+{
+ return fmin(x, y);
+}
+
FUNC1AD(gamma, m_tgamma,
"gamma($module, x, /)\n--\n\n"
"Gamma function at x.",
@@ -4192,7 +4226,9 @@ static PyMethodDef math_methods[] = {
MATH_FACTORIAL_METHODDEF
MATH_FLOOR_METHODDEF
MATH_FMA_METHODDEF
+ MATH_FMAX_METHODDEF
MATH_FMOD_METHODDEF
+ MATH_FMIN_METHODDEF
MATH_FREXP_METHODDEF
MATH_FSUM_METHODDEF
{"gamma", math_gamma, METH_O, math_gamma_doc},