From 2f3583040b19a3107c47d154c17455c68296c947 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 8 Jan 2025 15:58:02 +0000 Subject: [PATCH 1/6] PEP 768: Add some clarifications for buffer size --- peps/pep-0768.rst | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 5b70afe4ec6..2b3b49cb779 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -132,24 +132,27 @@ Runtime State Extensions A new structure is added to PyThreadState to support remote debugging: +debugging: + .. code-block:: C typedef struct _remote_debugger_support { int debugger_pending_call; - char debugger_script[MAX_SCRIPT_SIZE]; + char debugger_script_path[MAX_SCRIPT_PATH_SIZE]; + char *debugger_buffer; + Py_ssize_t debugger_buffer_size; } _PyRemoteDebuggerSupport; - This structure is appended to ``PyThreadState``, adding only a few fields that are **never accessed during normal execution**. The ``debugger_pending_call`` field indicates when a debugger has requested execution, while ``debugger_script`` provides Python code to be executed when the interpreter reaches a safe point. -The value for ``MAX_SCRIPT_SIZE`` will be a trade-off between binary size and -how big debugging scripts can be. As most of the logic should be in libraries -and arbitrary code can be executed with very short amount of Python we are -proposing to start with 4kb initially. This value can be extended in the future -if we ever need to. +The value for ``MAX_SCRIPT_PATH_SIZE`` will be a trade-off between binary size +and how big debugging scripts paths can be. To limit the memory overhead per +thread we will be limiting this to 512 bytes. This size will also be provided as +part of the debugger support structure so debuggers know how much they can +write. This value can be extended in the future if we ever need to. Debug Offsets Table @@ -201,7 +204,7 @@ When a debugger wants to attach to a Python process, it follows these steps: - Write a filename containing Python code to be executed into the ``debugger_script`` field in ``_PyRemoteDebuggerSupport``. - - Set ``debugger_pending_call`` flag in ``_PyRemoteDebuggerSupport`` + - Set ``debugger_pending_call`` flag in ``_PyRemoteDebuggerSupport`` to 1 - Set ``_PY_EVAL_PLEASE_STOP_BIT`` in the ``eval_breaker`` field Once the interpreter reaches the next safe point, it will execute the script @@ -224,6 +227,9 @@ the interpreter will execute the provided debugging code at the next safe point. This all happens in a completely safe context, since the interpreter is guaranteed to be in a consistent state whenever the eval breaker is checked. +The only valid values for ``debugger_pending_call`` will initially be 0 and 1 +and other values are reserved for future use. + An audit event will be raised before the code is executed, allowing this mechanism to be audited or disabled if desired by a system's administrator. @@ -464,6 +470,21 @@ in the file path to point to somewhere attacker controlled, this would allow them to force their malicious code to be executed rather than the code the debugger intends to run. +Using a Single Runtime Buffer +----------------------------------- + +During the review of this PEP it has been suggested using a single +shared buffer at the runtime level for all debugger communications. While this +appeared simpler and requring less memory , we discovered it would actually prevent scenarios +where multiple debuggers need to coordinate operations across different threads, +or where a single debugger needs to orchestrate complex debugging operations. A +single shared buffer would force serialization of all debugging operations, +making it impossible for debuggers to work independently on different threads. + +The per-thread buffer approach, despite its memory overhead in highly threaded +applications, enables these important debugging scenarios by allowing each +debugger to communicate independently with its target thread. + Thanks ====== From 7a9768c812530748dfe59eb44746e964d830cc2b Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 8 Jan 2025 16:55:55 +0000 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Matt Wozniski --- peps/pep-0768.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 2b3b49cb779..93fac0ef845 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -132,14 +132,11 @@ Runtime State Extensions A new structure is added to PyThreadState to support remote debugging: -debugging: - .. code-block:: C typedef struct _remote_debugger_support { int debugger_pending_call; char debugger_script_path[MAX_SCRIPT_PATH_SIZE]; - char *debugger_buffer; Py_ssize_t debugger_buffer_size; } _PyRemoteDebuggerSupport; @@ -475,7 +472,7 @@ Using a Single Runtime Buffer During the review of this PEP it has been suggested using a single shared buffer at the runtime level for all debugger communications. While this -appeared simpler and requring less memory , we discovered it would actually prevent scenarios +appeared simpler and requiring less memory, we discovered it would actually prevent scenarios where multiple debuggers need to coordinate operations across different threads, or where a single debugger needs to orchestrate complex debugging operations. A single shared buffer would force serialization of all debugging operations, From bb9c4bb208846ec03aa0f22c7ba7ea4ecda36333 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 8 Jan 2025 17:07:08 +0000 Subject: [PATCH 3/6] fixup! PEP 768: Add some clarifications for buffer size --- peps/pep-0768.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 93fac0ef845..16ee2f1b522 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -137,7 +137,7 @@ A new structure is added to PyThreadState to support remote debugging: typedef struct _remote_debugger_support { int debugger_pending_call; char debugger_script_path[MAX_SCRIPT_PATH_SIZE]; - Py_ssize_t debugger_buffer_size; + char *debugger_buffer; } _PyRemoteDebuggerSupport; This structure is appended to ``PyThreadState``, adding only a few fields that @@ -149,7 +149,7 @@ The value for ``MAX_SCRIPT_PATH_SIZE`` will be a trade-off between binary size and how big debugging scripts paths can be. To limit the memory overhead per thread we will be limiting this to 512 bytes. This size will also be provided as part of the debugger support structure so debuggers know how much they can -write. This value can be extended in the future if we ever need to. +write. This value can be extended in the future if we ever need to. Debug Offsets Table @@ -168,10 +168,11 @@ debugger support: .. code-block:: C struct _debugger_support { - uint64_t eval_breaker; // Location of the eval breaker flag - uint64_t remote_debugger_support; // Offset to our support structure - uint64_t debugger_pending_call; // Where to write the pending flag - uint64_t debugger_script; // Where to write the script path + uint64_t eval_breaker; // Location of the eval breaker flag + uint64_t remote_debugger_support; // Offset to our support structure + uint64_t debugger_pending_call; // Where to write the pending flag + uint64_t debugger_script_path; // Where to write the script path + uint64_t debugger_script_path_size; // Size of the script path buffer } debugger_support; These offsets allow debuggers to locate critical debugging control structures in @@ -179,7 +180,8 @@ the target process's memory space. The ``eval_breaker`` and ``remote_debugger_su offsets are relative to each ``PyThreadState``, while the ``debugger_pending_call`` and ``debugger_script`` offsets are relative to each ``_PyRemoteDebuggerSupport`` structure, allowing the new structure and its fields to be found regardless of -where they are in memory. +where they are in memory. ``debugger_script_path_sizee`` informs the attaching +tool of the size of the buffer. Attachment Protocol ------------------- From 62963caf5bd80400e0feb218d20c71a813006c07 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 8 Jan 2025 23:47:39 +0000 Subject: [PATCH 4/6] Update pep-0768.rst Co-authored-by: Matt Wozniski --- peps/pep-0768.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 16ee2f1b522..fef05efd460 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -146,7 +146,7 @@ indicates when a debugger has requested execution, while ``debugger_script`` provides Python code to be executed when the interpreter reaches a safe point. The value for ``MAX_SCRIPT_PATH_SIZE`` will be a trade-off between binary size -and how big debugging scripts paths can be. To limit the memory overhead per +and how big debugging scripts' paths can be. To limit the memory overhead per thread we will be limiting this to 512 bytes. This size will also be provided as part of the debugger support structure so debuggers know how much they can write. This value can be extended in the future if we ever need to. From a8ed5958ced31a70c1667d9c09611370fbc983e6 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Thu, 9 Jan 2025 21:35:12 +0000 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- peps/pep-0768.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index fef05efd460..099e53a4a59 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -180,7 +180,7 @@ the target process's memory space. The ``eval_breaker`` and ``remote_debugger_su offsets are relative to each ``PyThreadState``, while the ``debugger_pending_call`` and ``debugger_script`` offsets are relative to each ``_PyRemoteDebuggerSupport`` structure, allowing the new structure and its fields to be found regardless of -where they are in memory. ``debugger_script_path_sizee`` informs the attaching +where they are in memory. ``debugger_script_path_size`` informs the attaching tool of the size of the buffer. Attachment Protocol @@ -470,11 +470,11 @@ them to force their malicious code to be executed rather than the code the debugger intends to run. Using a Single Runtime Buffer ------------------------------------ +----------------------------- During the review of this PEP it has been suggested using a single shared buffer at the runtime level for all debugger communications. While this -appeared simpler and requiring less memory, we discovered it would actually prevent scenarios +appeared simpler and required less memory, we discovered it would actually prevent scenarios where multiple debuggers need to coordinate operations across different threads, or where a single debugger needs to orchestrate complex debugging operations. A single shared buffer would force serialization of all debugging operations, From 0dee585394602e827ae7d2790a3c6ed4eee5dc03 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Thu, 9 Jan 2025 21:35:33 +0000 Subject: [PATCH 6/6] Update peps/pep-0768.rst --- peps/pep-0768.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 099e53a4a59..8a1a825ddfd 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -137,7 +137,6 @@ A new structure is added to PyThreadState to support remote debugging: typedef struct _remote_debugger_support { int debugger_pending_call; char debugger_script_path[MAX_SCRIPT_PATH_SIZE]; - char *debugger_buffer; } _PyRemoteDebuggerSupport; This structure is appended to ``PyThreadState``, adding only a few fields that