Add PYTHONMALLOC env var

Issue #26516:

* Add PYTHONMALLOC environment variable to set the Python memory
  allocators and/or install debug hooks.
* PyMem_SetupDebugHooks() can now also be used on Python compiled in release
  mode.
* The PYTHONMALLOCSTATS environment variable can now also be used on Python
  compiled in release mode. It now has no effect if set to an empty string.
* In debug mode, debug hooks are now also installed on Python memory allocators
  when Python is configured without pymalloc.
This commit is contained in:
Victor Stinner 2016-03-14 12:04:26 +01:00
parent c877658d1f
commit 34be807ca4
13 changed files with 383 additions and 90 deletions

View File

@ -85,9 +85,12 @@ for the I/O buffer escapes completely the Python memory manager.
.. seealso:: .. seealso::
The :envvar:`PYTHONMALLOC` environment variable can be used to configure
the memory allocators used by Python.
The :envvar:`PYTHONMALLOCSTATS` environment variable can be used to print The :envvar:`PYTHONMALLOCSTATS` environment variable can be used to print
memory allocation statistics every time a new object arena is created, and statistics of the :ref:`pymalloc memory allocator <pymalloc>` every time a
on shutdown. new pymalloc object arena is created, and on shutdown.
Raw Memory Interface Raw Memory Interface
@ -343,25 +346,36 @@ Customize Memory Allocators
- detect write before the start of the buffer (buffer underflow) - detect write before the start of the buffer (buffer underflow)
- detect write after the end of the buffer (buffer overflow) - detect write after the end of the buffer (buffer overflow)
The function does nothing if Python is not compiled is debug mode. These hooks are installed by default if Python is compiled in debug
mode. 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.
Customize PyObject Arena Allocator .. _pymalloc:
==================================
Python has a *pymalloc* allocator for allocations smaller than 512 bytes. This The pymalloc allocator
allocator is optimized for small objects with a short lifetime. It uses memory ======================
mappings called "arenas" with a fixed size of 256 KB. It falls back to
:c:func:`PyMem_RawMalloc` and :c:func:`PyMem_RawRealloc` for allocations larger
than 512 bytes. *pymalloc* is the default allocator used by
:c:func:`PyObject_Malloc`.
The default arena allocator uses the following functions: Python has a *pymalloc* allocator optimized for small objects (smaller or equal
to 512 bytes) with a short lifetime. It uses memory mappings called "arenas"
with a fixed size of 256 KB. It falls back to :c:func:`PyMem_RawMalloc` and
:c:func:`PyMem_RawRealloc` for allocations larger than 512 bytes.
*pymalloc* is the default allocator of the :c:data:`PYMEM_DOMAIN_OBJ` domain
(:c:func:`PyObject_Malloc` & cie).
The arena allocator uses the following functions:
* :c:func:`VirtualAlloc` and :c:func:`VirtualFree` on Windows, * :c:func:`VirtualAlloc` and :c:func:`VirtualFree` on Windows,
* :c:func:`mmap` and :c:func:`munmap` if available, * :c:func:`mmap` and :c:func:`munmap` if available,
* :c:func:`malloc` and :c:func:`free` otherwise. * :c:func:`malloc` and :c:func:`free` otherwise.
Customize pymalloc Arena Allocator
----------------------------------
.. versionadded:: 3.4 .. versionadded:: 3.4
.. c:type:: PyObjectArenaAllocator .. c:type:: PyObjectArenaAllocator

View File

