Skip to content

Commit bf0a97c

Browse files
committed
PEP 768: Add some clarifications and minor edits
1 parent 6e1a745 commit bf0a97c

File tree

1 file changed

+28
-22
lines changed

1 file changed

+28
-22
lines changed

peps/pep-0768.rst

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -136,19 +136,20 @@ A new structure is added to PyThreadState to support remote debugging:
136136
137137
typedef struct _remote_debugger_support {
138138
int debugger_pending_call;
139-
char debugger_script[MAX_SCRIPT_SIZE];
139+
char debugger_script_path[MAX_SCRIPT_SIZE];
140140
} _PyRemoteDebuggerSupport;
141141
142142
143143
This structure is appended to ``PyThreadState``, adding only a few fields that
144144
are **never accessed during normal execution**. The ``debugger_pending_call`` field
145-
indicates when a debugger has requested execution, while ``debugger_script``
146-
provides Python code to be executed when the interpreter reaches a safe point.
145+
indicates when a debugger has requested execution, while ``debugger_script_path``
146+
provides a filesystem path to a Python source file (.py) that will be executed when
147+
the interpreter reaches a safe point. The path must point to a Python source file,
148+
not compiled Python code (.pyc) or any other format.
147149

148150
The value for ``MAX_SCRIPT_SIZE`` will be a trade-off between binary size and
149-
how big debugging scripts can be. As most of the logic should be in libraries
150-
and arbitrary code can be executed with very short amount of Python we are
151-
proposing to start with 4kb initially. This value can be extended in the future
151+
how long the path can be. As most paths are relatively short on modern systems,
152+
we are proposing to start with 4kb initially. This value can be extended in the future
152153
if we ever need to.
153154

154155

@@ -171,13 +172,13 @@ debugger support:
171172
uint64_t eval_breaker; // Location of the eval breaker flag
172173
uint64_t remote_debugger_support; // Offset to our support structure
173174
uint64_t debugger_pending_call; // Where to write the pending flag
174-
uint64_t debugger_script; // Where to write the script path
175+
uint64_t debugger_script_path; // Where to write the script path
175176
} debugger_support;
176177
177178
These offsets allow debuggers to locate critical debugging control structures in
178179
the target process's memory space. The ``eval_breaker`` and ``remote_debugger_support``
179180
offsets are relative to each ``PyThreadState``, while the ``debugger_pending_call``
180-
and ``debugger_script`` offsets are relative to each ``_PyRemoteDebuggerSupport``
181+
and ``debugger_script_path`` offsets are relative to each ``_PyRemoteDebuggerSupport``
181182
structure, allowing the new structure and its fields to be found regardless of
182183
where they are in memory.
183184

@@ -199,13 +200,19 @@ When a debugger wants to attach to a Python process, it follows these steps:
199200

200201
5. Write control information:
201202

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

207-
Once the interpreter reaches the next safe point, it will execute the script
208-
provided by the debugger.
214+
Once the interpreter reaches the next safe point, it will execute the Python code
215+
contained in the file specified by the debugger.
209216

210217
Interpreter Integration
211218
-----------------------
@@ -233,7 +240,7 @@ to be audited or disabled if desired by a system's administrator.
233240
if (tstate->eval_breaker) {
234241
if (tstate->remote_debugger_support.debugger_pending_call) {
235242
tstate->remote_debugger_support.debugger_pending_call = 0;
236-
const char *path = tstate->remote_debugger_support.debugger_script;
243+
const char *path = tstate->remote_debugger_support.debugger_script_path;
237244
if (*path) {
238245
if (0 != PySys_Audit("debugger_script", "%s", path)) {
239246
PyErr_Clear();
@@ -269,16 +276,17 @@ arbitrary Python code within the context of a specified Python process:
269276

270277
.. code-block:: python
271278
272-
def remote_exec(pid: int, code: str, timeout: int = 0) -> None:
279+
def remote_exec(pid: int, code: str) -> None:
273280
"""
274281
Executes a block of Python code in a given remote Python process.
275282
283+
This function returns immediately, and the code will be executed at the next
284+
available opportunity in the target process, similar to how signals are handled.
285+
There is no way to determine when or if the code has been executed.
286+
276287
Args:
277288
pid (int): The process ID of the target Python process.
278289
code (str): A string containing the Python code to be executed.
279-
timeout (int): An optional timeout for waiting for the remote
280-
process to execute the code. If the timeout is exceeded a
281-
``TimeoutError`` will be raised.
282290
"""
283291
284292
An example usage of the API would look like:
@@ -288,9 +296,7 @@ An example usage of the API would look like:
288296
import sys
289297
# Execute a print statement in a remote Python process with PID 12345
290298
try:
291-
sys.remote_exec(12345, "print('Hello from remote execution!')", timeout=3)
292-
except TimeoutError:
293-
print(f"The remote process took too long to execute the code")
299+
sys.remote_exec(12345, "print('Hello from remote execution!')")
294300
except Exception as e:
295301
print(f"Failed to execute code: {e}")
296302
@@ -393,8 +399,8 @@ Rejected Ideas
393399
Writing Python code into the buffer
394400
-----------------------------------
395401

396-
We have chosen to have debuggers write the code to be executed into a file
397-
whose path is written into a buffer in the remote process. This has been deemed
402+
We have chosen to have debuggers write the path to a file containing Python code
403+
into a buffer in the remote process. This has been deemed
398404
more secure than writing the Python code to be executed itself into a buffer in
399405
the remote process, because it means that an attacker who has gained arbitrary
400406
writes in a process but not arbitrary code execution or file system

0 commit comments

Comments
 (0)