Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0611c26

Browse files
committed
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.
1 parent af584a0 commit 0611c26

8 files changed

Lines changed: 126 additions & 19 deletions

File tree

Doc/c-api/memory.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,19 @@ Customize Memory Allocators
349349
allocator functions of the :c:data:`PYMEM_DOMAIN_OBJ` domain (ex:
350350
:c:func:`PyObject_Malloc`) are called
351351
352+
On error, the debug hooks use the :mod:`tracemalloc` module to get the
353+
traceback where a memory block was allocated. The traceback is only
354+
displayed if :mod:`tracemalloc` is tracing Python memory allocations and the
355+
memory block was traced.
356+
352357
These hooks are installed by default if Python is compiled in debug
353358
mode. The :envvar:`PYTHONMALLOC` environment variable can be used to install
354359
debug hooks on a Python compiled in release mode.
355360
356361
.. versionchanged:: 3.6
357362
This function now also works on Python compiled in release mode.
363+
On error, the debug hooks now use :mod:`tracemalloc` to get the traceback
364+
where a memory block was allocated.
358365
359366
360367
.. _pymalloc:

Doc/whatsnew/3.6.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,48 @@ the C library for all Python memory allocations using ``PYTHONMALLOC=malloc``.
129129
It helps to use external memory debuggers like Valgrind on a Python compiled in
130130
release mode.
131131

132-
(Contributed by Victor Stinner in :issue:`26516`.)
132+
On error, the debug hooks on Python memory allocators now use the
133+
:mod:`tracemalloc` module to get the traceback where a memory block was
134+
allocated.
135+
136+
Example of fatal error on buffer overflow using
137+
``python3.6 -X tracemalloc=5`` (store 5 frames in traces)::
138+
139+
Debug memory block at address p=0x7fbcd41666f8: API 'o'
140+
4 bytes originally requested
141+
The 7 pad bytes at p-7 are FORBIDDENBYTE, as expected.
142+
The 8 pad bytes at tail=0x7fbcd41666fc are not all FORBIDDENBYTE (0xfb):
143+
at tail+0: 0x02 *** OUCH
144+
at tail+1: 0xfb
145+
at tail+2: 0xfb
146+
at tail+3: 0xfb
147+
at tail+4: 0xfb
148+
at tail+5: 0xfb
149+
at tail+6: 0xfb
150+
at tail+7: 0xfb
151+
The block was made by call #1233329 to debug malloc/realloc.
152+
Data at p: 1a 2b 30 00
153+
154+
Memory block allocated at (most recent call first):
155+
File "test/test_bytes.py", line 323
156+
File "unittest/case.py", line 600
157+
File "unittest/case.py", line 648
158+
File "unittest/suite.py", line 122
159+
File "unittest/suite.py", line 84
160+
161+
Fatal Python error: bad trailing pad byte
162+
163+
Current thread 0x00007fbcdbd32700 (most recent call first):
164+
File "test/test_bytes.py", line 323 in test_hex
165+
File "unittest/case.py", line 600 in run
166+
File "unittest/case.py", line 648 in __call__
167+
File "unittest/suite.py", line 122 in run
168+
File "unittest/suite.py", line 84 in __call__
169+
File "unittest/suite.py", line 122 in run
170+
File "unittest/suite.py", line 84 in __call__
171+
...
172+
173+
(Contributed by Victor Stinner in :issue:`26516` and :issue:`26564`.)
133174

134175

135176
Other Language Changes

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Release date: tba
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #26564: On error, the debug hooks on Python memory allocators now use
14+
the :mod:`tracemalloc` module to get the traceback where a memory block was
15+
allocated.
16+
1317
- Issue #26558: The debug hooks on Python memory allocator
1418
:c:func:`PyObject_Malloc` now detect when functions are called without
1519
holding the GIL.

Modules/_tracemalloc.c

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,25 @@ py_tracemalloc_get_traces(PyObject *self, PyObject *obj)
11611161
return get_traces.list;
11621162
}
11631163