@ -621,6 +621,51 @@ conflict.
.. versionadded:: 3.4 .. versionadded:: 3.4
.. envvar:: PYTHONMALLOC
Set the Python memory allocators and/or install debug hooks.
Set the family of memory allocators used by Python:
* ``malloc``: use the :c:func:`malloc` function of the C library
for all Python memory allocators (:c:func:`PyMem_RawMalloc`,
:c:func:`PyMem_Malloc`, :c:func:`PyObject_Malloc` & cie).
* ``pymalloc``: :c:func:`PyObject_Malloc`, :c:func:`PyObject_Calloc` and
:c:func:`PyObject_Realloc` use the :ref:`pymalloc allocator <pymalloc>`.
Other Python memory allocators (:c:func:`PyMem_RawMalloc`,
:c:func:`PyMem_Malloc` & cie) use :c:func:`malloc`.
Install debug hooks:
* ``debug``: install debug hooks on top of the default memory allocator
* ``malloc_debug``: same than ``malloc`` but also install debug hooks
* ``pymalloc_debug``: same than ``malloc`` but also install debug hooks
See the :c:func:`PyMem_SetupDebugHooks` function for debug hooks on Python
memory allocators.
.. note::
``pymalloc`` and ``pymalloc_debug`` are not available if Python is
configured without ``pymalloc`` support.
.. versionadded:: 3.6
.. envvar:: PYTHONMALLOCSTATS
If set to a non-empty string, Python will print statistics of the
:ref:`pymalloc memory allocator <pymalloc>` every time a new pymalloc object
arena is created, and on shutdown.
This variable is ignored if the :envvar:`PYTHONMALLOC` environment variable
is used to force the :c:func:`malloc` allocator of the C library, or if
Python is configured without ``pymalloc`` support.
.. versionchanged:: 3.6
This variable can now also be used on Python compiled in release mode.
It now has no effect if set to an empty string.
Debug-mode variables Debug-mode variables
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
@ -636,9 +681,3 @@ if Python was configured with the ``--with-pydebug`` build option.
If set, Python will dump objects and reference counts still alive after If set, Python will dump objects and reference counts still alive after
shutting down the interpreter. shutting down the interpreter.
.. envvar:: PYTHONMALLOCSTATS
If set, Python will print memory allocation statistics every time a new
object arena is created, and on shutdown.

View File

@ -80,6 +80,9 @@ Summary -- Release highlights
PEP written by Carl Meyer PEP written by Carl Meyer
New Features
============
.. _whatsnew-fstrings: .. _whatsnew-fstrings:
PEP 498: Formatted string literals PEP 498: Formatted string literals
@ -98,6 +101,34 @@ evaluated at run time, and then formatted using the :func:`format` protocol.
See :pep:`498` and the main documentation at :ref:`f-strings`. See :pep:`498` and the main documentation at :ref:`f-strings`.
PYTHONMALLOC environment variable
---------------------------------
The new :envvar:`PYTHONMALLOC` environment variable allows to set the Python
memory allocators and/or install debug hooks.
It is now possible to install debug hooks on Python memory allocators on Python
compiled in release mode using ``PYTHONMALLOC=debug``. Effects of debug hooks:
* Newly allocated memory is filled with the byte ``0xCB``
* Freed memory is filled with the byte ``0xDB``
* Detect violations of Python memory allocator API. For example,
:c:func:`PyObject_Free` 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)
See the :c:func:`PyMem_SetupDebugHooks` function for debug hooks on Python
memory allocators.
It is now also possible to force the usage of the :c:func:`malloc` allocator of
the C library for all Python memory allocations using ``PYTHONMALLOC=malloc``.
It helps to use external memory debuggers like Valgrind on a Python compiled in
release mode.
(Contributed by Victor Stinner in :issue:`26516`.)
Other Language Changes Other Language Changes
====================== ======================

View File

