Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions Doc/c-api/bytes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,162 @@ called with a non-bytes parameter.
reallocation fails, the original bytes object at *\*bytes* is deallocated,
*\*bytes* is set to ``NULL``, :exc:`MemoryError` is set, and ``-1`` is
returned.

PyBytesWriter
-------------

The :c:type:`PyBytesWriter` API can be used to create a Python :class:`bytes`
object.

.. versionadded:: next

.. c:type:: PyBytesWriter

A bytes writer instance.

The API is **not thread safe**: a writer should only be used by a single
thread at the same time.

The instance must be destroyed by :c:func:`PyBytesWriter_Finish` on
success, or :c:func:`PyBytesWriter_Discard` on error.


Create, Finish, Discard
^^^^^^^^^^^^^^^^^^^^^^^

.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size)

Create a :c:type:`PyBytesWriter` to write *size* bytes.

If *size* is greater than zero, allocate *size* bytes, and set the
writer size to *size*. The caller is responsible to write *size*
bytes using :c:func:`PyBytesWriter_GetData`.

On error, set an exception and return NULL.

*size* must be positive or zero.

.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer)

Finish a :c:type:`PyBytesWriter` created by
:c:func:`PyBytesWriter_Create`.

On success, return a Python :class:`bytes` object.
On error, set an exception and return ``NULL``.

The writer instance is invalid after the call in any case.
No API can be called on the writer after :c:func:`PyBytesWriter_Finish`.

.. c:function:: PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)

Similar to :c:func:`PyBytesWriter_Finish`, but resize the writer
to *size* bytes before creating the :class:`bytes` object.

.. c:function:: PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)

Similar to :c:func:`PyBytesWriter_Finish`, but resize the writer
using *buf* pointer before creating the :class:`bytes` object.

Set an exception and return ``NULL`` if *buf* pointer is outside the
internal buffer bounds.

Function pseudo-code::

Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer);
return PyBytesWriter_FinishWithSize(writer, size);

.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)

Discard a :c:type:`PyBytesWriter` created by :c:func:`PyBytesWriter_Create`.

Do nothing if *writer* is ``NULL``.

The writer instance is invalid after the call.
No API can be called on the writer after :c:func:`PyBytesWriter_Discard`.

High-level API
^^^^^^^^^^^^^^

.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)

Grow the *writer* internal buffer by *size* bytes,
write *size* bytes of *bytes* at the *writer* end,
and add *size* to the *writer* size.

If *size* is equal to ``-1``, call ``strlen(bytes)`` to get the
string length.

On success, return ``0``.
On error, set an exception and return ``-1``.

.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)

Similar to :c:func:`PyBytes_FromFormat`, but write the output directly at
the writer end. Grow the writer internal buffer on demand. Then add the
written size to the writer size.

On success, return ``0``.
On error, set an exception and return ``-1``.


Getters
^^^^^^^

.. c:function:: Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)

Get the writer size.

.. c:function:: void* PyBytesWriter_GetData(PyBytesWriter *writer)

Get the writer data: start of the internal buffer.

The pointer is valid until :c:func:`PyBytesWriter_Finish` or
:c:func:`PyBytesWriter_Discard` is called on *writer*.


Low-level API
^^^^^^^^^^^^^

.. c:function:: int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)

Resize the writer to *size* bytes. It can be used to enlarge or to
shrink the writer.

Newly allocated bytes are left uninitialized.

On success, return ``0``.
On error, set an exception and return ``-1``.

*size* must be positive or zero.

.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)

Resize the writer by adding *grow* bytes to the current writer size.

Newly allocated bytes are left uninitialized.

On success, return ``0``.
On error, set an exception and return ``-1``.

*size* can be negative to shrink the writer.

.. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)

Similar to :c:func:`PyBytesWriter_Grow`, but update also the *buf*
pointer.

The *buf* pointer is moved if the internal buffer is moved in memory.
The *buf* relative position within the internal buffer is left
unchanged.

On error, set an exception and return ``NULL``.

*buf* must not be ``NULL``.