1164+
static traceback_t*
1165+
tracemalloc_get_traceback(const void *ptr)
1166+
{
1167+
trace_t trace;
1168+
int found;
1169+
1170+
if (!tracemalloc_config.tracing)
1171+
return NULL;
1172+
1173+
TABLES_LOCK();
1174+
found = _Py_HASHTABLE_GET(tracemalloc_traces, ptr, trace);
1175+
TABLES_UNLOCK();
1176+
1177+
if (!found)
1178+
return NULL;
1179+
1180+
return trace.traceback;
1181+
}
1182+
11641183
PyDoc_STRVAR(tracemalloc_get_object_traceback_doc,
11651184
"_get_object_traceback(obj)\n"
11661185
"\n"
@@ -1175,28 +1194,54 @@ py_tracemalloc_get_object_traceback(PyObject *self, PyObject *obj)
11751194
{
11761195
PyTypeObject *type;
11771196
void *ptr;
1178-
trace_t trace;
1179-
int found;
1180-
1181-
if (!tracemalloc_config.tracing)
1182-
Py_RETURN_NONE;
1197+
traceback_t *traceback;
11831198

11841199
type = Py_TYPE(obj);
11851200
if (PyType_IS_GC(type))
11861201
ptr = (void *)((char *)obj - sizeof(PyGC_Head));
11871202
else
11881203
ptr = (void *)obj;
11891204

1190-
TABLES_LOCK();
1191-
found = _Py_HASHTABLE_GET(tracemalloc_traces, ptr, trace);
1192-
TABLES_UNLOCK();
1193-
1194-
if (!found)
1205+
traceback = tracemalloc_get_traceback(ptr);
1206+
if (traceback == NULL)
11951207
Py_RETURN_NONE;
11961208

1197-
return traceback_to_pyobject(trace.traceback, NULL);
1209+
return traceback_to_pyobject(traceback, NULL);
1210+
}
1211+
1212+
#define PUTS(fd, str) _Py_write_noraise(fd, str, (int)strlen(str))
1213+
1214+
static void
1215+
_PyMem_DumpFrame(int fd, frame_t * frame)
1216+
{
1217+
PUTS(fd, " File \"");
1218+
_Py_DumpASCII(fd, frame->filename);
1219+
PUTS(fd, "\", line ");
1220+
_Py_DumpDecimal(fd, frame->lineno);
1221+
PUTS(fd, "\n");
11981222
}
11991223

1224+
/* Dump the traceback where a memory block was allocated into file descriptor
1225+
fd. The function may block on TABLES_LOCK() but it is unlikely. */
1226+
void
1227+
_PyMem_DumpTraceback(int fd, const void *ptr)
1228+
{
1229+
traceback_t *traceback;
1230+
int i;
1231+
1232+
traceback = tracemalloc_get_traceback(ptr);
1233+
if (traceback == NULL)
1234+
return;
1235+
1236+
PUTS(fd, "Memory block allocated at (most recent call first):\n");
1237+
for (i=0; i < traceback->nframe; i++) {
1238+
_PyMem_DumpFrame(fd, &traceback->frames[i]);
1239+
}
1240+
PUTS(fd, "\n");
1241+
}
1242+
1243+
#undef PUTS
1244+
12001245
PyDoc_STRVAR(tracemalloc_start_doc,
12011246
"start(nframe: int=1)\n"
12021247
"\n"

Modules/hashtable.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,9 @@ _Py_hashtable_copy(_Py_hashtable_t *src)
486486
void *data, *new_data;
487487

488488
dst = _Py_hashtable_new_full(src->data_size, src->num_buckets,
489-
src->hash_func, src->compare_func,
490-
src->copy_data_func, src->free_data_func,
491-
src->get_data_size_func, &src->alloc);
489+
src->hash_func, src->compare_func,
490+
src->copy_data_func, src->free_data_func,
491+
src->get_data_size_func, &src->alloc);
492492
if (dst == NULL)
493493
return NULL;
494494

Objects/bytearrayobject.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,7 @@ bytearray_hex(PyBytesObject *self)
28202820
{
28212821
char* argbuf = PyByteArray_AS_STRING(self);
28222822
Py_ssize_t arglen = PyByteArray_GET_SIZE(self);
2823+
PyByteArray_AS_STRING(self)[arglen+1] = 2;
28232824
return _Py_strhex(argbuf, arglen);
28242825
}
28252826

Objects/obmalloc.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#include "Python.h"
22

3+
4+
/* Defined in tracemalloc.c */
5+
extern void _PyMem_DumpTraceback(int fd, const void *ptr);
6+
7+
38
/* Python's malloc wrappers (see pymem.h) */
49

510
/*
@@ -2202,6 +2207,10 @@ _PyObject_DebugDumpAddress(const void *p)
22022207
}
22032208
fputc('\n', stderr);
22042209
}
2210+
fputc('\n', stderr);
2211+
2212+
fflush(stderr);
2213+
_PyMem_DumpTraceback(fileno(stderr), p);
22052214
}
22062215

22072216

Parser/pgenmain.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ Py_Exit(int sts)
3838
}
3939

4040
#ifdef WITH_THREAD
41-
/* Needed by obmalloc.c */
41+
/* Functions needed by obmalloc.c */
4242
int PyGILState_Check(void)
43-
{
44-
return 1;
45-
}
43+
{ return 1; }
44+
void _PyMem_DumpTraceback(int fd, const void *ptr)
45+
{}
4646
#endif
4747

4848
int

0 commit comments

Comments
 (0)