Skip to content

Commit b4e3c99

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

File tree

1 file changed

+24
-17
lines changed

1 file changed

+24
-17
lines changed

peps/pep-0768.rst

Lines changed: 24 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,19 @@ 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
206214
- Set ``_PY_EVAL_PLEASE_STOP_BIT`` in the ``eval_breaker`` field
207215

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

211219
Interpreter Integration
212220
-----------------------
@@ -237,7 +245,7 @@ to be audited or disabled if desired by a system's administrator.
237245
if (tstate->eval_breaker) {
238246
if (tstate->remote_debugger_support.debugger_pending_call) {
239247
tstate->remote_debugger_support.debugger_pending_call = 0;
240-
const char *path = tstate->remote_debugger_support.debugger_script;
248+
const char *path = tstate->remote_debugger_support.debugger_script_path;
241249
if (*path) {
242250
if (0 != PySys_Audit("debugger_script", "%s", path)) {
243251
PyErr_Clear();
@@ -273,16 +281,17 @@ arbitrary Python code within the context of a specified Python process:
273281

274282
.. code-block:: python
275283
276-
def remote_exec(pid: int, code: str, timeout: int = 0) -> None:
284+
def remote_exec(pid: int, code: str) -> None:
277285
"""
278286
Executes a block of Python code in a given remote Python process.
279287
288+
This function returns immediately, and the code will be executed at the next
289+
available opportunity in the target process, similar to how signals are handled.
290+
There is no way to determine when or if the code has been executed.
291+
280292
Args:
281293
pid (int): The process ID of the target Python process.
282294
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.
286295
"""
287296
288297
An example usage of the API would look like:
@@ -292,9 +301,7 @@ An example usage of the API would look like:
292301
import sys
293302
# Execute a print statement in a remote Python process with PID 12345
294303
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")
304+
sys.remote_exec(12345, "print('Hello from remote execution!')")
298305
except Exception as e:
299306
print(f"Failed to execute code: {e}")
300307
@@ -454,8 +461,8 @@ Rejected Ideas
454461
Writing Python code into the buffer
455462
-----------------------------------
456463

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
464+
We have chosen to have debuggers write the path to a file containing Python code
465+
into a buffer in the remote process. This has been deemed
459466
more secure than writing the Python code to be executed itself into a buffer in
460467
the remote process, because it means that an attacker who has gained arbitrary
461468
writes in a process but not arbitrary code execution or file system

0 commit comments

Comments
 (0)