Function pseudo-code::

Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer);
if (PyBytesWriter_Grow(writer, size) < 0) {
return NULL;
}
return (char*)PyBytesWriter_GetData(writer) + pos;
Binary file removed Doc/faq/python-video-icon.png
Binary file not shown.
72 changes: 72 additions & 0 deletions Doc/howto/remote_debugging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,78 @@
Remote debugging attachment protocol
====================================

This protocol enables external tools to attach to a running CPython process and
execute Python code remotely.

Most platforms require elevated privileges to attach to another Python process.

.. _permission-requirements:

Permission requirements
=======================

Attaching to a running Python process for remote debugging requires elevated
privileges on most platforms. The specific requirements and troubleshooting
steps depend on your operating system:

.. rubric:: Linux

The tracer process must have the ``CAP_SYS_PTRACE`` capability or equivalent
privileges. You can only trace processes you own and can signal. Tracing may
fail if the process is already being traced, or if it is running with
set-user-ID or set-group-ID. Security modules like Yama may further restrict
tracing.

To temporarily relax ptrace restrictions (until reboot), run:

``echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope``

.. note::

Disabling ``ptrace_scope`` reduces system hardening and should only be done
in trusted environments.

If running inside a container, use ``--cap-add=SYS_PTRACE`` or
``--privileged``, and run as root if needed.

Try re-running the command with elevated privileges:

``sudo -E !!``


.. rubric:: macOS

To attach to another process, you typically need to run your debugging tool
with elevated privileges. This can be done by using ``sudo`` or running as
root.

Even when attaching to processes you own, macOS may block debugging unless
the debugger is run with root privileges due to system security restrictions.


.. rubric:: Windows

To attach to another process, you usually need to run your debugging tool
with administrative privileges. Start the command prompt or terminal as
Administrator.

Some processes may still be inaccessible even with Administrator rights,
unless you have the ``SeDebugPrivilege`` privilege enabled.

To resolve file or folder access issues, adjust the security permissions:

1. Right-click the file or folder and select **Properties**.
2. Go to the **Security** tab to view users and groups with access.
3. Click **Edit** to modify permissions.
4. Select your user account.
5. In **Permissions**, check **Read** or **Full control** as needed.
6. Click **Apply**, then **OK** to confirm.


.. note::

Ensure you've satisfied all :ref:`permission-requirements` before proceeding.

This section describes the low-level protocol that enables external tools to
inject and execute a Python script within a running CPython process.

Expand Down
17 changes: 17 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,23 @@ New features
and :c:data:`Py_mod_abi`.
(Contributed by Petr Viktorin in :gh:`137210`.)

* Implement :pep:`782`, the :c:type:`PyBytesWriter` API. Add functions:

* :c:func:`PyBytesWriter_Create`
* :c:func:`PyBytesWriter_Discard`
* :c:func:`PyBytesWriter_FinishWithPointer`
* :c:func:`PyBytesWriter_FinishWithSize`
* :c:func:`PyBytesWriter_Finish`
* :c:func:`PyBytesWriter_Format`
* :c:func:`PyBytesWriter_GetData`
* :c:func:`PyBytesWriter_GetSize`
* :c:func:`PyBytesWriter_GrowAndUpdatePointer`
* :c:func:`PyBytesWriter_Grow`
* :c:func:`PyBytesWriter_Resize`
* :c:func:`PyBytesWriter_WriteBytes`

(Contributed by Victor Stinner in :gh:`129813`.)


Porting to Python 3.15
----------------------
Expand Down
43 changes: 43 additions & 0 deletions Include/cpython/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,46 @@ _PyBytes_Join(PyObject *sep, PyObject *iterable)
{
return PyBytes_Join(sep, iterable);
}


// --- PyBytesWriter API -----------------------------------------------------

typedef struct PyBytesWriter PyBytesWriter;

