Skip to content

bpo-43774: Remove unused PYMALLOC_DEBUG macro #25711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 29, 2021
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
19 changes: 11 additions & 8 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,20 @@ PyPreConfig
Name of the Python memory allocators:

* ``PYMEM_ALLOCATOR_NOT_SET`` (``0``): don't change memory allocators
(use defaults)
* ``PYMEM_ALLOCATOR_DEFAULT`` (``1``): default memory allocators
* ``PYMEM_ALLOCATOR_DEBUG`` (``2``): default memory allocators with
debug hooks
* ``PYMEM_ALLOCATOR_MALLOC`` (``3``): force usage of ``malloc()``
(use defaults).
* ``PYMEM_ALLOCATOR_DEFAULT`` (``1``): :ref:`default memory allocators
<default-memory-allocators>`.
* ``PYMEM_ALLOCATOR_DEBUG`` (``2``): :ref:`default memory allocators
<default-memory-allocators>` with :ref:`debug hooks
<pymem-debug-hooks>`.
* ``PYMEM_ALLOCATOR_MALLOC`` (``3``): use ``malloc()`` of the C library.
* ``PYMEM_ALLOCATOR_MALLOC_DEBUG`` (``4``): force usage of
``malloc()`` with debug hooks
``malloc()`` with :ref:`debug hooks <pymem-debug-hooks>`.
* ``PYMEM_ALLOCATOR_PYMALLOC`` (``5``): :ref:`Python pymalloc memory
allocator <pymalloc>`
allocator <pymalloc>`.
* ``PYMEM_ALLOCATOR_PYMALLOC_DEBUG`` (``6``): :ref:`Python pymalloc
memory allocator <pymalloc>` with debug hooks
memory allocator <pymalloc>` with :ref:`debug hooks
<pymem-debug-hooks>`.