@ -16,8 +16,17 @@ PyAPI_FUNC(void *) PyMem_RawMalloc(size_t size);
PyAPI_FUNC(void *) PyMem_RawCalloc(size_t nelem, size_t elsize); PyAPI_FUNC(void *) PyMem_RawCalloc(size_t nelem, size_t elsize);
PyAPI_FUNC(void *) PyMem_RawRealloc(void *ptr, size_t new_size); PyAPI_FUNC(void *) PyMem_RawRealloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyMem_RawFree(void *ptr); PyAPI_FUNC(void) PyMem_RawFree(void *ptr);
/* Configure the Python memory allocators. Pass NULL to use default
allocators. */
PyAPI_FUNC(int) _PyMem_SetupAllocators(const char *opt);
#ifdef WITH_PYMALLOC
PyAPI_FUNC(int) _PyMem_PymallocEnabled(void);
#endif #endif
#endif /* !Py_LIMITED_API */
/* BEWARE: /* BEWARE:

View File

@ -6,6 +6,7 @@ import pickle
import random import random
import subprocess import subprocess
import sys import sys
import sysconfig
import textwrap import textwrap
import time import time
import unittest import unittest
@ -521,6 +522,7 @@ class SkipitemTest(unittest.TestCase):
self.assertRaises(ValueError, _testcapi.parse_tuple_and_keywords, self.assertRaises(ValueError, _testcapi.parse_tuple_and_keywords,
(), {}, b'', [42]) (), {}, b'', [42])
@unittest.skipUnless(threading, 'Threading required for this test.') @unittest.skipUnless(threading, 'Threading required for this test.')
class TestThreadState(unittest.TestCase): class TestThreadState(unittest.TestCase):
@ -545,6 +547,7 @@ class TestThreadState(unittest.TestCase):
t.start() t.start()
t.join() t.join()
class Test_testcapi(unittest.TestCase): class Test_testcapi(unittest.TestCase):
def test__testcapi(self): def test__testcapi(self):
for name in dir(_testcapi): for name in dir(_testcapi):
@ -553,5 +556,61 @@ class Test_testcapi(unittest.TestCase):
test = getattr(_testcapi, name) test = getattr(_testcapi, name)
test() test()
class MallocTests(unittest.TestCase):
ENV = 'debug'
def check(self, code):
with support.SuppressCrashReport():
out = assert_python_failure('-c', code, PYTHONMALLOC=self.ENV)
stderr = out.err
return stderr.decode('ascii', 'replace')
def test_buffer_overflow(self):
out = self.check('import _testcapi; _testcapi.pymem_buffer_overflow()')
regex = (r"Debug memory block at address p=0x[0-9a-f]+: API 'm'\n"
r" 16 bytes originally requested\n"
r" The 7 pad bytes at p-7 are FORBIDDENBYTE, as expected.\n"
r" The 8 pad bytes at tail=0x[0-9a-f]+ are not all FORBIDDENBYTE \(0x[0-9a-f]{2}\):\n"
r" at tail\+0: 0x78 \*\*\* OUCH\n"
r" at tail\+1: 0xfb\n"
r" at tail\+2: 0xfb\n"
r" at tail\+3: 0xfb\n"
r" at tail\+4: 0xfb\n"
r" at tail\+5: 0xfb\n"
r" at tail\+6: 0xfb\n"
r" at tail\+7: 0xfb\n"
r" The block was made by call #[0-9]+ to debug malloc/realloc.\n"
r" Data at p: cb cb cb cb cb cb cb cb cb cb cb cb cb cb cb cb\n"
r"Fatal Python error: bad trailing pad byte")
self.assertRegex(out, regex)
def test_api_misuse(self):
out = self.check('import _testcapi; _testcapi.pymem_api_misuse()')
regex = (r"Debug memory block at address p=0x[0-9a-f]+: API 'm'\n"
r" 16 bytes originally requested\n"
r" The 7 pad bytes at p-7 are FORBIDDENBYTE, as expected.\n"
r" The 8 pad bytes at tail=0x[0-9a-f]+ are FORBIDDENBYTE, as expected.\n"
r" The block was made by call #[0-9]+ to debug malloc/realloc.\n"
r" Data at p: .*\n"
r"Fatal Python error: bad ID: Allocated using API 'm', verified using API 'r'\n")
self.assertRegex(out, regex)
class MallocDebugTests(MallocTests):
ENV = 'malloc_debug'
@unittest.skipUnless(sysconfig.get_config_var('WITH_PYMALLOC') == 1,
'need pymalloc')
class PymallocDebugTests(MallocTests):
ENV = 'pymalloc_debug'
@unittest.skipUnless(Py_DEBUG, 'need Py_DEBUG')
class DefaultMallocDebugTests(MallocTests):
ENV = ''
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -10,6 +10,19 @@ Release date: tba
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #26516: Add :envvar`PYTHONMALLOC` environment variable to set the
Python memory allocators and/or install debug hooks.
- Issue #26516: The :c:func`PyMem_SetupDebugHooks` function can now also be
used on Python compiled in release mode.
- Issue #26516: The :envvar:`PYTHONMALLOCSTATS` environment variable can now
also be used on Python compiled in release mode. It now has no effect if
set to an empty string.
- Issue #26516: In debug mode, debug hooks are now also installed on Python
memory allocators when Python is configured without pymalloc.
- Issue #26464: Fix str.translate() when string is ASCII and first replacements - Issue #26464: Fix str.translate() when string is ASCII and first replacements
removes character, but next replacement uses a non-ASCII character or a removes character, but next replacement uses a non-ASCII character or a
string longer than 1 character. Regression introduced in Python 3.5.0. string longer than 1 character. Regression introduced in Python 3.5.0.

View File

@ -2,6 +2,9 @@ This document describes some caveats about the use of Valgrind with
Python. Valgrind is used periodically by Python developers to try Python. Valgrind is used periodically by Python developers to try
to ensure there are no memory leaks or invalid memory reads/writes. to ensure there are no memory leaks or invalid memory reads/writes.
UPDATE: Python 3.6 now supports PYTHONMALLOC=malloc environment variable which
can be used to force the usage of the malloc() allocator of the C library.
If you don't want to read about the details of using Valgrind, there If you don't want to read about the details of using Valgrind, there
are still two things you must do to suppress the warnings. First, are still two things you must do to suppress the warnings. First,
you must use a suppressions file. One is supplied in you must use a suppressions file. One is supplied in

View File

@ -3616,6 +3616,33 @@ get_recursion_depth(PyObject *self, PyObject *args)
return PyLong_FromLong(tstate->recursion_depth - 1); return PyLong_FromLong(tstate->recursion_depth - 1);
} }
static PyObject*
pymem_buffer_overflow(PyObject *self, PyObject *args)
{
char *buffer;
/* Deliberate buffer overflow to check that PyMem_Free() detects
the overflow when debug hooks are installed. */
buffer = PyMem_Malloc(16);
buffer[16] = 'x';
PyMem_Free(buffer);
Py_RETURN_NONE;
}
static PyObject*
pymem_api_misuse(PyObject *self, PyObject *args)
{
char *buffer;
/* Deliberate misusage of Python allocators:
allococate with PyMem but release with PyMem_Raw. */
buffer = PyMem_Malloc(16);
PyMem_RawFree(buffer);
Py_RETURN_NONE;
}
static PyMethodDef TestMethods[] = { static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS}, {"raise_exception", raise_exception, METH_VARARGS},
@ -3798,6 +3825,8 @@ static PyMethodDef TestMethods[] = {
{"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS}, {"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS},
{"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS}, {"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS},
{"pymem_buffer_overflow", pymem_buffer_overflow, METH_NOARGS},
{"pymem_api_misuse", pymem_api_misuse, METH_NOARGS},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };

