On memory error, dump the memory block traceback
Issue #26564: _PyObject_DebugDumpAddress() now dumps the traceback where a memory block was allocated on memory block. Use the tracemalloc module to get the traceback.
This commit is contained in:
parent
af584a02a5
commit
0611c26a58
|
@ -349,12 +349,19 @@ Customize Memory Allocators
|
||||||
allocator functions of the :c:data:`PYMEM_DOMAIN_OBJ` domain (ex:
|
allocator functions of the :c:data:`PYMEM_DOMAIN_OBJ` domain (ex:
|
||||||
:c:func:`PyObject_Malloc`) are called
|
:c:func:`PyObject_Malloc`) 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 installed by default if Python is compiled in debug
|
These hooks are installed by default if Python is compiled in debug
|
||||||
mode. The :envvar:`PYTHONMALLOC` environment variable can be used to install
|
mode. The :envvar:`PYTHONMALLOC` environment variable can be used to install
|
||||||
debug hooks on a Python compiled in release mode.
|
debug hooks on a Python compiled in release mode.
|
||||||
|
|
||||||
.. versionchanged:: 3.6
|
.. versionchanged:: 3.6
|
||||||
This function now also works on Python compiled in release mode.
|
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.
|
||||||
|
|
||||||
|
|
||||||
.. _pymalloc:
|
.. _pymalloc:
|
||||||
|
|
|
@ -129,7 +129,48 @@ 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
|
It helps to use external memory debuggers like Valgrind on a Python compiled in
|
||||||
release mode.
|
release mode.
|
||||||
|
|
||||||
(Contributed by Victor Stinner in :issue:`26516`.)
|
On error, the debug hooks on Python memory allocators now use the
|
||||||
|
:mod:`tracemalloc` module to get the traceback where a memory block was
|
||||||
|
allocated.
|
||||||
|
|
||||||
|
Example of fatal error on buffer overflow using
|
||||||
|
``python3.6 -X tracemalloc=5`` (store 5 frames in traces)::
|
||||||
|
|
||||||
|
Debug memory block at address p=0x7fbcd41666f8: API 'o'
|
||||||
|
4 bytes originally requested
|
||||||
|
The 7 pad bytes at p-7 are FORBIDDENBYTE, as expected.
|
||||||
|
The 8 pad bytes at tail=0x7fbcd41666fc are not all FORBIDDENBYTE (0xfb):
|
||||||
|
at tail+0: 0x02 *** OUCH
|
||||||
|
at tail+1: 0xfb
|
||||||
|
at tail+2: 0xfb
|
||||||
|
at tail+3: 0xfb
|
||||||
|
at tail+4: 0xfb
|
||||||
|
at tail+5: 0xfb
|
||||||
|
at tail+6: 0xfb
|
||||||
|
at tail+7: 0xfb
|
||||||
|
The block was made by call #1233329 to debug malloc/realloc.
|
||||||
|
Data at p: 1a 2b 30 00
|
||||||
|
|
||||||
|
Memory block allocated at (most recent call first):
|
||||||
|
File "test/test_bytes.py", line 323
|
||||||
|
File "unittest/case.py", line 600
|
||||||
|
File "unittest/case.py", line 648
|
||||||
|
File "unittest/suite.py", line 122
|
||||||
|
File "unittest/suite.py", line 84
|
||||||
|
|
||||||
|
Fatal Python error: bad trailing pad byte
|
||||||
|
|
||||||
|
Current thread 0x00007fbcdbd32700 (most recent call first):
|
||||||
|
File "test/test_bytes.py", line 323 in test_hex
|
||||||
|
File "unittest/case.py", line 600 in run
|
||||||
|
File "unittest/case.py", line 648 in __call__
|
||||||
|
File "unittest/suite.py", line 122 in run
|
||||||
|
File "unittest/suite.py", line 84 in __call__
|
||||||
|
File "unittest/suite.py", line 122 in run
|
||||||
|
File "unittest/suite.py", line 84 in __call__
|
||||||
|
...
|
||||||
|
|
||||||
|
(Contributed by Victor Stinner in :issue:`26516` and :issue:`26564`.)
|
||||||
|
|
||||||
|
|
||||||
Other Language Changes
|
Other Language Changes
|
||||||
|
|
|
@ -10,6 +10,10 @@ Release date: tba
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #26564: On error, the debug hooks on Python memory allocators now use
|
||||||
|
the :mod:`tracemalloc` module to get the traceback where a memory block was
|
||||||
|
allocated.
|
||||||
|
|
||||||
- Issue #26558: The debug hooks on Python memory allocator
|
- Issue #26558: The debug hooks on Python memory allocator
|
||||||
:c:func:`PyObject_Malloc` now detect when functions are called without
|
:c:func:`PyObject_Malloc` now detect when functions are called without
|
||||||
holding the GIL.
|
holding the GIL.
|
||||||
|
|
|
@ -1161,6 +1161,25 @@ finally:
|
||||||
return get_traces.list;
|
return get_traces.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static traceback_t*
|
||||||
|
tracemalloc_get_traceback(const void *ptr)
|
||||||
|
{
|
||||||
|
trace_t trace;
|
||||||
|
int found;
|
||||||
|
|
||||||
|
if (!tracemalloc_config.tracing)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
TABLES_LOCK();
|
||||||
|
found = _Py_HASHTABLE_GET(tracemalloc_traces, ptr, trace);
|
||||||
|
TABLES_UNLOCK();
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return trace.traceback;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(tracemalloc_get_object_traceback_doc,
|
PyDoc_STRVAR(tracemalloc_get_object_traceback_doc,
|
||||||
"_get_object_traceback(obj)\n"
|
"_get_object_traceback(obj)\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -1175,11 +1194,7 @@ py_tracemalloc_get_object_traceback(PyObject *self, PyObject *obj)
|
||||||
{
|
{
|
||||||
PyTypeObject *type;
|
PyTypeObject *type;
|
||||||
void *ptr;
|
void *ptr;
|
||||||
trace_t trace;
|
traceback_t *traceback;
|
||||||
int found;
|
|
||||||
|
|
||||||
if (!tracemalloc_config.tracing)
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
|
|
||||||
type = Py_TYPE(obj);
|
type = Py_TYPE(obj);
|
||||||
if (PyType_IS_GC(type))
|
if (PyType_IS_GC(type))
|
||||||
|
@ -1187,16 +1202,46 @@ py_tracemalloc_get_object_traceback(PyObject *self, PyObject *obj)
|
||||||
else
|
else
|
||||||
ptr = (void *)obj;
|
ptr = (void *)obj;
|
||||||
|
|
||||||
TABLES_LOCK();
|
traceback = tracemalloc_get_traceback(ptr);
|
||||||
found = _Py_HASHTABLE_GET(tracemalloc_traces, ptr, trace);
|
if (traceback == NULL)
|
||||||
TABLES_UNLOCK();
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|
||||||
return traceback_to_pyobject(trace.traceback, NULL);
|
return traceback_to_pyobject(traceback, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define PUTS(fd, str) _Py_write_noraise(fd, str, (int)strlen(str))
|
||||||
|
|
||||||
|
static void
|
||||||
|
_PyMem_DumpFrame(int fd, frame_t * frame)
|
||||||
|
{
|
||||||
|
PUTS(fd, " File \"");
|
||||||
|
_Py_DumpASCII(fd, frame->filename);
|
||||||
|
PUTS(fd, "\", line ");
|
||||||
|
_Py_DumpDecimal(fd, frame->lineno);
|
||||||
|
PUTS(fd, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dump the traceback where a memory block was allocated into file descriptor
|
||||||
|
fd. The function may block on TABLES_LOCK() but it is unlikely. */
|
||||||
|
void
|
||||||
|
_PyMem_DumpTraceback(int fd, const void *ptr)
|
||||||
|
{
|
||||||
|
traceback_t *traceback;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
traceback = tracemalloc_get_traceback(ptr);
|
||||||
|
if (traceback == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PUTS(fd, "Memory block allocated at (most recent call first):\n");
|
||||||
|
for (i=0; i < traceback->nframe; i++) {
|
||||||
|
_PyMem_DumpFrame(fd, &traceback->frames[i]);
|
||||||
|
}
|
||||||
|
PUTS(fd, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef PUTS
|
||||||
|
|
||||||
PyDoc_STRVAR(tracemalloc_start_doc,
|
PyDoc_STRVAR(tracemalloc_start_doc,
|
||||||
"start(nframe: int=1)\n"
|
"start(nframe: int=1)\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
|
@ -486,9 +486,9 @@ _Py_hashtable_copy(_Py_hashtable_t *src)
|
||||||
void *data, *new_data;
|
void *data, *new_data;
|
||||||
|
|
||||||
dst = _Py_hashtable_new_full(src->data_size, src->num_buckets,
|
dst = _Py_hashtable_new_full(src->data_size, src->num_buckets,
|
||||||
src->hash_func, src->compare_func,
|
src->hash_func, src->compare_func,
|
||||||
src->copy_data_func, src->free_data_func,
|
src->copy_data_func, src->free_data_func,
|
||||||
src->get_data_size_func, &src->alloc);
|
src->get_data_size_func, &src->alloc);
|
||||||
if (dst == NULL)
|
if (dst == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
|
|
@ -2820,6 +2820,7 @@ bytearray_hex(PyBytesObject *self)
|
||||||
{
|
{
|
||||||
char* argbuf = PyByteArray_AS_STRING(self);
|
char* argbuf = PyByteArray_AS_STRING(self);
|
||||||
Py_ssize_t arglen = PyByteArray_GET_SIZE(self);
|
Py_ssize_t arglen = PyByteArray_GET_SIZE(self);
|
||||||
|
PyByteArray_AS_STRING(self)[arglen+1] = 2;
|
||||||
return _Py_strhex(argbuf, arglen);
|
return _Py_strhex(argbuf, arglen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Defined in tracemalloc.c */
|
||||||
|
extern void _PyMem_DumpTraceback(int fd, const void *ptr);
|
||||||
|
|
||||||
|
|
||||||
/* Python's malloc wrappers (see pymem.h) */
|
/* Python's malloc wrappers (see pymem.h) */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2202,6 +2207,10 @@ _PyObject_DebugDumpAddress(const void *p)
|
||||||
}
|
}
|
||||||
fputc('\n', stderr);
|
fputc('\n', stderr);
|
||||||
}
|
}
|
||||||
|
fputc('\n', stderr);
|
||||||
|
|
||||||
|
fflush(stderr);
|
||||||
|
_PyMem_DumpTraceback(fileno(stderr), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,11 @@ Py_Exit(int sts)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WITH_THREAD
|
#ifdef WITH_THREAD
|
||||||
/* Needed by obmalloc.c */
|
/* Functions needed by obmalloc.c */
|
||||||
int PyGILState_Check(void)
|
int PyGILState_Check(void)
|
||||||
{
|
{ return 1; }
|
||||||
return 1;
|
void _PyMem_DumpTraceback(int fd, const void *ptr)
|
||||||
}
|
{}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
Loading…
Reference in New Issue