summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@redhat.com>2018-10-23 15:39:40 (GMT)
committerGitHub <noreply@github.com>2018-10-23 15:39:40 (GMT)
commit82af0b63b07aa8d92b50098e382b458143cfc677 (patch)
tree77c2d497083f78ad622f75ffeb9680c6583d3317
parent96f2c739542d48edd6bd15c26b555c7e59d14cce (diff)
downloadcpython-82af0b63b07aa8d92b50098e382b458143cfc677.zip
cpython-82af0b63b07aa8d92b50098e382b458143cfc677.tar.gz
cpython-82af0b63b07aa8d92b50098e382b458143cfc677.tar.bz2
bpo-9263: _PyObject_Dump() detects freed memory (GH-10061)
_PyObject_Dump() now uses an heuristic to check if the object memory has been freed: log "<freed object>" in that case. The heuristic rely on the debug hooks on Python memory allocators which fills the memory with DEADBYTE (0xDB) when memory is deallocated. Use PYTHONMALLOC=debug to always enable these debug hooks.
-rw-r--r--Include/object.h1
-rw-r--r--Include/pymem.h4
-rw-r--r--Objects/object.c78
-rw-r--r--Objects/obmalloc.c16
4 files changed, 75 insertions, 24 deletions
diff --git a/Include/object.h b/Include/object.h
index c772dea..bcf78af 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -521,6 +521,7 @@ struct _Py_Identifier;
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
PyAPI_FUNC(void) _Py_BreakPoint(void);
PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
+PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
#endif
PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *);
PyAPI_FUNC(PyObject *) PyObject_Str(PyObject *);
diff --git a/Include/pymem.h b/Include/pymem.h
index 8ee0efd..ef6e0bb 100644
--- a/Include/pymem.h
+++ b/Include/pymem.h
@@ -55,7 +55,9 @@ PyAPI_FUNC(int) PyTraceMalloc_Untrack(
PyAPI_FUNC(PyObject*) _PyTraceMalloc_GetTraceback(
unsigned int domain,
uintptr_t ptr);
-#endif /* !Py_LIMITED_API */
+
+PyAPI_FUNC(int) _PyMem_IsFreed(void *ptr, size_t size);
+#endif /* !defined(Py_LIMITED_API) */
/* BEWARE:
diff --git a/Objects/object.c b/Objects/object.c
index ab1baa7..00c0bad 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -410,34 +410,66 @@ _Py_BreakPoint(void)
}
+/* Heuristic checking if the object memory has been deallocated.
+ Rely on the debug hooks on Python memory allocators which fills the memory
+ with DEADBYTE (0xDB) when memory is deallocated.
+
+ The function can be used to prevent segmentation fault on dereferencing
+ pointers like 0xdbdbdbdbdbdbdbdb. Such pointer is very unlikely to be mapped
+ in memory. */
+int
+_PyObject_IsFreed(PyObject *op)
+{
+ int freed = _PyMem_IsFreed(&op->ob_type, sizeof(op->ob_type));
+ /* ignore op->ob_ref: the value can have be modified
+ by Py_INCREF() and Py_DECREF(). */
+#ifdef Py_TRACE_REFS
+ freed &= _PyMem_IsFreed(&op->_ob_next, sizeof(op->_ob_next));
+ freed &= _PyMem_IsFreed(&op->_ob_prev, sizeof(op->_ob_prev));
+#endif
+ return freed;
+}
+
+
/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
void
_PyObject_Dump(PyObject* op)
{
- if (op == NULL)
- fprintf(stderr, "NULL\n");
- else {
- PyGILState_STATE gil;
- PyObject *error_type, *error_value, *error_traceback;
-
- fprintf(stderr, "object : ");
- gil = PyGILState_Ensure();
-
- PyErr_Fetch(&error_type, &error_value, &error_traceback);
- (void)PyObject_Print(op, stderr, 0);
- PyErr_Restore(error_type, error_value, error_traceback);
-
- PyGILState_Release(gil);
- /* XXX(twouters) cast refcount to long until %zd is
- universally available */
- fprintf(stderr, "\n"
- "type : %s\n"
- "refcount: %ld\n"
- "address : %p\n",
- Py_TYPE(op)==NULL ? "NULL" : Py_TYPE(op)->tp_name,
- (long)op->ob_refcnt,
- op);
+ if (op == NULL) {
+ fprintf(stderr, "<NULL object>\n");
+ fflush(stderr);
+ return;
+ }
+
+ if (_PyObject_IsFreed(op)) {
+ /* It seems like the object memory has been freed:
+ don't access it to prevent a segmentation fault. */
+ fprintf(stderr, "<freed object>\n");
}
+
+ PyGILState_STATE gil;
+ PyObject *error_type, *error_value, *error_traceback;
+
+ fprintf(stderr, "object : ");
+ fflush(stderr);
+ gil = PyGILState_Ensure();
+
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+ (void)PyObject_Print(op, stderr, 0);
+ fflush(stderr);
+ PyErr_Restore(error_type, error_value, error_traceback);
+
+ PyGILState_Release(gil);
+ /* XXX(twouters) cast refcount to long until %zd is
+ universally available */
+ fprintf(stderr, "\n"
+ "type : %s\n"
+ "refcount: %ld\n"
+ "address : %p\n",
+ Py_TYPE(op)==NULL ? "NULL" : Py_TYPE(op)->tp_name,
+ (long)op->ob_refcnt,
+ op);
+ fflush(stderr);
}
PyObject *
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index eb7cbfc..d58da35 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -2033,6 +2033,22 @@ _PyMem_DebugRawCalloc(void *ctx, size_t nelem, size_t elsize)
}
+/* Heuristic checking if the memory has been freed. Rely on the debug hooks on
+ Python memory allocators which fills the memory with DEADBYTE (0xDB) when
+ memory is deallocated. */
+int
+_PyMem_IsFreed(void *ptr, size_t size)
+{
+ unsigned char *bytes = ptr;
+ for (size_t i=0; i < size; i++) {
+ if (bytes[i] != DEADBYTE) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
/* The debug free first checks the 2*SST bytes on each end for sanity (in
particular, that the FORBIDDENBYTEs with the api ID are still intact).
Then fills the original bytes with DEADBYTE.