PyAPI_FUNC(PyBytesWriter *) PyBytesWriter_Create(
Py_ssize_t size);
PyAPI_FUNC(void) PyBytesWriter_Discard(
PyBytesWriter *writer);
PyAPI_FUNC(PyObject*) PyBytesWriter_Finish(
PyBytesWriter *writer);
PyAPI_FUNC(PyObject*) PyBytesWriter_FinishWithSize(
PyBytesWriter *writer,
Py_ssize_t size);
PyAPI_FUNC(PyObject*) PyBytesWriter_FinishWithPointer(
PyBytesWriter *writer,
void *buf);

PyAPI_FUNC(void*) PyBytesWriter_GetData(
PyBytesWriter *writer);
PyAPI_FUNC(Py_ssize_t) PyBytesWriter_GetSize(
PyBytesWriter *writer);

PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
PyBytesWriter *writer,
const void *bytes,
Py_ssize_t size);
PyAPI_FUNC(int) PyBytesWriter_Format(
PyBytesWriter *writer,
const char *format,
...);

PyAPI_FUNC(int) PyBytesWriter_Resize(
PyBytesWriter *writer,
Py_ssize_t size);
PyAPI_FUNC(int) PyBytesWriter_Grow(
PyBytesWriter *writer,
Py_ssize_t size);
PyAPI_FUNC(void*) PyBytesWriter_GrowAndUpdatePointer(
PyBytesWriter *writer,
Py_ssize_t size,
void *buf);
4 changes: 4 additions & 0 deletions Include/internal/pycore_bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ PyAPI_FUNC(void*) _PyBytesWriter_WriteBytes(_PyBytesWriter *writer,
const void *bytes,
Py_ssize_t size);

// Export for '_testcapi' shared extension.
PyAPI_FUNC(PyBytesWriter*) _PyBytesWriter_CreateByteArray(
Py_ssize_t size);

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ extern void _PyLineTable_InitAddressRange(
/** API for traversing the line number table. */
extern int _PyLineTable_NextAddressRange(PyCodeAddressRange *range);
extern int _PyLineTable_PreviousAddressRange(PyCodeAddressRange *range);
// This is used in dump_frame() in traceback.c without an attached tstate.
extern int _PyCode_Addr2LineNoTstate(PyCodeObject *co, int addr);

/** API for executors */
extern void _PyCode_Clear_Executors(PyCodeObject *code);
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_freelist_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern "C" {
# define Py_futureiters_MAXFREELIST 255
# define Py_object_stack_chunks_MAXFREELIST 4
# define Py_unicode_writers_MAXFREELIST 1
# define Py_bytes_writers_MAXFREELIST 1
# define Py_pycfunctionobject_MAXFREELIST 16
# define Py_pycmethodobject_MAXFREELIST 16
# define Py_pymethodobjects_MAXFREELIST 20
Expand Down Expand Up @@ -61,6 +62,7 @@ struct _Py_freelists {
struct _Py_freelist futureiters;
struct _Py_freelist object_stack_chunks;
struct _Py_freelist unicode_writers;
struct _Py_freelist bytes_writers;
struct _Py_freelist pycfunctionobject;
struct _Py_freelist pycmethodobject;
struct _Py_freelist pymethodobjects;
Expand Down
16 changes: 16 additions & 0 deletions Lib/asyncio/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,20 @@ def _print_cycle_exception(exception: CycleFoundException):
print(f"cycle: {inames}", file=sys.stderr)


def exit_with_permission_help_text():
"""
Prints a message pointing to platform-specific permission help text and exits the program.
This function is called when a PermissionError is encountered while trying
to attach to a process.
"""
print(
"Error: The specified process cannot be attached to due to insufficient permissions.\n"
"See the Python documentation for details on required privileges and troubleshooting:\n"
"https://docs.python.org/3.14/howto/remote_debugging.html#permission-requirements\n"
)
sys.exit(1)


def _get_awaited_by_tasks(pid: int) -> list:
try:
return get_all_awaited_by(pid)
Expand All @@ -230,6 +244,8 @@ def _get_awaited_by_tasks(pid: int) -> list:
e = e.__context__
print(f"Error retrieving tasks: {e}")
sys.exit(1)
except PermissionError as e:
exit_with_permission_help_text()


def display_awaited_by_tasks_table(pid: int) -> None:
Expand Down
Loading
Loading