``PYMEM_ALLOCATOR_PYMALLOC`` and ``PYMEM_ALLOCATOR_PYMALLOC_DEBUG`` are
not supported if Python is :option:`configured using --without-pymalloc
Expand Down
149 changes: 111 additions & 38 deletions Doc/c-api/memory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ Legend:
* ``malloc``: system allocators from the standard C library, C functions:
:c:func:`malloc`, :c:func:`calloc`, :c:func:`realloc` and :c:func:`free`.
* ``pymalloc``: :ref:`pymalloc memory allocator <pymalloc>`.
* "+ debug": with debug hooks installed by :c:func:`PyMem_SetupDebugHooks`.
* "+ debug": with :ref:`debug hooks on the Python memory allocators
<pymem-debug-hooks>`.
* "Debug build": :ref:`Python build in debug mode <debug-build>`.

.. _customize-memory-allocators:
Expand Down Expand Up @@ -478,45 +479,113 @@ Customize Memory Allocators

.. c:function:: void PyMem_SetupDebugHooks(void)

Setup hooks to detect bugs in the Python memory allocator functions.
Setup :ref:`debug hooks in the Python memory allocators <pymem-debug-hooks>`
to detect memory errors.


.. _pymem-debug-hooks:

Debug hooks on the Python memory allocators
===========================================

When :ref:`Python is built is debug mode <debug-build>`, the
:c:func:`PyMem_SetupDebugHooks` function is called at the :ref:`Python
preinitialization <c-preinit>` to setup debug hooks on Python memory allocators
to detect memory errors.

The :envvar:`PYTHONMALLOC` environment variable can be used to install debug
hooks on a Python compiled in release mode (ex: ``PYTHONMALLOC=debug``).

The :c:func:`PyMem_SetupDebugHooks` function can be used to set debug hooks
after calling :c:func:`PyMem_SetAllocator`.

These debug hooks fill dynamically allocated memory blocks with special,
recognizable bit patterns. Newly allocated memory is filled with the byte
``0xCD`` (``PYMEM_CLEANBYTE``), freed memory is filled with the byte ``0xDD``
(``PYMEM_DEADBYTE``). Memory blocks are surrounded by "forbidden bytes"
filled with the byte ``0xFD`` (``PYMEM_FORBIDDENBYTE``). Strings of these bytes
are unlikely to be valid addresses, floats, or ASCII strings.

Runtime checks:

- Detect API violations. For example, detect if :c:func:`PyObject_Free` is
called on a memory block allocated by :c:func:`PyMem_Malloc`.
- Detect write before the start of the buffer (buffer underflow).
- Detect write after the end of the buffer (buffer overflow).
- Check that the :term:`GIL <global interpreter lock>` is held when
allocator functions of :c:data:`PYMEM_DOMAIN_OBJ` (ex:
:c:func:`PyObject_Malloc`) and :c:data:`PYMEM_DOMAIN_MEM` (ex:
:c:func:`PyMem_Malloc`) domains are called.

On error, the debug hooks use the :mod:`tracemalloc` module to get the
traceback where a memory block was allocated. The traceback is only displayed
if :mod:`tracemalloc` is tracing Python memory allocations and the memory block
was traced.

Let *S* = ``sizeof(size_t)``. ``2*S`` bytes are added at each end of each block
of *N* bytes requested. The memory layout is like so, where p represents the
address returned by a malloc-like or realloc-like function (``p[i:j]`` means
the slice of bytes from ``*(p+i)`` inclusive up to ``*(p+j)`` exclusive; note
that the treatment of negative indices differs from a Python slice):

``p[-2*S:-S]``
Number of bytes originally asked for. This is a size_t, big-endian (easier
to read in a memory dump).
``p[-S]``
API identifier (ASCII character):

* ``'r'`` for :c:data:`PYMEM_DOMAIN_RAW`.
* ``'m'`` for :c:data:`PYMEM_DOMAIN_MEM`.
* ``'o'`` for :c:data:`PYMEM_DOMAIN_OBJ`.

``p[-S+1:0]``
Copies of PYMEM_FORBIDDENBYTE. Used to catch under- writes and reads.

``p[0:N]``
The requested memory, filled with copies of PYMEM_CLEANBYTE, used to catch
reference to uninitialized memory. When a realloc-like function is called
requesting a larger memory block, the new excess bytes are also filled with
PYMEM_CLEANBYTE. When a free-like function is called, these are
overwritten with PYMEM_DEADBYTE, to catch reference to freed memory. When
a realloc- like function is called requesting a smaller memory block, the
excess old bytes are also filled with PYMEM_DEADBYTE.

``p[N:N+S]``
Copies of PYMEM_FORBIDDENBYTE. Used to catch over- writes and reads.

``p[N+S:N+2*S]``
Only used if the ``PYMEM_DEBUG_SERIALNO`` macro is defined (not defined by
default).

A serial number, incremented by 1 on each call to a malloc-like or
realloc-like function. Big-endian ``size_t``. If "bad memory" is detected
later, the serial number gives an excellent way to set a breakpoint on the
next run, to capture the instant at which this block was passed out. The
static function bumpserialno() in obmalloc.c is the only place the serial
number is incremented, and exists so you can set such a breakpoint easily.

A realloc-like or free-like function first checks that the PYMEM_FORBIDDENBYTE
bytes at each end are intact. If they've been altered, diagnostic output is
written to stderr, and the program is aborted via Py_FatalError(). The other
main failure mode is provoking a memory error when a program reads up one of
the special bit patterns and tries to use it as an address. If you get in a
debugger then and look at the object, you're likely to see that it's entirely
filled with PYMEM_DEADBYTE (meaning freed memory is getting used) or
PYMEM_CLEANBYTE (meaning uninitialized memory is getting used).

Newly allocated memory is filled with the byte ``0xCD`` (``CLEANBYTE``),
freed memory is filled with the byte ``0xDD`` (``DEADBYTE``). Memory blocks
are surrounded by "forbidden bytes" (``FORBIDDENBYTE``: byte ``0xFD``).

Runtime checks:

- Detect API violations, ex: :c:func:`PyObject_Free` called on a buffer
allocated by :c:func:`PyMem_Malloc`
- Detect write before the start of the buffer (buffer underflow)
- Detect write after the end of the buffer (buffer overflow)
- Check that the :term:`GIL <global interpreter lock>` is held when
allocator functions of :c:data:`PYMEM_DOMAIN_OBJ` (ex:
:c:func:`PyObject_Malloc`) and :c:data:`PYMEM_DOMAIN_MEM` (ex:
:c:func:`PyMem_Malloc`) domains are called

On error, the debug hooks use the :mod:`tracemalloc` module to get the
traceback where a memory block was allocated. The traceback is only
displayed if :mod:`tracemalloc` is tracing Python memory allocations and the
memory block was traced.

These hooks are :ref:`installed by default <default-memory-allocators>` if
:ref:`Python is built in debug mode <debug-build>`.
The :envvar:`PYTHONMALLOC` environment variable can be used to install
debug hooks on a Python compiled in release mode.

.. versionchanged:: 3.6
This function now also works on Python compiled in release mode.
On error, the debug hooks now use :mod:`tracemalloc` to get the traceback
where a memory block was allocated. The debug hooks now also check
if the GIL is held when functions of :c:data:`PYMEM_DOMAIN_OBJ` and
:c:data:`PYMEM_DOMAIN_MEM` domains are called.
.. versionchanged:: 3.6
The :c:func:`PyMem_SetupDebugHooks` function now also works on Python
compiled in release mode. On error, the debug hooks now use
:mod:`tracemalloc` to get the traceback where a memory block was allocated.
The debug hooks now also check if the GIL is held when functions of
:c:data:`PYMEM_DOMAIN_OBJ` and :c:data:`PYMEM_DOMAIN_MEM` domains are
called.

.. versionchanged:: 3.8
Byte patterns ``0xCB`` (``CLEANBYTE``), ``0xDB`` (``DEADBYTE``) and
``0xFB`` (``FORBIDDENBYTE``) have been replaced with ``0xCD``, ``0xDD``
and ``0xFD`` to use the same values than Windows CRT debug ``malloc()``
and ``free()``.
.. versionchanged:: 3.8
Byte patterns ``0xCB`` (``PYMEM_CLEANBYTE``), ``0xDB`` (``PYMEM_DEADBYTE``)
and ``0xFB`` (``PYMEM_FORBIDDENBYTE``) have been replaced with ``0xCD``,
``0xDD`` and ``0xFD`` to use the same values than Windows CRT debug
``malloc()`` and ``free()``.


.. _pymalloc:
Expand All @@ -539,6 +608,10 @@ The arena allocator uses the following functions:
* :c:func:`mmap` and :c:func:`munmap` if available,
* :c:func:`malloc` and :c:func:`free` otherwise.

This allocator is disabled if Python is configured with the
:option:`--without-pymalloc` option. It can also be disabled at runtime using
the :envvar:`PYTHONMALLOC` environment variable (ex: ``PYTHONMALLOC=malloc``).

Customize pymalloc Arena Allocator
----------------------------------

Expand Down
6 changes: 1 addition & 5 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -799,17 +799,13 @@ conflict.
:c:data:`PYMEM_DOMAIN_MEM` and :c:data:`PYMEM_DOMAIN_OBJ` domains and use
the :c:func:`malloc` function for the :c:data:`PYMEM_DOMAIN_RAW` domain.

Install debug hooks:
Install :ref:`debug hooks <pymem-debug-hooks>`:

* ``debug``: install debug hooks on top of the :ref:`default memory
allocators <default-memory-allocators>`.
* ``malloc_debug``: same as ``malloc`` but also install debug hooks.
* ``pymalloc_debug``: same as ``pymalloc`` but also install debug hooks.

See the :ref:`default memory allocators <default-memory-allocators>` and the
:c:func:`PyMem_SetupDebugHooks` function (install debug hooks on Python
memory allocators).

.. versionchanged:: 3.7
Added the ``"default"`` allocator.

Expand Down
9 changes: 0 additions & 9 deletions Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,6 @@
# endif
#endif

/* Debug-mode build with pymalloc implies PYMALLOC_DEBUG.
* PYMALLOC_DEBUG is in error if pymalloc is not in use.
*/
#if defined(Py_DEBUG) && defined(WITH_PYMALLOC) && !defined(PYMALLOC_DEBUG)
#define PYMALLOC_DEBUG
#endif
#if defined(PYMALLOC_DEBUG) && !defined(WITH_PYMALLOC)
#error "PYMALLOC_DEBUG requires WITH_PYMALLOC"
#endif
#include "pymath.h"
#include "pymem.h"

Expand Down
4 changes: 2 additions & 2 deletions Include/objimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ Functions and macros for modules that implement new object types.

Note that objects created with PyObject_{New, NewVar} are allocated using the
specialized Python allocator (implemented in obmalloc.c), if WITH_PYMALLOC is
enabled. In addition, a special debugging allocator is used if PYMALLOC_DEBUG
is also #defined.
enabled. In addition, a special debugging allocator is used if Py_DEBUG
macro is also defined.

In case a specific form of memory management is needed (for example, if you
must use the platform malloc heap(s), or shared memory, or C++ local storage or
Expand Down
4 changes: 2 additions & 2 deletions Include/pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ extern "C" {
heap used by the Python DLL; it could be a disaster if you free()'ed that
directly in your own extension. Using PyMem_Free instead ensures Python
can return the memory to the proper heap. As another example, in
PYMALLOC_DEBUG mode, Python wraps all calls to all PyMem_ and PyObject_
memory functions in special debugging wrappers that add additional
a debug build (Py_DEBUG macro), Python wraps all calls to all PyMem_ and
PyObject_ memory functions in special debugging wrappers that add additional
debugging info to dynamic memory blocks. The system routines have no idea
what to do with that stuff, and the Python wrappers have no idea what to do
with raw blocks obtained directly by the system routines then.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Remove the now unused ``PYMALLOC_DEBUG`` macro. Debug hooks on memory
allocators are now installed by default if Python is built in debug mode (if
``Py_DEBUG`` macro is defined). Moreover, they can now be used on Python
build in release mode (ex: using ``PYTHONMALLOC=debug`` environment
variable).
82 changes: 3 additions & 79 deletions Misc/SpecialBuilds.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,90 +77,14 @@ envvar PYTHONDUMPREFS
combinerefs.py, were new in Python 2.3b1.


PYMALLOC_DEBUG
--------------

When pymalloc is enabled (WITH_PYMALLOC is defined), calls to the PyObject_
memory routines are handled by Python's own small-object allocator, while calls
to the PyMem_ memory routines are directed to the system malloc/ realloc/free.
If PYMALLOC_DEBUG is also defined, calls to both PyObject_ and PyMem_ memory
routines are directed to a special debugging mode of Python's small-object
allocator.

This mode fills dynamically allocated memory blocks with special, recognizable
bit patterns, and adds debugging info on each end of dynamically allocated
memory blocks. The special bit patterns are:

#define CLEANBYTE 0xCB /* clean (newly allocated) memory */
#define DEADBYTE 0xDB /* dead (newly freed) memory */
#define FORBIDDENBYTE 0xFB /* forbidden -- untouchable bytes */

Strings of these bytes are unlikely to be valid addresses, floats, or 7-bit
ASCII strings.

Let S = sizeof(size_t). 2*S bytes are added at each end of each block of N bytes
requested. The memory layout is like so, where p represents the address
returned by a malloc-like or realloc-like function (p[i:j] means the slice of
bytes from *(p+i) inclusive up to *(p+j) exclusive; note that the treatment of
negative indices differs from a Python slice):

p[-2*S:-S]
Number of bytes originally asked for. This is a size_t, big-endian (easier
to read in a memory dump).
p[-S]
API ID. See PEP 445. This is a character, but seems undocumented.
p[-S+1:0]
Copies of FORBIDDENBYTE. Used to catch under- writes and reads.
p[0:N]
The requested memory, filled with copies of CLEANBYTE, used to catch
reference to uninitialized memory. When a realloc-like function is called
requesting a larger memory block, the new excess bytes are also filled with
CLEANBYTE. When a free-like function is called, these are overwritten with
DEADBYTE, to catch reference to freed memory. When a realloc- like function
is called requesting a smaller memory block, the excess old bytes are also
filled with DEADBYTE.
p[N:N+S]
Copies of FORBIDDENBYTE. Used to catch over- writes and reads.
p[N+S:N+2*S]
A serial number, incremented by 1 on each call to a malloc-like or
realloc-like function. Big-endian size_t. If "bad memory" is detected
later, the serial number gives an excellent way to set a breakpoint on the
next run, to capture the instant at which this block was passed out. The
static function bumpserialno() in obmalloc.c is the only place the serial
number is incremented, and exists so you can set such a breakpoint easily.

A realloc-like or free-like function first checks that the FORBIDDENBYTEs at
each end are intact. If they've been altered, diagnostic output is written to
stderr, and the program is aborted via Py_FatalError(). The other main failure
mode is provoking a memory error when a program reads up one of the special bit
patterns and tries to use it as an address. If you get in a debugger then and
look at the object, you're likely to see that it's entirely filled with 0xDB
(meaning freed memory is getting used) or 0xCB (meaning uninitialized memory is
getting used).

Note that PYMALLOC_DEBUG requires WITH_PYMALLOC. Py_DEBUG implies
PYMALLOC_DEBUG (if WITH_PYMALLOC is enabled).

Special gimmicks:

envvar PYTHONMALLOCSTATS
If this envvar exists, a report of pymalloc summary statistics is printed to
stderr whenever a new arena is allocated, and also by Py_FinalizeEx().

Changed in 2.5: The number of extra bytes allocated is 4*sizeof(size_t).
Before it was 16 on all boxes, reflecting that Python couldn't make use of
allocations >= 2**32 bytes even on 64-bit boxes before 2.5.


Py_DEBUG
--------

This is what is generally meant by "a debug build" of Python.

Py_DEBUG implies LLTRACE, Py_REF_DEBUG, and PYMALLOC_DEBUG (if
WITH_PYMALLOC is enabled). In addition, C assert()s are enabled (via the C way:
by not defining NDEBUG), and some routines do additional sanity checks inside
"#ifdef Py_DEBUG" blocks.
Py_DEBUG implies LLTRACE and Py_REF_DEBUG. In addition, C assert()s are enabled
(via the C way: by not defining NDEBUG), and some routines do additional sanity
checks inside "#ifdef Py_DEBUG" blocks.


LLTRACE
Expand Down