View File

@ -93,14 +93,15 @@ static const char usage_5[] =
" The default module search path uses %s.\n" " The default module search path uses %s.\n"
"PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n"
"PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n"
"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n\ "PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n";
"; static const char usage_6[] =
static const char usage_6[] = "\ "PYTHONHASHSEED: if this variable is set to 'random', a random value is used\n"
PYTHONHASHSEED: if this variable is set to 'random', a random value is used\n\ " to seed the hashes of str, bytes and datetime objects. It can also be\n"
to seed the hashes of str, bytes and datetime objects. It can also be\n\ " set to an integer in the range [0,4294967295] to get hash values with a\n"
set to an integer in the range [0,4294967295] to get hash values with a\n\ " predictable seed.\n"
predictable seed.\n\ "PYTHONMALLOC: set the Python memory allocators and/or install debug hooks\n"
"; " on Python memory allocators. Use PYTHONMALLOC=debug to install debug\n"
" hooks.\n";
static int static int
usage(int exitcode, const wchar_t* program) usage(int exitcode, const wchar_t* program)
@ -341,6 +342,7 @@ Py_Main(int argc, wchar_t **argv)
int help = 0; int help = 0;
int version = 0; int version = 0;
int saw_unbuffered_flag = 0; int saw_unbuffered_flag = 0;
char *opt;
PyCompilerFlags cf; PyCompilerFlags cf;
PyObject *warning_option = NULL; PyObject *warning_option = NULL;
PyObject *warning_options = NULL; PyObject *warning_options = NULL;
@ -365,6 +367,13 @@ Py_Main(int argc, wchar_t **argv)
} }
} }
opt = Py_GETENV("PYTHONMALLOC");
if (_PyMem_SetupAllocators(opt) < 0) {
fprintf(stderr,
"Error in PYTHONMALLOC: unknown allocator \"%s\"!\n", opt);
exit(1);
}
Py_HashRandomizationFlag = 1; Py_HashRandomizationFlag = 1;
_PyRandom_Init(); _PyRandom_Init();

