Skip to content

Commit 58305c1

Browse files
committed
PEP 768: Add some clarifications and minor edits
1 parent 57c7e93 commit 58305c1

File tree

1 file changed

+25
-17
lines changed

1 file changed

+25
-17
lines changed

peps/pep-0768.rst

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,10 @@ A new structure is added to PyThreadState to support remote debugging:
141141
142142
This structure is appended to ``PyThreadState``, adding only a few fields that
143143
are **never accessed during normal execution**. The ``debugger_pending_call`` field
144-
indicates when a debugger has requested execution, while ``debugger_script``
145-
provides Python code to be executed when the interpreter reaches a safe point.
144+
indicates when a debugger has requested execution, while ``debugger_script_path``
145+
provides a filesystem path to a Python source file (.py) that will be executed when
146+
the interpreter reaches a safe point. The path must point to a Python source file,
147+
not compiled Python code (.pyc) or any other format.
146148

147149
The value for ``MAX_SCRIPT_PATH_SIZE`` will be a trade-off between binary size
148150
and how big debugging scripts' paths can be. To limit the memory overhead per
@@ -177,7 +179,7 @@ debugger support:
177179
These offsets allow debuggers to locate critical debugging control structures in
178180
the target process's memory space. The ``eval_breaker`` and ``remote_debugger_support``
179181
offsets are relative to each ``PyThreadState``, while the ``debugger_pending_call``
180-
and ``debugger_script`` offsets are relative to each ``_PyRemoteDebuggerSupport``
182+
and ``debugger_script_path`` offsets are relative to each ``_PyRemoteDebuggerSupport``
181183
structure, allowing the new structure and its fields to be found regardless of
182184
where they are in memory. ``debugger_script_path_size`` informs the attaching
183185
tool of the size of the buffer.
@@ -200,13 +202,20 @@ When a debugger wants to attach to a Python process, it follows these steps:
200202

201203
5. Write control information:
202204

203-
- Write a filename containing Python code to be executed into the
204-
``debugger_script`` field in ``_PyRemoteDebuggerSupport``.
205+
- Most debuggers will pause the process before writing to its memory. This is
206+
standard practice for tools like GDB, which use SIGSTOP or ptrace to pause the process.
207+
This approach prevents races when writing to process memory. Profilers and other tools
208+
that don't wish to stop the process can still use this interface, but they need to
209+
handle possible races, which is a normal consideration for profilers in general.
210+
211+
- Write a file path to a Python source file (.py) into the
212+
``debugger_script_path`` field in ``_PyRemoteDebuggerSupport``.
205213
- Set ``debugger_pending_call`` flag in ``_PyRemoteDebuggerSupport`` to 1
214+
- Set ``debugger_pending_call`` flag in ``_PyRemoteDebuggerSupport``
206215
- Set ``_PY_EVAL_PLEASE_STOP_BIT`` in the ``eval_breaker`` field
207216

208-
Once the interpreter reaches the next safe point, it will execute the script
209-
provided by the debugger.
217+
Once the interpreter reaches the next safe point, it will execute the Python code
218+
contained in the file specified by the debugger.
210219

211220
Interpreter Integration
212221
-----------------------
@@ -237,7 +246,7 @@ to be audited or disabled if desired by a system's administrator.
237246
if (tstate->eval_breaker) {
238247
if (tstate->remote_debugger_support.debugger_pending_call) {
239248
tstate->remote_debugger_support.debugger_pending_call = 0;
240-
const char *path = tstate->remote_debugger_support.debugger_script;
249+
const char *path = tstate->remote_debugger_support.debugger_script_path;
241250
if (*path) {
242251
if (0 != PySys_Audit("debugger_script", "%s", path)) {
243252
PyErr_Clear();
@@ -273,16 +282,17 @@ arbitrary Python code within the context of a specified Python process:
273282

274283
.. code-block:: python
275284
276-
def remote_exec(pid: int, code: str, timeout: int = 0) -> None:
285+
def remote_exec(pid: int, code: str) -> None:
277286
"""
278287
Executes a block of Python code in a given remote Python process.
279288
289+
This function returns immediately, and the code will be executed at the next
290+
available opportunity in the target process, similar to how signals are handled.
291+
There is no way to determine when or if the code has been executed.
292+
280293
Args:
281294
pid (int): The process ID of the target Python process.
282295
code (str): A string containing the Python code to be executed.
283-
timeout (int): An optional timeout for waiting for the remote
284-
process to execute the code. If the timeout is exceeded a
285-
``TimeoutError`` will be raised.
286296
"""
287297
288298
An example usage of the API would look like:
@@ -292,9 +302,7 @@ An example usage of the API would look like:
292302
import sys
293303
# Execute a print statement in a remote Python process with PID 12345
294304
try:
295-
sys.remote_exec(12345, "print('Hello from remote execution!')", timeout=3)
296-
except TimeoutError:
297-
print(f"The remote process took too long to execute the code")
305+
sys.remote_exec(12345, "print('Hello from remote execution!')")
298306
except Exception as e:
299307
print(f"Failed to execute code: {e}")
300308
@@ -454,8 +462,8 @@ Rejected Ideas
454462
Writing Python code into the buffer
455463
-----------------------------------
456464

457-
We have chosen to have debuggers write the code to be executed into a file
458-
whose path is written into a buffer in the remote process. This has been deemed
465+
We have chosen to have debuggers write the path to a file containing Python code
466+
into a buffer in the remote process. This has been deemed
459467
more secure than writing the Python code to be executed itself into a buffer in
460468
the remote process, because it means that an attacker who has gained arbitrary
461469
writes in a process but not arbitrary code execution or file system

0 commit comments

Comments
 (0)