| /****************************************************************************** | |
| * Python Remote Debugging Module - Shared Header | |
| * | |
| * This header provides common declarations, types, and utilities shared | |
| * across the remote debugging module implementation files. | |
| ******************************************************************************/ | |
| #ifndef Py_REMOTE_DEBUGGING_H | |
| #define Py_REMOTE_DEBUGGING_H | |
| /* _GNU_SOURCE must be defined before any system headers */ | |
| #ifndef _GNU_SOURCE | |
| #define _GNU_SOURCE | |
| #endif | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| #ifndef Py_BUILD_CORE_BUILTIN | |
| # ifndef Py_BUILD_CORE_MODULE | |
| # define Py_BUILD_CORE_MODULE 1 | |
| # endif | |
| #endif | |
| #include "Python.h" | |
| #include "internal/pycore_debug_offsets.h" // _Py_DebugOffsets | |
| #include "internal/pycore_frame.h" // FRAME_SUSPENDED_YIELD_FROM | |
| #include "internal/pycore_interpframe.h" // FRAME_OWNED_BY_INTERPRETER | |
| #include "internal/pycore_llist.h" // struct llist_node | |
| #include "internal/pycore_long.h" // _PyLong_GetZero | |
| #include "internal/pycore_pyerrors.h" // _PyErr_FormatFromCause | |
| #include "internal/pycore_pyhash.h" // _Py_HashPointerRaw | |
| #include "internal/pycore_stackref.h" // Py_TAG_BITS | |
| #include "../../Python/remote_debug.h" | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #ifndef HAVE_PROCESS_VM_READV | |
| # define HAVE_PROCESS_VM_READV 0 | |
| #endif | |
| #if defined(__APPLE__) | |
| #include <TargetConditionals.h> | |
| # if !defined(TARGET_OS_OSX) | |
| /* Older macOS SDKs do not define TARGET_OS_OSX */ | |
| # define TARGET_OS_OSX 1 | |
| # endif | |
| # if TARGET_OS_OSX | |
| # include <libproc.h> | |
| # include <sys/types.h> | |
| # define MAX_NATIVE_THREADS 4096 | |
| # endif | |
| #endif | |
| // Platforms that support pausing/resuming threads for accurate stack sampling | |
| #if defined(MS_WINDOWS) || defined(__linux__) || (defined(__APPLE__) && TARGET_OS_OSX) | |
| # define Py_REMOTE_DEBUG_SUPPORTS_BLOCKING 1 | |
| #endif | |
| #ifdef MS_WINDOWS | |
| #include <windows.h> | |
| #include <winternl.h> | |
| #endif | |
| #if defined(__APPLE__) && TARGET_OS_OSX | |
| typedef struct { | |
| mach_port_t task; | |
| int suspended; | |
| } _Py_RemoteDebug_ThreadsState; | |
| #elif defined(__linux__) | |
| typedef struct { | |
| pid_t *tids; // Points to unwinder's reusable buffer | |
| size_t count; // Number of threads currently seized | |
| } _Py_RemoteDebug_ThreadsState; | |
| #elif defined(MS_WINDOWS) | |
| typedef NTSTATUS (NTAPI *NtSuspendProcessFunc)(HANDLE ProcessHandle); | |
| typedef NTSTATUS (NTAPI *NtResumeProcessFunc)(HANDLE ProcessHandle); | |
| typedef struct { | |
| HANDLE hProcess; | |
| int suspended; | |
| } _Py_RemoteDebug_ThreadsState; | |
| #else | |
| typedef struct { | |
| int dummy; | |
| } _Py_RemoteDebug_ThreadsState; | |
| #endif | |
| #ifdef MS_WINDOWS | |
| #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) | |
| #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) | |
| #define STATUS_NO_MORE_ENTRIES ((NTSTATUS)0x8000001AL) | |
| typedef enum _WIN32_THREADSTATE { | |
| WIN32_THREADSTATE_INITIALIZED = 0, | |
| WIN32_THREADSTATE_READY = 1, | |
| WIN32_THREADSTATE_RUNNING = 2, | |
| WIN32_THREADSTATE_STANDBY = 3, | |
| WIN32_THREADSTATE_TERMINATED = 4, | |
| WIN32_THREADSTATE_WAITING = 5, | |
| WIN32_THREADSTATE_TRANSITION = 6, | |
| WIN32_THREADSTATE_UNKNOWN = 7 | |
| } WIN32_THREADSTATE; | |
| #endif | |
| /* ============================================================================ | |
| * MACROS AND CONSTANTS | |
| * ============================================================================ */ | |
| #define GET_MEMBER(type, obj, offset) \ | |
| (*(const type *)memcpy(&(type){0}, (const char *)(obj) + (offset), sizeof(type))) | |
| #define CLEAR_PTR_TAG(ptr) (((uintptr_t)(ptr) & ~Py_TAG_BITS)) | |
| #define GET_MEMBER_NO_TAG(type, obj, offset) (type)(CLEAR_PTR_TAG(GET_MEMBER(type, obj, offset))) | |
| /* Size macros for opaque buffers */ | |
| #define SIZEOF_BYTES_OBJ sizeof(PyBytesObject) | |
| #define SIZEOF_CODE_OBJ sizeof(PyCodeObject) | |
| #define SIZEOF_GEN_OBJ sizeof(PyGenObject) | |
| #define SIZEOF_INTERP_FRAME sizeof(_PyInterpreterFrame) | |
| #define SIZEOF_LLIST_NODE sizeof(struct llist_node) | |
| #define SIZEOF_PAGE_CACHE_ENTRY sizeof(page_cache_entry_t) | |
| #define SIZEOF_PYOBJECT sizeof(PyObject) | |
| #define SIZEOF_SET_OBJ sizeof(PySetObject) | |
| #define SIZEOF_TASK_OBJ 4096 | |
| #define SIZEOF_THREAD_STATE sizeof(PyThreadState) | |
| #define SIZEOF_TYPE_OBJ sizeof(PyTypeObject) | |
| #define SIZEOF_UNICODE_OBJ sizeof(PyUnicodeObject) | |
| #define SIZEOF_LONG_OBJ sizeof(PyLongObject) | |
| #define SIZEOF_GC_RUNTIME_STATE sizeof(struct _gc_runtime_state) | |
| #define SIZEOF_INTERPRETER_STATE sizeof(PyInterpreterState) | |
| /* Maximum sizes for validation to prevent buffer overflows from corrupted data */ | |
| #define MAX_STACK_CHUNK_SIZE (16 * 1024 * 1024) /* 16 MB max for stack chunks */ | |
| #define MAX_LONG_DIGITS 64 /* Allows values up to ~2^1920 */ | |
| #define MAX_SET_TABLE_SIZE (1 << 20) /* 1 million entries max for set iteration */ | |
| #define MAX_FRAME_CHAIN_DEPTH (1024 + 512) /* Iteration bound for frame chain walks */ | |
| #define MAX_TASK_WAITER_WALK_TASKS (1 << 14) /* Total-task bound for waiter walks */ | |
| #ifndef MAX | |
| #define MAX(a, b) ((a) > (b) ? (a) : (b)) | |
| #endif | |
| #ifdef Py_GIL_DISABLED | |
| #define INTERP_STATE_MIN_SIZE MAX(MAX(MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \ | |
| offsetof(PyInterpreterState, tlbc_indices.tlbc_generation) + sizeof(uint32_t)), \ | |
| offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \ | |
| offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*)), \ | |
| offsetof(PyInterpreterState, gc.frame) + sizeof(_PyInterpreterFrame *)) | |
| #else | |
| #define INTERP_STATE_MIN_SIZE MAX(MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \ | |
| offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \ | |
| offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*)), \ | |
| offsetof(PyInterpreterState, gc.frame) + sizeof(_PyInterpreterFrame *)) | |
| #endif | |
| #define INTERP_STATE_BUFFER_SIZE MAX(INTERP_STATE_MIN_SIZE, 256) | |
| #define MAX_TLBC_SIZE 2048 | |
| /* Thread status flags */ | |
| #define THREAD_STATUS_HAS_GIL (1 << 0) | |
| #define THREAD_STATUS_ON_CPU (1 << 1) | |
| #define THREAD_STATUS_UNKNOWN (1 << 2) | |
| #define THREAD_STATUS_GIL_REQUESTED (1 << 3) | |
| #define THREAD_STATUS_HAS_EXCEPTION (1 << 4) | |
| #define THREAD_STATUS_MAIN_THREAD (1 << 5) | |
| /* Exception cause macro */ | |
| #define set_exception_cause(unwinder, exc_type, message) \ | |
| do { \ | |
| assert(PyErr_Occurred() && "function returned -1 without setting exception"); \ | |
| if (unwinder->debug && !_Py_RemoteDebug_IsFatalReadError()) { \ | |
| _set_debug_exception_cause(exc_type, message); \ | |
| } \ | |
| } while (0) | |
| /* ============================================================================ | |
| * TYPE DEFINITIONS | |
| * ============================================================================ */ | |
| struct _Py_AsyncioModuleDebugOffsets { | |
| struct _asyncio_task_object { | |
| uint64_t size; | |
| uint64_t task_name; | |
| uint64_t task_awaited_by; | |
| uint64_t task_is_task; | |
| uint64_t task_awaited_by_is_set; | |
| uint64_t task_coro; | |
| uint64_t task_node; | |
| } asyncio_task_object; | |
| struct _asyncio_interpreter_state { | |
| uint64_t size; | |
| uint64_t asyncio_tasks_head; | |
| } asyncio_interpreter_state; | |
| struct _asyncio_thread_state { | |
| uint64_t size; | |
| uint64_t asyncio_running_loop; | |
| uint64_t asyncio_running_task; | |
| uint64_t asyncio_tasks_head; | |
| } asyncio_thread_state; | |
| }; | |
| typedef struct { | |
| PyObject *func_name; | |
| PyObject *file_name; | |
| int first_lineno; | |
| PyObject *linetable; // bytes | |
| PyObject *last_frame_info; | |
| ptrdiff_t last_addrq; | |
| uintptr_t addr_code_adaptive; | |
| } CachedCodeMetadata; | |
| /* Frame cache constants and types */ | |
| #define FRAME_CACHE_MAX_THREADS 32 | |
| #define FRAME_CACHE_MAX_FRAMES 1024 | |
| typedef struct { | |
| uintptr_t frame; | |
| uintptr_t seq; | |
| } FrameCacheAnchor; | |
| typedef struct { | |
| uint64_t thread_id; // 0 = empty slot | |
| uintptr_t thread_state_addr; | |
| uintptr_t last_profiled_frame_seq; // sequence paired with addrs[0] | |
| uintptr_t addrs[FRAME_CACHE_MAX_FRAMES]; | |
| Py_ssize_t num_addrs; | |
| PyObject *thread_id_obj; // owned reference, NULL if empty | |
| PyObject *frame_list; // owned reference, NULL if empty | |
| } FrameCacheEntry; | |
| #define INTERPRETER_THREAD_CACHE_SIZE 32 | |
| #if (INTERPRETER_THREAD_CACHE_SIZE & (INTERPRETER_THREAD_CACHE_SIZE - 1)) != 0 | |
| # error "INTERPRETER_THREAD_CACHE_SIZE must be a power of two" | |
| #endif | |
| // The two per-interpreter L2 caches below are split into per-field tables so | |
| // that a writer rebinding one slot cannot leave stale data in a field owned by | |
| // the other when the slot is reused across interpreters. | |
| typedef struct { | |
| uintptr_t interpreter_addr; | |
| uintptr_t thread_state_addr; | |
| } InterpreterTstateCacheEntry; | |
| typedef struct { | |
| uintptr_t interpreter_addr; | |
| uint64_t code_object_generation; | |
| } InterpreterGenerationCacheEntry; | |
| // Carries already-read thread state and/or frame buffers across helpers so the | |
| // downstream callee can skip a remote read. Address fields are caller-supplied | |
| // inputs; buffer pointers (tstate, frame) are NULL unless a prior batched read | |
| // successfully populated them. | |
| typedef struct { | |
| const char *tstate; | |
| uintptr_t tstate_addr; | |
| const char *frame; | |
| uintptr_t frame_addr; | |
| } RemoteReadPrefetch; | |
| /* Statistics for profiling performance analysis */ | |
| typedef struct { | |
| uint64_t total_samples; // Total number of get_stack_trace calls | |
| uint64_t frame_cache_hits; // Full cache hits (entire stack unchanged) | |
| uint64_t frame_cache_misses; // Cache misses requiring full walk | |
| uint64_t frame_cache_partial_hits; // Partial hits (stopped at cached frame) | |
| uint64_t frames_read_from_cache; // Total frames retrieved from cache | |
| uint64_t frames_read_from_memory; // Total frames read from remote memory | |
| uint64_t memory_reads; // Total remote memory read operations | |
| uint64_t memory_bytes_read; // Total bytes read from remote memory | |
| uint64_t code_object_cache_hits; // Code object cache hits | |
| uint64_t code_object_cache_misses; // Code object cache misses | |
| uint64_t stale_cache_invalidations; // Times stale entries were cleared | |
| uint64_t batched_read_attempts; // Batched remote-read attempts | |
| uint64_t batched_read_successes; // Attempts that read all requested segments | |
| uint64_t batched_read_misses; // Attempts that fell back or partially read | |
| uint64_t batched_read_segments_requested; // Segments requested by batched reads | |
| uint64_t batched_read_segments_completed; // Segments completed by batched reads | |
| } UnwinderStats; | |
| #if defined(__GNUC__) || defined(__clang__) | |
| # define REMOTE_DEBUG_UNLIKELY(value) __builtin_expect(!!(value), 0) | |
| #else | |
| # define REMOTE_DEBUG_UNLIKELY(value) (value) | |
| #endif | |
| /* Stats tracking macros - no-op when stats collection is disabled */ | |
| #define STATS_INC(unwinder, field) \ | |
| do { if (REMOTE_DEBUG_UNLIKELY((unwinder)->collect_stats)) (unwinder)->stats.field++; } while(0) | |
| #define STATS_ADD(unwinder, field, val) \ | |
| do { if (REMOTE_DEBUG_UNLIKELY((unwinder)->collect_stats)) (unwinder)->stats.field += (val); } while(0) | |
| #if HAVE_PROCESS_VM_READV | |
| # define STATS_BATCHED_READ(unwinder, requested, completed) \ | |
| do { \ | |
| if (REMOTE_DEBUG_UNLIKELY((unwinder)->collect_stats)) { \ | |
| (unwinder)->stats.batched_read_attempts++; \ | |
| (unwinder)->stats.batched_read_segments_requested += (uint64_t)(requested); \ | |
| (unwinder)->stats.batched_read_segments_completed += (uint64_t)(completed); \ | |
| if ((completed) == (requested)) { \ | |
| (unwinder)->stats.batched_read_successes++; \ | |
| } \ | |
| else { \ | |
| (unwinder)->stats.batched_read_misses++; \ | |
| } \ | |
| } \ | |
| } while(0) | |
| #else | |
| # define STATS_BATCHED_READ(unwinder, requested, completed) ((void)0) | |
| #endif | |
| typedef struct { | |
| PyTypeObject *RemoteDebugging_Type; | |
| PyTypeObject *TaskInfo_Type; | |
| PyTypeObject *LocationInfo_Type; | |
| PyTypeObject *FrameInfo_Type; | |
| PyTypeObject *CoroInfo_Type; | |
| PyTypeObject *ThreadInfo_Type; | |
| PyTypeObject *InterpreterInfo_Type; | |
| PyTypeObject *AwaitedInfo_Type; | |
| PyTypeObject *GCStatsInfo_Type; | |
| PyTypeObject *BinaryWriter_Type; | |
| PyTypeObject *BinaryReader_Type; | |
| PyTypeObject *GCMonitor_Type; | |
| } RemoteDebuggingState; | |
| enum _ThreadState { | |
| THREAD_STATE_RUNNING, | |
| THREAD_STATE_IDLE, | |
| THREAD_STATE_GIL_WAIT, | |
| THREAD_STATE_UNKNOWN | |
| }; | |
| enum _ProfilingMode { | |
| PROFILING_MODE_WALL = 0, | |
| PROFILING_MODE_CPU = 1, | |
| PROFILING_MODE_GIL = 2, | |
| PROFILING_MODE_ALL = 3, | |
| PROFILING_MODE_EXCEPTION = 4 | |
| }; | |
| typedef struct { | |
| PyObject_HEAD | |
| proc_handle_t handle; | |
| uintptr_t runtime_start_address; | |
| struct _Py_DebugOffsets debug_offsets; | |
| int async_debug_offsets_available; | |
| struct _Py_AsyncioModuleDebugOffsets async_debug_offsets; | |
| uintptr_t interpreter_addr; | |
| uintptr_t tstate_addr; | |
| _Py_hashtable_t *code_object_cache; | |
| int debug; | |
| int only_active_thread; | |
| int mode; | |
| int skip_non_matching_threads; | |
| int native; | |
| int gc; | |
| int opcodes; | |
| int cache_frames; | |
| int collect_stats; // whether to collect statistics | |
| uint32_t stale_invalidation_counter; // counter for throttling frame_cache_invalidate_stale | |
| // L1 single-entry shortcut over cached_tstates[]: most workloads sample one | |
| // interpreter, so check these pairs before hashing into the table below. | |
| uintptr_t cached_tstate_interpreter_addr; | |
| uintptr_t cached_tstate_addr; | |
| uintptr_t cached_generation_interpreter_addr; | |
| uint64_t cached_code_object_generation; | |
| RemoteDebuggingState *cached_state; | |
| FrameCacheEntry *frame_cache; // preallocated array of FRAME_CACHE_MAX_THREADS entries | |
| UnwinderStats stats; // statistics for performance analysis | |
| InterpreterTstateCacheEntry cached_tstates[INTERPRETER_THREAD_CACHE_SIZE]; | |
| InterpreterGenerationCacheEntry cached_generations[INTERPRETER_THREAD_CACHE_SIZE]; | |
| #ifdef Py_GIL_DISABLED | |
| uint32_t tlbc_generation; | |
| _Py_hashtable_t *tlbc_cache; | |
| #endif | |
| #ifdef __APPLE__ | |
| uint64_t thread_id_offset; | |
| int thread_id_offset_initialized; | |
| #endif | |
| #ifdef MS_WINDOWS | |
| PVOID win_process_buffer; | |
| ULONG win_process_buffer_size; | |
| #endif | |
| // Thread stopping state (only on platforms that support it) | |
| #ifdef Py_REMOTE_DEBUG_SUPPORTS_BLOCKING | |
| _Py_RemoteDebug_ThreadsState threads_state; | |
| int threads_stopped; // 1 if threads are currently stopped | |
| #endif | |
| #ifdef __linux__ | |
| pid_t *thread_tids; // Reusable buffer for thread IDs | |
| size_t thread_tids_capacity; // Current capacity of thread_tids buffer | |
| #endif | |
| } RemoteUnwinderObject; | |
| #define RemoteUnwinder_CAST(op) ((RemoteUnwinderObject *)(op)) | |
| typedef struct { | |
| int lineno; | |
| int end_lineno; | |
| int column; | |
| int end_column; | |
| } LocationInfo; | |
| typedef struct { | |
| uintptr_t remote_addr; | |
| size_t size; | |
| void *local_copy; | |
| } StackChunkInfo; | |
| typedef struct { | |
| StackChunkInfo *chunks; | |
| size_t count; | |
| } StackChunkList; | |
| typedef struct { | |
| proc_handle_t handle; | |
| uintptr_t runtime_start_address; | |
| struct _Py_DebugOffsets debug_offsets; | |
| int debug; | |
| } RuntimeOffsets; | |
| /* | |
| * Context for frame chain traversal operations. | |
| */ | |
| typedef struct { | |
| /* Inputs */ | |
| uintptr_t frame_addr; // Starting frame address | |
| uintptr_t thread_state_addr; // Owning thread state address | |
| uintptr_t base_frame_addr; // Sentinel at bottom (for validation) | |
| uintptr_t gc_frame; // GC frame address (0 if not tracking) | |
| FrameCacheAnchor last_profiled; // Last cached frame anchor | |
| StackChunkList *chunks; // Pre-copied stack chunks | |
| int skip_first_frame; // Skip frame_addr itself (continue from its caller) | |
| RemoteReadPrefetch prefetch; // Optional already-read thread/frame buffers | |
| /* Outputs */ | |
| PyObject *frame_info; // List to append FrameInfo objects | |
| uintptr_t *frame_addrs; // Array of visited frame addresses | |
| Py_ssize_t num_addrs; // Count of addresses collected | |
| Py_ssize_t max_addrs; // Capacity of frame_addrs array | |
| uintptr_t last_frame_visited; // Last frame address visited | |
| int stopped_at_cached_frame; // Whether we stopped at cached frame | |
| } FrameWalkContext; | |
| /* | |
| * Context for code object parsing. | |
| */ | |
| typedef struct { | |
| uintptr_t code_addr; // Code object address in remote process | |
| uintptr_t instruction_pointer; // Current instruction pointer | |
| int32_t tlbc_index; // Thread-local bytecode index (free-threading) | |
| } CodeObjectContext; | |
| typedef struct { | |
| PyObject_HEAD | |
| RuntimeOffsets offsets; | |
| } GCMonitorObject; | |
| #define GCMonitor_CAST(op) ((GCMonitorObject *)(op)) | |
| /* Function pointer types for iteration callbacks */ | |
| typedef int (*thread_processor_func)( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t thread_state_addr, | |
| unsigned long tid, | |
| void *context | |
| ); | |
| typedef int (*interpreter_processor_func)( | |
| RuntimeOffsets *offsets, | |
| uintptr_t interpreter_state_addr, | |
| int64_t iid, | |
| void *context | |
| ); | |
| /* ============================================================================ | |
| * STRUCTSEQ DESCRIPTORS (extern declarations) | |
| * ============================================================================ */ | |
| extern PyStructSequence_Desc TaskInfo_desc; | |
| extern PyStructSequence_Desc LocationInfo_desc; | |
| extern PyStructSequence_Desc FrameInfo_desc; | |
| extern PyStructSequence_Desc CoroInfo_desc; | |
| extern PyStructSequence_Desc ThreadInfo_desc; | |
| extern PyStructSequence_Desc InterpreterInfo_desc; | |
| extern PyStructSequence_Desc AwaitedInfo_desc; | |
| extern PyStructSequence_Desc GCStatsInfo_desc; | |
| /* ============================================================================ | |
| * UTILITY FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| /* State access functions */ | |
| extern RemoteDebuggingState *RemoteDebugging_GetState(PyObject *module); | |
| extern RemoteDebuggingState *RemoteDebugging_GetStateFromType(PyTypeObject *type); | |
| extern RemoteDebuggingState *RemoteDebugging_GetStateFromObject(PyObject *obj); | |
| extern int RemoteDebugging_InitState(RemoteDebuggingState *st); | |
| /* Cache management */ | |
| extern void cached_code_metadata_destroy(void *ptr); | |
| /* Validation */ | |
| extern int is_prerelease_version(uint64_t version); | |
| extern int validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets); | |
| #define PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS (-2) | |
| /* ============================================================================ | |
| * MEMORY READING FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| extern int read_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *result); | |
| extern int read_char(RemoteUnwinderObject *unwinder, uintptr_t address, char *result); | |
| extern int read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr); | |
| /* Python object reading */ | |
| extern PyObject *read_py_str(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t max_len); | |
| extern PyObject *read_py_bytes(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t max_len); | |
| extern long read_py_long(RemoteUnwinderObject *unwinder, uintptr_t address); | |
| /* ============================================================================ | |
| * CODE OBJECT FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| extern int parse_code_object( | |
| RemoteUnwinderObject *unwinder, | |
| PyObject **result, | |
| const CodeObjectContext *ctx | |
| ); | |
| extern PyObject *make_location_info( | |
| RemoteUnwinderObject *unwinder, | |
| int lineno, | |
| int end_lineno, | |
| int col_offset, | |
| int end_col_offset | |
| ); | |
| extern PyObject *make_frame_info( | |
| RemoteUnwinderObject *unwinder, | |
| PyObject *file, | |
| PyObject *location, // LocationInfo structseq or None for synthetic frames | |
| PyObject *func, | |
| PyObject *opcode | |
| ); | |
| /* Line table parsing */ | |
| extern bool parse_linetable( | |
| const uintptr_t addrq, | |
| const char* linetable, | |
| Py_ssize_t linetable_size, | |
| int firstlineno, | |
| LocationInfo* info | |
| ); | |
| /* TLBC cache (only for Py_GIL_DISABLED) */ | |
| #ifdef Py_GIL_DISABLED | |
| typedef struct { | |
| void *tlbc_array; | |
| Py_ssize_t tlbc_array_size; | |
| uint32_t generation; | |
| } TLBCCacheEntry; | |
| extern void tlbc_cache_entry_destroy(void *ptr); | |
| extern TLBCCacheEntry *get_tlbc_cache_entry(RemoteUnwinderObject *self, uintptr_t code_addr, uint32_t current_generation); | |
| extern int cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t code_addr, uintptr_t tlbc_array_addr, uint32_t generation); | |
| #endif | |
| /* ============================================================================ | |
| * FRAME FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| extern int is_frame_valid( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t frame_addr, | |
| uintptr_t code_object_addr | |
| ); | |
| extern int parse_frame_object( | |
| RemoteUnwinderObject *unwinder, | |
| PyObject** result, | |
| uintptr_t address, | |
| uintptr_t* address_of_code_object, | |
| uintptr_t* previous_frame | |
| ); | |
| extern int parse_frame_from_chunks( | |
| RemoteUnwinderObject *unwinder, | |
| PyObject **result, | |
| uintptr_t address, | |
| uintptr_t *previous_frame, | |
| uintptr_t *stackpointer, | |
| StackChunkList *chunks | |
| ); | |
| /* Stack chunk management */ | |
| extern void cleanup_stack_chunks(StackChunkList *chunks); | |
| extern int copy_stack_chunks(RemoteUnwinderObject *unwinder, uintptr_t tstate_addr, StackChunkList *out_chunks); | |
| extern void *find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr); | |
| extern int process_frame_chain( | |
| RemoteUnwinderObject *unwinder, | |
| FrameWalkContext *ctx | |
| ); | |
| /* Frame cache functions */ | |
| extern int frame_cache_init(RemoteUnwinderObject *unwinder); | |
| extern void frame_cache_cleanup(RemoteUnwinderObject *unwinder); | |
| extern FrameCacheEntry *frame_cache_find(RemoteUnwinderObject *unwinder, uint64_t thread_id); | |
| extern FrameCacheEntry *frame_cache_find_by_tstate(RemoteUnwinderObject *unwinder, uintptr_t tstate_addr); | |
| extern int clear_last_profiled_frames(RemoteUnwinderObject *unwinder); | |
| extern int set_last_profiled_frame(RemoteUnwinderObject *unwinder, uintptr_t tstate_addr, uintptr_t frame_addr); | |
| extern void frame_cache_invalidate_stale(RemoteUnwinderObject *unwinder, PyObject *result); | |
| extern int frame_cache_lookup_and_extend( | |
| RemoteUnwinderObject *unwinder, | |
| uint64_t thread_id, | |
| uintptr_t thread_state_addr, | |
| FrameCacheAnchor anchor, | |
| PyObject *frame_info, | |
| uintptr_t *frame_addrs, | |
| Py_ssize_t *num_addrs, | |
| Py_ssize_t max_addrs); | |
| extern int frame_cache_anchor_matches( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t thread_state_addr, | |
| FrameCacheAnchor anchor); | |
| // Returns: 1 = stored, 0 = not stored (graceful), -1 = error | |
| // Only stores complete stacks that reach base_frame_addr | |
| extern int frame_cache_store( | |
| RemoteUnwinderObject *unwinder, | |
| uint64_t thread_id, | |
| PyObject *frame_list, | |
| const uintptr_t *addrs, | |
| Py_ssize_t num_addrs, | |
| uintptr_t thread_state_addr, | |
| uintptr_t last_profiled_frame_seq, | |
| uintptr_t base_frame_addr, | |
| uintptr_t last_frame_visited); | |
| extern int collect_frames_with_cache( | |
| RemoteUnwinderObject *unwinder, | |
| FrameWalkContext *ctx, | |
| uint64_t thread_id); | |
| /* ============================================================================ | |
| * THREAD FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| extern int iterate_threads( | |
| RemoteUnwinderObject *unwinder, | |
| thread_processor_func processor, | |
| void *context | |
| ); | |
| extern int populate_initial_state_data( | |
| int all_threads, | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t runtime_start_address, | |
| uintptr_t *interpreter_state, | |
| uintptr_t *tstate | |
| ); | |
| extern int find_running_frame( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t address_of_thread, | |
| uintptr_t *frame | |
| ); | |
| extern int get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread_id); | |
| extern PyObject* unwind_stack_for_thread( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t *current_tstate, | |
| uintptr_t gil_holder_tstate, | |
| uintptr_t gc_frame, | |
| uintptr_t main_thread_tstate, | |
| const RemoteReadPrefetch *prefetch | |
| ); | |
| /* Thread stopping functions (for blocking mode) */ | |
| extern void _Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st); | |
| extern int _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st); | |
| extern void _Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st); | |
| /* ============================================================================ | |
| * INTERPRETER FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| extern int | |
| iterate_interpreters( | |
| RuntimeOffsets *offsets, | |
| interpreter_processor_func processor, | |
| void *context | |
| ); | |
| /* ============================================================================ | |
| * ASYNCIO FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| extern uintptr_t _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle); | |
| extern int read_async_debug(RemoteUnwinderObject *unwinder); | |
| extern int ensure_async_debug_offsets(RemoteUnwinderObject *unwinder); | |
| /* Task parsing */ | |
| extern PyObject *parse_task_name(RemoteUnwinderObject *unwinder, uintptr_t task_address); | |
| extern int parse_task( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t task_address, | |
| PyObject *render_to | |
| ); | |
| extern int parse_async_frame_chain( | |
| RemoteUnwinderObject *unwinder, | |
| PyObject *calls, | |
| uintptr_t address_of_thread, | |
| uintptr_t running_task_frame | |
| ); | |
| extern int process_single_task_node( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t task_addr, | |
| PyObject **task_info, | |
| PyObject *result | |
| ); | |
| extern int find_running_task_in_thread( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t thread_state_addr, | |
| uintptr_t *running_task_addr | |
| ); | |
| extern int get_task_frame( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t task_addr, | |
| uintptr_t *frame_addr | |
| ); | |
| extern int append_awaited_by( | |
| RemoteUnwinderObject *unwinder, | |
| unsigned long tid, | |
| uintptr_t head_addr, | |
| PyObject *result | |
| ); | |
| /* Thread processors for asyncio */ | |
| extern int process_thread_for_awaited_by( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t thread_state_addr, | |
| unsigned long tid, | |
| void *context | |
| ); | |
| extern int process_thread_for_async_stack_trace( | |
| RemoteUnwinderObject *unwinder, | |
| uintptr_t thread_state_addr, | |
| unsigned long tid, | |
| void *context | |
| ); | |
| /* ============================================================================ | |
| * SUBPROCESS ENUMERATION FUNCTION DECLARATIONS | |
| * ============================================================================ */ | |
| /* Get all child PIDs of a process. | |
| * Returns a new Python list of PIDs, or NULL on error with exception set. | |
| * If recursive is true, includes all descendants (children, grandchildren, etc.) | |
| */ | |
| extern PyObject *enumerate_child_pids(pid_t target_pid, int recursive); | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* Py_REMOTE_DEBUGGING_H */ |