View File

@ -2,7 +2,19 @@
/* Python's malloc wrappers (see pymem.h) */ /* Python's malloc wrappers (see pymem.h) */
#ifdef PYMALLOC_DEBUG /* WITH_PYMALLOC && PYMALLOC_DEBUG */ /*
* Basic types
* I don't care if these are defined in <sys/types.h> or elsewhere. Axiom.
*/
#undef uchar
#define uchar unsigned char /* assuming == 8 bits */
#undef uint
#define uint unsigned int /* assuming >= 16 bits */
#undef uptr
#define uptr Py_uintptr_t
/* Forward declaration */ /* Forward declaration */
static void* _PyMem_DebugMalloc(void *ctx, size_t size); static void* _PyMem_DebugMalloc(void *ctx, size_t size);
static void* _PyMem_DebugCalloc(void *ctx, size_t nelem, size_t elsize); static void* _PyMem_DebugCalloc(void *ctx, size_t nelem, size_t elsize);
@ -11,7 +23,6 @@ static void* _PyMem_DebugRealloc(void *ctx, void *ptr, size_t size);
static void _PyObject_DebugDumpAddress(const void *p); static void _PyObject_DebugDumpAddress(const void *p);
static void _PyMem_DebugCheckAddress(char api_id, const void *p); static void _PyMem_DebugCheckAddress(char api_id, const void *p);
#endif
#if defined(__has_feature) /* Clang */ #if defined(__has_feature) /* Clang */
#if __has_feature(address_sanitizer) /* is ASAN enabled? */ #if __has_feature(address_sanitizer) /* is ASAN enabled? */
@ -147,7 +158,6 @@ _PyObject_ArenaFree(void *ctx, void *ptr, size_t size)
#endif #endif
#define PYMEM_FUNCS PYRAW_FUNCS #define PYMEM_FUNCS PYRAW_FUNCS
#ifdef PYMALLOC_DEBUG
typedef struct { typedef struct {
/* We tag each block with an API ID in order to tag API violations */ /* We tag each block with an API ID in order to tag API violations */
char api_id; char api_id;
@ -164,10 +174,9 @@ static struct {
}; };
#define PYDBG_FUNCS _PyMem_DebugMalloc, _PyMem_DebugCalloc, _PyMem_DebugRealloc, _PyMem_DebugFree #define PYDBG_FUNCS _PyMem_DebugMalloc, _PyMem_DebugCalloc, _PyMem_DebugRealloc, _PyMem_DebugFree
#endif
static PyMemAllocatorEx _PyMem_Raw = { static PyMemAllocatorEx _PyMem_Raw = {
#ifdef PYMALLOC_DEBUG #ifdef Py_DEBUG
&_PyMem_Debug.raw, PYDBG_FUNCS &_PyMem_Debug.raw, PYDBG_FUNCS
#else #else
NULL, PYRAW_FUNCS NULL, PYRAW_FUNCS
@ -175,7 +184,7 @@ static PyMemAllocatorEx _PyMem_Raw = {
}; };
static PyMemAllocatorEx _PyMem = { static PyMemAllocatorEx _PyMem = {
#ifdef PYMALLOC_DEBUG #ifdef Py_DEBUG
&_PyMem_Debug.mem, PYDBG_FUNCS &_PyMem_Debug.mem, PYDBG_FUNCS
#else #else
NULL, PYMEM_FUNCS NULL, PYMEM_FUNCS
@ -183,13 +192,71 @@ static PyMemAllocatorEx _PyMem = {
}; };
static PyMemAllocatorEx _PyObject = { static PyMemAllocatorEx _PyObject = {
#ifdef PYMALLOC_DEBUG #ifdef Py_DEBUG
&_PyMem_Debug.obj, PYDBG_FUNCS &_PyMem_Debug.obj, PYDBG_FUNCS
#else #else
NULL, PYOBJ_FUNCS NULL, PYOBJ_FUNCS
#endif #endif
}; };
int
_PyMem_SetupAllocators(const char *opt)
{
if (opt == NULL || *opt == '\0') {
/* PYTHONMALLOC is empty or is not set or ignored (-E/-I command line
options): use default allocators */
#ifdef Py_DEBUG
# ifdef WITH_PYMALLOC
opt = "pymalloc_debug";
# else
opt = "malloc_debug";
# endif
#else
/* !Py_DEBUG */
# ifdef WITH_PYMALLOC
opt = "pymalloc";
# else
opt = "malloc";
# endif
#endif
}
if (strcmp(opt, "debug") == 0) {
PyMem_SetupDebugHooks();
}
else if (strcmp(opt, "malloc") == 0 || strcmp(opt, "malloc_debug") == 0)
{
PyMemAllocatorEx alloc = {NULL, PYRAW_FUNCS};
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
if (strcmp(opt, "malloc_debug") == 0)
PyMem_SetupDebugHooks();
}
#ifdef WITH_PYMALLOC
else if (strcmp(opt, "pymalloc") == 0
|| strcmp(opt, "pymalloc_debug") == 0)
{
PyMemAllocatorEx mem_alloc = {NULL, PYRAW_FUNCS};
PyMemAllocatorEx obj_alloc = {NULL, PYOBJ_FUNCS};
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &mem_alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &mem_alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &obj_alloc);
if (strcmp(opt, "pymalloc_debug") == 0)
PyMem_SetupDebugHooks();
}
#endif
else {
/* unknown allocator */
return -1;
}
return 0;
}
#undef PYRAW_FUNCS #undef PYRAW_FUNCS
#undef PYMEM_FUNCS #undef PYMEM_FUNCS
#undef PYOBJ_FUNCS #undef PYOBJ_FUNCS
@ -205,12 +272,34 @@ static PyObjectArenaAllocator _PyObject_Arena = {NULL,
#endif #endif
}; };
static int
_PyMem_DebugEnabled(void)
{
return (_PyObject.malloc == _PyMem_DebugMalloc);
}
#ifdef WITH_PYMALLOC
int
_PyMem_PymallocEnabled(void)
{
if (_PyMem_DebugEnabled()) {
return (_PyMem_Debug.obj.alloc.malloc == _PyObject_Malloc);
}
else {
return (_PyObject.malloc == _PyObject_Malloc);
}
}
#endif
void void
PyMem_SetupDebugHooks(void) PyMem_SetupDebugHooks(void)
{ {
#ifdef PYMALLOC_DEBUG
PyMemAllocatorEx alloc; PyMemAllocatorEx alloc;
/* hooks already installed */
if (_PyMem_DebugEnabled())
return;
alloc.malloc = _PyMem_DebugMalloc; alloc.malloc = _PyMem_DebugMalloc;
alloc.calloc = _PyMem_DebugCalloc; alloc.calloc = _PyMem_DebugCalloc;
alloc.realloc = _PyMem_DebugRealloc; alloc.realloc = _PyMem_DebugRealloc;
@ -233,7 +322,6 @@ PyMem_SetupDebugHooks(void)
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &_PyMem_Debug.obj.alloc); PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &_PyMem_Debug.obj.alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc); PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
} }
#endif
} }
void void
@ -264,7 +352,6 @@ PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)
case PYMEM_DOMAIN_OBJ: _PyObject = *allocator; break; case PYMEM_DOMAIN_OBJ: _PyObject = *allocator; break;
/* ignore unknown domain */ /* ignore unknown domain */
} }
} }
void void
@ -642,22 +729,6 @@ static int running_on_valgrind = -1;
#define SIMPLELOCK_LOCK(lock) /* acquire released lock */ #define SIMPLELOCK_LOCK(lock) /* acquire released lock */
#define SIMPLELOCK_UNLOCK(lock) /* release acquired lock */ #define SIMPLELOCK_UNLOCK(lock) /* release acquired lock */
/*
* Basic types
* I don't care if these are defined in <sys/types.h> or elsewhere. Axiom.
*/
#undef uchar
#define uchar unsigned char /* assuming == 8 bits */
#undef uint
#define uint unsigned int /* assuming >= 16 bits */
#undef ulong
#define ulong unsigned long /* assuming >= 32 bits */
#undef uptr
#define uptr Py_uintptr_t
/* When you say memory, my mind reasons in terms of (pointers to) blocks */ /* When you say memory, my mind reasons in terms of (pointers to) blocks */
typedef uchar block; typedef uchar block;
@ -949,11 +1020,15 @@ new_arena(void)
struct arena_object* arenaobj; struct arena_object* arenaobj;
uint excess; /* number of bytes above pool alignment */ uint excess; /* number of bytes above pool alignment */
void *address; void *address;
static int debug_stats = -1;
#ifdef PYMALLOC_DEBUG if (debug_stats == -1) {
if (Py_GETENV("PYTHONMALLOCSTATS")) char *opt = Py_GETENV("PYTHONMALLOCSTATS");
debug_stats = (opt != NULL && *opt != '\0');
}
if (debug_stats)
_PyObject_DebugMallocStats(stderr); _PyObject_DebugMallocStats(stderr);
#endif
if (unused_arena_objects == NULL) { if (unused_arena_objects == NULL) {
uint i; uint i;
uint numarenas; uint numarenas;
@ -1709,7 +1784,7 @@ _Py_GetAllocatedBlocks(void)
#endif /* WITH_PYMALLOC */ #endif /* WITH_PYMALLOC */
#ifdef PYMALLOC_DEBUG
/*==========================================================================*/ /*==========================================================================*/
/* A x-platform debugging allocator. This doesn't manage memory directly, /* A x-platform debugging allocator. This doesn't manage memory directly,
* it wraps a real allocator, adding extra debugging info to the memory blocks. * it wraps a real allocator, adding extra debugging info to the memory blocks.
@ -1767,31 +1842,6 @@ write_size_t(void *p, size_t n)
} }
} }
#ifdef Py_DEBUG
/* Is target in the list? The list is traversed via the nextpool pointers.
* The list may be NULL-terminated, or circular. Return 1 if target is in
* list, else 0.
*/
static int
pool_is_in_list(const poolp target, poolp list)
{
poolp origlist = list;
assert(target != NULL);
if (list == NULL)
return 0;
do {
if (target == list)
return 1;
list = list->nextpool;
} while (list != NULL && list != origlist);
return 0;
}
#else
#define pool_is_in_list(X, Y) 1
#endif /* Py_DEBUG */
/* Let S = sizeof(size_t). The debug malloc asks for 4*S extra bytes and /* Let S = sizeof(size_t). The debug malloc asks for 4*S extra bytes and
fills them with useful stuff, here calling the underlying malloc's result p: fills them with useful stuff, here calling the underlying malloc's result p:
@ -2106,7 +2156,6 @@ _PyObject_DebugDumpAddress(const void *p)
} }
} }
#endif /* PYMALLOC_DEBUG */
static size_t static size_t
printone(FILE *out, const char* msg, size_t value) printone(FILE *out, const char* msg, size_t value)
@ -2158,8 +2207,30 @@ _PyDebugAllocatorStats(FILE *out,
(void)printone(out, buf2, num_blocks * sizeof_block); (void)printone(out, buf2, num_blocks * sizeof_block);
} }
#ifdef WITH_PYMALLOC #ifdef WITH_PYMALLOC
#ifdef Py_DEBUG
/* Is target in the list? The list is traversed via the nextpool pointers.
* The list may be NULL-terminated, or circular. Return 1 if target is in
* list, else 0.
*/
static int
pool_is_in_list(const poolp target, poolp list)
{
poolp origlist = list;
assert(target != NULL);
if (list == NULL)
return 0;
do {
if (target == list)
return 1;
list = list->nextpool;
} while (list != NULL && list != origlist);
return 0;
}
#endif
/* Print summary info to "out" about the state of pymalloc's structures. /* Print summary info to "out" about the state of pymalloc's structures.
* In Py_DEBUG mode, also perform some expensive internal consistency * In Py_DEBUG mode, also perform some expensive internal consistency
* checks. * checks.
@ -2233,7 +2304,9 @@ _PyObject_DebugMallocStats(FILE *out)
if (p->ref.count == 0) { if (p->ref.count == 0) {
/* currently unused */ /* currently unused */
#ifdef Py_DEBUG
assert(pool_is_in_list(p, arenas[i].freepools)); assert(pool_is_in_list(p, arenas[i].freepools));
#endif
continue; continue;
} }
++numpools[sz]; ++numpools[sz];
@ -2273,9 +2346,8 @@ _PyObject_DebugMallocStats(FILE *out)
quantization += p * ((POOL_SIZE - POOL_OVERHEAD) % size); quantization += p * ((POOL_SIZE - POOL_OVERHEAD) % size);
} }
fputc('\n', out); fputc('\n', out);
#ifdef PYMALLOC_DEBUG if (_PyMem_DebugEnabled())
(void)printone(out, "# times object malloc called", serialno); (void)printone(out, "# times object malloc called", serialno);
#endif
(void)printone(out, "# arenas allocated total", ntimes_arena_allocated); (void)printone(out, "# arenas allocated total", ntimes_arena_allocated);
(void)printone(out, "# arenas reclaimed", ntimes_arena_allocated - narenas); (void)printone(out, "# arenas reclaimed", ntimes_arena_allocated - narenas);
(void)printone(out, "# arenas highwater mark", narenas_highwater); (void)printone(out, "# arenas highwater mark", narenas_highwater);
@ -2303,6 +2375,7 @@ _PyObject_DebugMallocStats(FILE *out)
#endif /* #ifdef WITH_PYMALLOC */ #endif /* #ifdef WITH_PYMALLOC */
#ifdef Py_USING_MEMORY_DEBUGGER #ifdef Py_USING_MEMORY_DEBUGGER
/* Make this function last so gcc won't inline it since the definition is /* Make this function last so gcc won't inline it since the definition is
* after the reference. * after the reference.

View File

@ -24,6 +24,9 @@ main(int argc, char **argv)
int i, res; int i, res;
char *oldloc; char *oldloc;
/* Force malloc() allocator to bootstrap Python */
(void)_PyMem_SetupAllocators("malloc");
argv_copy = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*) * (argc+1)); argv_copy = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*) * (argc+1));
argv_copy2 = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*) * (argc+1)); argv_copy2 = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*) * (argc+1));
if (!argv_copy || !argv_copy2) { if (!argv_copy || !argv_copy2) {
@ -62,7 +65,13 @@ main(int argc, char **argv)
setlocale(LC_ALL, oldloc); setlocale(LC_ALL, oldloc);
PyMem_RawFree(oldloc); PyMem_RawFree(oldloc);
res = Py_Main(argc, argv_copy); res = Py_Main(argc, argv_copy);
/* Force again malloc() allocator to release memory blocks allocated
before Py_Main() */
(void)_PyMem_SetupAllocators("malloc");
for (i = 0; i < argc; i++) { for (i = 0; i < argc; i++) {
PyMem_RawFree(argv_copy2[i]); PyMem_RawFree(argv_copy2[i]);
} }

View File

@ -702,9 +702,12 @@ Py_FinalizeEx(void)
if (Py_GETENV("PYTHONDUMPREFS")) if (Py_GETENV("PYTHONDUMPREFS"))
_Py_PrintReferenceAddresses(stderr); _Py_PrintReferenceAddresses(stderr);
#endif /* Py_TRACE_REFS */ #endif /* Py_TRACE_REFS */
#ifdef PYMALLOC_DEBUG #ifdef WITH_PYMALLOC
if (Py_GETENV("PYTHONMALLOCSTATS")) if (_PyMem_PymallocEnabled()) {
_PyObject_DebugMallocStats(stderr); char *opt = Py_GETENV("PYTHONMALLOCSTATS");
if (opt != NULL && *opt != '\0')
_PyObject_DebugMallocStats(stderr);
}
#endif #endif
call_ll_exitfuncs(); call_ll_exitfuncs();

View File

@ -1151,8 +1151,10 @@ static PyObject *
sys_debugmallocstats(PyObject *self, PyObject *args) sys_debugmallocstats(PyObject *self, PyObject *args)
{ {
#ifdef WITH_PYMALLOC #ifdef WITH_PYMALLOC
_PyObject_DebugMallocStats(stderr); if (_PyMem_PymallocEnabled()) {
fputc('\n', stderr); _PyObject_DebugMallocStats(stderr);
fputc('\n', stderr);
}
#endif #endif
_PyObject_DebugTypeStats(stderr); _PyObject_DebugTypeStats(stderr);